diff --git a/Reqnroll.VisualStudio/Editor/Completions/DeveroomCompletionSource.cs b/Reqnroll.VisualStudio/Editor/Completions/DeveroomCompletionSource.cs index 54973d64..ee429bf5 100644 --- a/Reqnroll.VisualStudio/Editor/Completions/DeveroomCompletionSource.cs +++ b/Reqnroll.VisualStudio/Editor/Completions/DeveroomCompletionSource.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable namespace Reqnroll.VisualStudio.Editor.Completions; public class DeveroomCompletionSource : DeveroomCompletionSourceBase @@ -65,13 +65,30 @@ private T GetTagData(IMappingTagSpan[] tagSpans, string type) wh private List GetStepCompletions(DeveroomGherkinStep step) { var discoveryService = _project.GetDiscoveryService(); - var bindingRegistry = discoveryService.BindingRegistryCache.Value; + var featureFiles = _project.GetProjectFiles(".feature"); + var configuration = _project.GetDeveroomConfiguration(); + var stepDefinitions = discoveryService.BindingRegistryCache.Value.StepDefinitions; - var sampler = new StepDefinitionSampler(); - - return bindingRegistry.StepDefinitions + var steps = stepDefinitions .Where(sd => sd.IsValid && sd.StepDefinitionType == step.ScenarioBlock) - .Select(sd => new Completion(sampler.GetStepDefinitionSample(sd))) + .ToArray(); + + var stepDefinitionUsageFinder = new StepDefinitionUsageFinder(_ideScope); + var orderedSteps = stepDefinitionUsageFinder + .FindUsageCounts(steps, featureFiles, configuration) + .OrderByDescending(kvp => kvp.Value) + .ToList(); + + return orderedSteps + .Select((step, index) => + { + var isTop5 = index < 5; + return new Completion(step.Key) + { + DisplayText = isTop5 ? $"★ {step.Key}" : step.Key, + Description = $"Step usage: {step.Value}" + }; + }) .ToList(); } @@ -133,19 +150,19 @@ private void AddCompletionsFromExpectedTokens(TokenType[] expectedTokens, List FindUsages(ProjectStepDefinitionBinding[ return featureFiles.SelectMany(ff => FindUsages(stepDefinitions, ff, configuration)); } + public Dictionary FindUsageCounts( + ProjectStepDefinitionBinding[] stepDefinitions, + string[] featureFiles, + DeveroomConfiguration configuration) + { + return featureFiles + .Select(ff => FindUsageCounts(stepDefinitions, ff, configuration)) + .SelectMany(dict => dict) + .GroupBy(kvp => kvp.Key) + .ToDictionary( + group => group.Key, + group => group.Sum(kvp => kvp.Value) + ); + } + private IEnumerable FindUsages(ProjectStepDefinitionBinding[] stepDefinitions, string featureFilePath, DeveroomConfiguration configuration) => LoadContent(featureFilePath, out string featureFileContent) ? FindUsagesFromContent(stepDefinitions, featureFileContent, featureFilePath, configuration) : Enumerable.Empty(); + private Dictionary FindUsageCounts( + ProjectStepDefinitionBinding[] stepDefinitions, + string featureFilePath, + DeveroomConfiguration configuration) => + LoadContent(featureFilePath, out string featureFileContent) + ? FindUsageCountsFromContent(stepDefinitions, featureFileContent, featureFilePath, configuration) + : new Dictionary(); + private bool LoadContent(string featureFilePath, out string content) { if (LoadAlreadyOpenedContent(featureFilePath, out string openedContent)) @@ -111,6 +136,46 @@ public IEnumerable FindUsagesFromContent(ProjectStepDefinit } } + public Dictionary FindUsageCountsFromContent(ProjectStepDefinitionBinding[] stepDefinitions, + string featureFileContent, string featureFilePath, DeveroomConfiguration configuration) + { + var dialectProvider = ReqnrollGherkinDialectProvider.Get(configuration.DefaultFeatureLanguage); + var parser = new DeveroomGherkinParser(dialectProvider, _ideScope.MonitoringService); + + parser.ParseAndCollectErrors(featureFileContent, _ideScope.Logger, out var gherkinDocument, out _); + + var featureNode = gherkinDocument?.Feature; + if (featureNode == null) + return new Dictionary(); + + var dummyRegistry = ProjectBindingRegistry.FromBindings(stepDefinitions); + + var featureContext = new UsageFinderContext(featureNode); + + var stepUsageCounts = new Dictionary(); + + foreach (var scenarioDefinition in featureNode.FlattenStepsContainers()) + { + var context = new UsageFinderContext(scenarioDefinition, featureContext); + + foreach (var step in scenarioDefinition.Steps) + { + var matchResult = dummyRegistry.MatchStep(step, context); + if (matchResult.HasDefined) + { + var stepText = new StepDefinitionSampler().GetStepDefinitionSample(matchResult.Items[0].MatchedStepDefinition); + + if (stepUsageCounts.ContainsKey(stepText)) + stepUsageCounts[stepText]++; + else + stepUsageCounts[stepText] = 1; + } + } + } + + return stepUsageCounts; + } + private SourceLocation GetSourceLocation(Step step, string featureFilePath) => new(featureFilePath, step.Location.Line, step.Location.Column);