AbstractEditorFactory.cs 15.7 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// 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.
4 5

using System;
6
using System.IO;
7
using System.Runtime.InteropServices;
D
Dustin Campbell 已提交
8
using System.Runtime.Versioning;
9
using System.Threading;
10
using System.Threading.Tasks;
11
using Microsoft.CodeAnalysis;
12 13
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Editing;
14 15
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
16
using Microsoft.CodeAnalysis.FileHeaders;
17
using Microsoft.CodeAnalysis.Formatting;
18
using Microsoft.CodeAnalysis.Shared.Extensions;
19
using Microsoft.CodeAnalysis.Shared.Utilities;
20 21 22 23
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Designer.Interfaces;
using Microsoft.VisualStudio.Editor;
24
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
25 26 27 28
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
D
David Wengier 已提交
29
using Roslyn.Utilities;
30 31 32 33 34 35

namespace Microsoft.VisualStudio.LanguageServices.Implementation
{
    /// <summary>
    /// The base class of both the Roslyn editor factories.
    /// </summary>
36
    internal abstract class AbstractEditorFactory : IVsEditorFactory, IVsEditorFactoryNotify
37 38
    {
        private readonly IComponentModel _componentModel;
39
        private Microsoft.VisualStudio.OLE.Interop.IServiceProvider? _oleServiceProvider;
40 41
        private bool _encoding;

42
        protected AbstractEditorFactory(IComponentModel componentModel)
43
            => _componentModel = componentModel;
44 45

        protected abstract string ContentTypeName { get; }
46
        protected abstract string LanguageName { get; }
47 48
        protected abstract SyntaxGenerator SyntaxGenerator { get; }
        protected abstract AbstractFileHeaderHelper FileHeaderHelper { get; }
49 50

        public void SetEncoding(bool value)
51
            => _encoding = value;
52 53

        int IVsEditorFactory.Close()
54
            => VSConstants.S_OK;
55 56 57 58

        public int CreateEditorInstance(
            uint grfCreateDoc,
            string pszMkDocument,
59
            string? pszPhysicalView,
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
            IVsHierarchy vsHierarchy,
            uint itemid,
            IntPtr punkDocDataExisting,
            out IntPtr ppunkDocView,
            out IntPtr ppunkDocData,
            out string pbstrEditorCaption,
            out Guid pguidCmdUI,
            out int pgrfCDW)
        {
            ppunkDocView = IntPtr.Zero;
            ppunkDocData = IntPtr.Zero;
            pbstrEditorCaption = string.Empty;
            pguidCmdUI = Guid.Empty;
            pgrfCDW = 0;

C
Cyrus Najmabadi 已提交
75
            var physicalView = pszPhysicalView ?? "Code";
76
            IVsTextBuffer? textBuffer = null;
77 78 79 80 81

            // Is this document already open? If so, let's see if it's a IVsTextBuffer we should re-use. This allows us
            // to properly handle multiple windows open for the same document.
            if (punkDocDataExisting != IntPtr.Zero)
            {
82
                var docDataExisting = Marshal.GetObjectForIUnknown(punkDocDataExisting);
83 84 85 86 87 88 89 90 91 92

                textBuffer = docDataExisting as IVsTextBuffer;

                if (textBuffer == null)
                {
                    // We are incompatible with the existing doc data
                    return VSConstants.VS_E_INCOMPATIBLEDOCDATA;
                }
            }

93 94
            var editorAdaptersFactoryService = _componentModel.GetService<IVsEditorAdaptersFactoryService>();

95 96 97
            // Do we need to create a text buffer?
            if (textBuffer == null)
            {
98 99 100
                var contentTypeRegistryService = _componentModel.GetService<IContentTypeRegistryService>();
                var contentType = contentTypeRegistryService.GetContentType(ContentTypeName);
                textBuffer = editorAdaptersFactoryService.CreateVsTextBufferAdapter(_oleServiceProvider, contentType);
101 102 103

                if (_encoding)
                {
C
CyrusNajmabadi 已提交
104
                    if (textBuffer is IVsUserData userData)
105 106
                    {
                        // The editor shims require that the boxed value when setting the PromptOnLoad flag is a uint
107
                        var hresult = userData.SetData(
108 109 110 111 112 113 114 115 116 117 118 119 120
                            VSConstants.VsTextBufferUserDataGuid.VsBufferEncodingPromptOnLoad_guid,
                            (uint)__PROMPTONLOADFLAGS.codepagePrompt);

                        if (ErrorHandler.Failed(hresult))
                        {
                            return hresult;
                        }
                    }
                }
            }

            // If the text buffer is marked as read-only, ensure that the padlock icon is displayed
            // next the new window's title and that [Read Only] is appended to title.
121
            var readOnlyStatus = READONLYSTATUS.ROSTATUS_NotReadOnly;
C
CyrusNajmabadi 已提交
122
            if (ErrorHandler.Succeeded(textBuffer.GetStateFlags(out var textBufferFlags)) &&
123 124 125 126 127 128 129 130 131 132
                0 != (textBufferFlags & ((uint)BUFFERSTATEFLAGS.BSF_FILESYS_READONLY | (uint)BUFFERSTATEFLAGS.BSF_USER_READONLY)))
            {
                readOnlyStatus = READONLYSTATUS.ROSTATUS_ReadOnly;
            }

            switch (physicalView)
            {
                case "Form":

                    // We must create the WinForms designer here
133
                    var loaderName = GetWinFormsLoaderName(vsHierarchy);
134 135 136
                    var designerService = (IVSMDDesignerService)_oleServiceProvider.QueryService<SVSMDDesignerService>();
                    var designerLoader = (IVSMDDesignerLoader)designerService.CreateDesignerLoader(loaderName);
                    if (designerLoader is null)
137 138 139
                    {
                        goto case "Code";
                    }
140

141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
                    try
                    {
                        designerLoader.Initialize(_oleServiceProvider, vsHierarchy, (int)itemid, (IVsTextLines)textBuffer);
                        pbstrEditorCaption = designerLoader.GetEditorCaption((int)readOnlyStatus);

                        var designer = designerService.CreateDesigner(_oleServiceProvider, designerLoader);
                        ppunkDocView = Marshal.GetIUnknownForObject(designer.View);
                        pguidCmdUI = designer.CommandGuid;
                    }
                    catch
                    {
                        designerLoader.Dispose();
                        throw;
                    }

                    break;

                case "Code":

160
                    var codeWindow = editorAdaptersFactoryService.CreateVsCodeWindowAdapter(_oleServiceProvider);
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
                    codeWindow.SetBuffer((IVsTextLines)textBuffer);

                    codeWindow.GetEditorCaption(readOnlyStatus, out pbstrEditorCaption);

                    ppunkDocView = Marshal.GetIUnknownForObject(codeWindow);
                    pguidCmdUI = VSConstants.GUID_TextEditorFactory;

                    break;

                default:

                    return VSConstants.E_INVALIDARG;
            }

            ppunkDocData = Marshal.GetIUnknownForObject(textBuffer);

            return VSConstants.S_OK;
        }

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        private string? GetWinFormsLoaderName(IVsHierarchy vsHierarchy)
        {
            const string LoaderName = "Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader";
            const string NewLoaderName = "Microsoft.VisualStudio.Design.Core.Serialization.CodeDom.VSCodeDomDesignerLoader";

            // If this is a netcoreapp3.0 (or newer), we must create the newer WinForms designer.
            // TODO: This check will eventually move into the WinForms designer itself.
            if (!vsHierarchy.TryGetTargetFrameworkMoniker((uint)VSConstants.VSITEMID.Root, out var targetFrameworkMoniker) ||
                string.IsNullOrWhiteSpace(targetFrameworkMoniker))
            {
                return LoaderName;
            }

            try
            {
                var frameworkName = new FrameworkName(targetFrameworkMoniker);
                if (frameworkName.Identifier == ".NETCoreApp" && frameworkName.Version?.Major >= 3)
                {
                    return NewLoaderName;
                }
            }
            catch
            {
                // Fall back to the old loader name if there are any failures
                // while parsing the TFM.
            }

            return LoaderName;
        }

210
        public int MapLogicalView(ref Guid rguidLogicalView, out string? pbstrPhysicalView)
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
        {
            pbstrPhysicalView = null;

            if (rguidLogicalView == VSConstants.LOGVIEWID.Primary_guid ||
                rguidLogicalView == VSConstants.LOGVIEWID.Debugging_guid ||
                rguidLogicalView == VSConstants.LOGVIEWID.Code_guid ||
                rguidLogicalView == VSConstants.LOGVIEWID.TextView_guid)
            {
                return VSConstants.S_OK;
            }
            else if (rguidLogicalView == VSConstants.LOGVIEWID.Designer_guid)
            {
                pbstrPhysicalView = "Form";
                return VSConstants.S_OK;
            }
            else
            {
                return VSConstants.E_NOTIMPL;
            }
        }

        int IVsEditorFactory.SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp)
        {
            _oleServiceProvider = psp;
            return VSConstants.S_OK;
        }

