Skip to content

Commit 621a763

Browse files
committed
lsp: autocompletion and hover information across module and file boundaries
1 parent a42bdb9 commit 621a763

10 files changed

+242
-110
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- compiler: implicit `src` module for package imports if folder exists
88
- compiler: `import root` imports the current package (imports `src` if folder exists)
99
- lsp: improved autocompletion and hover information
10+
- lsp: autocompletion and hover information across module and file boundaries
1011

1112
## v0.0.15
1213

langsrv/handler-completion.go

+25-37
Original file line numberDiff line numberDiff line change
@@ -11,81 +11,69 @@ import (
1111

1212
func textDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (interface{}, error) {
1313
rc := NewReqContextAtPosition(&params.TextDocumentPositionParams)
14-
sourceFile := rc.textDocumentEntry.sourceFile
15-
if sourceFile == nil {
16-
return nil, nil
17-
}
18-
globalDeclarations := sourceFile.Declarations
19-
for _, sameModuleFile := range rc.textDocumentEntry.module.Files {
20-
fileUrl := "file://" + sameModuleFile
21-
if rc.item.URI == fileUrl {
22-
continue
23-
}
24-
docEntry := langserver.documentCache.documents[fileUrl]
25-
if docEntry == nil || docEntry.sourceFile == nil {
26-
continue
27-
}
28-
29-
globalDeclarations = append(globalDeclarations, docEntry.sourceFile.ExportedDeclarations()...)
30-
}
3114

3215
completionItems := []protocol.CompletionItem{}
33-
for _, decl := range globalDeclarations {
34-
insertText := insertTextForDecl(decl)
16+
for _, imported := range rc.globalDeclarations(context) {
17+
insertText := insertTextForImportedDecl(imported)
3518
var detail string
36-
if decl.Meta().ModuleName != "" {
37-
detail = string(decl.Meta().ModuleName) +
19+
if imported.decl.Meta().ModuleName != "" {
20+
detail = string(imported.decl.Meta().ModuleName) +
3821
"." +
39-
string(decl.DeclName())
22+
string(imported.decl.DeclName())
4023
}
4124
completionItems = append(completionItems, protocol.CompletionItem{
42-
Label: string(decl.DeclName()),
43-
Kind: completionItemKindForDecl(decl),
25+
Label: string(imported.decl.DeclName()),
26+
Kind: completionItemKindForDecl(imported.decl),
4427
InsertText: &insertText,
4528
Detail: &detail,
46-
Documentation: documentationMarkupContentForDecl(decl),
29+
Documentation: documentationMarkupContentForDecl(imported.decl),
4730
})
4831
}
4932
return &completionItems, nil
5033
}
5134

52-
func insertTextForDecl(decl ast.Decl) string {
53-
switch decl := decl.(type) {
35+
func insertTextForImportedDecl(imported importedDecl) string {
36+
var importPrefix string
37+
if imported.importDecl != nil {
38+
importPrefix = fmt.Sprintf("%s.", imported.importDecl.DeclName())
39+
}
40+
41+
switch decl := imported.decl.(type) {
5442
case ast.DeclFunc:
55-
return insertTextForCallableDeclParams(decl, decl.Impl.Parameters)
43+
return insertTextForCallableDeclParams(decl, importPrefix, decl.Impl.Parameters)
5644
case ast.DeclExternFunc:
57-
return insertTextForCallableDeclParams(decl, decl.Parameters)
45+
return insertTextForCallableDeclParams(decl, importPrefix, decl.Parameters)
5846
case ast.DeclData:
59-
return insertTextForCallableDeclFields(decl, decl.Fields)
47+
return insertTextForCallableDeclFields(decl, importPrefix, decl.Fields)
6048
default:
6149
return string(decl.DeclName())
6250
}
6351
}
6452

65-
func insertTextForCallableDeclParams(decl ast.Decl, parameters []ast.DeclParameter) string {
53+
func insertTextForCallableDeclParams(decl ast.Decl, importPrefix string, parameters []ast.DeclParameter) string {
6654
paramNames := make([]string, len(parameters))
6755
for i, param := range parameters {
6856
paramNames[i] = string(param.DeclName())
6957
}
70-
return insertTextForCallableDecl(decl, paramNames)
58+
return insertTextForCallableDecl(decl, importPrefix, paramNames)
7159
}
7260

73-
func insertTextForCallableDeclFields(decl ast.Decl, fields []ast.DeclField) string {
61+
func insertTextForCallableDeclFields(decl ast.Decl, importPrefix string, fields []ast.DeclField) string {
7462
fieldNames := make([]string, len(fields))
7563
for i, param := range fields {
7664
fieldNames[i] = string(param.DeclName())
7765
}
78-
return insertTextForCallableDecl(decl, fieldNames)
66+
return insertTextForCallableDecl(decl, importPrefix, fieldNames)
7967
}
8068

81-
func insertTextForCallableDecl(decl ast.Decl, parameters []string) string {
69+
func insertTextForCallableDecl(decl ast.Decl, importPrefix string, parameters []string) string {
8270
if len(parameters) == 0 {
8371
return string(decl.DeclName())
8472
}
8573
if len(parameters) == 1 {
86-
return fmt.Sprintf("%s %s", decl.DeclName(), parameters[0])
74+
return fmt.Sprintf("%s%s %s", importPrefix, decl.DeclName(), parameters[0])
8775
}
88-
return fmt.Sprintf("(%s %s)", decl.DeclName(), strings.Join(parameters, ", "))
76+
return fmt.Sprintf("(%s%s %s)", importPrefix, decl.DeclName(), strings.Join(parameters, ", "))
8977
}
9078

9179
func documentationMarkupContentForDecl(decl ast.Decl) *protocol.MarkupContent {

langsrv/handler-declaration.go

+8-25
Original file line numberDiff line numberDiff line change
@@ -7,44 +7,27 @@ import (
77

88
func textDocumentDeclaration(context *glsp.Context, params *protocol.DeclarationParams) (interface{}, error) {
99
rc := NewReqContextAtPosition(&params.TextDocumentPositionParams)
10-
sourceFile := rc.textDocumentEntry.sourceFile
11-
if sourceFile == nil {
12-
return nil, nil
13-
}
10+
1411
token, _, err := rc.findToken()
1512
if err != nil {
1613
return nil, nil
1714
}
1815

19-
globalDeclarations := sourceFile.Declarations
20-
for _, sameModuleFile := range rc.textDocumentEntry.module.Files {
21-
fileUrl := "file://" + sameModuleFile
22-
if rc.item.URI == fileUrl {
23-
continue
24-
}
25-
docEntry := langserver.documentCache.documents[fileUrl]
26-
if docEntry == nil || docEntry.sourceFile == nil {
27-
continue
28-
}
29-
30-
globalDeclarations = append(globalDeclarations, docEntry.sourceFile.ExportedDeclarations()...)
31-
}
32-
33-
for _, decl := range globalDeclarations {
34-
if string(decl.DeclName()) != token || decl.Meta().Source == nil {
16+
for _, imported := range rc.globalDeclarations(context) {
17+
if string(imported.decl.DeclName()) != token || imported.decl.Meta().Source == nil {
3518
continue
3619
}
3720
return &[]protocol.LocationLink{
3821
{
39-
TargetURI: protocol.DocumentUri(decl.Meta().Source.FileName),
22+
TargetURI: protocol.DocumentUri(imported.decl.Meta().Source.FileName),
4023
TargetRange: protocol.Range{
4124
Start: protocol.Position{
42-
Line: uint32(decl.Meta().Source.Start.Line),
43-
Character: uint32(decl.Meta().Source.Start.Column),
25+
Line: uint32(imported.decl.Meta().Source.Start.Line),
26+
Character: uint32(imported.decl.Meta().Source.Start.Column),
4427
},
4528
End: protocol.Position{
46-
Line: uint32(decl.Meta().Source.End.Line),
47-
Character: uint32(decl.Meta().Source.End.Line),
29+
Line: uint32(imported.decl.Meta().Source.End.Line),
30+
Character: uint32(imported.decl.Meta().Source.End.Line),
4831
},
4932
},
5033
},

langsrv/handler-definition.go

+3-19
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,14 @@ import (
77

88
func textDocumentDefinition(context *glsp.Context, params *protocol.DefinitionParams) (interface{}, error) {
99
rc := NewReqContextAtPosition(&params.TextDocumentPositionParams)
10-
sourceFile := rc.textDocumentEntry.sourceFile
11-
if sourceFile == nil {
12-
return nil, nil
13-
}
10+
1411
token, _, err := rc.findToken()
1512
if err != nil && token == "" {
1613
return nil, nil
1714
}
1815

19-
globalDeclarations := sourceFile.Declarations
20-
for _, sameModuleFile := range rc.textDocumentEntry.module.Files {
21-
fileUrl := "file://" + sameModuleFile
22-
if rc.item.URI == fileUrl {
23-
continue
24-
}
25-
docEntry := langserver.documentCache.documents[fileUrl]
26-
if docEntry == nil || docEntry.sourceFile == nil {
27-
continue
28-
}
29-
30-
globalDeclarations = append(globalDeclarations, docEntry.sourceFile.ExportedDeclarations()...)
31-
}
32-
33-
for _, decl := range globalDeclarations {
16+
for _, imported := range rc.globalDeclarations(context) {
17+
decl := imported.decl
3418
if string(decl.DeclName()) != token || decl.Meta().Source == nil {
3519
continue
3620
}

langsrv/handler-hover.go

+3-19
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,14 @@ import (
77

88
func textDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
99
rc := NewReqContextAtPosition(&params.TextDocumentPositionParams)
10-
sourceFile := rc.sourceFile
11-
if sourceFile == nil {
12-
return nil, nil
13-
}
10+
1411
name, tokenRange, err := rc.findToken()
1512
if err != nil && tokenRange == nil {
1613
return nil, nil
1714
}
1815

19-
globalDeclarations := sourceFile.Declarations
20-
for _, sameModuleFile := range rc.textDocumentEntry.module.Files {
21-
fileUrl := "file://" + sameModuleFile
22-
if rc.item.URI == fileUrl {
23-
continue
24-
}
25-
docEntry := langserver.documentCache.documents[fileUrl]
26-
if docEntry == nil || docEntry.sourceFile == nil {
27-
continue
28-
}
29-
30-
globalDeclarations = append(globalDeclarations, docEntry.sourceFile.ExportedDeclarations()...)
31-
}
32-
33-
for _, decl := range globalDeclarations {
16+
for _, imported := range rc.globalDeclarations(context) {
17+
decl := imported.decl
3418
if string(decl.DeclName()) != name {
3519
continue
3620
}

langsrv/handler-initialized.go

+15
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
package langsrv
22

33
import (
4+
"path"
5+
"path/filepath"
6+
"strings"
7+
48
"github.com/tliron/glsp"
59
protocol "github.com/tliron/glsp/protocol_3_16"
610
)
711

812
func initialized(context *glsp.Context, params *protocol.InitializedParams) error {
13+
for _, root := range langserver.workspaceRoots {
14+
matches, err := filepath.Glob(path.Join(strings.TrimPrefix("file://", root), "*/*.lithia"))
15+
if err != nil {
16+
langserver.server.Log.Errorf("package detection failed, due %s", err)
17+
continue
18+
}
19+
for _, match := range matches {
20+
mod := langserver.resolver.ResolvePackageAndModuleForReferenceFile(match)
21+
openModuleTextDocumentsIfNeeded(context, mod)
22+
}
23+
}
924
return nil
1025
}

langsrv/handler-text-document-did-change.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ func textDocumentDidChange(context *glsp.Context, params *protocol.DidChangeText
2626
syntaxErrs = append(syntaxErrs, errs...)
2727
langserver.documentCache.documents[params.TextDocument.URI].fileParser = fileParser
2828
langserver.documentCache.documents[params.TextDocument.URI].sourceFile = sourceFile
29-
publishSyntaxErrorDiagnostics(context, params.TextDocument.URI, uint32(params.TextDocument.Version), syntaxErrs)
29+
30+
analyzeErrs := analyzeErrorsForSourceFile(context, mod, *sourceFile)
31+
publishSyntaxErrorDiagnostics(context, params.TextDocument.URI, uint32(params.TextDocument.Version), syntaxErrs, analyzeErrs)
3032
return nil
3133
}

langsrv/handler-text-document-did-open.go

+29-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package langsrv
22

33
import (
4+
"fmt"
45
"os"
56

67
"github.com/tliron/glsp"
78
protocol "github.com/tliron/glsp/protocol_3_16"
9+
"github.com/vknabel/lithia/ast"
810
"github.com/vknabel/lithia/parser"
911
"github.com/vknabel/lithia/resolution"
1012
)
@@ -27,7 +29,8 @@ func textDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocu
2729
module: mod,
2830
}
2931

30-
publishSyntaxErrorDiagnostics(context, params.TextDocument.URI, uint32(params.TextDocument.Version), syntaxErrs)
32+
analyzeErrs := analyzeErrorsForSourceFile(context, mod, *sourceFile)
33+
publishSyntaxErrorDiagnostics(context, params.TextDocument.URI, uint32(params.TextDocument.Version), syntaxErrs, analyzeErrs)
3134
return nil
3235
}
3336

@@ -46,9 +49,11 @@ func openModuleTextDocumentsIfNeeded(context *glsp.Context, mod resolution.Resol
4649
syntaxErrs := make([]parser.SyntaxError, 0)
4750
bytes, err := os.ReadFile(filePath)
4851
if err != nil {
52+
langserver.server.Log.Errorf("failed to read %s, due %s", fileUri, err.Error())
4953
continue
5054
}
51-
fileParser, errs := lithiaParser.Parse(mod.AbsoluteModuleName(), string(fileUri), string(bytes))
55+
contents := string(bytes)
56+
fileParser, errs := lithiaParser.Parse(mod.AbsoluteModuleName(), string(fileUri), contents)
5257
syntaxErrs = append(syntaxErrs, errs...)
5358
sourceFile, errs := fileParser.ParseSourceFile()
5459
syntaxErrs = append(syntaxErrs, errs...)
@@ -60,6 +65,27 @@ func openModuleTextDocumentsIfNeeded(context *glsp.Context, mod resolution.Resol
6065
module: mod,
6166
}
6267

63-
publishSyntaxErrorDiagnosticsForFile(context, fileUri, syntaxErrs)
68+
analyzeErrs := analyzeErrorsForSourceFile(context, mod, *sourceFile)
69+
publishSyntaxErrorDiagnosticsForFile(context, fileUri, syntaxErrs, analyzeErrs)
6470
}
6571
}
72+
73+
func analyzeErrorsForSourceFile(context *glsp.Context, mod resolution.ResolvedModule, sourceFile ast.SourceFile) []analyzeError {
74+
analyzeErrs := make([]analyzeError, 0)
75+
for _, decl := range sourceFile.Declarations {
76+
if _, ok := decl.(ast.DeclImport); !ok {
77+
continue
78+
}
79+
importDecl := decl.(ast.DeclImport)
80+
resolvedModule, err := langserver.resolver.ResolveModuleFromPackage(mod.Package(), importDecl.ModuleName)
81+
if err != nil {
82+
analyzeErrs = append(
83+
analyzeErrs,
84+
newAnalyzeErrorAtLocation("error", fmt.Sprintf("module %s not found", importDecl.ModuleName), importDecl.Meta().Source),
85+
)
86+
} else {
87+
openModuleTextDocumentsIfNeeded(context, resolvedModule)
88+
}
89+
}
90+
return analyzeErrs
91+
}

0 commit comments

Comments
 (0)