File: LanguageService\CSharpDebuggerIntelliSenseContext.cs
Web Access
Project: src\src\VisualStudio\CSharp\Impl\Microsoft.VisualStudio.LanguageServices.CSharp_xvpujndh_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.LanguageServices.Implementation.DebuggerIntelliSense;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Projection;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
using TextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan;
 
namespace Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService
{
    internal class CSharpDebuggerIntelliSenseContext : AbstractDebuggerIntelliSenseContext
    {
        private const string StatementTerminator = ";";
 
        public CSharpDebuggerIntelliSenseContext(
            IWpfTextView view,
            IVsTextView vsTextView,
            IVsTextLines debuggerBuffer,
            ITextBuffer contextBuffer,
            TextSpan[] currentStatementSpan,
            IComponentModel componentModel,
            IServiceProvider serviceProvider)
            : base(view,
                vsTextView,
                debuggerBuffer,
                contextBuffer,
                currentStatementSpan,
                componentModel,
                serviceProvider,
                componentModel.GetService<IContentTypeRegistryService>().GetContentType(ContentTypeNames.CSharpContentType))
        {
        }
 
        // Test constructor
        internal CSharpDebuggerIntelliSenseContext(
            IWpfTextView view,
            ITextBuffer contextBuffer,
            TextSpan[] currentStatementSpan,
            IComponentModel componentModel,
            bool immediateWindow)
            : base(view,
                contextBuffer,
                currentStatementSpan,
                componentModel,
                componentModel.GetService<IContentTypeRegistryService>().GetContentType(ContentTypeNames.CSharpContentType),
                immediateWindow)
        {
        }
 
        protected override IProjectionBuffer GetAdjustedBuffer(int contextPoint, Document document, ITrackingSpan debuggerMappedSpan)
        {
            // Determine the position in the buffer at which to end the tracking span representing
            // the part of the imaginary buffer before the text in the view. 
            var tree = document.GetSyntaxTreeSynchronously(CancellationToken.None);
            var token = tree.FindTokenOnLeftOfPosition(contextPoint, CancellationToken.None);
 
            // Typically, the separator between the text before adjustedStart and debuggerMappedSpan is
            // a semicolon (StatementTerminator), unless a specific condition outlined later in the
            // method is encountered.
            var separatorBeforeDebuggerMappedSpan = StatementTerminator;
            var adjustedStart = token.FullSpan.End;
 
            // Special case to handle class designer because it asks for debugger IntelliSense using
            // spans between members.
            if (contextPoint > token.Span.End &&
                token.IsKindOrHasMatchingText(SyntaxKind.CloseBraceToken) &&
                token.Parent.IsKind(SyntaxKind.Block) &&
                token.Parent.Parent is MemberDeclarationSyntax)
            {
                adjustedStart = contextPoint;
            }
            else if (token.IsKindOrHasMatchingText(SyntaxKind.CloseBraceToken) &&
                token.Parent.IsKind(SyntaxKind.Block))
            {
                adjustedStart = token.SpanStart;
            }
            else if (token.IsKindOrHasMatchingText(SyntaxKind.SemicolonToken) &&
                token.Parent is StatementSyntax)
            {
                // If the context is at a semicolon terminated statement, then we use the start of
                // that statement as the adjusted context position. This is to ensure the placement
                // of debuggerMappedSpan is in the same block as token originally was. For example,
                // 
                // for (int i = 0; i < 10; i++)
                //   [Console.WriteLine(i);]
                //
                // where [] denotes CurrentStatementSpan, should use the start of CurrentStatementSpan
                // as the adjusted context, and should not place a semicolon before debuggerMappedSpan.
                // Not doing either of those would place debuggerMappedSpan outside the for loop.
                separatorBeforeDebuggerMappedSpan = string.Empty;
                adjustedStart = token.Parent.SpanStart;
            }
 
            var beforeAdjustedStart = ContextBuffer.CurrentSnapshot.CreateTrackingSpan(Span.FromBounds(0, adjustedStart), SpanTrackingMode.EdgeNegative);
            var afterAdjustedStart = ContextBuffer.CurrentSnapshot.CreateTrackingSpanFromIndexToEnd(adjustedStart, SpanTrackingMode.EdgePositive);
 
            return ProjectionBufferFactoryService.CreateProjectionBuffer(
                projectionEditResolver: null,
                sourceSpans: [beforeAdjustedStart, separatorBeforeDebuggerMappedSpan, debuggerMappedSpan, StatementTerminator, afterAdjustedStart],
                options: ProjectionBufferOptions.None,
                contentType: ContentType);
        }
 
        public override bool CompletionStartsOnQuestionMark
        {
            get { return false; }
        }
    }
}