diff --git a/ls/ls.go b/ls/ls.go index b487f087..40b497e 100644 --- a/ls/ls.go +++ b/ls/ls.go @@ -83,6 +83,15 @@ type Config struct { Jobs int } +var extToFileType = map[string]string{ + ".h": "cpp", + ".hpp": "cpp", +} + +type translationOpts struct { + loadRelToIde bool +} + var yellow = color.New(color.FgHiYellow) func (ls *INOLanguageServer) writeLock(logger jsonrpc.FunctionLogger, requireClangd bool) { @@ -603,7 +612,8 @@ func (ls *INOLanguageServer) textDocumentDefinitionReqFromIDE(ctx context.Contex var ideLocations []lsp.Location if clangLocations != nil { - ideLocations, err = ls.clang2IdeLocationsArray(logger, clangLocations) + opts := translationOpts{loadRelToIde: true} + ideLocations, err = ls.clang2IdeLocationsArray2(logger, clangLocations, &opts) if err != nil { logger.Logf("Error: %v", err) ls.Close() @@ -989,15 +999,13 @@ func (ls *INOLanguageServer) exitNotifFromIDE(logger jsonrpc.FunctionLogger) { ls.Close() } -func (ls *INOLanguageServer) textDocumentDidOpenNotifFromIDE(logger jsonrpc.FunctionLogger, ideParam *lsp.DidOpenTextDocumentParams) { - ls.writeLock(logger, true) - defer ls.writeUnlock(logger) +func (ls *INOLanguageServer) implTextDocumentDidOpenNotifFromIDE(logger jsonrpc.FunctionLogger, ideParam lsp.TextDocumentItem) *jsonrpc.ResponseError { - ideTextDocItem := ideParam.TextDocument + ideTextDocItem := ideParam clangURI, _, err := ls.ide2ClangDocumentURI(logger, ideTextDocItem.URI) if err != nil { logger.Logf("Error: %s", err) - return + return &jsonrpc.ResponseError{Code: jsonrpc.ErrorCodesInternalError, Message: err.Error()} } if ls.ideURIIsPartOfTheSketch(ideTextDocItem.URI) { @@ -1017,7 +1025,7 @@ func (ls *INOLanguageServer) textDocumentDidOpenNotifFromIDE(logger jsonrpc.Func // Notify clangd that sketchCpp has been opened only once if ls.sketchTrackedFilesCount != 1 { logger.Logf("Clang already notified, do not notify it anymore") - return + return nil } } @@ -1045,7 +1053,17 @@ func (ls *INOLanguageServer) textDocumentDidOpenNotifFromIDE(logger jsonrpc.Func logger.Logf("Error sending notification to clangd server: %v", err) logger.Logf("Please restart the language server.") ls.Close() + return &jsonrpc.ResponseError{Code: jsonrpc.ErrorCodesInternalError, Message: err.Error()} + } + return nil + +} + +func (ls *INOLanguageServer) textDocumentDidOpenNotifFromIDE(logger jsonrpc.FunctionLogger, ideParam *lsp.DidOpenTextDocumentParams) { + ls.writeLock(logger, true) + defer ls.writeUnlock(logger) + ls.implTextDocumentDidOpenNotifFromIDE(logger, ideParam.TextDocument) } func (ls *INOLanguageServer) textDocumentDidChangeNotifFromIDE(logger jsonrpc.FunctionLogger, ideParams *lsp.DidChangeTextDocumentParams) { @@ -1619,6 +1637,16 @@ type UnknownURIError struct { URI lsp.DocumentURI } +// UnknownFileExtensionError when a file extension isn't recognized +// and a file with it has to be opened. +type UnknownFileExtensionError struct { + extension string +} + func (e *UnknownURIError) Error() string { return "Document is not available: " + e.URI.String() } + +func (e *UnknownFileExtensionError) Error() string { + return "Unknown file extension " + e.extension +} diff --git a/ls/ls_clang_to_ide.go b/ls/ls_clang_to_ide.go index 882d1b5..d8ef4ca 100644 --- a/ls/ls_clang_to_ide.go +++ b/ls/ls_clang_to_ide.go @@ -27,12 +27,16 @@ func (ls *INOLanguageServer) clangURIRefersToIno(clangURI lsp.DocumentURI) bool return clangURI.AsPath().EquivalentTo(ls.buildSketchCpp) } +func (ls *INOLanguageServer) clang2IdeRangeAndDocumentURI(logger jsonrpc.FunctionLogger, clangURI lsp.DocumentURI, clangRange lsp.Range) (lsp.DocumentURI, lsp.Range, bool, error) { + return ls.clang2IdeRangeAndDocumentURI2(logger, clangURI, clangRange, nil) +} + // Convert Range and DocumentURI from Clang to IDE. // Returns: // - The IDE DocumentURI and Range // - a boolean that is true if the clang range is in the preprocessed area of the sketch // - an error -func (ls *INOLanguageServer) clang2IdeRangeAndDocumentURI(logger jsonrpc.FunctionLogger, clangURI lsp.DocumentURI, clangRange lsp.Range) (lsp.DocumentURI, lsp.Range, bool, error) { +func (ls *INOLanguageServer) clang2IdeRangeAndDocumentURI2(logger jsonrpc.FunctionLogger, clangURI lsp.DocumentURI, clangRange lsp.Range, opts *translationOpts) (lsp.DocumentURI, lsp.Range, bool, error) { // Sketchbook/Sketch/Sketch.ino <-> build-path/sketch/Sketch.ino.cpp // Sketchbook/Sketch/AnotherTab.ino <-> build-path/sketch/Sketch.ino.cpp (different section from above) if ls.clangURIRefersToIno(clangURI) { @@ -80,7 +84,25 @@ func (ls *INOLanguageServer) clang2IdeRangeAndDocumentURI(logger jsonrpc.Functio return lsp.NilURI, lsp.NilRange, false, err } idePath := ls.sketchRoot.JoinPath(rel).String() - ideURI, err := ls.idePathToIdeURI(logger, idePath) + + var ideURI lsp.DocumentURI + if opts == nil || !opts.loadRelToIde { + ideURI, err = ls.idePathToIdeURI(logger, idePath) + } else { + doc, ok := ls.trackedIdeDocs[idePath] + if !ok { + var shouldOpen bool + doc, shouldOpen, err = makeTextDocumentItem(logger, idePath) + if err != nil { + logger.Logf("ERROR: could not open '%s': %s", idePath, err) + } + if shouldOpen { + ls.implTextDocumentDidOpenNotifFromIDE(logger, doc) + } + + } + ideURI = doc.URI + } if ideRange.End.Line > 0 { ideRange.End.Line-- } @@ -296,9 +318,12 @@ func (ls *INOLanguageServer) cland2IdeTextEdits(logger jsonrpc.FunctionLogger, c } func (ls *INOLanguageServer) clang2IdeLocationsArray(logger jsonrpc.FunctionLogger, clangLocations []lsp.Location) ([]lsp.Location, error) { + return ls.clang2IdeLocationsArray2(logger, clangLocations, nil) +} +func (ls *INOLanguageServer) clang2IdeLocationsArray2(logger jsonrpc.FunctionLogger, clangLocations []lsp.Location, opts *translationOpts) ([]lsp.Location, error) { ideLocations := []lsp.Location{} for _, clangLocation := range clangLocations { - ideLocation, inPreprocessed, err := ls.clang2IdeLocation(logger, clangLocation) + ideLocation, inPreprocessed, err := ls.clang2IdeLocation2(logger, clangLocation, opts) if err != nil { logger.Logf("ERROR converting location %s: %s", clangLocation, err) return nil, err @@ -313,7 +338,10 @@ func (ls *INOLanguageServer) clang2IdeLocationsArray(logger jsonrpc.FunctionLogg } func (ls *INOLanguageServer) clang2IdeLocation(logger jsonrpc.FunctionLogger, clangLocation lsp.Location) (lsp.Location, bool, error) { - ideURI, ideRange, inPreprocessed, err := ls.clang2IdeRangeAndDocumentURI(logger, clangLocation.URI, clangLocation.Range) + return ls.clang2IdeLocation2(logger, clangLocation, nil) +} +func (ls *INOLanguageServer) clang2IdeLocation2(logger jsonrpc.FunctionLogger, clangLocation lsp.Location, opts *translationOpts) (lsp.Location, bool, error) { + ideURI, ideRange, inPreprocessed, err := ls.clang2IdeRangeAndDocumentURI2(logger, clangLocation.URI, clangLocation.Range, opts) return lsp.Location{ URI: ideURI, Range: ideRange, diff --git a/ls/ls_ide_to_clang.go b/ls/ls_ide_to_clang.go index e6ccc11..55a51a0 100644 --- a/ls/ls_ide_to_clang.go +++ b/ls/ls_ide_to_clang.go @@ -17,13 +17,43 @@ package ls import ( "fmt" + "os" + "path/filepath" "github.com/arduino/arduino-language-server/sourcemapper" "go.bug.st/lsp" "go.bug.st/lsp/jsonrpc" ) +func getFileType(ext string) string { + if fileType, ok := extToFileType[ext]; ok { + return fileType + } + return "unknown" +} func (ls *INOLanguageServer) idePathToIdeURI(logger jsonrpc.FunctionLogger, inoPath string) (lsp.DocumentURI, error) { + return ls.idePathToIdeURI2(logger, inoPath, nil) +} + +func makeTextDocumentItem(logger jsonrpc.FunctionLogger, path string) (lsp.TextDocumentItem, bool, error) { + ext := filepath.Ext(path) + filetype := getFileType(path) + uri := lsp.NewDocumentURI(path) + if filetype == "unknown" { + return lsp.TextDocumentItem{URI: uri, LanguageID: filetype}, false, &UnknownFileExtensionError{ext} + } + languageID := filetype + version := 0 + + text, err := os.ReadFile(path) + if err != nil { + logger.Logf("Could not read file: %v", err) + return lsp.TextDocumentItem{}, false, err + } + return lsp.TextDocumentItem{URI: uri, LanguageID: languageID, Version: version, Text: string(text)}, true, nil + +} +func (ls *INOLanguageServer) idePathToIdeURI2(logger jsonrpc.FunctionLogger, inoPath string) (lsp.DocumentURI, error) { if inoPath == sourcemapper.NotIno.File { return sourcemapper.NotInoURI, nil }