/// <summary>
        /// Returns the CodeFragment at the given position if such a fragment exists and null otherwise.
        /// If the given position is equal to the end of the fragment, that fragment is returned if includeEnd is set to true.
        /// If a fragment is determined for the given position, returns the corresponding token index as out parameter.
        /// Note that token indices are no longer valid as soon as the file is modified (possibly e.g. by queued background processing).
        /// Any query or attempt operation on the returned token index may result in an exception once it lost its validity.
        /// Returns null if the given file or the specified position is null,
        /// or if the specified position is not within the current Content range.
        /// </summary>
        public static CodeFragment?TryGetFragmentAt(
            this FileContentManager file,
            Position?pos,
            out CodeFragment.TokenIndex?tIndex,
            bool includeEnd = false)
        {
            tIndex = null;
            if (file == null || pos == null || !file.ContainsPosition(pos))
            {
                return(null);
            }
            var start    = pos.Line;
            var previous = file.GetTokenizedLine(start).Where(token => token.Range.Start.Column <= pos.Column).ToImmutableArray();

            while (!previous.Any() && --start >= 0)
            {
                previous = file.GetTokenizedLine(start);
            }
            if (!previous.Any())
            {
                return(null);
            }

            var lastPreceding = previous.Last().WithLineNumOffset(start);
            var overlaps      = includeEnd
                ? pos <= lastPreceding.Range.End
                : pos < lastPreceding.Range.End;

            tIndex = overlaps ? new CodeFragment.TokenIndex(file, start, previous.Length - 1) : null;
            return(overlaps ? lastPreceding : null);
        }
예제 #2
0
        /// <summary>
        /// Givent a position, verifies that the position is within the given file, and
        /// returns the effective indentation (i.e. the indentation when ignoring excess brackets throughout the file)
        /// at that position (i.e. not including the char at the given position).
        /// </summary>
        internal static int IndentationAt(this FileContentManager file, Position pos)
        {
            if (!file.ContainsPosition(pos))
            {
                throw new ArgumentException("given position is not within file");
            }
            var line  = file.GetLine(pos.Line);
            var index = pos.Column;

            // check if the given position is within a string or a comment, or denotes an excess bracket,
            // and find the next closest position that isn't
            if (index >= line.WithoutEnding.Length)
            {
                return(line.FinalIndentation());                                   // not necessary, but saves doing the rest of the computation
            }
            index = line.FindInCode(trimmed => trimmed.Length - 1, 0, pos.Column); // if the given position is within a string, then this is the most convenient way to get the closest position that isn't..
            if (index < 0)
            {
                return(line.Indentation); // perfectly valid scenario (if there is no relevant code before the given position)
            }

            // check how much the indentation changes in all valid (i.e. non-comment, non-string, non-excess-brackets) code up to that point
            // NOTE: per specification, this routine returns the indentation not incuding the char at the given position,
            // but if this char was within non-code text, then we need to include this char ... see (*)
            var indexInCode = IndexInRelevantCode(index, line);

            QsCompilerError.Verify(indexInCode >= 0, "index in code should be positive");
            var code        = RelevantCode(line).Substring(0, index < pos.Column ? indexInCode + 1 : indexInCode); // (*) - yep, that's a bit awkward, but the cleanest I can come up with right now
            var indentation = line.Indentation + NrIndents(code) - NrUnindents(code);

            QsCompilerError.Verify(indentation >= 0, "computed indentation at any position in the file should be positive");
            return(indentation);
        }
        /// <summary>
        /// Returns the position and the kind of the closest specialization preceding the given position,
        /// and the name of the callable it belongs to as well as its position as Nullable.
        /// Returns null if the given file or position is null, or if no preceding callable can be found (e.g. because the callable name is invalid).
        /// If a callable name but no specializations (preceding or otherwise) within that callable can be found,
        /// assumes that the correct specialization is an auto-inserted default body,
        /// and returns QsBody as well as the start position of the callable declaration along with the callable name and its position.
        /// If a callable name as well as existing specializations can be found, but no specialization precedes the given position,
        /// returns null for the specialization kind as well as for its position.
        /// </summary>
        public static ((string, Position), (QsSpecializationKind?, Position?))? TryGetClosestSpecialization(
            this FileContentManager file, Position pos)
        {
            QsSpecializationKind?GetSpecializationKind(CodeFragment fragment)
            {
                var specDecl = fragment.Kind.DeclaredSpecialization();

                if (specDecl.IsNull)
                {
                    return(null);
                }
                var((kind, gen), typeArgs) = specDecl.Item;  // note: if we want to support type specializations we need to compute the signature of the spec to find the right one
                return(kind);
            }

            if (file == null || pos == null || !file.ContainsPosition(pos))
            {
                return(null);
            }
            file.SyncRoot.EnterReadLock();
            try
            {
                var declarations  = file.CallableDeclarationTokens();
                var precedingDecl = declarations.TakeWhile(tIndex => tIndex.GetFragment().Range.Start < pos);
                if (!precedingDecl.Any())
                {
                    return(null);
                }

                var closestCallable  = precedingDecl.Last();
                var callablePosition = closestCallable.GetFragment().Range.Start;
                var callableName     = closestCallable.GetFragment().Kind.DeclaredCallableName(null);
                if (callableName == null)
                {
                    return(null);
                }

                var specializations = FileHeader.FilterCallableSpecializations(closestCallable.GetChildren(deep: false).Select(tIndex => tIndex.GetFragment()));
                var precedingSpec   = specializations.TakeWhile(fragment => fragment.Range.Start < pos);
                var lastPreceding   = precedingSpec.Any() ? precedingSpec.Last() : null;

                if (specializations.Any() && lastPreceding == null)
                {
                    // the given position is within a callable declaration
                    return((callableName, callablePosition), (null, null));
                }
                return(lastPreceding == null
                    ? ((callableName, callablePosition), (QsSpecializationKind.QsBody, callablePosition))
                    : ((callableName, callablePosition), (GetSpecializationKind(lastPreceding), lastPreceding.Range.Start)));
            }
            finally
            {
                file.SyncRoot.ExitReadLock();
            }
        }
        /// <summary>
        /// Returns the name of the namespace to which the given position belongs.
        /// Returns null if the given file or position is null, or if no such namespace can be found
        /// (e.g. because the namespace name is invalid).
        /// </summary>
        public static string?TryGetNamespaceAt(this FileContentManager file, Position pos)
        {
            if (file == null || pos == null || !file.ContainsPosition(pos))
            {
                return(null);
            }
            var namespaces = file.GetNamespaceDeclarations();
            var preceding  = namespaces.TakeWhile(tuple => tuple.Item2.Start < pos);

            return(preceding.Any() ? preceding.Last().Item1 : null);
        }
