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 1 commit
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,30 @@ 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
var buffer = TryGetTextBufferFromDocData(RunningDocumentTable.GetDocumentData(docCookie));
if (buffer != null)
{
OnBeforeNonRoslynDocumentWindowShow(buffer, firstShow);
}
}
}

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

protected virtual void OnBeforeNonRoslynDocumentWindowShow(ITextBuffer buffer, bool firstShow)
{
}

protected virtual void OnBeforeNonRoslynDocumentClose(ITextBuffer buffer)
{
}

private IList<DocumentId> GetDocumentIdsFromDocCookie(uint docCookie)
{
List<DocumentKey> documentKeys;
Expand Down Expand Up @@ -300,35 +318,44 @@ 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)
// let others know about non roslyn document close
var buffer = TryGetTextBufferFromDocData(RunningDocumentTable.GetDocumentData(docCookie));
if (buffer != null)
Copy link
Member

Choose a reason for hiding this comment

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

What happens if the document, after already being open, was then added to the workspace? At this point this code path will get skipped, and the view unsubscribe might not run?

Trying to do this state tracking in this class really isn't the right thing to do. We should just have a separate class to subscribe to the text view notifications.

{
var document = _documentMap[documentKey];
document.ProcessClose(updateActiveContext);
Contract.ThrowIfFalse(documentKeys.Remove(documentKey));
OnBeforeNonRoslynDocumentClose(buffer);
}

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

// 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,34 @@
// 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.ComponentModelHost;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
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 +140,18 @@ public int OnCmdUIContextChanged([ComAliasName("Microsoft.VisualStudio.Shell.Int
return VSConstants.E_NOTIMPL;
}

public event EventHandler<EventArgs> NonRoslynBufferTextChanged;

public void OnNonRoslynBufferOpened(ITextBuffer buffer)
{
_tracker.OnOpened(buffer);
}

public void OnNonRoslynBufferClosed(ITextBuffer buffer)
{
_tracker.OnClosed(buffer);
}

public void Dispose()
{
if (_cookie != VSConstants.VSCOOKIE_NIL && _monitorSelection != null)
Expand Down Expand Up @@ -262,5 +282,51 @@ 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<ITextBuffer> _buffers;

public NonRoslynTextBufferTracker(VisualStudioDocumentTrackingService owner)
{
_owner = owner;
_buffers = new HashSet<ITextBuffer>();
}

public void OnOpened(ITextBuffer buffer)
{
AssertIsForeground();

if (_buffers.Contains(buffer))
Copy link
Member

Choose a reason for hiding this comment

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

What happens for multiple views over the same buffer?

{
return;
}

_buffers.Add(buffer);
buffer.PostChanged += OnTextChanged;
}

public void OnClosed(ITextBuffer buffer)
{
AssertIsForeground();

if (!_buffers.Contains(buffer))
{
return;
}

buffer.PostChanged -= OnTextChanged;
_buffers.Remove(buffer);
}

private void OnTextChanged(object sender, EventArgs e)
{
_owner.NonRoslynBufferTextChanged?.Invoke(sender, e);
}
}
}
}
23 changes: 21 additions & 2 deletions src/VisualStudio/Core/Def/RoslynDocumentProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.VisualStudio.LanguageServices.Implementation;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;

namespace Microsoft.VisualStudio.LanguageServices
{
Expand All @@ -23,10 +24,28 @@ 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(ITextBuffer buffer, bool firstShow)
{
base.OnBeforeNonRoslynDocumentWindowShow(buffer, firstShow);

if (!firstShow)
{
_documentTrackingService.DocumentFrameShowing(frame, id, firstShow);
return;
}

_documentTrackingService?.OnNonRoslynBufferOpened(buffer);
}

protected override void OnBeforeNonRoslynDocumentClose(ITextBuffer buffer)
{
base.OnBeforeNonRoslynDocumentClose(buffer);

_documentTrackingService?.OnNonRoslynBufferClosed(buffer);
}
}
}