From 206f5f4d68cc995672859c1f9c1a5ed950185ede Mon Sep 17 00:00:00 2001 From: brotifypacha Date: Wed, 3 Jul 2024 18:39:34 +0300 Subject: [PATCH 1/8] show documentation for each known item in completion --- components/completion.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/components/completion.go b/components/completion.go index 965ec8d..18072ba 100644 --- a/components/completion.go +++ b/components/completion.go @@ -84,19 +84,39 @@ func CompletionInThisFile(file view.ProtoFile) (result *[]defines.CompletionItem kindEnum := defines.CompletionItemKindEnum res := protoKeywordCompletionItems for _, enums := range file.Proto().Enums() { + + doc := formatHover(SymbolDefinition{ + Type: DefinitionTypeEnum, + Enum: enums, + }) + res = append(res, defines.CompletionItem{ Label: enums.Protobuf().Name, Kind: &kindEnum, InsertText: &enums.Protobuf().Name, + Documentation: defines.MarkupContent{ + Kind: defines.MarkupKindMarkdown, + Value: doc, + }, }) } kindClass := defines.CompletionItemKindClass for _, message := range file.Proto().Messages() { + + doc := formatHover(SymbolDefinition{ + Type: DefinitionTypeMessage, + Message: message, + }) + res = append(res, defines.CompletionItem{ Label: message.Protobuf().Name, Kind: &kindClass, InsertText: &message.Protobuf().Name, + Documentation: defines.MarkupContent{ + Kind: defines.MarkupKindMarkdown, + Value: doc, + }, }) } return &res, nil From ee06abb134c7342d82ac6a609ba7c164a48dd5d7 Mon Sep 17 00:00:00 2001 From: brotifypacha Date: Thu, 4 Jul 2024 09:54:45 +0300 Subject: [PATCH 2/8] extract package name completion to a separate function --- components/completion.go | 59 +++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/components/completion.go b/components/completion.go index 18072ba..8ef2768 100644 --- a/components/completion.go +++ b/components/completion.go @@ -37,28 +37,7 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. word := getWord(line_str, int(req.Position.Character-1), false) if req.Context.TriggerKind != defines.CompletionTriggerKindTriggerCharacter { res, err := CompletionInThisFile(proto_file) - - kindModule := defines.CompletionItemKindModule - for _, im := range proto_file.Proto().Imports() { - import_uri, err := view.GetDocumentUriFromImportPath(req.TextDocument.Uri, im.ProtoImport.Filename) - if err != nil { - continue - } - - file, err := view.ViewManager.GetFile(import_uri) - if err != nil { - continue - } - - if len(file.Proto().Packages()) > 0 { - *res = append(*res, defines.CompletionItem{ - Label: file.Proto().Packages()[0].ProtoPackage.Name, - Kind: &kindModule, - InsertText: &file.Proto().Packages()[0].ProtoPackage.Name, - }) - } - - } + *res = append(*res, GetImportedPackages(proto_file)...) return res, err } @@ -80,6 +59,42 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. return nil, nil } +func GetImportedPackages(proto_file view.ProtoFile) (res []defines.CompletionItem) { + unique := make(map[string]struct{}) + for _, im := range proto_file.Proto().Imports() { + import_uri, err := view.GetDocumentUriFromImportPath(proto_file.URI(), im.ProtoImport.Filename) + if err != nil { + continue + } + + file, err := view.ViewManager.GetFile(import_uri) + if err != nil { + continue + } + + if len(file.Proto().Packages()) == 0 { + continue + } + + packageName := file.Proto().Packages()[0].ProtoPackage.Name + + if _, exist := unique[packageName]; exist { + continue + } + + kindModule := defines.CompletionItemKindModule + unique[packageName] = struct{}{} + res = append(res, defines.CompletionItem{ + Label: packageName, + Kind: &kindModule, + InsertText: &packageName, + }) + + } + + return res +} + func CompletionInThisFile(file view.ProtoFile) (result *[]defines.CompletionItem, err error) { kindEnum := defines.CompletionItemKindEnum res := protoKeywordCompletionItems From 02d3fd6c07a45fea796667e593de5e25c08d42ce Mon Sep 17 00:00:00 2001 From: brotifypacha Date: Thu, 4 Jul 2024 09:56:33 +0300 Subject: [PATCH 3/8] should also check full wordWithDot equality --- components/completion.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/completion.go b/components/completion.go index 8ef2768..51d2d48 100644 --- a/components/completion.go +++ b/components/completion.go @@ -35,6 +35,7 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. } line_str := proto_file.ReadLine(int(req.Position.Line)) word := getWord(line_str, int(req.Position.Character-1), false) + wordWithDot := getWord(line_str, int(req.Position.Character-1), true) if req.Context.TriggerKind != defines.CompletionTriggerKindTriggerCharacter { res, err := CompletionInThisFile(proto_file) *res = append(*res, GetImportedPackages(proto_file)...) @@ -52,7 +53,12 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. continue } - if len(file.Proto().Packages()) > 0 && file.Proto().Packages()[0].ProtoPackage.Name == word { + if len(file.Proto().Packages()) == 0 { + continue + } + + packageName := file.Proto().Packages()[0].ProtoPackage.Name + if packageName == word || packageName+"." == wordWithDot { return CompletionInThisFile(file) } } From f4d5068853b74dac5658a0f9d7dcc34ca947d70c Mon Sep 17 00:00:00 2001 From: brotifypacha Date: Thu, 4 Jul 2024 10:12:58 +0300 Subject: [PATCH 4/8] multiple imports could point to the same package --- components/completion.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/components/completion.go b/components/completion.go index 51d2d48..4cdac6a 100644 --- a/components/completion.go +++ b/components/completion.go @@ -36,12 +36,18 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. line_str := proto_file.ReadLine(int(req.Position.Line)) word := getWord(line_str, int(req.Position.Character-1), false) wordWithDot := getWord(line_str, int(req.Position.Character-1), true) + + var res []defines.CompletionItem + if req.Context.TriggerKind != defines.CompletionTriggerKindTriggerCharacter { - res, err := CompletionInThisFile(proto_file) - *res = append(*res, GetImportedPackages(proto_file)...) - return res, err + res = protoKeywordCompletionItems + res = append(res, CompletionInThisFile(proto_file)...) + res = append(res, GetImportedPackages(proto_file)...) + + return &res, err } + for _, im := range proto_file.Proto().Imports() { import_uri, err := view.GetDocumentUriFromImportPath(req.TextDocument.Uri, im.ProtoImport.Filename) if err != nil { @@ -59,10 +65,10 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. packageName := file.Proto().Packages()[0].ProtoPackage.Name if packageName == word || packageName+"." == wordWithDot { - return CompletionInThisFile(file) + res = append(res, CompletionInThisFile(file)...) } } - return nil, nil + return &res, nil } func GetImportedPackages(proto_file view.ProtoFile) (res []defines.CompletionItem) { @@ -101,9 +107,8 @@ func GetImportedPackages(proto_file view.ProtoFile) (res []defines.CompletionIte return res } -func CompletionInThisFile(file view.ProtoFile) (result *[]defines.CompletionItem, err error) { +func CompletionInThisFile(file view.ProtoFile) (res []defines.CompletionItem) { kindEnum := defines.CompletionItemKindEnum - res := protoKeywordCompletionItems for _, enums := range file.Proto().Enums() { doc := formatHover(SymbolDefinition{ @@ -140,5 +145,5 @@ func CompletionInThisFile(file view.ProtoFile) (result *[]defines.CompletionItem }, }) } - return &res, nil + return res } From 8340502160c2524c972261d1be62478e013192e6 Mon Sep 17 00:00:00 2001 From: brotifypacha Date: Thu, 4 Jul 2024 10:15:32 +0300 Subject: [PATCH 5/8] reuse kind types --- components/completion.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/components/completion.go b/components/completion.go index 4cdac6a..0345f4f 100644 --- a/components/completion.go +++ b/components/completion.go @@ -7,10 +7,16 @@ import ( "github.com/TobiasYin/go-lsp/lsp/defines" ) -var protoKeywordCompletionItems []defines.CompletionItem +var ( + protoKeywordCompletionItems []defines.CompletionItem + + kindKeyword = defines.CompletionItemKindKeyword + kindModule = defines.CompletionItemKindModule + kindClass = defines.CompletionItemKindClass + kindEnum = defines.CompletionItemKindEnum +) func init() { - kindKeyword := defines.CompletionItemKindKeyword for _, keyword := range []string{"string", "bytes", "double", "float", "int32", "int64", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64", "bool", "message", "enum", "service", "rpc", "optional", "repeated", "required", @@ -94,7 +100,6 @@ func GetImportedPackages(proto_file view.ProtoFile) (res []defines.CompletionIte continue } - kindModule := defines.CompletionItemKindModule unique[packageName] = struct{}{} res = append(res, defines.CompletionItem{ Label: packageName, @@ -108,7 +113,6 @@ func GetImportedPackages(proto_file view.ProtoFile) (res []defines.CompletionIte } func CompletionInThisFile(file view.ProtoFile) (res []defines.CompletionItem) { - kindEnum := defines.CompletionItemKindEnum for _, enums := range file.Proto().Enums() { doc := formatHover(SymbolDefinition{ @@ -127,7 +131,6 @@ func CompletionInThisFile(file view.ProtoFile) (res []defines.CompletionItem) { }) } - kindClass := defines.CompletionItemKindClass for _, message := range file.Proto().Messages() { doc := formatHover(SymbolDefinition{ From aa4885f9dbb577fffaa241207b62bbffad81a889 Mon Sep 17 00:00:00 2001 From: brotifypacha Date: Thu, 4 Jul 2024 12:03:09 +0300 Subject: [PATCH 6/8] fix no completion when in the middle of multi.element package --- components/completion.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/components/completion.go b/components/completion.go index 0345f4f..d0c1995 100644 --- a/components/completion.go +++ b/components/completion.go @@ -3,6 +3,7 @@ package components import ( "context" "pls/proto/view" + "strings" "github.com/TobiasYin/go-lsp/lsp/defines" ) @@ -45,12 +46,19 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. var res []defines.CompletionItem - if req.Context.TriggerKind != defines.CompletionTriggerKindTriggerCharacter { + // suggest imported packages that match what user has typed so far + // + // word = "google" + // suggest = [ google.protobuf , google.api ] + for _, pkg := range GetImportedPackages(proto_file) { + if strings.HasPrefix(*pkg.InsertText, wordWithDot) { + res = append(res, pkg) + } + } - res = protoKeywordCompletionItems + if req.Context.TriggerKind != defines.CompletionTriggerKindTriggerCharacter { + res = append(res, protoKeywordCompletionItems...) res = append(res, CompletionInThisFile(proto_file)...) - res = append(res, GetImportedPackages(proto_file)...) - return &res, err } From a4f9dd3fca63b88ccb389ed128f85d3061e05a22 Mon Sep 17 00:00:00 2001 From: brotifypacha Date: Thu, 4 Jul 2024 14:11:35 +0300 Subject: [PATCH 7/8] extract package symbol completion to a separate function --- components/completion.go | 48 +++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/components/completion.go b/components/completion.go index d0c1995..8fd1617 100644 --- a/components/completion.go +++ b/components/completion.go @@ -41,7 +41,6 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. return nil, nil } line_str := proto_file.ReadLine(int(req.Position.Line)) - word := getWord(line_str, int(req.Position.Character-1), false) wordWithDot := getWord(line_str, int(req.Position.Character-1), true) var res []defines.CompletionItem @@ -62,26 +61,9 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. return &res, err } - for _, im := range proto_file.Proto().Imports() { - import_uri, err := view.GetDocumentUriFromImportPath(req.TextDocument.Uri, im.ProtoImport.Filename) - if err != nil { - continue - } - - file, err := view.ViewManager.GetFile(import_uri) - if err != nil { - continue - } + packageName := strings.TrimSuffix(wordWithDot, ".") + res = append(res, CompletionInPackage(proto_file, packageName)...) - if len(file.Proto().Packages()) == 0 { - continue - } - - packageName := file.Proto().Packages()[0].ProtoPackage.Name - if packageName == word || packageName+"." == wordWithDot { - res = append(res, CompletionInThisFile(file)...) - } - } return &res, nil } @@ -120,6 +102,32 @@ func GetImportedPackages(proto_file view.ProtoFile) (res []defines.CompletionIte return res } +func CompletionInPackage(file view.ProtoFile, packageName string) (res []defines.CompletionItem) { + + for _, im := range file.Proto().Imports() { + import_uri, err := view.GetDocumentUriFromImportPath(file.URI(), im.ProtoImport.Filename) + if err != nil { + continue + } + + imported_file, err := view.ViewManager.GetFile(import_uri) + if err != nil { + continue + } + + if len(imported_file.Proto().Packages()) == 0 { + continue + } + + importedPackage := imported_file.Proto().Packages()[0].ProtoPackage.Name + if importedPackage == packageName { + res = append(res, CompletionInThisFile(imported_file)...) + } + + } + return res +} + func CompletionInThisFile(file view.ProtoFile) (res []defines.CompletionItem) { for _, enums := range file.Proto().Enums() { From ab4feac65041f9ab14d73bc6dcecccf900873016 Mon Sep 17 00:00:00 2001 From: brotifypacha Date: Thu, 4 Jul 2024 12:29:46 +0300 Subject: [PATCH 8/8] add completion timeout --- components/completion.go | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/components/completion.go b/components/completion.go index 8fd1617..29298c8 100644 --- a/components/completion.go +++ b/components/completion.go @@ -4,6 +4,7 @@ import ( "context" "pls/proto/view" "strings" + "time" "github.com/TobiasYin/go-lsp/lsp/defines" ) @@ -15,6 +16,8 @@ var ( kindModule = defines.CompletionItemKindModule kindClass = defines.CompletionItemKindClass kindEnum = defines.CompletionItemKindEnum + + defaultCompletionTimeout = time.Millisecond * 500 ) func init() { @@ -36,6 +39,9 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. if !view.IsProtoFile(req.TextDocument.Uri) { return nil, nil } + ctx, cancel := context.WithTimeout(ctx, defaultCompletionTimeout) + defer cancel() + proto_file, err := view.ViewManager.GetFile(req.TextDocument.Uri) if err != nil || proto_file.Proto() == nil { return nil, nil @@ -49,7 +55,7 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. // // word = "google" // suggest = [ google.protobuf , google.api ] - for _, pkg := range GetImportedPackages(proto_file) { + for _, pkg := range GetImportedPackages(ctx, proto_file) { if strings.HasPrefix(*pkg.InsertText, wordWithDot) { res = append(res, pkg) } @@ -57,19 +63,25 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines. if req.Context.TriggerKind != defines.CompletionTriggerKindTriggerCharacter { res = append(res, protoKeywordCompletionItems...) - res = append(res, CompletionInThisFile(proto_file)...) + res = append(res, CompletionInThisFile(ctx, proto_file)...) return &res, err } packageName := strings.TrimSuffix(wordWithDot, ".") - res = append(res, CompletionInPackage(proto_file, packageName)...) + res = append(res, CompletionInPackage(ctx, proto_file, packageName)...) return &res, nil } -func GetImportedPackages(proto_file view.ProtoFile) (res []defines.CompletionItem) { +func GetImportedPackages(ctx context.Context, proto_file view.ProtoFile) (res []defines.CompletionItem) { unique := make(map[string]struct{}) for _, im := range proto_file.Proto().Imports() { + select { + case <-ctx.Done(): + return + default: + } + import_uri, err := view.GetDocumentUriFromImportPath(proto_file.URI(), im.ProtoImport.Filename) if err != nil { continue @@ -102,9 +114,14 @@ func GetImportedPackages(proto_file view.ProtoFile) (res []defines.CompletionIte return res } -func CompletionInPackage(file view.ProtoFile, packageName string) (res []defines.CompletionItem) { - +func CompletionInPackage(ctx context.Context, file view.ProtoFile, packageName string) (res []defines.CompletionItem) { for _, im := range file.Proto().Imports() { + select { + case <-ctx.Done(): + return + default: + } + import_uri, err := view.GetDocumentUriFromImportPath(file.URI(), im.ProtoImport.Filename) if err != nil { continue @@ -121,14 +138,20 @@ func CompletionInPackage(file view.ProtoFile, packageName string) (res []defines importedPackage := imported_file.Proto().Packages()[0].ProtoPackage.Name if importedPackage == packageName { - res = append(res, CompletionInThisFile(imported_file)...) + res = append(res, CompletionInThisFile(ctx, imported_file)...) } } return res } -func CompletionInThisFile(file view.ProtoFile) (res []defines.CompletionItem) { +func CompletionInThisFile(ctx context.Context, file view.ProtoFile) (res []defines.CompletionItem) { + select { + case <-ctx.Done(): + return + default: + } + for _, enums := range file.Proto().Enums() { doc := formatHover(SymbolDefinition{