예제 #5
0
        /// <summary>
        /// Returns the position right after where the fragment containing the given position ends.
        /// If the closest previous ending was on the last character in a line, then the returned position is on the same line after the last character.
        /// Updates the given position to point to the first character in the fragment that contains code.
        /// Throws an ArgumentException if the given position is not smaller than the position after the last piece of code in the file (given by LastInFile).
        /// Throws an ArgumentException if the given position is not within file.
        /// </summary>
        internal static Position FragmentEnd(this FileContentManager file, ref Position current)
        {
            if (!file.ContainsPosition(current))
            {
                throw new ArgumentException("given position is not within file");
            }
            var lastInFile = LastInFile(file);

            if (lastInFile <= current)
            {
                throw new ArgumentException("no fragment exists at the given position");
            }
예제 #6
0
        /// <summary>
        /// Returns the completion environment at the given position in the file or null if the environment cannot be
        /// determined. Stores the code fragment found at or before the given position into an out parameter.
        /// </summary>
        private static (CompletionScope?, QsFragmentKind?) GetCompletionEnvironment(
            FileContentManager file, Position position, out CodeFragment?fragment)
        {
            if (!file.ContainsPosition(position))
            {
                fragment = null;
                return(null, null);
            }
            var token = GetTokenAtOrBefore(file, position);

            if (token is null)
            {
                fragment = null;
                return(null, null);
            }

            fragment = token.GetFragment();
            var relativeIndentation = fragment.Indentation - file.IndentationAt(position);

            QsCompilerError.Verify(Math.Abs(relativeIndentation) <= 1);

            var parents = new[] { token }
            .Concat(token.GetNonEmptyParents())
            .Skip(relativeIndentation + 1)
            .Select(index => index.GetFragment())
            .ToImmutableList();
            var scope =
                parents.IsEmpty ? CompletionScope.TopLevel
                : parents.FirstOrDefault()?.Kind?.IsNamespaceDeclaration ?? false ? CompletionScope.NamespaceTopLevel
                : parents.Any(parent => parent.Kind?.IsFunctionDeclaration ?? false) ? CompletionScope.Function
                : parents.FirstOrDefault()?.Kind?.IsOperationDeclaration ?? false ? CompletionScope.OperationTopLevel
                : parents.Any(parent => parent.Kind?.IsOperationDeclaration ?? false) ? CompletionScope.Operation
                : null;
            var previous =
                relativeIndentation == 0 && IsPositionAfterDelimiter(file, fragment, position) ? fragment.Kind
                : relativeIndentation == 0 ? token.PreviousOnScope()?.GetFragment().Kind
                : relativeIndentation == 1 ? token.GetNonEmptyParent()?.GetFragment().Kind
                : null;

            return(scope, previous);
        }