public void SimpleList_FindItemAtPosition(string input, int position, string expectedItemValue)
        {
            SimpleList list = MSBuildExpression.ParseSimpleList(input);

            DumpList(list, input);

            SimpleListItem actualItem = list.FindItemAt(position);

            Assert.NotNull(actualItem);

            Assert.Equal(expectedItemValue, actualItem.Value);

            string actualInput = input.Substring(
                startIndex: actualItem.AbsoluteStart,
                length: actualItem.AbsoluteEnd - actualItem.AbsoluteStart
                );

            Assert.Equal(expectedItemValue, actualInput);
        }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            Log.Verbose("Evaluate completions for {XmlLocation:l}", location);

            List <CompletionItem> completions = new List <CompletionItem>();

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                XSAttribute attribute;
                if (!location.CanCompleteAttributeValue(out attribute, onElementWithPath: WellKnownElementPaths.Target) || !SupportedAttributeNames.Contains(attribute.Name))
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (not the value of a supported attribute on a 'Target' element).", location);

                    return(null);
                }

                Range            targetRange        = attribute.ValueRange;
                HashSet <string> excludeTargetNames = new HashSet <string>();

                // Handle potentially composite (i.e. "Value1;Value2;Value3") values, where it's legal to have them.
                if (attribute.Name != "Name" && attribute.Value.IndexOf(';') != -1)
                {
                    int startPosition    = projectDocument.XmlPositions.GetAbsolutePosition(attribute.ValueRange.Start);
                    int relativePosition = location.AbsolutePosition - startPosition;

                    SimpleList     list           = MSBuildExpression.ParseSimpleList(attribute.Value);
                    SimpleListItem itemAtPosition = list.FindItemAt(relativePosition);
                    if (itemAtPosition == null)
                    {
                        return(null);
                    }

                    Log.Verbose("Completions will replace item {ItemValue} spanning [{ItemStartPosition}..{ItemEndPosition}) in MSBuild simple list expression {ListExpression}.",
                                itemAtPosition.Value,
                                itemAtPosition.AbsoluteStart,
                                itemAtPosition.AbsoluteEnd,
                                attribute.Value
                                );

                    targetRange = projectDocument.XmlPositions.GetRange(
                        absoluteStartPosition: startPosition + itemAtPosition.AbsoluteStart,
                        absoluteEndPosition: startPosition + itemAtPosition.AbsoluteEnd
                        );
                }

                completions.AddRange(
                    GetCompletionItems(projectDocument, targetRange, excludeTargetNames)
                    );
            }

            if (completions.Count == 0)
            {
                return(null); // No completions provided.
            }
            Log.Verbose("Offering {CompletionCount} completion(s) for {XmlLocation:l}", completions.Count, location);

            return(new CompletionList(completions,
                                      isIncomplete: false // Consider this list to be exhaustive
                                      ));
        }