Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

track non roslyn text buffer changes as well as roslyn text buffer chang... #2515

Merged
merged 4 commits into from
May 7, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Features/Core/SolutionCrawler/IDocumentTrackingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,12 @@ internal interface IDocumentTrackingService : IWorkspaceService
ImmutableArray<DocumentId> GetVisibleDocuments();

event EventHandler<DocumentId> ActiveDocumentChanged;

/// <summary>
/// Events for Non Roslyn text buffer changes.
///
/// It raises events for buffers opened in a view in host.
/// </summary>
event EventHandler<EventArgs> NonRoslynBufferTextChanged;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,36 @@ public GlobalOperationAwareIdleProcessor(
base(listener, backOffTimeSpanInMs, shutdownToken)
{
this.Processor = processor;

_globalOperation = null;
_globalOperationTask = SpecializedTasks.EmptyTask;

_globalOperationNotificationService = globalOperationNotificationService;
_globalOperationNotificationService.Started += OnGlobalOperationStarted;
_globalOperationNotificationService.Stopped += OnGlobalOperationStopped;

if (this.Processor._documentTracker != null)
{
this.Processor._documentTracker.NonRoslynBufferTextChanged += OnNonRoslynBufferTextChanged;
}
}

private void OnNonRoslynBufferTextChanged(object sender, EventArgs e)
{
// There are 2 things incremental processor takes care of
//
// #1 is making sure we delay processing any work until there is enough idle (ex, typing) in host.
// #2 is managing cancellation and pending works.
//
// we used to do #1 and #2 only for Roslyn files. and that is usually fine since most of time solution contains only roslyn files.
//
// but for mixed solution (ex, Roslyn files + HTML + JS + CSS), #2 still makes sense but #1 doesn't. We want
// to pause any work while something is going on in other project types as well.
//
// we need to make sure we play nice with neighbors as well.
//
// now, we don't care where changes are coming from. if there is any change in host, we puase oursevles for a while.
this.UpdateLastAccessTime();
}

protected Task GlobalOperationTask
Expand Down Expand Up @@ -114,6 +138,11 @@ public virtual void Shutdown()
{
_globalOperationNotificationService.Started -= OnGlobalOperationStarted;
_globalOperationNotificationService.Stopped -= OnGlobalOperationStopped;

if (this.Processor._documentTracker != null)
{
this.Processor._documentTracker.NonRoslynBufferTextChanged -= OnNonRoslynBufferTextChanged;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,22 @@ private void OnBeforeDocumentWindowShow(IVsWindowFrame frame, uint docCookie, bo
{
OnBeforeDocumentWindowShow(frame, id, firstShow);
}

if (ids.Count == 0)
{
// deal with non roslyn text file opened in the editor
OnBeforeNonRoslynDocumentWindowShow(frame, firstShow);
}
}

protected virtual void OnBeforeDocumentWindowShow(IVsWindowFrame frame, DocumentId id, bool firstShow)
{
}

protected virtual void OnBeforeNonRoslynDocumentWindowShow(IVsWindowFrame frame, bool firstShow)
{
}

private IList<DocumentId> GetDocumentIdsFromDocCookie(uint docCookie)
{
List<DocumentKey> documentKeys;
Expand Down Expand Up @@ -300,35 +310,37 @@ private IList<DocumentId> GetDocumentIdsFromDocCookie(uint docCookie)
private void CloseDocuments(uint docCookie, string monikerToKeep)
{
List<DocumentKey> documentKeys;
if (_docCookiesToOpenDocumentKeys.TryGetValue(docCookie, out documentKeys))
if (!_docCookiesToOpenDocumentKeys.TryGetValue(docCookie, out documentKeys))
{
// We will remove from documentKeys the things we successfully closed,
// so clone the list so we can mutate while enumerating
var documentsToClose = documentKeys.Where(key => !StringComparer.OrdinalIgnoreCase.Equals(key.Moniker, monikerToKeep)).ToList();

// For a given set of open linked or shared files, we may be closing one of the
// documents (e.g. excluding a linked file from one of its owning projects or
// unloading one of the head projects for a shared project) or the entire set of
// documents (e.g. closing the tab of a shared document). If the entire set of
// documents is closing, then we should avoid the process of updating the active
// context document between the closing of individual documents in the set. In the
// case of closing the tab of a shared document, this avoids updating the shared
// item context hierarchy for the entire shared project to head project owning the
// last documentKey in this list.
var updateActiveContext = documentsToClose.Count == 1;

foreach (var documentKey in documentsToClose)
{
var document = _documentMap[documentKey];
document.ProcessClose(updateActiveContext);
Contract.ThrowIfFalse(documentKeys.Remove(documentKey));
}
return;
}

// If we removed all the keys, then remove the list entirely
if (documentKeys.Count == 0)
{
_docCookiesToOpenDocumentKeys.Remove(docCookie);
}
// We will remove from documentKeys the things we successfully closed,
// so clone the list so we can mutate while enumerating
var documentsToClose = documentKeys.Where(key => !StringComparer.OrdinalIgnoreCase.Equals(key.Moniker, monikerToKeep)).ToList();

// For a given set of open linked or shared files, we may be closing one of the
// documents (e.g. excluding a linked file from one of its owning projects or
// unloading one of the head projects for a shared project) or the entire set of
// documents (e.g. closing the tab of a shared document). If the entire set of
// documents is closing, then we should avoid the process of updating the active
// context document between the closing of individual documents in the set. In the
// case of closing the tab of a shared document, this avoids updating the shared
// item context hierarchy for the entire shared project to head project owning the
// last documentKey in this list.
var updateActiveContext = documentsToClose.Count == 1;

foreach (var documentKey in documentsToClose)
{
var document = _documentMap[documentKey];
document.ProcessClose(updateActiveContext);
Contract.ThrowIfFalse(documentKeys.Remove(documentKey));
}

// If we removed all the keys, then remove the list entirely
if (documentKeys.Count == 0)
{
_docCookiesToOpenDocumentKeys.Remove(docCookie);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation
{
[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
internal class VisualStudioDocumentTrackingService : IDocumentTrackingService, IVsSelectionEvents, IDisposable
{
private readonly NonRoslynTextBufferTracker _tracker;

private IVsMonitorSelection _monitorSelection;
private uint _cookie;
private ImmutableList<FrameListener> _visibleFrames;
private IVsWindowFrame _activeFrame;

public VisualStudioDocumentTrackingService(IServiceProvider serviceProvider)
{
_tracker = new NonRoslynTextBufferTracker(this);

_visibleFrames = ImmutableList<FrameListener>.Empty;

_monitorSelection = (IVsMonitorSelection)serviceProvider.GetService(typeof(SVsShellMonitorSelection));
Expand Down Expand Up @@ -132,6 +139,13 @@ public int OnCmdUIContextChanged([ComAliasName("Microsoft.VisualStudio.Shell.Int
return VSConstants.E_NOTIMPL;
}

public event EventHandler<EventArgs> NonRoslynBufferTextChanged;

public void OnNonRoslynViewOpened(ITextView view)
{
_tracker.OnOpened(view);
}

public void Dispose()
{
if (_cookie != VSConstants.VSCOOKIE_NIL && _monitorSelection != null)
Expand Down Expand Up @@ -262,5 +276,54 @@ internal string GetDebuggerDisplay()
return caption.ToString();
}
}

/// <summary>
/// It tracks non roslyn text buffer text changes.
/// </summary>
private class NonRoslynTextBufferTracker : ForegroundThreadAffinitizedObject
{
private readonly VisualStudioDocumentTrackingService _owner;
private readonly HashSet<ITextView> _views;

public NonRoslynTextBufferTracker(VisualStudioDocumentTrackingService owner)
{
_owner = owner;
_views = new HashSet<ITextView>();
}

public void OnOpened(ITextView view)
{
AssertIsForeground();

if (!_views.Add(view))
{
return;
}

view.TextBuffer.PostChanged += OnTextChanged;
view.Closed += OnClosed;
}

private void OnClosed(object sender, EventArgs e)
{
AssertIsForeground();

var view = sender as ITextView;
if (view == null || !_views.Contains(view))
{
return;
}

view.TextBuffer.PostChanged -= OnTextChanged;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to unsubscribe Closed as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!!! thank you

view.Closed -= OnClosed;

_views.Remove(view);
}

private void OnTextChanged(object sender, EventArgs e)
{
_owner.NonRoslynBufferTextChanged?.Invoke(sender, e);
}
}
}
}
50 changes: 48 additions & 2 deletions src/VisualStudio/Core/Def/RoslynDocumentProvider.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.LanguageServices.Implementation;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;

namespace Microsoft.VisualStudio.LanguageServices
{
Expand All @@ -23,10 +26,53 @@ public RoslynDocumentProvider(

protected override void OnBeforeDocumentWindowShow(IVsWindowFrame frame, DocumentId id, bool firstShow)
{
if (_documentTrackingService != null)
base.OnBeforeDocumentWindowShow(frame, id, firstShow);

_documentTrackingService?.DocumentFrameShowing(frame, id, firstShow);
}

protected override void OnBeforeNonRoslynDocumentWindowShow(IVsWindowFrame frame, bool firstShow)
{
base.OnBeforeNonRoslynDocumentWindowShow(frame, firstShow);

if (!firstShow)
{
return;
}

var view = GetTextViewFromFrame(frame);
if (view != null)
{
_documentTrackingService.DocumentFrameShowing(frame, id, firstShow);
_documentTrackingService?.OnNonRoslynViewOpened(view);
}
}

private ITextView GetTextViewFromFrame(IVsWindowFrame frame)
{
object documentView;
if (ErrorHandler.Failed(frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out documentView)))
{
return null;
}

var vsTextView = documentView as IVsTextView;
if (vsTextView != null)
{
return EditorAdaptersFactoryService.GetWpfTextView(vsTextView);
}

var codeWindow = documentView as IVsCodeWindow;
if (codeWindow == null)
{
return null;
}

if (ErrorHandler.Failed(codeWindow.GetPrimaryView(out vsTextView)) || vsTextView == null)
{
return null;
}

return EditorAdaptersFactoryService.GetWpfTextView(vsTextView);
}
}
}