InvisibleEditor.cs 8.3 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
// 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.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Extensions;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.TextManager.Interop;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
    internal partial class InvisibleEditor : IInvisibleEditor
    {
        private readonly IServiceProvider _serviceProvider;
        private readonly string _filePath;
        private readonly bool _needsSave = false;

        /// <summary>
        /// The text buffer. null if the object has been disposed.
        /// </summary>
        private ITextBuffer _buffer;
        private IVsTextLines _vsTextLines;
        private IVsInvisibleEditor _invisibleEditor;
        private OLE.Interop.IOleUndoManager _manager;
        private readonly bool _needsUndoRestored;

31 32 33 34 35 36 37
        /// <remarks>
        /// <para>The optional project is used to obtain an <see cref="IVsProject"/> 1nstance. When this instance is
        /// provided, Visual Studio will use <see cref="IVsProject.IsDocumentInProject"/> to attempt to locate the
        /// specified file within a project. If no project is specified, Visual Studio falls back to using
        /// <see cref="IVsUIShellOpenDocument4.IsDocumentInAProject2"/>, which performs a much slower query of all
        /// projects in the solution.</para>
        /// </remarks>
38
        public InvisibleEditor(IServiceProvider serviceProvider, string filePath, IVsHierarchy hierarchyOpt, bool needsSave, bool needsUndoDisabled)
39 40 41 42 43 44
        {
            _serviceProvider = serviceProvider;
            _filePath = filePath;
            _needsSave = needsSave;

            var invisibleEditorManager = (IIntPtrReturningVsInvisibleEditorManager)serviceProvider.GetService(typeof(SVsInvisibleEditorManager));
45
            var vsProject = TryGetProjectOfHierarchy(hierarchyOpt);
46
            var invisibleEditorPtr = IntPtr.Zero;
47
            Marshal.ThrowExceptionForHR(invisibleEditorManager.RegisterInvisibleEditor(filePath, vsProject, 0, null, out invisibleEditorPtr));
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

            try
            {
                _invisibleEditor = (IVsInvisibleEditor)Marshal.GetUniqueObjectForIUnknown(invisibleEditorPtr);

                var docDataPtr = IntPtr.Zero;
                Marshal.ThrowExceptionForHR(_invisibleEditor.GetDocData(fEnsureWritable: needsSave ? 1 : 0, riid: typeof(IVsTextLines).GUID, ppDocData: out docDataPtr));

                try
                {
                    var docData = Marshal.GetObjectForIUnknown(docDataPtr);
                    _vsTextLines = docData as IVsTextLines;
                    var vsTextBuffer = (IVsTextBuffer)docData;
                    var editorAdapterFactoryService = serviceProvider.GetMefService<IVsEditorAdaptersFactoryService>();
                    _buffer = editorAdapterFactoryService.GetDocumentBuffer(vsTextBuffer);
                    if (needsUndoDisabled)
                    {
                        Marshal.ThrowExceptionForHR(vsTextBuffer.GetUndoManager(out _manager));
C
CyrusNajmabadi 已提交
66
                        Marshal.ThrowExceptionForHR((_manager as IVsUndoState).IsEnabled(out var isEnabled));
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
                        _needsUndoRestored = isEnabled != 0;
                        if (_needsUndoRestored)
                        {
                            _manager.DiscardFrom(null); // Discard the undo history for this document
                            _manager.Enable(0); // Disable Undo for this document
                        }
                    }
                }
                finally
                {
                    Marshal.Release(docDataPtr);
                }
            }
            finally
            {
                // We need to clean up the extra reference we have, now that we have an RCW holding onto the object.
                Marshal.Release(invisibleEditorPtr);
            }
        }

87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
        private IVsProject TryGetProjectOfHierarchy(IVsHierarchy hierarchyOpt)
        {
            // The invisible editor manager will fail in cases where the IVsProject passed to it is not consistent with
            // the IVsProject known to IVsSolution (e.g. if the object is a wrapper like AbstractHostObject created by
            // the CPS-based project system). This method returns an IVsProject instance known to the solution, or null
            // if the project could not be determined.
            if (hierarchyOpt == null)
            {
                return null;
            }

            if (!ErrorHandler.Succeeded(hierarchyOpt.GetGuidProperty(
                (uint)VSConstants.VSITEMID.Root,
                (int)__VSHPROPID.VSHPROPID_ProjectIDGuid,
                out var projectId)))
            {
                return null;
            }

            var solution = (IVsSolution)_serviceProvider.GetService(typeof(SVsSolution));
            if (!ErrorHandler.Succeeded(solution.GetProjectOfGuid(projectId, out var projectHierarchy)))
            {
                return null;
            }

            return projectHierarchy as IVsProject;
        }

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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
        public IVsTextLines VsTextLines
        {
            get
            {
                return _vsTextLines;
            }
        }

        public ITextBuffer TextBuffer
        {
            get
            {
                if (_buffer == null)
                {
                    throw new ObjectDisposedException(GetType().Name);
                }

                return _buffer;
            }
        }

        /// <summary>
        /// Closes the invisible editor and saves the underlying document as appropriate.
        /// </summary>
        public void Dispose()
        {
            _buffer = null;
            _vsTextLines = null;

            try
            {
                if (_needsSave)
                {
                    // We need to tell this document to save before we get rid of the invisible editor. Otherwise,
                    // the invisible editor never actually makes the document go away. Check out CLockHolder::ReleaseEditLock
                    // in env\msenv\core\editmgr.cpp for details. We choose this particular technique for saving files
                    // since it's what the old cslangsvc.dll used.
                    var runningDocumentTable4 = (IVsRunningDocumentTable4)_serviceProvider.GetService(typeof(SVsRunningDocumentTable));

                    if (runningDocumentTable4.IsMonikerValid(_filePath))
                    {
                        var cookie = runningDocumentTable4.GetDocumentCookie(_filePath);
                        var runningDocumentTable = (IVsRunningDocumentTable)runningDocumentTable4;

                        // Old cslangsvc.dll requested not to add to MRU for, and I quote, "performance!". Makes sense not
                        // to include it in the MRU anyways.
                        ErrorHandler.ThrowOnFailure(runningDocumentTable.ModifyDocumentFlags(cookie, (uint)_VSRDTFLAGS.RDT_DontAddToMRU, fSet: 1));

                        runningDocumentTable.SaveDocuments((uint)__VSRDTSAVEOPTIONS.RDTSAVEOPT_SaveIfDirty, pHier: null, itemid: 0, docCookie: cookie);
                    }
                }

                if (_needsUndoRestored && _manager != null)
                {
                    _manager.Enable(1);
                    _manager = null;
                }

C
Charles Stoner 已提交
173
                // Clean up our RCW. This RCW is a unique RCW, so this is actually safe to do!
174 175 176 177 178
                Marshal.ReleaseComObject(_invisibleEditor);
                _invisibleEditor = null;

                GC.SuppressFinalize(this);
            }
B
beep boop 已提交
179
            catch (Exception ex) when (FatalError.Report(ex))
180 181
            {
            }
B
beep boop 已提交
182
        }
183

184 185
#pragma warning disable CA1821 // Remove empty Finalizers
#if DEBUG
B
beep boop 已提交
186
        ~InvisibleEditor()
187
        {
B
beep boop 已提交
188
            Debug.Assert(Environment.HasShutdownStarted, GetType().Name + " was leaked without Dispose being called.");
189
        }
190 191
#endif
#pragma warning restore CA1821 // Remove empty Finalizers
192
    }
B
beep boop 已提交
193
}