        int IVsEditorFactoryNotify.NotifyDependentItemSaved(IVsHierarchy pHier, uint itemidParent, string pszMkDocumentParent, uint itemidDpendent, string pszMkDocumentDependent)
239
            => VSConstants.S_OK;
240 241 242 243

        int IVsEditorFactoryNotify.NotifyItemAdded(uint grfEFN, IVsHierarchy pHier, uint itemid, string pszMkDocument)
        {
            // Is this being added from a template?
244
            if (((__EFNFLAGS)grfEFN & __EFNFLAGS.EFN_ClonedFromTemplate) != 0)
245
            {
246
                var waitIndicator = _componentModel.GetService<IWaitIndicator>();
247
                // TODO(cyrusn): Can this be cancellable?
248
                waitIndicator.Wait(
249 250
                    "Intellisense",
                    allowCancel: false,
251
                    action: c => FormatDocumentCreatedFromTemplate(pHier, itemid, pszMkDocument, c.CancellationToken));
252 253 254 255 256 257
            }

            return VSConstants.S_OK;
        }

        int IVsEditorFactoryNotify.NotifyItemRenamed(IVsHierarchy pHier, uint itemid, string pszMkDocumentOld, string pszMkDocumentNew)
258
            => VSConstants.S_OK;
259

260 261 262
        protected virtual Task<Document> OrganizeUsingsCreatedFromTemplateAsync(Document document, CancellationToken cancellationToken)
            => Formatter.OrganizeImportsAsync(document, cancellationToken);

263
        private void FormatDocumentCreatedFromTemplate(IVsHierarchy hierarchy, uint itemid, string filePath, CancellationToken cancellationToken)
264
        {
265 266 267
            // A file has been created on disk which the user added from the "Add Item" dialog. We need
            // to include this in a workspace to figure out the right options it should be formatted with.
            // This requires us to place it in the correct project.
268
            var workspace = _componentModel.GetService<VisualStudioWorkspace>();
269
            var solution = workspace.CurrentSolution;
270

271
            ProjectId? projectIdToAddTo = null;
272

273
            foreach (var projectId in solution.ProjectIds)
274
            {
275
                if (workspace.GetHierarchy(projectId) == hierarchy)
276
                {
277 278 279 280
                    projectIdToAddTo = projectId;
                    break;
                }
            }
281

282 283 284 285 286 287 288 289 290 291 292
            if (projectIdToAddTo == null)
            {
                // We don't have a project for this, so we'll just make up a fake project altogether
                var temporaryProject = solution.AddProject(
                    name: nameof(FormatDocumentCreatedFromTemplate),
                    assemblyName: nameof(FormatDocumentCreatedFromTemplate),
                    language: LanguageName);

                solution = temporaryProject.Solution;
                projectIdToAddTo = temporaryProject.Id;
            }
293

294 295
            var documentId = DocumentId.CreateNewId(projectIdToAddTo);
            var forkedSolution = solution.AddDocument(DocumentInfo.Create(documentId, filePath, loader: new FileTextLoader(filePath, defaultEncoding: null), filePath: filePath));
296
            var addedDocument = forkedSolution.GetDocument(documentId)!;
297

C
CyrusNajmabadi 已提交
298
            var rootToFormat = addedDocument.GetSyntaxRootSynchronously(cancellationToken);
D
David Wengier 已提交
299
            Contract.ThrowIfNull(rootToFormat);
C
Carol Hu 已提交
300
            var documentOptions = ThreadHelper.JoinableTaskFactory.Run(() => addedDocument.GetOptionsAsync(cancellationToken));
301

302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
            // Apply file header preferences
            var fileHeaderTemplate = documentOptions.GetOption(CodeStyleOptions2.FileHeaderTemplate);
            if (!string.IsNullOrEmpty(fileHeaderTemplate))
            {
                var documentWithFileHeader = ThreadHelper.JoinableTaskFactory.Run(() =>
                {
                    var newLineText = documentOptions.GetOption(FormattingOptions.NewLine, rootToFormat.Language);
                    var newLineTrivia = SyntaxGenerator.EndOfLine(newLineText);
                    return AbstractFileHeaderCodeFixProvider.GetTransformedSyntaxRootAsync(
                        SyntaxGenerator.SyntaxFacts,
                        FileHeaderHelper,
                        newLineTrivia,
                        addedDocument,
                        cancellationToken);
                });

                addedDocument = addedDocument.WithSyntaxRoot(documentWithFileHeader);
                rootToFormat = documentWithFileHeader;
            }

322 323
            // Organize using directives
            addedDocument = ThreadHelper.JoinableTaskFactory.Run(() => OrganizeUsingsCreatedFromTemplateAsync(addedDocument, cancellationToken));
324
            rootToFormat = ThreadHelper.JoinableTaskFactory.Run(() => addedDocument.GetRequiredSyntaxRootAsync(cancellationToken).AsTask());
325

326 327 328 329
            // Format document
            var unformattedText = addedDocument.GetTextSynchronously(cancellationToken);
            var formattedRoot = Formatter.Format(rootToFormat, workspace, documentOptions, cancellationToken);
            var formattedText = formattedRoot.GetText(unformattedText.Encoding, unformattedText.ChecksumAlgorithm);
330

331
            // Ensure the line endings are normalized. The formatter doesn't touch everything if it doesn't need to.
332
            var targetLineEnding = documentOptions.GetOption(FormattingOptions.NewLine)!;
333

334 335 336
            var originalText = formattedText;
            foreach (var originalLine in originalText.Lines)
            {
337
                var originalNewLine = originalText.ToString(CodeAnalysis.Text.TextSpan.FromBounds(originalLine.End, originalLine.EndIncludingLineBreak));
338 339 340 341 342 343 344

                // Check if we have a line ending, so we don't go adding one to the end if we don't need to.
                if (originalNewLine.Length > 0 && originalNewLine != targetLineEnding)
                {
                    var currentLine = formattedText.Lines[originalLine.LineNumber];
                    var currentSpan = CodeAnalysis.Text.TextSpan.FromBounds(currentLine.End, currentLine.EndIncludingLineBreak);
                    formattedText = formattedText.WithChanges(new TextChange(currentSpan, targetLineEnding));
345 346
                }
            }
347 348 349

            IOUtilities.PerformIO(() =>
            {
C
Cyrus Najmabadi 已提交
350 351 352
                using var textWriter = new StreamWriter(filePath, append: false, encoding: formattedText.Encoding);
                // We pass null here for cancellation, since cancelling in the middle of the file write would leave the file corrupted
                formattedText.Write(textWriter, cancellationToken: CancellationToken.None);
353
            });
354 355 356
        }
    }
}