/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/VisualStudio/Core/Def/DebuggerIntelliSense/AbstractDebuggerIntelliSenseContext.cs
296 строк
13 KB
Cyrus Najmabadi
Make sealed
26 мар 2025, 22:21
26 мар 2025, 22:21
d99b771
Код
Авторство
О чём код?
// 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.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.DebuggerIntelliSense; internal abstract class AbstractDebuggerIntelliSenseContext : IDisposable { private readonly IWpfTextView _textView; private readonly IContentType _originalContentType; protected readonly IProjectionBufferFactoryService ProjectionBufferFactoryService; protected readonly TextManager.Interop.TextSpan CurrentStatementSpan; private IProjectionBuffer _projectionBuffer; private DebuggerTextView _debuggerTextView; private DebuggerIntelliSenseWorkspace _workspace; private ImmediateWindowContext _immediateWindowContext; private readonly IBufferGraphFactoryService _bufferGraphFactoryService; private readonly bool _isImmediateWindow; private sealed class ImmediateWindowContext { public int CurrentLineIndex = -1; public int QuestionIndex = -2; public IProjectionBuffer ElisionBuffer; public IProjectionBuffer ProjectionBuffer; } protected AbstractDebuggerIntelliSenseContext( IWpfTextView wpfTextView, IVsTextView vsTextView, IVsTextLines vsDebuggerTextLines, ITextBuffer contextBuffer, TextManager.Interop.TextSpan[] currentStatementSpan, IComponentModel componentModel, IServiceProvider serviceProvider, IContentType contentType) { _textView = wpfTextView; DebuggerTextLines = vsDebuggerTextLines; this.ContextBuffer = contextBuffer; this.CurrentStatementSpan = currentStatementSpan[0]; ContentType = contentType; _originalContentType = _textView.TextBuffer.ContentType; this.ProjectionBufferFactoryService = componentModel.GetService<IProjectionBufferFactoryService>(); _bufferGraphFactoryService = componentModel.GetService<IBufferGraphFactoryService>(); _isImmediateWindow = IsImmediateWindow((IVsUIShell)serviceProvider.GetService(typeof(SVsUIShell)), vsTextView); } // Constructor for testing protected AbstractDebuggerIntelliSenseContext( IWpfTextView wpfTextView, ITextBuffer contextBuffer, Microsoft.VisualStudio.TextManager.Interop.TextSpan[] currentStatementSpan, IComponentModel componentModel, IContentType contentType, bool isImmediateWindow) { _textView = wpfTextView; this.ContextBuffer = contextBuffer; this.CurrentStatementSpan = currentStatementSpan[0]; ContentType = contentType; this.ProjectionBufferFactoryService = componentModel.GetService<IProjectionBufferFactoryService>(); _bufferGraphFactoryService = componentModel.GetService<IBufferGraphFactoryService>(); _isImmediateWindow = isImmediateWindow; } public IVsTextLines DebuggerTextLines { get; } public ITextView DebuggerTextView { get { return _debuggerTextView; } } public ITextBuffer Buffer { get { return _projectionBuffer; } } public IContentType ContentType { get; } protected bool InImmediateWindow { get { return _immediateWindowContext != null; } } internal ITextBuffer ContextBuffer { get; private set; } public abstract bool CompletionStartsOnQuestionMark { get; } protected abstract IProjectionBuffer GetAdjustedBuffer(int contextPoint, Document document, ITrackingSpan debuggerMappedSpan); // Since the immediate window doesn't actually tell us when we change lines, we'll have to // determine ourselves when to rebuild our tracking spans to include only the last (input) // line of the buffer. public void RebuildSpans() { // Not in the immediate window, no work to do. if (!this.InImmediateWindow) { return; } // Reset the question mark location, since we may have to search for one again. _immediateWindowContext.QuestionIndex = -2; SetupImmediateWindowProjectionBuffer(); } internal bool TryInitialize() => this.TrySetContext(_isImmediateWindow); private bool TrySetContext( bool isImmediateWindow) { // Get the workspace, and from there, the solution and document containing this buffer. // If there's an ExternalSource, we won't get a document. Give up in that case. var document = ContextBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) { _projectionBuffer = null; _debuggerTextView = null; _workspace = null; _immediateWindowContext = null; return false; } var solution = document.Project.Solution; // Get the appropriate ITrackingSpan for the window the user is typing in var viewSnapshot = _textView.TextSnapshot; _immediateWindowContext = null; var debuggerMappedSpan = isImmediateWindow ? CreateImmediateWindowProjectionMapping(out _immediateWindowContext) : viewSnapshot.CreateFullTrackingSpan(SpanTrackingMode.EdgeInclusive); // Wrap the original ContextBuffer in a projection buffer that we can make read-only this.ContextBuffer = this.ProjectionBufferFactoryService.CreateProjectionBuffer(null, [this.ContextBuffer.CurrentSnapshot.CreateFullTrackingSpan(SpanTrackingMode.EdgeInclusive)], ProjectionBufferOptions.None, ContentType); // Make projection readonly so we can't edit it by mistake. using (var regionEdit = this.ContextBuffer.CreateReadOnlyRegionEdit()) { regionEdit.CreateReadOnlyRegion(new Span(0, this.ContextBuffer.CurrentSnapshot.Length), SpanTrackingMode.EdgeInclusive, EdgeInsertionMode.Deny); regionEdit.Apply(); } var contextPoint = this.ContextBuffer.CurrentSnapshot.GetLineFromLineNumber(CurrentStatementSpan.iEndLine).Start + CurrentStatementSpan.iEndIndex; // Get the adjusted buffer _projectionBuffer = GetAdjustedBuffer(contextPoint, document, debuggerMappedSpan); // Fork the solution using this new primary buffer for the document and all of its linked documents. var forkedSolution = solution.WithDocumentText(document.Id, _projectionBuffer.CurrentSnapshot.AsText(), PreservationMode.PreserveIdentity); foreach (var link in document.GetLinkedDocumentIds()) { forkedSolution = forkedSolution.WithDocumentText(link, _projectionBuffer.CurrentSnapshot.AsText(), PreservationMode.PreserveIdentity); } // Put it into a new workspace, and open it and its related documents // with the projection buffer as the text. _workspace = new DebuggerIntelliSenseWorkspace(forkedSolution); _workspace.OpenDocument(document.Id, _projectionBuffer.AsTextContainer()); foreach (var link in document.GetLinkedDocumentIds()) { _workspace.OpenDocument(link, _projectionBuffer.AsTextContainer()); } // Start getting the compilation so the PartialSolution will be ready when the user starts typing in the window document.Project.GetCompilationAsync(System.Threading.CancellationToken.None); _textView.TextBuffer.ChangeContentType(ContentType, null); var bufferGraph = _bufferGraphFactoryService.CreateBufferGraph(_projectionBuffer); _debuggerTextView = new DebuggerTextView(_textView, bufferGraph, DebuggerTextLines, InImmediateWindow); return true; } internal void SetContentType(bool install) { var contentType = install ? ContentType : _originalContentType; _textView.TextBuffer.ChangeContentType(contentType, null); } private ITrackingSpan CreateImmediateWindowProjectionMapping(out ImmediateWindowContext immediateWindowContext) { var caretLine = _textView.Caret.ContainingTextViewLine.Extent; var currentLineIndex = _textView.TextSnapshot.GetLineNumberFromPosition(caretLine.Start.Position); var debuggerMappedSpan = _textView.TextSnapshot.CreateFullTrackingSpan(SpanTrackingMode.EdgeInclusive); var projectionBuffer = this.ProjectionBufferFactoryService.CreateProjectionBuffer(null, [debuggerMappedSpan], ProjectionBufferOptions.PermissiveEdgeInclusiveSourceSpans, ContentType); // There's currently a bug in the editor (515925) where an elision buffer can't be projected into // another projection buffer. So workaround by using a second projection buffer that only // projects the text we care about var elisionProjectionBuffer = this.ProjectionBufferFactoryService.CreateProjectionBuffer(null, [projectionBuffer.CurrentSnapshot.CreateFullTrackingSpan(SpanTrackingMode.EdgeInclusive)], ProjectionBufferOptions.None, ContentType); immediateWindowContext = new ImmediateWindowContext() { ProjectionBuffer = projectionBuffer, ElisionBuffer = elisionProjectionBuffer }; _textView.TextBuffer.PostChanged += TextBuffer_PostChanged; SetupImmediateWindowProjectionBuffer(); return elisionProjectionBuffer.CurrentSnapshot.CreateFullTrackingSpan(SpanTrackingMode.EdgeInclusive); } private void TextBuffer_PostChanged(object sender, EventArgs e) => SetupImmediateWindowProjectionBuffer(); /// <summary> /// If there's a ? mark, we want to skip the ? mark itself, and include the text that follows it /// </summary> private void SetupImmediateWindowProjectionBuffer() { var caretLine = _textView.Caret.ContainingTextViewLine.Extent; var currentLineIndex = _textView.TextSnapshot.GetLineNumberFromPosition(caretLine.Start.Position); var questionIndex = GetQuestionIndex(caretLine.GetText()); if (_immediateWindowContext.QuestionIndex != questionIndex || _immediateWindowContext.CurrentLineIndex != currentLineIndex) { _immediateWindowContext.QuestionIndex = questionIndex; _immediateWindowContext.CurrentLineIndex = currentLineIndex; _immediateWindowContext.ProjectionBuffer.DeleteSpans(0, _immediateWindowContext.ProjectionBuffer.CurrentSnapshot.SpanCount); _immediateWindowContext.ProjectionBuffer.InsertSpan(0, _textView.TextSnapshot.CreateTrackingSpanFromIndexToEnd(caretLine.Start.Position + questionIndex + 1, SpanTrackingMode.EdgeInclusive)); } } private static int GetQuestionIndex(string text) { for (var i = 0; i < text.Length; i++) { if (!char.IsWhiteSpace(text[i])) { // Assume that the ? will be the first non-whitespace if it's being used as a // command return text[i] == '?' ? i : -1; } } return -1; } private static bool IsImmediateWindow(IVsUIShell shellService, IVsTextView textView) { Marshal.ThrowExceptionForHR(shellService.GetToolWindowEnum(out var windowEnum)); Marshal.ThrowExceptionForHR(textView.GetBuffer(out _)); var frame = new IVsWindowFrame[1]; var immediateWindowGuid = Guid.Parse(ToolWindowGuids80.ImmediateWindow); while (windowEnum.Next(1, frame, out _) == VSConstants.S_OK) { Marshal.ThrowExceptionForHR(frame[0].GetGuidProperty((int)__VSFPROPID.VSFPROPID_GuidPersistenceSlot, out var toolWindowGuid)); if (toolWindowGuid == immediateWindowGuid) { Marshal.ThrowExceptionForHR(frame[0].QueryViewInterface(typeof(IVsTextView).GUID, out var frameTextView)); try { var immediateWindowTextView = Marshal.GetObjectForIUnknown(frameTextView) as IVsTextView; return textView == immediateWindowTextView; } finally { Marshal.Release(frameTextView); } } } return false; } public void Dispose() { // Unsubscribe from events _textView.TextBuffer.PostChanged -= TextBuffer_PostChanged; _debuggerTextView.Cleanup(); // The buffer graph subscribes to events of its source buffers, we're no longer interested _projectionBuffer.DeleteSpans(0, _projectionBuffer.CurrentSnapshot.SpanCount); // The next request will use a new workspace _workspace.Dispose(); } }