From 6701ff907bd698f06d70860e137f3948d999070e Mon Sep 17 00:00:00 2001 From: Camden Cheek Date: Tue, 9 Jul 2024 12:14:38 -0600 Subject: [PATCH 1/2] implement SurroundingContext --- .../graphqlbackend/codeintel.codenav.graphql | 2 +- .../transport/graphql/root_resolver.go | 11 ++- .../transport/graphql/root_resolver_usages.go | 43 ++++++--- .../codenav/transport/graphql/util_lines.go | 94 +++++++++++++++++++ internal/codeintel/resolvers/codenav.go | 2 +- 5 files changed, 131 insertions(+), 21 deletions(-) create mode 100644 internal/codeintel/codenav/transport/graphql/util_lines.go diff --git a/cmd/frontend/graphqlbackend/codeintel.codenav.graphql b/cmd/frontend/graphqlbackend/codeintel.codenav.graphql index e06295687bf6..4823be2ed50c 100644 --- a/cmd/frontend/graphqlbackend/codeintel.codenav.graphql +++ b/cmd/frontend/graphqlbackend/codeintel.codenav.graphql @@ -733,7 +733,7 @@ type Usage { or exceeds the number of available lines, the value is interpreted as until the start/end of the file. """ - surroundingContent(surroundingLines: SurroundingLines = { linesBefore: 0, linesAfter: 0 }): String + surroundingContent(surroundingLines: SurroundingLines = { linesBefore: 0, linesAfter: 0 }): String! """ Describes the relationship between this usage and the input symbol diff --git a/internal/codeintel/codenav/transport/graphql/root_resolver.go b/internal/codeintel/codenav/transport/graphql/root_resolver.go index d70a76e58991..01ac1e9fa993 100644 --- a/internal/codeintel/codenav/transport/graphql/root_resolver.go +++ b/internal/codeintel/codenav/transport/graphql/root_resolver.go @@ -28,6 +28,8 @@ import ( "github.com/sourcegraph/sourcegraph/lib/errors" ) +var ErrNotEnabled = errors.New("experimentalFeatures.scipBasedAPIs is not enabled") + type rootResolver struct { svc CodeNavService autoindexingSvc AutoIndexingService @@ -118,7 +120,7 @@ func (r *rootResolver) CodeGraphData(ctx context.Context, opts *resolverstubs.Co endObservation.OnCancel(ctx, 1, observation.Args{}) if !conf.SCIPBasedAPIsEnabled() { - return nil, nil + return nil, ErrNotEnabled } makeResolvers := func(prov resolverstubs.CodeGraphDataProvenance) ([]resolverstubs.CodeGraphDataResolver, error) { @@ -198,7 +200,7 @@ func (r *rootResolver) UsagesForSymbol(ctx context.Context, unresolvedArgs *reso ctx, _, endObservation := r.operations.usagesForSymbol.WithErrors(ctx, &err, observation.Args{Attrs: unresolvedArgs.Attrs()}) if !conf.SCIPBasedAPIsEnabled() { - return nil, nil + return nil, ErrNotEnabled } numPreciseResults := 0 @@ -219,6 +221,7 @@ func (r *rootResolver) UsagesForSymbol(ctx context.Context, unresolvedArgs *reso } remainingCount := int(args.RemainingCount) provsForSCIPData := args.Symbol.ProvenancesForSCIPData() + linesGetter := newCachedLinesGetter(r.gitserverClient, 5*1024*1024 /* 5MB */) usageResolvers := []resolverstubs.UsageResolver{} if provsForSCIPData.Precise { @@ -248,7 +251,7 @@ func (r *rootResolver) UsagesForSymbol(ctx context.Context, unresolvedArgs *reso } } else { for _, result := range syntacticResult.Matches { - usageResolvers = append(usageResolvers, NewSyntacticUsageResolver(result, args.Repo, args.CommitID)) + usageResolvers = append(usageResolvers, NewSyntacticUsageResolver(result, args.Repo, args.CommitID, linesGetter)) } numSyntacticResults = len(syntacticResult.Matches) remainingCount = remainingCount - numSyntacticResults @@ -268,7 +271,7 @@ func (r *rootResolver) UsagesForSymbol(ctx context.Context, unresolvedArgs *reso } } else { for _, result := range results { - usageResolvers = append(usageResolvers, NewSearchBasedUsageResolver(result, args.Repo, args.CommitID)) + usageResolvers = append(usageResolvers, NewSearchBasedUsageResolver(result, args.Repo, args.CommitID, linesGetter)) } } } diff --git a/internal/codeintel/codenav/transport/graphql/root_resolver_usages.go b/internal/codeintel/codenav/transport/graphql/root_resolver_usages.go index b738a8928cd9..c2bfdaa45510 100644 --- a/internal/codeintel/codenav/transport/graphql/root_resolver_usages.go +++ b/internal/codeintel/codenav/transport/graphql/root_resolver_usages.go @@ -29,15 +29,16 @@ func (u *usageConnectionResolver) PageInfo() resolverstubs.PageInfo { } type usageResolver struct { - symbol *symbolInformationResolver - provenance resolverstubs.CodeGraphDataProvenance - kind resolverstubs.SymbolUsageKind - usageRange resolverstubs.UsageRangeResolver + symbol *symbolInformationResolver + provenance resolverstubs.CodeGraphDataProvenance + kind resolverstubs.SymbolUsageKind + linesGetter LinesGetter + usageRange *usageRangeResolver } var _ resolverstubs.UsageResolver = &usageResolver{} -func NewSyntacticUsageResolver(usage codenav.SyntacticMatch, repository types.Repo, revision api.CommitID) resolverstubs.UsageResolver { +func NewSyntacticUsageResolver(usage codenav.SyntacticMatch, repository types.Repo, revision api.CommitID, linesGetter LinesGetter) resolverstubs.UsageResolver { var kind resolverstubs.SymbolUsageKind if scip.SymbolRole_Definition.Matches(usage.Occurrence) { kind = resolverstubs.UsageKindDefinition @@ -48,8 +49,9 @@ func NewSyntacticUsageResolver(usage codenav.SyntacticMatch, repository types.Re symbol: &symbolInformationResolver{ name: usage.Occurrence.Symbol, }, - provenance: resolverstubs.ProvenanceSyntactic, - kind: kind, + provenance: resolverstubs.ProvenanceSyntactic, + kind: kind, + linesGetter: linesGetter, usageRange: &usageRangeResolver{ repository: repository, revision: revision, @@ -58,7 +60,7 @@ func NewSyntacticUsageResolver(usage codenav.SyntacticMatch, repository types.Re }, } } -func NewSearchBasedUsageResolver(usage codenav.SearchBasedMatch, repository types.Repo, revision api.CommitID) resolverstubs.UsageResolver { +func NewSearchBasedUsageResolver(usage codenav.SearchBasedMatch, repository types.Repo, revision api.CommitID, linesGetter LinesGetter) resolverstubs.UsageResolver { var kind resolverstubs.SymbolUsageKind if usage.IsDefinition { kind = resolverstubs.UsageKindDefinition @@ -66,9 +68,10 @@ func NewSearchBasedUsageResolver(usage codenav.SearchBasedMatch, repository type kind = resolverstubs.UsageKindReference } return &usageResolver{ - symbol: nil, - provenance: resolverstubs.ProvenanceSearchBased, - kind: kind, + symbol: nil, + provenance: resolverstubs.ProvenanceSearchBased, + kind: kind, + linesGetter: linesGetter, usageRange: &usageRangeResolver{ repository: repository, revision: revision, @@ -100,11 +103,21 @@ func (u *usageResolver) UsageRange(ctx context.Context) (resolverstubs.UsageRang return u.usageRange, nil } -func (u *usageResolver) SurroundingContent(_ context.Context, args *struct { +func (u *usageResolver) SurroundingContent(ctx context.Context, args *struct { *resolverstubs.SurroundingLines `json:"surroundingLines"` -}) (*string, error) { - //TODO implement me - panic("implement me") +}) (string, error) { + lines, err := u.linesGetter.Get( + ctx, + u.usageRange.repository.Name, + u.usageRange.revision, + u.usageRange.path.RawValue(), + int(u.usageRange.range_.Start.Line-*args.LinesBefore), + int(u.usageRange.range_.End.Line+*args.LinesAfter+1), + ) + if err != nil { + return "", err + } + return string(lines), nil } func (u *usageResolver) UsageKind() resolverstubs.SymbolUsageKind { diff --git a/internal/codeintel/codenav/transport/graphql/util_lines.go b/internal/codeintel/codenav/transport/graphql/util_lines.go new file mode 100644 index 000000000000..9e7f7fdecffc --- /dev/null +++ b/internal/codeintel/codenav/transport/graphql/util_lines.go @@ -0,0 +1,94 @@ +package graphql + +import ( + "context" + "io" + "sync" + + "github.com/sourcegraph/sourcegraph/internal/api" + "github.com/sourcegraph/sourcegraph/internal/byteutils" + "github.com/sourcegraph/sourcegraph/internal/gitserver" +) + +type LinesGetter interface { + Get(ctx context.Context, repo api.RepoName, commit api.CommitID, path string, startLine, endLine int) ([]byte, error) +} + +type cacheKey struct { + repo api.RepoName + revision api.CommitID + path string +} + +type cacheValue struct { + contents []byte + index byteutils.LineIndex +} + +type cachedLinesGetter struct { + mu sync.RWMutex + cache map[cacheKey]cacheValue + maxCachedBytes int + freeBytes int + gitserver gitserver.Client +} + +var _ LinesGetter = (*cachedLinesGetter)(nil) + +func newCachedLinesGetter(gitserver gitserver.Client, size int) *cachedLinesGetter { + return &cachedLinesGetter{ + cache: make(map[cacheKey]cacheValue), + maxCachedBytes: size, + freeBytes: size, + gitserver: gitserver, + } +} + +func (c *cachedLinesGetter) Get(ctx context.Context, repo api.RepoName, commit api.CommitID, path string, startLine, endLine int) ([]byte, error) { + key := cacheKey{repo, commit, path} + + c.mu.RLock() + if value, ok := c.cache[key]; ok { + c.mu.RUnlock() + start, end := value.index.LinesRange(startLine, endLine) + return value.contents[start:end], nil + } + c.mu.RUnlock() + + r, err := c.gitserver.NewFileReader(ctx, repo, commit, path) + if err != nil { + return nil, err + } + defer r.Close() + + contents, err := io.ReadAll(r) + if err != nil { + return nil, err + } + index := byteutils.NewLineIndex(contents) + start, end := index.LinesRange(startLine, endLine) + lines := contents[start:end] + + if len(contents) > c.maxCachedBytes { + // Don't both trying to fit it in the cache + return lines, nil + } + + c.mu.Lock() + defer c.mu.Unlock() + + // Make room for the file in the cache. This cache doesn't need to be high + // performance -- just randomly delete things until we have room. + for k, v := range c.cache { + if c.freeBytes >= len(contents) { + break + } + delete(c.cache, k) + c.freeBytes += len(v.contents) + } + + c.cache[key] = cacheValue{contents, index} + c.freeBytes -= len(contents) + + return lines, nil +} diff --git a/internal/codeintel/resolvers/codenav.go b/internal/codeintel/resolvers/codenav.go index d067cb16e48f..c089a8631eea 100644 --- a/internal/codeintel/resolvers/codenav.go +++ b/internal/codeintel/resolvers/codenav.go @@ -622,7 +622,7 @@ type UsageResolver interface { UsageRange(context.Context) (UsageRangeResolver, error) SurroundingContent(_ context.Context, args *struct { *SurroundingLines `json:"surroundingLines"` - }) (*string, error) + }) (string, error) UsageKind() SymbolUsageKind } From 7a566936319a34f011e0304bf0a9c147b7623665 Mon Sep 17 00:00:00 2001 From: Camden Cheek Date: Thu, 11 Jul 2024 01:28:51 -0600 Subject: [PATCH 2/2] bazel configure --- internal/codeintel/codenav/transport/graphql/BUILD.bazel | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/codeintel/codenav/transport/graphql/BUILD.bazel b/internal/codeintel/codenav/transport/graphql/BUILD.bazel index b8579b18366b..fe578fd39831 100644 --- a/internal/codeintel/codenav/transport/graphql/BUILD.bazel +++ b/internal/codeintel/codenav/transport/graphql/BUILD.bazel @@ -19,6 +19,7 @@ go_library( "root_resolver_stencil.go", "root_resolver_usages.go", "util_cursor.go", + "util_lines.go", "util_locations.go", ], importpath = "github.com/sourcegraph/sourcegraph/internal/codeintel/codenav/transport/graphql", @@ -28,6 +29,7 @@ go_library( "//cmd/frontend/graphqlbackend/graphqlutil", "//internal/api", "//internal/authz", + "//internal/byteutils", "//internal/codeintel/codenav", "//internal/codeintel/codenav/shared", "//internal/codeintel/core",