From f54294317cd3edc3c75b202e0a3fb4af2447be90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sat, 24 Aug 2024 13:01:18 +0200 Subject: [PATCH 01/13] WIP reporting error diagnostics by using `c3c build --test` --- .vscode/settings.json | 3 + server/cmd/lsp/main.go | 4 +- server/internal/lsp/handlers/Initialize.go | 3 +- .../lsp/handlers/TextDocumentDidChange.go | 4 + .../internal/lsp/project_state/diagnostics.go | 90 +++++++++++++++++++ server/internal/lsp/project_state/state.go | 11 +-- 6 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 server/internal/lsp/project_state/diagnostics.go diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..082b194 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "makefile.configureOnOpen": false +} \ No newline at end of file diff --git a/server/cmd/lsp/main.go b/server/cmd/lsp/main.go index 55df6d7..91fa045 100644 --- a/server/cmd/lsp/main.go +++ b/server/cmd/lsp/main.go @@ -12,8 +12,8 @@ import ( flag "github.com/spf13/pflag" ) -const version = "0.1.1" -const prerelease = false +const version = "0.2.0" +const prerelease = true const appName = "C3-LSP" func getVersion() string { diff --git a/server/internal/lsp/handlers/Initialize.go b/server/internal/lsp/handlers/Initialize.go index 82f6df8..00de5d1 100644 --- a/server/internal/lsp/handlers/Initialize.go +++ b/server/internal/lsp/handlers/Initialize.go @@ -6,6 +6,7 @@ import ( "github.com/pherrymason/c3-lsp/pkg/cast" "github.com/pherrymason/c3-lsp/pkg/document" "github.com/pherrymason/c3-lsp/pkg/fs" + "github.com/pherrymason/c3-lsp/pkg/utils" "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -48,7 +49,7 @@ func (h *Handlers) Initialize(serverName string, serverVersion string, capabilit } if params.RootURI != nil { - h.state.SetProjectRootURI(*params.RootURI) + h.state.SetProjectRootURI(utils.NormalizePath(*params.RootURI)) h.indexWorkspace() } diff --git a/server/internal/lsp/handlers/TextDocumentDidChange.go b/server/internal/lsp/handlers/TextDocumentDidChange.go index e10751f..31abe1f 100644 --- a/server/internal/lsp/handlers/TextDocumentDidChange.go +++ b/server/internal/lsp/handlers/TextDocumentDidChange.go @@ -1,11 +1,15 @@ package handlers import ( + "github.com/pherrymason/c3-lsp/internal/lsp/project_state" "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) func (h *Handlers) TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error { h.state.UpdateDocument(params.TextDocument.URI, params.ContentChanges, h.parser) + + project_state.RefreshDiagnostics(h.state, context.Notify, false) + return nil } diff --git a/server/internal/lsp/project_state/diagnostics.go b/server/internal/lsp/project_state/diagnostics.go new file mode 100644 index 0000000..cf492b4 --- /dev/null +++ b/server/internal/lsp/project_state/diagnostics.go @@ -0,0 +1,90 @@ +package project_state + +import ( + "bytes" + "log" + "os/exec" + "strconv" + "strings" + + "github.com/pherrymason/c3-lsp/pkg/cast" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func RefreshDiagnostics(state *ProjectState, notify glsp.NotifyFunc, delay bool) { + if state.calculatingDiagnostics { + return + } + + state.calculatingDiagnostics = true + + //cmdString := fmt.Sprintf("c3c build --test --path %s", state.GetProjectRootURI()) + command := exec.Command("c3c", "build", "--test") + command.Dir = state.GetProjectRootURI() + + // set var to get the output + var out bytes.Buffer + var stdErr bytes.Buffer + + // set the output to our variable + command.Stdout = &out + command.Stderr = &stdErr + err := command.Run() + log.Println("output:", out.String()) + log.Println("output:", stdErr.String()) + if err != nil { + log.Println("An error:", err) + errorInfo := extractErrors(stdErr.String()) + + diagnostics := []protocol.Diagnostic{ + errorInfo.Diagnostic, + } + + go notify( + protocol.ServerTextDocumentPublishDiagnostics, + protocol.PublishDiagnosticsParams{ + URI: state.GetProjectRootURI() + "/src/" + errorInfo.File, + Diagnostics: diagnostics, + }) + } +} + +type ErrorInfo struct { + File string + Diagnostic protocol.Diagnostic +} + +func extractErrors(output string) ErrorInfo { + var errorInfo ErrorInfo + + lines := strings.Split(output, "\n") + for _, line := range lines { + if strings.HasPrefix(line, "Error") { + // Procesa la línea de error + parts := strings.Split(line, "|") + if len(parts) == 4 { + line, err := strconv.Atoi(parts[2]) + if err != nil { + continue + } + + errorInfo = ErrorInfo{ + File: parts[1], + Diagnostic: protocol.Diagnostic{ + Range: protocol.Range{ + Start: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(0)}, + End: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(1)}, + }, + Severity: cast.ToPtr(protocol.DiagnosticSeverityError), + Source: cast.ToPtr("c3c build --test"), + Message: parts[3], + }, + } + } + break // Asumimos que solo te interesa el primer error + } + } + + return errorInfo +} diff --git a/server/internal/lsp/project_state/state.go b/server/internal/lsp/project_state/state.go index d70fc9d..2374f3b 100644 --- a/server/internal/lsp/project_state/state.go +++ b/server/internal/lsp/project_state/state.go @@ -17,11 +17,12 @@ import ( // ProjectState will be the center of knowledge of everything parsed. type ProjectState struct { - _documents map[string]*document.Document - documents *document.DocumentStore - symbolsTable symbols_table.SymbolsTable - indexByFQN IndexStore // TODO simplify this and use trie.Trie directly! - languageVersion Version + _documents map[string]*document.Document + documents *document.DocumentStore + symbolsTable symbols_table.SymbolsTable + indexByFQN IndexStore // TODO simplify this and use trie.Trie directly! + languageVersion Version + calculatingDiagnostics bool logger commonlog.Logger debugEnabled bool From 634ff9f4e97e2c5a4165a949214a4e8a1838bb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sat, 24 Aug 2024 13:14:36 +0200 Subject: [PATCH 02/13] Fix line number of diagnostic. --- server/internal/lsp/project_state/diagnostics.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/internal/lsp/project_state/diagnostics.go b/server/internal/lsp/project_state/diagnostics.go index cf492b4..378e217 100644 --- a/server/internal/lsp/project_state/diagnostics.go +++ b/server/internal/lsp/project_state/diagnostics.go @@ -68,13 +68,14 @@ func extractErrors(output string) ErrorInfo { if err != nil { continue } + line -= 1 errorInfo = ErrorInfo{ File: parts[1], Diagnostic: protocol.Diagnostic{ Range: protocol.Range{ Start: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(0)}, - End: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(1)}, + End: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(99)}, }, Severity: cast.ToPtr(protocol.DiagnosticSeverityError), Source: cast.ToPtr("c3c build --test"), From a23c46df022709c7a022513b50cb3da83d544438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sun, 25 Aug 2024 16:38:24 +0200 Subject: [PATCH 03/13] Run diagnostics in project initialize. --- server/internal/lsp/handlers/Initialize.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/internal/lsp/handlers/Initialize.go b/server/internal/lsp/handlers/Initialize.go index 00de5d1..ad3cee1 100644 --- a/server/internal/lsp/handlers/Initialize.go +++ b/server/internal/lsp/handlers/Initialize.go @@ -3,6 +3,7 @@ package handlers import ( "os" + "github.com/pherrymason/c3-lsp/internal/lsp/project_state" "github.com/pherrymason/c3-lsp/pkg/cast" "github.com/pherrymason/c3-lsp/pkg/document" "github.com/pherrymason/c3-lsp/pkg/fs" @@ -51,6 +52,8 @@ func (h *Handlers) Initialize(serverName string, serverVersion string, capabilit if params.RootURI != nil { h.state.SetProjectRootURI(utils.NormalizePath(*params.RootURI)) h.indexWorkspace() + + project_state.RefreshDiagnostics(h.state, context.Notify, false) } return protocol.InitializeResult{ From 12abcc5a099afa584584a4abdcb5db6e324d28bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sun, 25 Aug 2024 17:34:06 +0200 Subject: [PATCH 04/13] Simplify architecture by removing Handlers type object. cmdLineArguments() directly returns a server.Options --- server/cmd/lsp/main.go | 86 +--------- server/internal/lsp/handlers/Initialize.go | 80 --------- .../lsp/handlers/TextDocumentCompletion.go | 25 --- .../lsp/handlers/TextDocumentDeclaration.go | 33 ---- .../lsp/handlers/TextDocumentDefinition.go | 33 ---- .../lsp/handlers/TextDocumentDidChange.go | 15 -- .../lsp/handlers/TextDocumentDidClose.go | 11 -- .../lsp/handlers/TextDocumentDidOpen.go | 31 ---- .../lsp/handlers/TextDocumentDidSave.go | 11 -- .../lsp/handlers/TextDocumentHover.go | 42 ----- .../lsp/handlers/TextDocumentSignatureHelp.go | 95 ----------- .../WorkspaceDidChangeWatchedFiles.go | 34 ---- server/internal/lsp/handlers/handlers.go | 21 --- server/internal/lsp/server.go | 161 ------------------ 14 files changed, 5 insertions(+), 673 deletions(-) delete mode 100644 server/internal/lsp/handlers/Initialize.go delete mode 100644 server/internal/lsp/handlers/TextDocumentCompletion.go delete mode 100644 server/internal/lsp/handlers/TextDocumentDeclaration.go delete mode 100644 server/internal/lsp/handlers/TextDocumentDefinition.go delete mode 100644 server/internal/lsp/handlers/TextDocumentDidChange.go delete mode 100644 server/internal/lsp/handlers/TextDocumentDidClose.go delete mode 100644 server/internal/lsp/handlers/TextDocumentDidOpen.go delete mode 100644 server/internal/lsp/handlers/TextDocumentDidSave.go delete mode 100644 server/internal/lsp/handlers/TextDocumentHover.go delete mode 100644 server/internal/lsp/handlers/TextDocumentSignatureHelp.go delete mode 100644 server/internal/lsp/handlers/WorkspaceDidChangeWatchedFiles.go delete mode 100644 server/internal/lsp/handlers/handlers.go delete mode 100644 server/internal/lsp/server.go diff --git a/server/cmd/lsp/main.go b/server/cmd/lsp/main.go index 91fa045..28233cf 100644 --- a/server/cmd/lsp/main.go +++ b/server/cmd/lsp/main.go @@ -3,13 +3,10 @@ package main import ( "fmt" "log" - "runtime/debug" "time" "github.com/getsentry/sentry-go" - "github.com/pherrymason/c3-lsp/internal/lsp" - "github.com/pherrymason/c3-lsp/pkg/option" - flag "github.com/spf13/pflag" + "github.com/pherrymason/c3-lsp/internal/lsp/server" ) const version = "0.2.0" @@ -25,15 +22,15 @@ func getVersion() string { } func main() { - options := cmdLineArguments() + options, showHelp := cmdLineArguments() commitHash := buildInfo() - if options.showHelp { + if showHelp { printHelp(appName, getVersion(), commitHash) return } - if options.sendCrashReports { + if options.SendCrashReports { err := sentry.Init(sentry.ClientOptions{ Dsn: "https://76f9fe6a1d3e2be7c9083891a644b0a3@o124652.ingest.us.sentry.io/4507278372110336", Release: fmt.Sprintf("c3.lsp@%s+%s", getVersion(), commitHash), @@ -49,79 +46,6 @@ func main() { defer sentry.Recover() } - c3Version := option.None[string]() - if options.c3Version != "" { - c3Version = option.Some(options.c3Version) - } - - logFilePath := option.None[string]() - if options.logFilePath != "" { - logFilePath = option.Some(options.logFilePath) - } - - server := lsp.NewServer(lsp.ServerOpts{ - Name: appName, - Version: version, - C3Version: c3Version, - LogFilepath: logFilePath, - SendCrashReports: options.sendCrashReports, - Debug: options.debug, - }) + server := server.NewServer(options, appName, version) server.Run() } - -type Options struct { - showHelp bool - c3Version string - logFilePath string - debug bool - sendCrashReports bool -} - -func cmdLineArguments() Options { - var showHelp = flag.Bool("help", false, "Shows this help") - - var sendCrashReports = flag.Bool("send-crash-reports", false, "Automatically reports crashes to server.") - - var logFilePath = flag.String("log-path", "", "Enables logs and sets its filepath") - var debug = flag.Bool("debug", false, "Enables debug mode") - - var c3Version = flag.String("lang-version", "", "Specify C3 language version.") - - flag.Parse() - - return Options{ - showHelp: *showHelp, - c3Version: *c3Version, - logFilePath: *logFilePath, - debug: *debug, - sendCrashReports: *sendCrashReports, - } -} - -func printAppGreet(appName string, version string, commit string) { - fmt.Printf("%s version %s (%s)\n", appName, version, commit) -} - -func printHelp(appName string, version string, commit string) { - printAppGreet(appName, version, commit) - - fmt.Println("\nOptions") - flag.PrintDefaults() -} - -func buildInfo() string { - var Commit = func() string { - if info, ok := debug.ReadBuildInfo(); ok { - for _, setting := range info.Settings { - if setting.Key == "vcs.revision" { - return setting.Value - } - } - } - - return "" - }() - - return Commit -} diff --git a/server/internal/lsp/handlers/Initialize.go b/server/internal/lsp/handlers/Initialize.go deleted file mode 100644 index ad3cee1..0000000 --- a/server/internal/lsp/handlers/Initialize.go +++ /dev/null @@ -1,80 +0,0 @@ -package handlers - -import ( - "os" - - "github.com/pherrymason/c3-lsp/internal/lsp/project_state" - "github.com/pherrymason/c3-lsp/pkg/cast" - "github.com/pherrymason/c3-lsp/pkg/document" - "github.com/pherrymason/c3-lsp/pkg/fs" - "github.com/pherrymason/c3-lsp/pkg/utils" - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -// Support "Hover" -func (h *Handlers) Initialize(serverName string, serverVersion string, capabilities protocol.ServerCapabilities, context *glsp.Context, params *protocol.InitializeParams) (any, error) { - //capabilities := handler.CreateServerCapabilities() - - change := protocol.TextDocumentSyncKindIncremental - capabilities.TextDocumentSync = protocol.TextDocumentSyncOptions{ - OpenClose: cast.ToPtr(true), - Change: &change, - Save: cast.ToPtr(true), - } - capabilities.DeclarationProvider = true - capabilities.CompletionProvider = &protocol.CompletionOptions{ - TriggerCharacters: []string{".", ":"}, - } - capabilities.SignatureHelpProvider = &protocol.SignatureHelpOptions{ - TriggerCharacters: []string{"(", ","}, - RetriggerCharacters: []string{")"}, - } - capabilities.Workspace = &protocol.ServerCapabilitiesWorkspace{ - FileOperations: &protocol.ServerCapabilitiesWorkspaceFileOperations{ - DidDelete: &protocol.FileOperationRegistrationOptions{ - Filters: []protocol.FileOperationFilter{{ - Pattern: protocol.FileOperationPattern{ - Glob: "**/*.{c3,c3i}", - }, - }}, - }, - DidRename: &protocol.FileOperationRegistrationOptions{ - Filters: []protocol.FileOperationFilter{{ - Pattern: protocol.FileOperationPattern{ - Glob: "**/*.{c3,c3i}", - }, - }}, - }, - }, - } - - if params.RootURI != nil { - h.state.SetProjectRootURI(utils.NormalizePath(*params.RootURI)) - h.indexWorkspace() - - project_state.RefreshDiagnostics(h.state, context.Notify, false) - } - - return protocol.InitializeResult{ - Capabilities: capabilities, - ServerInfo: &protocol.InitializeResultServerInfo{ - Name: serverName, - Version: &serverVersion, - }, - }, nil -} - -func (h *Handlers) indexWorkspace() { - path, _ := fs.UriToPath(h.state.GetProjectRootURI()) - files, _ := fs.ScanForC3(fs.GetCanonicalPath(path)) - //s.server.Log.Debug(fmt.Sprint("Workspace FILES:", len(files), files)) - - for _, filePath := range files { - //h.language.Debug(fmt.Sprint("Parsing ", filePath)) - - content, _ := os.ReadFile(filePath) - doc := document.NewDocumentFromString(filePath, string(content)) - h.state.RefreshDocumentIdentifiers(&doc, h.parser) - } -} diff --git a/server/internal/lsp/handlers/TextDocumentCompletion.go b/server/internal/lsp/handlers/TextDocumentCompletion.go deleted file mode 100644 index a980e28..0000000 --- a/server/internal/lsp/handlers/TextDocumentCompletion.go +++ /dev/null @@ -1,25 +0,0 @@ -package handlers - -import ( - ctx "github.com/pherrymason/c3-lsp/internal/lsp/context" - "github.com/pherrymason/c3-lsp/pkg/utils" - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -// Support "Completion" -// Returns: []CompletionItem | CompletionList | nil -func (h *Handlers) TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { - - cursorContext := ctx.BuildFromDocumentPosition( - params.Position, - utils.NormalizePath(params.TextDocument.URI), - h.state, - ) - - suggestions := h.search.BuildCompletionList( - cursorContext, - h.state, - ) - return suggestions, nil -} diff --git a/server/internal/lsp/handlers/TextDocumentDeclaration.go b/server/internal/lsp/handlers/TextDocumentDeclaration.go deleted file mode 100644 index eefe217..0000000 --- a/server/internal/lsp/handlers/TextDocumentDeclaration.go +++ /dev/null @@ -1,33 +0,0 @@ -package handlers - -import ( - _prot "github.com/pherrymason/c3-lsp/internal/lsp/protocol" - "github.com/pherrymason/c3-lsp/pkg/fs" - "github.com/pherrymason/c3-lsp/pkg/symbols" - "github.com/pherrymason/c3-lsp/pkg/utils" - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -// Support "Go to declaration" -func (h *Handlers) TextDocumentDeclaration(context *glsp.Context, params *protocol.DeclarationParams) (any, error) { - identifierOption := h.search.FindSymbolDeclarationInWorkspace( - utils.NormalizePath(params.TextDocument.URI), - symbols.NewPositionFromLSPPosition(params.Position), - h.state, - ) - - if identifierOption.IsNone() { - return nil, nil - } - - symbol := identifierOption.Get() - if !symbol.HasSourceCode() { - return nil, nil - } - - return protocol.Location{ - URI: fs.ConvertPathToURI(symbol.GetDocumentURI()), - Range: _prot.Lsp_NewRangeFromRange(symbol.GetIdRange()), - }, nil -} diff --git a/server/internal/lsp/handlers/TextDocumentDefinition.go b/server/internal/lsp/handlers/TextDocumentDefinition.go deleted file mode 100644 index 57f9080..0000000 --- a/server/internal/lsp/handlers/TextDocumentDefinition.go +++ /dev/null @@ -1,33 +0,0 @@ -package handlers - -import ( - _prot "github.com/pherrymason/c3-lsp/internal/lsp/protocol" - "github.com/pherrymason/c3-lsp/pkg/fs" - "github.com/pherrymason/c3-lsp/pkg/symbols" - "github.com/pherrymason/c3-lsp/pkg/utils" - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -// Returns: Location | []Location | []LocationLink | nil -func (h *Handlers) TextDocumentDefinition(context *glsp.Context, params *protocol.DefinitionParams) (any, error) { - identifierOption := h.search.FindSymbolDeclarationInWorkspace( - utils.NormalizePath(params.TextDocument.URI), - symbols.NewPositionFromLSPPosition(params.Position), - h.state, - ) - - if identifierOption.IsNone() { - return nil, nil - } - - symbol := identifierOption.Get() - if !symbol.HasSourceCode() { - return nil, nil - } - - return protocol.Location{ - URI: fs.ConvertPathToURI(symbol.GetDocumentURI()), - Range: _prot.Lsp_NewRangeFromRange(symbol.GetIdRange()), - }, nil -} diff --git a/server/internal/lsp/handlers/TextDocumentDidChange.go b/server/internal/lsp/handlers/TextDocumentDidChange.go deleted file mode 100644 index 31abe1f..0000000 --- a/server/internal/lsp/handlers/TextDocumentDidChange.go +++ /dev/null @@ -1,15 +0,0 @@ -package handlers - -import ( - "github.com/pherrymason/c3-lsp/internal/lsp/project_state" - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -func (h *Handlers) TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error { - h.state.UpdateDocument(params.TextDocument.URI, params.ContentChanges, h.parser) - - project_state.RefreshDiagnostics(h.state, context.Notify, false) - - return nil -} diff --git a/server/internal/lsp/handlers/TextDocumentDidClose.go b/server/internal/lsp/handlers/TextDocumentDidClose.go deleted file mode 100644 index c269304..0000000 --- a/server/internal/lsp/handlers/TextDocumentDidClose.go +++ /dev/null @@ -1,11 +0,0 @@ -package handlers - -import ( - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -func (h *Handlers) TextDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error { - h.state.CloseDocument(params.TextDocument.URI) - return nil -} diff --git a/server/internal/lsp/handlers/TextDocumentDidOpen.go b/server/internal/lsp/handlers/TextDocumentDidOpen.go deleted file mode 100644 index 23a5860..0000000 --- a/server/internal/lsp/handlers/TextDocumentDidOpen.go +++ /dev/null @@ -1,31 +0,0 @@ -package handlers - -import ( - "github.com/pherrymason/c3-lsp/pkg/document" - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -func (h *Handlers) TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocumentParams) error { - /* - doc, err := h.documents.Open(*params, context.Notify) - if err != nil { - //glspServer.Log.Debug("Could not open file document.") - return err - } - - if doc != nil { - h.state.RefreshDocumentIdentifiers(doc, h.parser) - } - */ - - langID := params.TextDocument.LanguageID - if langID != "c3" { - return nil - } - - doc := document.NewDocumentFromDocURI(params.TextDocument.URI, params.TextDocument.Text, params.TextDocument.Version) - h.state.RefreshDocumentIdentifiers(doc, h.parser) - - return nil -} diff --git a/server/internal/lsp/handlers/TextDocumentDidSave.go b/server/internal/lsp/handlers/TextDocumentDidSave.go deleted file mode 100644 index 5f64ec3..0000000 --- a/server/internal/lsp/handlers/TextDocumentDidSave.go +++ /dev/null @@ -1,11 +0,0 @@ -package handlers - -import ( - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -// Support "Hover" -func (h *Handlers) TextDocumentDidSave(ctx *glsp.Context, params *protocol.DidSaveTextDocumentParams) error { - return nil -} diff --git a/server/internal/lsp/handlers/TextDocumentHover.go b/server/internal/lsp/handlers/TextDocumentHover.go deleted file mode 100644 index 8ff2297..0000000 --- a/server/internal/lsp/handlers/TextDocumentHover.go +++ /dev/null @@ -1,42 +0,0 @@ -package handlers - -import ( - "github.com/pherrymason/c3-lsp/pkg/symbols" - "github.com/pherrymason/c3-lsp/pkg/utils" - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -// Support "Hover" -func (h *Handlers) TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) { - pos := symbols.NewPositionFromLSPPosition(params.Position) - docId := utils.NormalizePath(params.TextDocument.URI) - foundSymbolOption := h.search.FindSymbolDeclarationInWorkspace(docId, pos, h.state) - if foundSymbolOption.IsNone() { - return nil, nil - } - - foundSymbol := foundSymbolOption.Get() - - // expected behaviour: - // hovering on variables: display variable type + any description - // hovering on functions: display function signature - // hovering on members: same as variable - - extraLine := "" - - _, isModule := foundSymbol.(*symbols.Module) - if !isModule { - extraLine += "\n\nIn module **[" + foundSymbol.GetModuleString() + "]**" - } - - hover := protocol.Hover{ - Contents: protocol.MarkupContent{ - Kind: protocol.MarkupKindMarkdown, - Value: "```c3" + "\n" + foundSymbol.GetHoverInfo() + "\n```" + - extraLine, - }, - } - - return &hover, nil -} diff --git a/server/internal/lsp/handlers/TextDocumentSignatureHelp.go b/server/internal/lsp/handlers/TextDocumentSignatureHelp.go deleted file mode 100644 index e3fdea8..0000000 --- a/server/internal/lsp/handlers/TextDocumentSignatureHelp.go +++ /dev/null @@ -1,95 +0,0 @@ -package handlers - -import ( - "strings" - - "github.com/pherrymason/c3-lsp/pkg/document/sourcecode" - "github.com/pherrymason/c3-lsp/pkg/option" - "github.com/pherrymason/c3-lsp/pkg/symbols" - "github.com/pherrymason/c3-lsp/pkg/utils" - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -// textDocument/signatureHelp: {"context":{"isRetrigger":false,"triggerCharacter":"(","triggerKind":2},"position":{"character":20,"line":8},"textDocument":{"uri":"file:///Volumes/Development/raul/projects/game-dev/raul-game-project/murder-c3/src/main.c3"}} -func (h *Handlers) TextDocumentSignatureHelp(context *glsp.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { - // Rewind position after previous "(" - docId := utils.NormalizePath(params.TextDocument.URI) - doc := h.state.GetDocument(docId) - posOption := doc.SourceCode.RewindBeforePreviousParenthesis(symbols.NewPositionFromLSPPosition(params.Position)) - - if posOption.IsNone() { - return nil, nil - } - - foundSymbolOption := h.search.FindSymbolDeclarationInWorkspace( - docId, - posOption.Get(), - h.state, - ) - if foundSymbolOption.IsNone() { - return nil, nil - } - - foundSymbol := foundSymbolOption.Get() - function, ok := foundSymbol.(*symbols.Function) - if !ok { - return nil, nil - } - - parameters := []protocol.ParameterInformation{} - argsToStringify := []string{} - for _, arg := range function.GetArguments() { - argsToStringify = append( - argsToStringify, - arg.GetType().String()+" "+arg.GetName(), - ) - parameters = append( - parameters, - protocol.ParameterInformation{ - Label: arg.GetType().String() + " " + arg.GetName(), - }, - ) - } - - // Count number of commas (,) written from previous `(` - activeParameter := countWrittenArguments(posOption.Get(), doc.SourceCode) - signature := protocol.SignatureInformation{ - Label: function.GetFQN() + "(" + strings.Join(argsToStringify, ", ") + ")", - Parameters: parameters, - Documentation: "", // TODO: Parse comments on functions to include them here. - } - if activeParameter.IsSome() { - arg := activeParameter.Get() - signature.ActiveParameter = &arg - } - - signatureHelp := protocol.SignatureHelp{ - Signatures: []protocol.SignatureInformation{signature}, - } - - return &signatureHelp, nil -} - -func countWrittenArguments(startArgumentsPosition symbols.Position, s sourcecode.SourceCode) option.Option[uint32] { - index := startArgumentsPosition.IndexIn(s.Text) - commas := uint32(0) - length := len(s.Text) - for { - if index >= length { - break - } - - if rune(s.Text[index]) == ')' { - return option.None[uint32]() - } - - if rune(s.Text[index]) == ',' { - commas++ - } - - index++ - } - - return option.Some(commas) -} diff --git a/server/internal/lsp/handlers/WorkspaceDidChangeWatchedFiles.go b/server/internal/lsp/handlers/WorkspaceDidChangeWatchedFiles.go deleted file mode 100644 index ae231c4..0000000 --- a/server/internal/lsp/handlers/WorkspaceDidChangeWatchedFiles.go +++ /dev/null @@ -1,34 +0,0 @@ -package handlers - -import ( - "github.com/pherrymason/c3-lsp/pkg/utils" - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -func (h *Handlers) WorkspaceDidChangeWatchedFiles(context *glsp.Context, params *protocol.DidChangeWatchedFilesParams) error { - return nil -} - -func (h *Handlers) WorkspaceDidDeleteFiles(context *glsp.Context, params *protocol.DeleteFilesParams) error { - for _, file := range params.Files { - // The file has been removed! update our indices - docId := utils.NormalizePath(file.URI) - //h.documents.Delete(file.URI) - h.state.DeleteDocument(docId) - } - - return nil -} - -func (h *Handlers) WorkspaceDidRenameFiles(context *glsp.Context, params *protocol.RenameFilesParams) error { - for _, file := range params.Files { - //h.documents.Rename(file.OldURI, file.NewURI) - - oldDocId := utils.NormalizePath(file.OldURI) - newDocId := utils.NormalizePath(file.NewURI) - h.state.RenameDocument(oldDocId, newDocId) - } - - return nil -} diff --git a/server/internal/lsp/handlers/handlers.go b/server/internal/lsp/handlers/handlers.go deleted file mode 100644 index 903ae44..0000000 --- a/server/internal/lsp/handlers/handlers.go +++ /dev/null @@ -1,21 +0,0 @@ -package handlers - -import ( - l "github.com/pherrymason/c3-lsp/internal/lsp/project_state" - s "github.com/pherrymason/c3-lsp/internal/lsp/search" - p "github.com/pherrymason/c3-lsp/pkg/parser" -) - -type Handlers struct { - state *l.ProjectState - parser *p.Parser - search s.Search -} - -func NewHandlers(state *l.ProjectState, parser *p.Parser, search s.Search) Handlers { - return Handlers{ - state: state, - parser: parser, - search: search, - } -} diff --git a/server/internal/lsp/server.go b/server/internal/lsp/server.go deleted file mode 100644 index 7718eca..0000000 --- a/server/internal/lsp/server.go +++ /dev/null @@ -1,161 +0,0 @@ -package lsp - -import ( - "fmt" - - "github.com/pherrymason/c3-lsp/internal/lsp/handlers" - "github.com/pherrymason/c3-lsp/internal/lsp/project_state" - l "github.com/pherrymason/c3-lsp/internal/lsp/project_state" - "github.com/pherrymason/c3-lsp/internal/lsp/search" - "github.com/pherrymason/c3-lsp/pkg/option" - p "github.com/pherrymason/c3-lsp/pkg/parser" - "github.com/pkg/errors" - "github.com/tliron/commonlog" - _ "github.com/tliron/commonlog/simple" - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" - glspserv "github.com/tliron/glsp/server" - "golang.org/x/mod/semver" -) - -type Server struct { - server *glspserv.Server -} - -// ServerOpts holds the options to create a new Server. -type ServerOpts struct { - Name string - Version string - C3Version option.Option[string] - LogFilepath option.Option[string] - SendCrashReports bool - Debug bool -} - -func NewServer(opts ServerOpts) *Server { - var logpath *string - if opts.LogFilepath.IsSome() { - v := opts.LogFilepath.Get() - logpath = &v - } - - commonlog.Configure(2, logpath) // This increases logging verbosity (optional) - - logger := commonlog.GetLogger(fmt.Sprintf("%s.parser", opts.Name)) - - if opts.SendCrashReports { - logger.Debug("Sending crash reports") - } else { - logger.Debug("No crash reports") - } - - if opts.C3Version.IsSome() { - logger.Debug(fmt.Sprintf("C3 Language version specified: %s", opts.C3Version.Get())) - } - - handler := protocol.Handler{} - glspServer := glspserv.NewServer(&handler, opts.Name, true) - - requestedLanguageVersion := checkRequestedLanguageVersion(opts.C3Version) - - state := l.NewProjectState(logger, option.Some(requestedLanguageVersion.Number), opts.Debug) - parser := p.NewParser(logger) - search := search.NewSearch(logger, opts.Debug) - handlers := handlers.NewHandlers(&state, &parser, search) - - handler.Initialized = func(context *glsp.Context, params *protocol.InitializedParams) error { - /* - context.Notify(protocol.ServerWorkspaceWorkspaceFolders, protocol.PublishDiagnosticsParams{ - URI: doc.URI, - Diagnostics: diagnostics, - })*/ - /*sendCrashStatus := "disabled" - if opts.SendCrashReports { - sendCrashStatus = "enabled" - } - - context.Notify(protocol.ServerWindowShowMessage, protocol.ShowMessageParams{ - Type: protocol.MessageTypeInfo, - Message: fmt.Sprintf("SendCrash: %s", sendCrashStatus), - }) - */ - return nil - } - handler.Shutdown = shutdown - handler.SetTrace = setTrace - - handler.Initialize = func(context *glsp.Context, params *protocol.InitializeParams) (any, error) { - capabilities := handler.CreateServerCapabilities() - return handlers.Initialize( - opts.Name, - opts.Version, - capabilities, - context, - params, - ) - } - - handler.TextDocumentDidOpen = handlers.TextDocumentDidOpen - handler.TextDocumentDidChange = handlers.TextDocumentDidChange - handler.TextDocumentDidClose = handlers.TextDocumentDidClose - handler.TextDocumentDidSave = handlers.TextDocumentDidSave - handler.TextDocumentHover = handlers.TextDocumentHover - handler.TextDocumentDeclaration = handlers.TextDocumentDeclaration - handler.TextDocumentDefinition = handlers.TextDocumentDefinition - handler.TextDocumentCompletion = handlers.TextDocumentCompletion - handler.TextDocumentSignatureHelp = handlers.TextDocumentSignatureHelp - handler.WorkspaceDidChangeWatchedFiles = handlers.WorkspaceDidChangeWatchedFiles - handler.WorkspaceDidDeleteFiles = handlers.WorkspaceDidDeleteFiles - handler.WorkspaceDidRenameFiles = handlers.WorkspaceDidRenameFiles - - handler.CompletionItemResolve = func(context *glsp.Context, params *protocol.CompletionItem) (*protocol.CompletionItem, error) { - return params, nil - } - - handler.WorkspaceDidChangeWorkspaceFolders = func(context *glsp.Context, params *protocol.DidChangeWorkspaceFoldersParams) error { - - return nil - } - - server := &Server{ - server: glspServer, - } - - return server -} - -// Run starts the Language Server in stdio mode. -func (s *Server) Run() error { - return errors.Wrap(s.server.RunStdio(), "lsp") -} - -func shutdown(context *glsp.Context) error { - protocol.SetTraceValue(protocol.TraceValueOff) - return nil -} - -func setTrace(context *glsp.Context, params *protocol.SetTraceParams) error { - protocol.SetTraceValue(params.Value) - return nil -} - -func checkRequestedLanguageVersion(version option.Option[string]) project_state.Version { - supportedVersions := project_state.SupportedVersions() - - if version.IsNone() { - return supportedVersions[len(supportedVersions)-1] - } - - for _, sVersion := range supportedVersions { - if sVersion.Number == "dummy" { - continue - } - - compare := semver.Compare("v"+sVersion.Number, "v"+version.Get()) - if compare == 0 { - return sVersion - } - } - - panic("c3 language version not supported") -} From 5b82ea220577b1ed2147d18aaf122c1d2c86e989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sun, 25 Aug 2024 17:34:39 +0200 Subject: [PATCH 05/13] Change handler files --- server/cmd/lsp/args.go | 66 +++++++ server/internal/lsp/server/Initialize.go | 80 +++++++++ .../lsp/server/TextDocumentCompletion.go | 25 +++ .../lsp/server/TextDocumentDeclaration.go | 33 ++++ .../lsp/server/TextDocumentDefinition.go | 33 ++++ .../lsp/server/TextDocumentDidChange.go | 15 ++ .../lsp/server/TextDocumentDidClose.go | 11 ++ .../lsp/server/TextDocumentDidOpen.go | 31 ++++ .../lsp/server/TextDocumentDidSave.go | 11 ++ .../internal/lsp/server/TextDocumentHover.go | 42 +++++ .../lsp/server/TextDocumentSignatureHelp.go | 95 ++++++++++ .../server/WorkspaceDidChangeWatchedFiles.go | 34 ++++ server/internal/lsp/server/server.go | 167 ++++++++++++++++++ 13 files changed, 643 insertions(+) create mode 100644 server/cmd/lsp/args.go create mode 100644 server/internal/lsp/server/Initialize.go create mode 100644 server/internal/lsp/server/TextDocumentCompletion.go create mode 100644 server/internal/lsp/server/TextDocumentDeclaration.go create mode 100644 server/internal/lsp/server/TextDocumentDefinition.go create mode 100644 server/internal/lsp/server/TextDocumentDidChange.go create mode 100644 server/internal/lsp/server/TextDocumentDidClose.go create mode 100644 server/internal/lsp/server/TextDocumentDidOpen.go create mode 100644 server/internal/lsp/server/TextDocumentDidSave.go create mode 100644 server/internal/lsp/server/TextDocumentHover.go create mode 100644 server/internal/lsp/server/TextDocumentSignatureHelp.go create mode 100644 server/internal/lsp/server/WorkspaceDidChangeWatchedFiles.go create mode 100644 server/internal/lsp/server/server.go diff --git a/server/cmd/lsp/args.go b/server/cmd/lsp/args.go new file mode 100644 index 0000000..3ee0fbe --- /dev/null +++ b/server/cmd/lsp/args.go @@ -0,0 +1,66 @@ +package main + +import ( + "flag" + "fmt" + "runtime/debug" + + "github.com/pherrymason/c3-lsp/internal/lsp/server" + "github.com/pherrymason/c3-lsp/pkg/option" +) + +func cmdLineArguments() (server.ServerOpts, bool) { + var showHelp = flag.Bool("help", false, "Shows this help") + + var sendCrashReports = flag.Bool("send-crash-reports", false, "Automatically reports crashes to server.") + + var logFilePath = flag.String("log-path", "", "Enables logs and sets its filepath") + var debug = flag.Bool("debug", false, "Enables debug mode") + + var c3Version = flag.String("lang-version", "", "Specify C3 language version.") + + flag.Parse() + + c3VersionOpt := option.None[string]() + if *c3Version != "" { + c3VersionOpt = option.Some(*c3Version) + } + logFilePathOpt := option.None[string]() + if *logFilePath != "" { + logFilePathOpt = option.Some(*logFilePath) + } + + return server.ServerOpts{ + C3Version: c3VersionOpt, + LogFilepath: logFilePathOpt, + Debug: *debug, + SendCrashReports: *sendCrashReports, + }, *showHelp +} + +func printAppGreet(appName string, version string, commit string) { + fmt.Printf("%s version %s (%s)\n", appName, version, commit) +} + +func printHelp(appName string, version string, commit string) { + printAppGreet(appName, version, commit) + + fmt.Println("\nOptions") + flag.PrintDefaults() +} + +func buildInfo() string { + var Commit = func() string { + if info, ok := debug.ReadBuildInfo(); ok { + for _, setting := range info.Settings { + if setting.Key == "vcs.revision" { + return setting.Value + } + } + } + + return "" + }() + + return Commit +} diff --git a/server/internal/lsp/server/Initialize.go b/server/internal/lsp/server/Initialize.go new file mode 100644 index 0000000..4daef74 --- /dev/null +++ b/server/internal/lsp/server/Initialize.go @@ -0,0 +1,80 @@ +package server + +import ( + "os" + + "github.com/pherrymason/c3-lsp/internal/lsp/project_state" + "github.com/pherrymason/c3-lsp/pkg/cast" + "github.com/pherrymason/c3-lsp/pkg/document" + "github.com/pherrymason/c3-lsp/pkg/fs" + "github.com/pherrymason/c3-lsp/pkg/utils" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +// Support "Hover" +func (h *Server) Initialize(serverName string, serverVersion string, capabilities protocol.ServerCapabilities, context *glsp.Context, params *protocol.InitializeParams) (any, error) { + //capabilities := handler.CreateServerCapabilities() + + change := protocol.TextDocumentSyncKindIncremental + capabilities.TextDocumentSync = protocol.TextDocumentSyncOptions{ + OpenClose: cast.ToPtr(true), + Change: &change, + Save: cast.ToPtr(true), + } + capabilities.DeclarationProvider = true + capabilities.CompletionProvider = &protocol.CompletionOptions{ + TriggerCharacters: []string{".", ":"}, + } + capabilities.SignatureHelpProvider = &protocol.SignatureHelpOptions{ + TriggerCharacters: []string{"(", ","}, + RetriggerCharacters: []string{")"}, + } + capabilities.Workspace = &protocol.ServerCapabilitiesWorkspace{ + FileOperations: &protocol.ServerCapabilitiesWorkspaceFileOperations{ + DidDelete: &protocol.FileOperationRegistrationOptions{ + Filters: []protocol.FileOperationFilter{{ + Pattern: protocol.FileOperationPattern{ + Glob: "**/*.{c3,c3i}", + }, + }}, + }, + DidRename: &protocol.FileOperationRegistrationOptions{ + Filters: []protocol.FileOperationFilter{{ + Pattern: protocol.FileOperationPattern{ + Glob: "**/*.{c3,c3i}", + }, + }}, + }, + }, + } + + if params.RootURI != nil { + h.state.SetProjectRootURI(utils.NormalizePath(*params.RootURI)) + h.indexWorkspace() + + project_state.RefreshDiagnostics(h.state, context.Notify, false) + } + + return protocol.InitializeResult{ + Capabilities: capabilities, + ServerInfo: &protocol.InitializeResultServerInfo{ + Name: serverName, + Version: &serverVersion, + }, + }, nil +} + +func (h *Server) indexWorkspace() { + path, _ := fs.UriToPath(h.state.GetProjectRootURI()) + files, _ := fs.ScanForC3(fs.GetCanonicalPath(path)) + //s.server.Log.Debug(fmt.Sprint("Workspace FILES:", len(files), files)) + + for _, filePath := range files { + //h.language.Debug(fmt.Sprint("Parsing ", filePath)) + + content, _ := os.ReadFile(filePath) + doc := document.NewDocumentFromString(filePath, string(content)) + h.state.RefreshDocumentIdentifiers(&doc, h.parser) + } +} diff --git a/server/internal/lsp/server/TextDocumentCompletion.go b/server/internal/lsp/server/TextDocumentCompletion.go new file mode 100644 index 0000000..0b721ae --- /dev/null +++ b/server/internal/lsp/server/TextDocumentCompletion.go @@ -0,0 +1,25 @@ +package server + +import ( + ctx "github.com/pherrymason/c3-lsp/internal/lsp/context" + "github.com/pherrymason/c3-lsp/pkg/utils" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +// Support "Completion" +// Returns: []CompletionItem | CompletionList | nil +func (h *Server) TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { + + cursorContext := ctx.BuildFromDocumentPosition( + params.Position, + utils.NormalizePath(params.TextDocument.URI), + h.state, + ) + + suggestions := h.search.BuildCompletionList( + cursorContext, + h.state, + ) + return suggestions, nil +} diff --git a/server/internal/lsp/server/TextDocumentDeclaration.go b/server/internal/lsp/server/TextDocumentDeclaration.go new file mode 100644 index 0000000..a8d4031 --- /dev/null +++ b/server/internal/lsp/server/TextDocumentDeclaration.go @@ -0,0 +1,33 @@ +package server + +import ( + _prot "github.com/pherrymason/c3-lsp/internal/lsp/protocol" + "github.com/pherrymason/c3-lsp/pkg/fs" + "github.com/pherrymason/c3-lsp/pkg/symbols" + "github.com/pherrymason/c3-lsp/pkg/utils" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +// Support "Go to declaration" +func (h *Server) TextDocumentDeclaration(context *glsp.Context, params *protocol.DeclarationParams) (any, error) { + identifierOption := h.search.FindSymbolDeclarationInWorkspace( + utils.NormalizePath(params.TextDocument.URI), + symbols.NewPositionFromLSPPosition(params.Position), + h.state, + ) + + if identifierOption.IsNone() { + return nil, nil + } + + symbol := identifierOption.Get() + if !symbol.HasSourceCode() { + return nil, nil + } + + return protocol.Location{ + URI: fs.ConvertPathToURI(symbol.GetDocumentURI()), + Range: _prot.Lsp_NewRangeFromRange(symbol.GetIdRange()), + }, nil +} diff --git a/server/internal/lsp/server/TextDocumentDefinition.go b/server/internal/lsp/server/TextDocumentDefinition.go new file mode 100644 index 0000000..63344db --- /dev/null +++ b/server/internal/lsp/server/TextDocumentDefinition.go @@ -0,0 +1,33 @@ +package server + +import ( + _prot "github.com/pherrymason/c3-lsp/internal/lsp/protocol" + "github.com/pherrymason/c3-lsp/pkg/fs" + "github.com/pherrymason/c3-lsp/pkg/symbols" + "github.com/pherrymason/c3-lsp/pkg/utils" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +// Returns: Location | []Location | []LocationLink | nil +func (h *Server) TextDocumentDefinition(context *glsp.Context, params *protocol.DefinitionParams) (any, error) { + identifierOption := h.search.FindSymbolDeclarationInWorkspace( + utils.NormalizePath(params.TextDocument.URI), + symbols.NewPositionFromLSPPosition(params.Position), + h.state, + ) + + if identifierOption.IsNone() { + return nil, nil + } + + symbol := identifierOption.Get() + if !symbol.HasSourceCode() { + return nil, nil + } + + return protocol.Location{ + URI: fs.ConvertPathToURI(symbol.GetDocumentURI()), + Range: _prot.Lsp_NewRangeFromRange(symbol.GetIdRange()), + }, nil +} diff --git a/server/internal/lsp/server/TextDocumentDidChange.go b/server/internal/lsp/server/TextDocumentDidChange.go new file mode 100644 index 0000000..b0376fe --- /dev/null +++ b/server/internal/lsp/server/TextDocumentDidChange.go @@ -0,0 +1,15 @@ +package server + +import ( + "github.com/pherrymason/c3-lsp/internal/lsp/project_state" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func (h *Server) TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error { + h.state.UpdateDocument(params.TextDocument.URI, params.ContentChanges, h.parser) + + project_state.RefreshDiagnostics(h.state, context.Notify, false) + + return nil +} diff --git a/server/internal/lsp/server/TextDocumentDidClose.go b/server/internal/lsp/server/TextDocumentDidClose.go new file mode 100644 index 0000000..e31fb0a --- /dev/null +++ b/server/internal/lsp/server/TextDocumentDidClose.go @@ -0,0 +1,11 @@ +package server + +import ( + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func (h *Server) TextDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error { + h.state.CloseDocument(params.TextDocument.URI) + return nil +} diff --git a/server/internal/lsp/server/TextDocumentDidOpen.go b/server/internal/lsp/server/TextDocumentDidOpen.go new file mode 100644 index 0000000..2f801bf --- /dev/null +++ b/server/internal/lsp/server/TextDocumentDidOpen.go @@ -0,0 +1,31 @@ +package server + +import ( + "github.com/pherrymason/c3-lsp/pkg/document" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func (h *Server) TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocumentParams) error { + /* + doc, err := h.documents.Open(*params, context.Notify) + if err != nil { + //glspServer.Log.Debug("Could not open file document.") + return err + } + + if doc != nil { + h.state.RefreshDocumentIdentifiers(doc, h.parser) + } + */ + + langID := params.TextDocument.LanguageID + if langID != "c3" { + return nil + } + + doc := document.NewDocumentFromDocURI(params.TextDocument.URI, params.TextDocument.Text, params.TextDocument.Version) + h.state.RefreshDocumentIdentifiers(doc, h.parser) + + return nil +} diff --git a/server/internal/lsp/server/TextDocumentDidSave.go b/server/internal/lsp/server/TextDocumentDidSave.go new file mode 100644 index 0000000..7b1eae0 --- /dev/null +++ b/server/internal/lsp/server/TextDocumentDidSave.go @@ -0,0 +1,11 @@ +package server + +import ( + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +// Support "Hover" +func (h *Server) TextDocumentDidSave(ctx *glsp.Context, params *protocol.DidSaveTextDocumentParams) error { + return nil +} diff --git a/server/internal/lsp/server/TextDocumentHover.go b/server/internal/lsp/server/TextDocumentHover.go new file mode 100644 index 0000000..d8cdd18 --- /dev/null +++ b/server/internal/lsp/server/TextDocumentHover.go @@ -0,0 +1,42 @@ +package server + +import ( + "github.com/pherrymason/c3-lsp/pkg/symbols" + "github.com/pherrymason/c3-lsp/pkg/utils" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +// Support "Hover" +func (h *Server) TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) { + pos := symbols.NewPositionFromLSPPosition(params.Position) + docId := utils.NormalizePath(params.TextDocument.URI) + foundSymbolOption := h.search.FindSymbolDeclarationInWorkspace(docId, pos, h.state) + if foundSymbolOption.IsNone() { + return nil, nil + } + + foundSymbol := foundSymbolOption.Get() + + // expected behaviour: + // hovering on variables: display variable type + any description + // hovering on functions: display function signature + // hovering on members: same as variable + + extraLine := "" + + _, isModule := foundSymbol.(*symbols.Module) + if !isModule { + extraLine += "\n\nIn module **[" + foundSymbol.GetModuleString() + "]**" + } + + hover := protocol.Hover{ + Contents: protocol.MarkupContent{ + Kind: protocol.MarkupKindMarkdown, + Value: "```c3" + "\n" + foundSymbol.GetHoverInfo() + "\n```" + + extraLine, + }, + } + + return &hover, nil +} diff --git a/server/internal/lsp/server/TextDocumentSignatureHelp.go b/server/internal/lsp/server/TextDocumentSignatureHelp.go new file mode 100644 index 0000000..2dcf560 --- /dev/null +++ b/server/internal/lsp/server/TextDocumentSignatureHelp.go @@ -0,0 +1,95 @@ +package server + +import ( + "strings" + + "github.com/pherrymason/c3-lsp/pkg/document/sourcecode" + "github.com/pherrymason/c3-lsp/pkg/option" + "github.com/pherrymason/c3-lsp/pkg/symbols" + "github.com/pherrymason/c3-lsp/pkg/utils" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +// textDocument/signatureHelp: {"context":{"isRetrigger":false,"triggerCharacter":"(","triggerKind":2},"position":{"character":20,"line":8},"textDocument":{"uri":"file:///Volumes/Development/raul/projects/game-dev/raul-game-project/murder-c3/src/main.c3"}} +func (h *Server) TextDocumentSignatureHelp(context *glsp.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { + // Rewind position after previous "(" + docId := utils.NormalizePath(params.TextDocument.URI) + doc := h.state.GetDocument(docId) + posOption := doc.SourceCode.RewindBeforePreviousParenthesis(symbols.NewPositionFromLSPPosition(params.Position)) + + if posOption.IsNone() { + return nil, nil + } + + foundSymbolOption := h.search.FindSymbolDeclarationInWorkspace( + docId, + posOption.Get(), + h.state, + ) + if foundSymbolOption.IsNone() { + return nil, nil + } + + foundSymbol := foundSymbolOption.Get() + function, ok := foundSymbol.(*symbols.Function) + if !ok { + return nil, nil + } + + parameters := []protocol.ParameterInformation{} + argsToStringify := []string{} + for _, arg := range function.GetArguments() { + argsToStringify = append( + argsToStringify, + arg.GetType().String()+" "+arg.GetName(), + ) + parameters = append( + parameters, + protocol.ParameterInformation{ + Label: arg.GetType().String() + " " + arg.GetName(), + }, + ) + } + + // Count number of commas (,) written from previous `(` + activeParameter := countWrittenArguments(posOption.Get(), doc.SourceCode) + signature := protocol.SignatureInformation{ + Label: function.GetFQN() + "(" + strings.Join(argsToStringify, ", ") + ")", + Parameters: parameters, + Documentation: "", // TODO: Parse comments on functions to include them here. + } + if activeParameter.IsSome() { + arg := activeParameter.Get() + signature.ActiveParameter = &arg + } + + signatureHelp := protocol.SignatureHelp{ + Signatures: []protocol.SignatureInformation{signature}, + } + + return &signatureHelp, nil +} + +func countWrittenArguments(startArgumentsPosition symbols.Position, s sourcecode.SourceCode) option.Option[uint32] { + index := startArgumentsPosition.IndexIn(s.Text) + commas := uint32(0) + length := len(s.Text) + for { + if index >= length { + break + } + + if rune(s.Text[index]) == ')' { + return option.None[uint32]() + } + + if rune(s.Text[index]) == ',' { + commas++ + } + + index++ + } + + return option.Some(commas) +} diff --git a/server/internal/lsp/server/WorkspaceDidChangeWatchedFiles.go b/server/internal/lsp/server/WorkspaceDidChangeWatchedFiles.go new file mode 100644 index 0000000..f941f04 --- /dev/null +++ b/server/internal/lsp/server/WorkspaceDidChangeWatchedFiles.go @@ -0,0 +1,34 @@ +package server + +import ( + "github.com/pherrymason/c3-lsp/pkg/utils" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func (h *Server) WorkspaceDidChangeWatchedFiles(context *glsp.Context, params *protocol.DidChangeWatchedFilesParams) error { + return nil +} + +func (h *Server) WorkspaceDidDeleteFiles(context *glsp.Context, params *protocol.DeleteFilesParams) error { + for _, file := range params.Files { + // The file has been removed! update our indices + docId := utils.NormalizePath(file.URI) + //h.documents.Delete(file.URI) + h.state.DeleteDocument(docId) + } + + return nil +} + +func (h *Server) WorkspaceDidRenameFiles(context *glsp.Context, params *protocol.RenameFilesParams) error { + for _, file := range params.Files { + //h.documents.Rename(file.OldURI, file.NewURI) + + oldDocId := utils.NormalizePath(file.OldURI) + newDocId := utils.NormalizePath(file.NewURI) + h.state.RenameDocument(oldDocId, newDocId) + } + + return nil +} diff --git a/server/internal/lsp/server/server.go b/server/internal/lsp/server/server.go new file mode 100644 index 0000000..7798b84 --- /dev/null +++ b/server/internal/lsp/server/server.go @@ -0,0 +1,167 @@ +package server + +import ( + "fmt" + + "github.com/pherrymason/c3-lsp/internal/lsp/project_state" + l "github.com/pherrymason/c3-lsp/internal/lsp/project_state" + "github.com/pherrymason/c3-lsp/internal/lsp/search" + "github.com/pherrymason/c3-lsp/pkg/option" + p "github.com/pherrymason/c3-lsp/pkg/parser" + "github.com/pkg/errors" + "github.com/tliron/commonlog" + _ "github.com/tliron/commonlog/simple" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" + glspserv "github.com/tliron/glsp/server" + "golang.org/x/mod/semver" +) + +type Server struct { + server *glspserv.Server + version string + + state *l.ProjectState + parser *p.Parser + search search.Search +} + +// ServerOpts holds the options to create a new Server. +type ServerOpts struct { + C3Version option.Option[string] + LogFilepath option.Option[string] + SendCrashReports bool + Debug bool +} + +func NewServer(opts ServerOpts, appName string, version string) *Server { + var logpath *string + if opts.LogFilepath.IsSome() { + v := opts.LogFilepath.Get() + logpath = &v + } + + commonlog.Configure(2, logpath) // This increases logging verbosity (optional) + + logger := commonlog.GetLogger(fmt.Sprintf("%s.parser", appName)) + + if opts.SendCrashReports { + logger.Debug("Sending crash reports") + } else { + logger.Debug("No crash reports") + } + + if opts.C3Version.IsSome() { + logger.Debug(fmt.Sprintf("C3 Language version specified: %s", opts.C3Version.Get())) + } + + handler := protocol.Handler{} + glspServer := glspserv.NewServer(&handler, appName, true) + + requestedLanguageVersion := checkRequestedLanguageVersion(opts.C3Version) + + state := l.NewProjectState(logger, option.Some(requestedLanguageVersion.Number), opts.Debug) + parser := p.NewParser(logger) + search := search.NewSearch(logger, opts.Debug) + + server := &Server{ + server: glspServer, + version: version, + + state: &state, + parser: &parser, + search: search, + } + + handler.Initialized = func(context *glsp.Context, params *protocol.InitializedParams) error { + /* + context.Notify(protocol.ServerWorkspaceWorkspaceFolders, protocol.PublishDiagnosticsParams{ + URI: doc.URI, + Diagnostics: diagnostics, + })*/ + /*sendCrashStatus := "disabled" + if opts.SendCrashReports { + sendCrashStatus = "enabled" + } + + context.Notify(protocol.ServerWindowShowMessage, protocol.ShowMessageParams{ + Type: protocol.MessageTypeInfo, + Message: fmt.Sprintf("SendCrash: %s", sendCrashStatus), + }) + */ + return nil + } + handler.Shutdown = shutdown + handler.SetTrace = setTrace + + handler.Initialize = func(context *glsp.Context, params *protocol.InitializeParams) (any, error) { + capabilities := handler.CreateServerCapabilities() + return server.Initialize( + appName, + server.version, + capabilities, + context, + params, + ) + } + + handler.TextDocumentDidOpen = server.TextDocumentDidOpen + handler.TextDocumentDidChange = server.TextDocumentDidChange + handler.TextDocumentDidClose = server.TextDocumentDidClose + handler.TextDocumentDidSave = server.TextDocumentDidSave + handler.TextDocumentHover = server.TextDocumentHover + handler.TextDocumentDeclaration = server.TextDocumentDeclaration + handler.TextDocumentDefinition = server.TextDocumentDefinition + handler.TextDocumentCompletion = server.TextDocumentCompletion + handler.TextDocumentSignatureHelp = server.TextDocumentSignatureHelp + handler.WorkspaceDidChangeWatchedFiles = server.WorkspaceDidChangeWatchedFiles + handler.WorkspaceDidDeleteFiles = server.WorkspaceDidDeleteFiles + handler.WorkspaceDidRenameFiles = server.WorkspaceDidRenameFiles + + handler.CompletionItemResolve = func(context *glsp.Context, params *protocol.CompletionItem) (*protocol.CompletionItem, error) { + return params, nil + } + + handler.WorkspaceDidChangeWorkspaceFolders = func(context *glsp.Context, params *protocol.DidChangeWorkspaceFoldersParams) error { + + return nil + } + + return server +} + +// Run starts the Language Server in stdio mode. +func (s *Server) Run() error { + return errors.Wrap(s.server.RunStdio(), "lsp") +} + +func shutdown(context *glsp.Context) error { + protocol.SetTraceValue(protocol.TraceValueOff) + return nil +} + +func setTrace(context *glsp.Context, params *protocol.SetTraceParams) error { + protocol.SetTraceValue(params.Value) + return nil +} + +func checkRequestedLanguageVersion(version option.Option[string]) project_state.Version { + supportedVersions := project_state.SupportedVersions() + + if version.IsNone() { + return supportedVersions[len(supportedVersions)-1] + } + + for _, sVersion := range supportedVersions { + if sVersion.Number == "dummy" { + continue + } + + compare := semver.Compare("v"+sVersion.Number, "v"+version.Get()) + if compare == 0 { + return sVersion + } + } + + panic("c3 language version not supported") +} From 82ea320f8ae0387f981ffc31b8b26ae27f216e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sun, 25 Aug 2024 17:52:12 +0200 Subject: [PATCH 06/13] Define new server option c3c-path to specify a custom path to c3c binary. --- server/cmd/lsp/args.go | 9 ++++++++- server/internal/lsp/project_state/state.go | 8 ++++++++ .../diagnostics.go => server/Diagnostics.go} | 18 ++++++++++++------ server/internal/lsp/server/Initialize.go | 9 ++++----- .../lsp/server/TextDocumentDidChange.go | 7 +++---- server/internal/lsp/server/server.go | 11 +++++++++-- 6 files changed, 44 insertions(+), 18 deletions(-) rename server/internal/lsp/{project_state/diagnostics.go => server/Diagnostics.go} (81%) diff --git a/server/cmd/lsp/args.go b/server/cmd/lsp/args.go index 3ee0fbe..0e30ca1 100644 --- a/server/cmd/lsp/args.go +++ b/server/cmd/lsp/args.go @@ -11,13 +11,14 @@ import ( func cmdLineArguments() (server.ServerOpts, bool) { var showHelp = flag.Bool("help", false, "Shows this help") - var sendCrashReports = flag.Bool("send-crash-reports", false, "Automatically reports crashes to server.") var logFilePath = flag.String("log-path", "", "Enables logs and sets its filepath") var debug = flag.Bool("debug", false, "Enables debug mode") var c3Version = flag.String("lang-version", "", "Specify C3 language version.") + var c3cPath = flag.String("c3c-path", "", "Path where c3c is located.") + var diagnosticsDelay = flag.Int("diagnostics-delay", 2000, "Delay in milliseconds to check diagnostics.") flag.Parse() @@ -25,6 +26,10 @@ func cmdLineArguments() (server.ServerOpts, bool) { if *c3Version != "" { c3VersionOpt = option.Some(*c3Version) } + c3cPathOpt := option.None[string]() + if *c3cPath != "" { + c3cPathOpt = option.Some(*c3cPath) + } logFilePathOpt := option.None[string]() if *logFilePath != "" { logFilePathOpt = option.Some(*logFilePath) @@ -32,6 +37,8 @@ func cmdLineArguments() (server.ServerOpts, bool) { return server.ServerOpts{ C3Version: c3VersionOpt, + C3CPath: c3cPathOpt, + DiagnosticsDelay: uint(*diagnosticsDelay), LogFilepath: logFilePathOpt, Debug: *debug, SendCrashReports: *sendCrashReports, diff --git a/server/internal/lsp/project_state/state.go b/server/internal/lsp/project_state/state.go index 2374f3b..a92f3a7 100644 --- a/server/internal/lsp/project_state/state.go +++ b/server/internal/lsp/project_state/state.go @@ -55,6 +55,14 @@ func (s *ProjectState) SetProjectRootURI(rootURI string) { s.documents.RootURI = rootURI } +func (s *ProjectState) IsCalculatingDiagnostics() bool { + return s.calculatingDiagnostics +} + +func (s *ProjectState) SetCalculateDiagnostics(running bool) { + s.calculatingDiagnostics = running +} + func (s *ProjectState) GetDocument(docId string) *document.Document { return s._documents[docId] } diff --git a/server/internal/lsp/project_state/diagnostics.go b/server/internal/lsp/server/Diagnostics.go similarity index 81% rename from server/internal/lsp/project_state/diagnostics.go rename to server/internal/lsp/server/Diagnostics.go index 378e217..2c4cc3a 100644 --- a/server/internal/lsp/project_state/diagnostics.go +++ b/server/internal/lsp/server/Diagnostics.go @@ -1,4 +1,4 @@ -package project_state +package server import ( "bytes" @@ -7,20 +7,24 @@ import ( "strconv" "strings" + "github.com/pherrymason/c3-lsp/internal/lsp/project_state" "github.com/pherrymason/c3-lsp/pkg/cast" "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) -func RefreshDiagnostics(state *ProjectState, notify glsp.NotifyFunc, delay bool) { - if state.calculatingDiagnostics { +func (s *Server) RefreshDiagnostics(state *project_state.ProjectState, notify glsp.NotifyFunc, delay bool) { + if state.IsCalculatingDiagnostics() { return } - state.calculatingDiagnostics = true + state.SetCalculateDiagnostics(true) - //cmdString := fmt.Sprintf("c3c build --test --path %s", state.GetProjectRootURI()) - command := exec.Command("c3c", "build", "--test") + binary := "c3c" + if s.options.C3CPath.IsSome() { + binary = s.options.C3CPath.Get() + } + command := exec.Command(binary, "build", "--test") command.Dir = state.GetProjectRootURI() // set var to get the output @@ -48,6 +52,8 @@ func RefreshDiagnostics(state *ProjectState, notify glsp.NotifyFunc, delay bool) Diagnostics: diagnostics, }) } + + state.SetCalculateDiagnostics(false) } type ErrorInfo struct { diff --git a/server/internal/lsp/server/Initialize.go b/server/internal/lsp/server/Initialize.go index 4daef74..d5493ba 100644 --- a/server/internal/lsp/server/Initialize.go +++ b/server/internal/lsp/server/Initialize.go @@ -3,7 +3,6 @@ package server import ( "os" - "github.com/pherrymason/c3-lsp/internal/lsp/project_state" "github.com/pherrymason/c3-lsp/pkg/cast" "github.com/pherrymason/c3-lsp/pkg/document" "github.com/pherrymason/c3-lsp/pkg/fs" @@ -13,7 +12,7 @@ import ( ) // Support "Hover" -func (h *Server) Initialize(serverName string, serverVersion string, capabilities protocol.ServerCapabilities, context *glsp.Context, params *protocol.InitializeParams) (any, error) { +func (s *Server) Initialize(serverName string, serverVersion string, capabilities protocol.ServerCapabilities, context *glsp.Context, params *protocol.InitializeParams) (any, error) { //capabilities := handler.CreateServerCapabilities() change := protocol.TextDocumentSyncKindIncremental @@ -50,10 +49,10 @@ func (h *Server) Initialize(serverName string, serverVersion string, capabilitie } if params.RootURI != nil { - h.state.SetProjectRootURI(utils.NormalizePath(*params.RootURI)) - h.indexWorkspace() + s.state.SetProjectRootURI(utils.NormalizePath(*params.RootURI)) + s.indexWorkspace() - project_state.RefreshDiagnostics(h.state, context.Notify, false) + s.RefreshDiagnostics(s.state, context.Notify, false) } return protocol.InitializeResult{ diff --git a/server/internal/lsp/server/TextDocumentDidChange.go b/server/internal/lsp/server/TextDocumentDidChange.go index b0376fe..c7b6701 100644 --- a/server/internal/lsp/server/TextDocumentDidChange.go +++ b/server/internal/lsp/server/TextDocumentDidChange.go @@ -1,15 +1,14 @@ package server import ( - "github.com/pherrymason/c3-lsp/internal/lsp/project_state" "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) -func (h *Server) TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error { - h.state.UpdateDocument(params.TextDocument.URI, params.ContentChanges, h.parser) +func (s *Server) TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error { + s.state.UpdateDocument(params.TextDocument.URI, params.ContentChanges, s.parser) - project_state.RefreshDiagnostics(h.state, context.Notify, false) + s.RefreshDiagnostics(s.state, context.Notify, false) return nil } diff --git a/server/internal/lsp/server/server.go b/server/internal/lsp/server/server.go index 7798b84..43c3148 100644 --- a/server/internal/lsp/server/server.go +++ b/server/internal/lsp/server/server.go @@ -19,6 +19,7 @@ import ( type Server struct { server *glspserv.Server + options ServerOpts version string state *l.ProjectState @@ -28,8 +29,13 @@ type Server struct { // ServerOpts holds the options to create a new Server. type ServerOpts struct { - C3Version option.Option[string] - LogFilepath option.Option[string] + C3Version option.Option[string] + C3CPath option.Option[string] + LogFilepath option.Option[string] + + DiagnosticsDelay uint + DiagnosticsEnabled bool + SendCrashReports bool Debug bool } @@ -66,6 +72,7 @@ func NewServer(opts ServerOpts, appName string, version string) *Server { server := &Server{ server: glspServer, + options: opts, version: version, state: &state, From cdfddc80084a76731e29806171168b299d885c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sun, 25 Aug 2024 17:58:00 +0200 Subject: [PATCH 07/13] Add new options to vscode extension. --- client/vscode/extension.js | 8 ++++++++ client/vscode/package.json | 10 ++++++++++ server/cmd/lsp/args.go | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/client/vscode/extension.js b/client/vscode/extension.js index 4548000..adf9a6d 100644 --- a/client/vscode/extension.js +++ b/client/vscode/extension.js @@ -26,6 +26,14 @@ module.exports = { args.push('--lang-version '+config.get('c3.version')); } + if (config.get('c3.path')) { + args.push('--c3c-path '+config.get('c3.path')); + } + + if (config.get('diagnosticsDelay')) { + args.push('--diagnostics-delay '+config.get('diagnosticsDelay')); + } + const serverOptions = { run: { command: executable, diff --git a/client/vscode/package.json b/client/vscode/package.json index 6e5995f..ee1ebac 100644 --- a/client/vscode/package.json +++ b/client/vscode/package.json @@ -54,10 +54,20 @@ "default": false, "markdownDescription": "Sends crash reports to server to help fixing bugs." }, + "c3lspclient.lsp.diagnosticsDelay": { + "type": "integer", + "default": 2000, + "markdownDescription": "Delay calculation of code diagnostics after modifications in source. In milliseconds, default 2000 ms." + }, "c3lspclient.lsp.c3.version": { "type": "string", "default": null, "markdownDescription": "Specify C3 language version. If omited, LSP will use the last version it supports." + }, + "c3lspclient.lsp.c3.path": { + "type": "string", + "default": null, + "markdownDescription": "Path to C3C binary. Use it if not defined already in your PATH environment variable or if you want to use a different one." } } } diff --git a/server/cmd/lsp/args.go b/server/cmd/lsp/args.go index 0e30ca1..2df7d15 100644 --- a/server/cmd/lsp/args.go +++ b/server/cmd/lsp/args.go @@ -18,7 +18,7 @@ func cmdLineArguments() (server.ServerOpts, bool) { var c3Version = flag.String("lang-version", "", "Specify C3 language version.") var c3cPath = flag.String("c3c-path", "", "Path where c3c is located.") - var diagnosticsDelay = flag.Int("diagnostics-delay", 2000, "Delay in milliseconds to check diagnostics.") + var diagnosticsDelay = flag.Int("diagnostics-delay", 2000, "Delay calculation of code diagnostics after modifications in source. In milliseconds, default 2000 ms.") flag.Parse() From 91f4d84603dcc849badea0762d119a99184dc2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sun, 25 Aug 2024 17:58:27 +0200 Subject: [PATCH 08/13] bump vscode extension version --- client/vscode/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/vscode/package.json b/client/vscode/package.json index ee1ebac..ecaeb98 100644 --- a/client/vscode/package.json +++ b/client/vscode/package.json @@ -2,7 +2,7 @@ "name": "c3-lsp-client", "displayName": "C3 Language Server client", "description": "Language Server client for C3. Download C3 server from https://github.com/pherrymason/c3-lsp", - "version": "0.0.3", + "version": "0.1.0", "publisher": "rferras", "engines": { "vscode": "^1.40.0" From 240505124d4423fc272ecd05f046e5bfcf23d392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sun, 25 Aug 2024 18:23:02 +0200 Subject: [PATCH 09/13] Delay run of diagnostics. --- server/cmd/lsp/args.go | 3 +- server/go.mod | 2 + server/go.sum | 2 + server/internal/lsp/server/Diagnostics.go | 43 +++++++++++-------- server/internal/lsp/server/Initialize.go | 2 +- .../lsp/server/TextDocumentDidChange.go | 2 +- server/internal/lsp/server/server.go | 8 +++- 7 files changed, 41 insertions(+), 21 deletions(-) diff --git a/server/cmd/lsp/args.go b/server/cmd/lsp/args.go index 2df7d15..4c6aca2 100644 --- a/server/cmd/lsp/args.go +++ b/server/cmd/lsp/args.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "runtime/debug" + "time" "github.com/pherrymason/c3-lsp/internal/lsp/server" "github.com/pherrymason/c3-lsp/pkg/option" @@ -38,7 +39,7 @@ func cmdLineArguments() (server.ServerOpts, bool) { return server.ServerOpts{ C3Version: c3VersionOpt, C3CPath: c3cPathOpt, - DiagnosticsDelay: uint(*diagnosticsDelay), + DiagnosticsDelay: time.Duration(*diagnosticsDelay), LogFilepath: logFilePathOpt, Debug: *debug, SendCrashReports: *sendCrashReports, diff --git a/server/go.mod b/server/go.mod index 598696e..08da16c 100644 --- a/server/go.mod +++ b/server/go.mod @@ -14,6 +14,8 @@ require ( require golang.org/x/mod v0.20.0 +require github.com/bep/debounce v1.2.1 // indirect + require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/dave/jennifer v1.7.0 diff --git a/server/go.sum b/server/go.sum index 761f4ab..68aa82e 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,5 +1,7 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/dave/jennifer v1.7.0 h1:uRbSBH9UTS64yXbh4FrMHfgfY762RD+C7bUPKODpSJE= github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/server/internal/lsp/server/Diagnostics.go b/server/internal/lsp/server/Diagnostics.go index 2c4cc3a..0363b95 100644 --- a/server/internal/lsp/server/Diagnostics.go +++ b/server/internal/lsp/server/Diagnostics.go @@ -13,7 +13,7 @@ import ( protocol "github.com/tliron/glsp/protocol_3_16" ) -func (s *Server) RefreshDiagnostics(state *project_state.ProjectState, notify glsp.NotifyFunc, delay bool) { +func (s *Server) RunDiagnostics(state *project_state.ProjectState, notify glsp.NotifyFunc, delay bool) { if state.IsCalculatingDiagnostics() { return } @@ -34,26 +34,35 @@ func (s *Server) RefreshDiagnostics(state *project_state.ProjectState, notify gl // set the output to our variable command.Stdout = &out command.Stderr = &stdErr - err := command.Run() - log.Println("output:", out.String()) - log.Println("output:", stdErr.String()) - if err != nil { - log.Println("An error:", err) - errorInfo := extractErrors(stdErr.String()) - - diagnostics := []protocol.Diagnostic{ - errorInfo.Diagnostic, + + runDiagnostics := func() { + err := command.Run() + log.Println("output:", out.String()) + log.Println("output:", stdErr.String()) + if err != nil { + log.Println("An error:", err) + errorInfo := extractErrors(stdErr.String()) + + diagnostics := []protocol.Diagnostic{ + errorInfo.Diagnostic, + } + + go notify( + protocol.ServerTextDocumentPublishDiagnostics, + protocol.PublishDiagnosticsParams{ + URI: state.GetProjectRootURI() + "/src/" + errorInfo.File, + Diagnostics: diagnostics, + }) } - go notify( - protocol.ServerTextDocumentPublishDiagnostics, - protocol.PublishDiagnosticsParams{ - URI: state.GetProjectRootURI() + "/src/" + errorInfo.File, - Diagnostics: diagnostics, - }) + state.SetCalculateDiagnostics(false) } - state.SetCalculateDiagnostics(false) + if delay { + s.diagnosticDebounced(runDiagnostics) + } else { + runDiagnostics() + } } type ErrorInfo struct { diff --git a/server/internal/lsp/server/Initialize.go b/server/internal/lsp/server/Initialize.go index d5493ba..e98d577 100644 --- a/server/internal/lsp/server/Initialize.go +++ b/server/internal/lsp/server/Initialize.go @@ -52,7 +52,7 @@ func (s *Server) Initialize(serverName string, serverVersion string, capabilitie s.state.SetProjectRootURI(utils.NormalizePath(*params.RootURI)) s.indexWorkspace() - s.RefreshDiagnostics(s.state, context.Notify, false) + s.RunDiagnostics(s.state, context.Notify, false) } return protocol.InitializeResult{ diff --git a/server/internal/lsp/server/TextDocumentDidChange.go b/server/internal/lsp/server/TextDocumentDidChange.go index c7b6701..5d23c9f 100644 --- a/server/internal/lsp/server/TextDocumentDidChange.go +++ b/server/internal/lsp/server/TextDocumentDidChange.go @@ -8,7 +8,7 @@ import ( func (s *Server) TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error { s.state.UpdateDocument(params.TextDocument.URI, params.ContentChanges, s.parser) - s.RefreshDiagnostics(s.state, context.Notify, false) + s.RunDiagnostics(s.state, context.Notify, true) return nil } diff --git a/server/internal/lsp/server/server.go b/server/internal/lsp/server/server.go index 43c3148..62f7ad4 100644 --- a/server/internal/lsp/server/server.go +++ b/server/internal/lsp/server/server.go @@ -2,7 +2,9 @@ package server import ( "fmt" + "time" + "github.com/bep/debounce" "github.com/pherrymason/c3-lsp/internal/lsp/project_state" l "github.com/pherrymason/c3-lsp/internal/lsp/project_state" "github.com/pherrymason/c3-lsp/internal/lsp/search" @@ -25,6 +27,8 @@ type Server struct { state *l.ProjectState parser *p.Parser search search.Search + + diagnosticDebounced func(func()) } // ServerOpts holds the options to create a new Server. @@ -33,7 +37,7 @@ type ServerOpts struct { C3CPath option.Option[string] LogFilepath option.Option[string] - DiagnosticsDelay uint + DiagnosticsDelay time.Duration DiagnosticsEnabled bool SendCrashReports bool @@ -78,6 +82,8 @@ func NewServer(opts ServerOpts, appName string, version string) *Server { state: &state, parser: &parser, search: search, + + diagnosticDebounced: debounce.New(opts.DiagnosticsDelay * time.Millisecond), } handler.Initialized = func(context *glsp.Context, params *protocol.InitializedParams) error { From 915a7b2f84ba5fca9ca372c92cb10a3514acc804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sun, 25 Aug 2024 18:33:13 +0200 Subject: [PATCH 10/13] Use reported file as absolute path. Use reported character number. --- server/internal/lsp/server/Diagnostics.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/server/internal/lsp/server/Diagnostics.go b/server/internal/lsp/server/Diagnostics.go index 0363b95..48886cb 100644 --- a/server/internal/lsp/server/Diagnostics.go +++ b/server/internal/lsp/server/Diagnostics.go @@ -50,7 +50,7 @@ func (s *Server) RunDiagnostics(state *project_state.ProjectState, notify glsp.N go notify( protocol.ServerTextDocumentPublishDiagnostics, protocol.PublishDiagnosticsParams{ - URI: state.GetProjectRootURI() + "/src/" + errorInfo.File, + URI: errorInfo.File, Diagnostics: diagnostics, }) } @@ -78,27 +78,32 @@ func extractErrors(output string) ErrorInfo { if strings.HasPrefix(line, "Error") { // Procesa la línea de error parts := strings.Split(line, "|") - if len(parts) == 4 { + if len(parts) == 5 { line, err := strconv.Atoi(parts[2]) if err != nil { continue } line -= 1 + character, err := strconv.Atoi(parts[3]) + if err != nil { + continue + } + character -= 1 errorInfo = ErrorInfo{ File: parts[1], Diagnostic: protocol.Diagnostic{ Range: protocol.Range{ - Start: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(0)}, + Start: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(character)}, End: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(99)}, }, Severity: cast.ToPtr(protocol.DiagnosticSeverityError), Source: cast.ToPtr("c3c build --test"), - Message: parts[3], + Message: parts[4], }, } } - break // Asumimos que solo te interesa el primer error + break } } From 51a02ebea99abe05ee479e9e357fbdaabf1ef368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Sun, 25 Aug 2024 18:34:45 +0200 Subject: [PATCH 11/13] Prepare to disable diagnostics if c3c does not return the expected format for errors. --- server/internal/lsp/server/Diagnostics.go | 47 +++++++++++++---------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/server/internal/lsp/server/Diagnostics.go b/server/internal/lsp/server/Diagnostics.go index 48886cb..6b610f7 100644 --- a/server/internal/lsp/server/Diagnostics.go +++ b/server/internal/lsp/server/Diagnostics.go @@ -78,29 +78,34 @@ func extractErrors(output string) ErrorInfo { if strings.HasPrefix(line, "Error") { // Procesa la línea de error parts := strings.Split(line, "|") - if len(parts) == 5 { - line, err := strconv.Atoi(parts[2]) - if err != nil { - continue + if parts[0] == "Error" { + if len(parts) != 5 { + // Disable future diagnostics, looks like c3c is an old version. } - line -= 1 - character, err := strconv.Atoi(parts[3]) - if err != nil { - continue - } - character -= 1 - - errorInfo = ErrorInfo{ - File: parts[1], - Diagnostic: protocol.Diagnostic{ - Range: protocol.Range{ - Start: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(character)}, - End: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(99)}, + if len(parts) == 5 { + line, err := strconv.Atoi(parts[2]) + if err != nil { + continue + } + line -= 1 + character, err := strconv.Atoi(parts[3]) + if err != nil { + continue + } + character -= 1 + + errorInfo = ErrorInfo{ + File: parts[1], + Diagnostic: protocol.Diagnostic{ + Range: protocol.Range{ + Start: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(character)}, + End: protocol.Position{Line: protocol.UInteger(line), Character: protocol.UInteger(99)}, + }, + Severity: cast.ToPtr(protocol.DiagnosticSeverityError), + Source: cast.ToPtr("c3c build --test"), + Message: parts[4], }, - Severity: cast.ToPtr(protocol.DiagnosticSeverityError), - Source: cast.ToPtr("c3c build --test"), - Message: parts[4], - }, + } } } break From 1500970d074b7d6b7accb0757610fee7f6f1fbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Mon, 26 Aug 2024 19:49:35 +0200 Subject: [PATCH 12/13] Disable diagnostics if client does not support it. --- server/cmd/lsp/args.go | 13 ++++---- server/internal/lsp/server/Diagnostics.go | 36 ++++++++++++++--------- server/internal/lsp/server/Initialize.go | 4 +++ 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/server/cmd/lsp/args.go b/server/cmd/lsp/args.go index 4c6aca2..467dec9 100644 --- a/server/cmd/lsp/args.go +++ b/server/cmd/lsp/args.go @@ -37,12 +37,13 @@ func cmdLineArguments() (server.ServerOpts, bool) { } return server.ServerOpts{ - C3Version: c3VersionOpt, - C3CPath: c3cPathOpt, - DiagnosticsDelay: time.Duration(*diagnosticsDelay), - LogFilepath: logFilePathOpt, - Debug: *debug, - SendCrashReports: *sendCrashReports, + C3Version: c3VersionOpt, + C3CPath: c3cPathOpt, + DiagnosticsDelay: time.Duration(*diagnosticsDelay), + DiagnosticsEnabled: true, + LogFilepath: logFilePathOpt, + Debug: *debug, + SendCrashReports: *sendCrashReports, }, *showHelp } diff --git a/server/internal/lsp/server/Diagnostics.go b/server/internal/lsp/server/Diagnostics.go index 6b610f7..f94ea53 100644 --- a/server/internal/lsp/server/Diagnostics.go +++ b/server/internal/lsp/server/Diagnostics.go @@ -17,6 +17,9 @@ func (s *Server) RunDiagnostics(state *project_state.ProjectState, notify glsp.N if state.IsCalculatingDiagnostics() { return } + if !s.options.DiagnosticsEnabled { + return + } state.SetCalculateDiagnostics(true) @@ -41,18 +44,22 @@ func (s *Server) RunDiagnostics(state *project_state.ProjectState, notify glsp.N log.Println("output:", stdErr.String()) if err != nil { log.Println("An error:", err) - errorInfo := extractErrors(stdErr.String()) + errorInfo, diagnosticsDisabled := extractErrors(stdErr.String()) - diagnostics := []protocol.Diagnostic{ - errorInfo.Diagnostic, - } + if diagnosticsDisabled { + s.options.DiagnosticsEnabled = false + } else { + diagnostics := []protocol.Diagnostic{ + errorInfo.Diagnostic, + } - go notify( - protocol.ServerTextDocumentPublishDiagnostics, - protocol.PublishDiagnosticsParams{ - URI: errorInfo.File, - Diagnostics: diagnostics, - }) + go notify( + protocol.ServerTextDocumentPublishDiagnostics, + protocol.PublishDiagnosticsParams{ + URI: errorInfo.File, + Diagnostics: diagnostics, + }) + } } state.SetCalculateDiagnostics(false) @@ -70,8 +77,9 @@ type ErrorInfo struct { Diagnostic protocol.Diagnostic } -func extractErrors(output string) ErrorInfo { +func extractErrors(output string) (ErrorInfo, bool) { var errorInfo ErrorInfo + diagnosticsDisabled := false lines := strings.Split(output, "\n") for _, line := range lines { @@ -81,8 +89,8 @@ func extractErrors(output string) ErrorInfo { if parts[0] == "Error" { if len(parts) != 5 { // Disable future diagnostics, looks like c3c is an old version. - } - if len(parts) == 5 { + diagnosticsDisabled = true + } else { line, err := strconv.Atoi(parts[2]) if err != nil { continue @@ -112,5 +120,5 @@ func extractErrors(output string) ErrorInfo { } } - return errorInfo + return errorInfo, diagnosticsDisabled } diff --git a/server/internal/lsp/server/Initialize.go b/server/internal/lsp/server/Initialize.go index e98d577..c40b898 100644 --- a/server/internal/lsp/server/Initialize.go +++ b/server/internal/lsp/server/Initialize.go @@ -55,6 +55,10 @@ func (s *Server) Initialize(serverName string, serverVersion string, capabilitie s.RunDiagnostics(s.state, context.Notify, false) } + if *params.Capabilities.TextDocument.PublishDiagnostics.RelatedInformation == false { + s.options.DiagnosticsEnabled = false + } + return protocol.InitializeResult{ Capabilities: capabilities, ServerInfo: &protocol.InitializeResultServerInfo{ From c23271311229254e562f4743ce17b4a434cd57ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Ferr=C3=A0s?= Date: Tue, 27 Aug 2024 08:04:03 +0200 Subject: [PATCH 13/13] Fix debounce diagnostics behaving wrong. --- server/internal/lsp/project_state/state.go | 19 +++++-------------- server/internal/lsp/server/Diagnostics.go | 7 ------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/server/internal/lsp/project_state/state.go b/server/internal/lsp/project_state/state.go index a92f3a7..d70fc9d 100644 --- a/server/internal/lsp/project_state/state.go +++ b/server/internal/lsp/project_state/state.go @@ -17,12 +17,11 @@ import ( // ProjectState will be the center of knowledge of everything parsed. type ProjectState struct { - _documents map[string]*document.Document - documents *document.DocumentStore - symbolsTable symbols_table.SymbolsTable - indexByFQN IndexStore // TODO simplify this and use trie.Trie directly! - languageVersion Version - calculatingDiagnostics bool + _documents map[string]*document.Document + documents *document.DocumentStore + symbolsTable symbols_table.SymbolsTable + indexByFQN IndexStore // TODO simplify this and use trie.Trie directly! + languageVersion Version logger commonlog.Logger debugEnabled bool @@ -55,14 +54,6 @@ func (s *ProjectState) SetProjectRootURI(rootURI string) { s.documents.RootURI = rootURI } -func (s *ProjectState) IsCalculatingDiagnostics() bool { - return s.calculatingDiagnostics -} - -func (s *ProjectState) SetCalculateDiagnostics(running bool) { - s.calculatingDiagnostics = running -} - func (s *ProjectState) GetDocument(docId string) *document.Document { return s._documents[docId] } diff --git a/server/internal/lsp/server/Diagnostics.go b/server/internal/lsp/server/Diagnostics.go index f94ea53..952707d 100644 --- a/server/internal/lsp/server/Diagnostics.go +++ b/server/internal/lsp/server/Diagnostics.go @@ -14,15 +14,10 @@ import ( ) func (s *Server) RunDiagnostics(state *project_state.ProjectState, notify glsp.NotifyFunc, delay bool) { - if state.IsCalculatingDiagnostics() { - return - } if !s.options.DiagnosticsEnabled { return } - state.SetCalculateDiagnostics(true) - binary := "c3c" if s.options.C3CPath.IsSome() { binary = s.options.C3CPath.Get() @@ -61,8 +56,6 @@ func (s *Server) RunDiagnostics(state *project_state.ProjectState, notify glsp.N }) } } - - state.SetCalculateDiagnostics(false) } if delay {