ReadOnlyDocumentTracker.cs 5.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
// 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.Diagnostics;
using Microsoft.CodeAnalysis.EditAndContinue;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue
{
    internal sealed class ReadOnlyDocumentTracker : ForegroundThreadAffinitizedObject, IDisposable
    {
        private readonly IEditAndContinueWorkspaceService _encService;
        private readonly Workspace _workspace;

        // null after the object is disposed
        private Dictionary<DocumentId, IReadOnlyRegion> _readOnlyRegions;

        // invoked on UI thread
        private readonly Action<DocumentId, SessionReadOnlyReason, ProjectReadOnlyReason> _onReadOnlyDocumentEditAttempt;

        public ReadOnlyDocumentTracker(IEditAndContinueWorkspaceService encService, Action<DocumentId, SessionReadOnlyReason, ProjectReadOnlyReason> onReadOnlyDocumentEditAttempt)
            : base(assertIsForeground: true)
        {
            Debug.Assert(encService.DebuggingSession != null);

            _encService = encService;
            _readOnlyRegions = new Dictionary<DocumentId, IReadOnlyRegion>();
            _workspace = encService.DebuggingSession.InitialSolution.Workspace;
            _onReadOnlyDocumentEditAttempt = onReadOnlyDocumentEditAttempt;

            _workspace.DocumentClosed += OnDocumentClosed;
            _workspace.DocumentOpened += OnDocumentOpened;

            foreach (var documentId in _workspace.GetOpenDocumentIds())
            {
                TrackDocument(documentId);
            }
        }

        public Workspace Workspace
        {
            get { return _workspace; }
        }

        private void OnDocumentOpened(object sender, DocumentEventArgs e)
        {
            InvokeBelowInputPriority(() => TrackDocument(e.Document.Id));
        }

        private void OnDocumentClosed(object sender, DocumentEventArgs e)
        {
            // The buffer is gone by now, so we don't need to remove the read-only region from it, just clean up our dictionary.
            InvokeBelowInputPriority(() =>
            {
                if (_readOnlyRegions != null)
                {
                    _readOnlyRegions.Remove(e.Document.Id);
                }
            });
        }

        private void TrackDocument(DocumentId documentId)
        {
            AssertIsForeground();

            if (_readOnlyRegions == null || _readOnlyRegions.ContainsKey(documentId))
            {
                return;
            }

            var textBuffer = GetTextBuffer(_workspace, documentId);
            using (var readOnlyEdit = textBuffer.CreateReadOnlyRegionEdit())
            {
                _readOnlyRegions.Add(documentId, readOnlyEdit.CreateDynamicReadOnlyRegion(Span.FromBounds(0, readOnlyEdit.Snapshot.Length), SpanTrackingMode.EdgeInclusive, EdgeInsertionMode.Deny,
                    isEdit => IsRegionReadOnly(documentId, isEdit)));

                readOnlyEdit.Apply();
            }
        }

        private bool IsRegionReadOnly(DocumentId documentId, bool isEdit)
        {
            AssertIsForeground();

            SessionReadOnlyReason sessionReason;
            ProjectReadOnlyReason projectReason;
91
            bool isReadOnly = _encService.IsProjectReadOnly(documentId.ProjectId, out sessionReason, out projectReason);
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141

            if (isEdit && isReadOnly)
            {
                if (_onReadOnlyDocumentEditAttempt != null)
                {
                    _onReadOnlyDocumentEditAttempt(documentId, sessionReason, projectReason);
                }
            }

            return isReadOnly;
        }

        public void Dispose()
        {
            AssertIsForeground();

            _workspace.DocumentClosed -= OnDocumentClosed;
            _workspace.DocumentOpened -= OnDocumentOpened;

            // event handlers may be queued after the disposal - they should be a no-op
            foreach (var documentAndRegion in _readOnlyRegions)
            {
                RemoveReadOnlyRegionFromBuffer(documentAndRegion.Key, documentAndRegion.Value);
            }

            _readOnlyRegions = null;
        }

        private void RemoveReadOnlyRegionFromBuffer(DocumentId documentId, IReadOnlyRegion region)
        {
            AssertIsForeground();

            var textBuffer = GetTextBuffer(_workspace, documentId);
            using (var readOnlyEdit = textBuffer.CreateReadOnlyRegionEdit())
            {
                readOnlyEdit.RemoveReadOnlyRegion(region);
                readOnlyEdit.Apply();
            }
        }

        private static ITextBuffer GetTextBuffer(Workspace workspace, DocumentId documentId)
        {
            var doc = workspace.CurrentSolution.GetDocument(documentId);
            SourceText text;
            doc.TryGetText(out text);
            var snapshot = text.FindCorrespondingEditorTextSnapshot();
            return snapshot.TextBuffer;
        }
    }
}