Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve completion #17

Merged
merged 8 commits into from
Jul 5, 2024
158 changes: 123 additions & 35 deletions components/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,24 @@ package components
import (
"context"
"pls/proto/view"
"strings"
"time"

"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

defaultCompletionTimeout = time.Millisecond * 500
)

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",
Expand All @@ -29,41 +39,50 @@ 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
}
line_str := proto_file.ReadLine(int(req.Position.Line))
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,
})
}
wordWithDot := getWord(line_str, int(req.Position.Character-1), true)

var res []defines.CompletionItem

// suggest imported packages that match what user has typed so far
//
// word = "google"
// suggest = [ google.protobuf , google.api ]
for _, pkg := range GetImportedPackages(ctx, proto_file) {
if strings.HasPrefix(*pkg.InsertText, wordWithDot) {
res = append(res, pkg)
}
}

return res, err
if req.Context.TriggerKind != defines.CompletionTriggerKindTriggerCharacter {
res = append(res, protoKeywordCompletionItems...)
res = append(res, CompletionInThisFile(ctx, proto_file)...)
return &res, err
}

packageName := strings.TrimSuffix(wordWithDot, ".")
res = append(res, CompletionInPackage(ctx, proto_file, packageName)...)

return &res, nil
}

func GetImportedPackages(ctx context.Context, proto_file view.ProtoFile) (res []defines.CompletionItem) {
unique := make(map[string]struct{})
for _, im := range proto_file.Proto().Imports() {
import_uri, err := view.GetDocumentUriFromImportPath(req.TextDocument.Uri, im.ProtoImport.Filename)
select {
case <-ctx.Done():
return
default:
}

import_uri, err := view.GetDocumentUriFromImportPath(proto_file.URI(), im.ProtoImport.Filename)
if err != nil {
continue
}
Expand All @@ -73,31 +92,100 @@ func Completion(ctx context.Context, req *defines.CompletionParams) (*[]defines.
continue
}

if len(file.Proto().Packages()) > 0 && file.Proto().Packages()[0].ProtoPackage.Name == word {
return CompletionInThisFile(file)
if len(file.Proto().Packages()) == 0 {
continue
}

packageName := file.Proto().Packages()[0].ProtoPackage.Name

if _, exist := unique[packageName]; exist {
continue
}

unique[packageName] = struct{}{}
res = append(res, defines.CompletionItem{
Label: packageName,
Kind: &kindModule,
InsertText: &packageName,
})

}

return res
}

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
}

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(ctx, imported_file)...)
}

}
return nil, nil
return res
}

func CompletionInThisFile(file view.ProtoFile) (result *[]defines.CompletionItem, err error) {
kindEnum := defines.CompletionItemKindEnum
res := protoKeywordCompletionItems
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{
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
return res
}
Loading