/// <summary> /// Returns a sequence of suggestions for replacing ranges over array indices with the corresponding library call, /// provided the corresponding library is referenced. /// Returns an empty enumerable if this is not the case or any of the given arguments is null. /// </summary> internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForIndexRange (this FileContentManager file, CompilationUnit compilation, LSP.Range range) { if (file == null || compilation == null || range?.Start == null) { return(Enumerable.Empty <(string, WorkspaceEdit)>()); } var indexRangeNamespaces = compilation.GlobalSymbols.NamespacesContainingCallable(BuiltIn.IndexRange.Name); if (!indexRangeNamespaces.Contains(BuiltIn.IndexRange.Namespace)) { return(Enumerable.Empty <(string, WorkspaceEdit)>()); } var suggestedOpenDir = file.OpenDirectiveSuggestions(range.Start.Line, BuiltIn.IndexRange.Namespace); /// Returns true the given expression is of the form "0 .. Length(args) - 1", /// as well as the range of the entire expression and the argument tuple "(args)" as out parameters. bool IsIndexRange(QsExpression iterExpr, Position offset, out LSP.Range exprRange, out LSP.Range argRange) { if (iterExpr.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .RangeLiteral rangeExpression && iterExpr.Range.IsValue && // iterable expression is a valid range literal rangeExpression.Item1.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .IntLiteral intLiteralExpression && intLiteralExpression.Item == 0L && // .. starting at 0 .. rangeExpression.Item2.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .SUB SUBExpression && // .. and ending in subracting .. SUBExpression.Item2.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .IntLiteral subIntLiteralExpression && subIntLiteralExpression.Item == 1L && // .. 1 from .. SUBExpression.Item1.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .CallLikeExpression callLikeExression && // .. a call .. callLikeExression.Item1.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .Identifier identifier && // .. to and identifier .. identifier.Item1.Symbol is QsSymbolKind <QsSymbol> .Symbol symName && symName.Item.Value == BuiltIn.Length.Name.Value && // .. "Length" called with .. callLikeExression.Item2.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .ValueTuple valueTuple && callLikeExression.Item2.Range.IsValue) // .. a valid argument tuple { exprRange = DiagnosticTools.GetAbsoluteRange(offset, iterExpr.Range.Item); argRange = DiagnosticTools.GetAbsoluteRange(offset, callLikeExression.Item2.Range.Item); return(true); } (exprRange, argRange) = (null, null); return(false); } /// Returns the text edits for replacing an range over the indices with the corresponding library call if the given code fragment is a suitable for-loop intro. /// The returned edits do *not* include an edit for adding the corresponding open-directive if necessary. IEnumerable <TextEdit> IndexRangeEdits(CodeFragment fragment) { if (fragment.Kind is QsFragmentKind.ForLoopIntro forLoopIntro && // todo: in principle we could give these suggestions for any index range IsIndexRange(forLoopIntro.Item2, fragment.GetRange().Start, out var iterExprRange, out var argTupleRange)) { yield return(new TextEdit() { Range = new LSP.Range() { Start = iterExprRange.Start, End = argTupleRange.Start }, NewText = BuiltIn.IndexRange.Name.Value }); yield return(new TextEdit() { Range = new LSP.Range() { Start = argTupleRange.End, End = iterExprRange.End }, NewText = "" }); } } var fragments = file.FragmentsOverlappingWithRange(range); var edits = fragments.SelectMany(IndexRangeEdits); return(edits.Any() ? new[] { ("Use IndexRange to iterate over indices.", file.GetWorkspaceEdit(suggestedOpenDir.Concat(edits).ToArray())) }