/// <summary> /// Gets the <see cref="IVisualStudioHostDocument"/> for the file at the given filePath. /// If we are on the foreground thread and this document is already open in the editor, /// then we also attempt to associate the text buffer with it. /// Otherwise, if we are on a background thread, then this text buffer association will happen on a scheduled task /// whenever <see cref="NotifyDocumentRegisteredToProjectAndStartToRaiseEvents"/> is invoked for the returned document. /// </summary> public IVisualStudioHostDocument TryGetDocumentForFile( IVisualStudioHostProject hostProject, string filePath, SourceCodeKind sourceCodeKind, Func <ITextBuffer, bool> canUseTextBuffer, Func <uint, IReadOnlyList <string> > getFolderNames, EventHandler updatedOnDiskHandler = null, EventHandler <bool> openedHandler = null, EventHandler <bool> closingHandler = null) { var documentKey = new DocumentKey(hostProject, filePath); StandardTextDocument document; lock (_gate) { if (_documentMap.TryGetValue(documentKey, out document)) { return(document); } } ITextBuffer openTextBuffer = null; uint foundCookie = VSConstants.VSCOOKIE_NIL; if (IsForeground()) { // If we are on the foreground thread and this document is already open in the editor we want to associate the text buffer with it. // Otherwise, we are on a background thread, and this text buffer association will happen on a scheduled task // whenever NotifyDocumentRegisteredToProjectAndStartToRaiseEvents is invoked for the returned document. // However, determining if a document is already open is a bit complicated. With the introduction // of the lazy tabs feature in Dev12, a document may be open (i.e. it has a tab in the shell) but not // actually initialized (no data has been loaded for it because its contents have not actually been // viewed or used). We only care about documents that are open AND initialized. // That means we can't call IVsRunningDocumentTable::FindAndLockDocument to find the document; if the // document is open but not initialized, the call will force initialization. This is bad for two // reasons: // 1.) It circumvents lazy tabs for any document that is part of a VB or C# project. // 2.) Initialization may cause a whole host of other code to run synchronously, such as taggers. // Instead, we check if the document is already initialized, and avoid asking for the doc data and // hierarchy if it is not. if (_runningDocumentTable.TryGetCookieForInitializedDocument(documentKey.Moniker, out foundCookie)) { object foundDocData = _runningDocumentTable.GetDocumentData(foundCookie); openTextBuffer = TryGetTextBufferFromDocData(foundDocData); if (openTextBuffer == null) { // We're open but not open as a normal text buffer. This can happen if the // project system (say in ASP.NET cases) is telling us to add a file which // actually isn't a normal text file at all. return(null); } if (!canUseTextBuffer(openTextBuffer)) { return(null); } } } lock (_gate) { // If this is being added through a public call to Workspace.AddDocument (say, ApplyChanges) then we might // already have a document ID that we should be using here. _documentIdHints.TryGetValue(filePath, out var id); document = new StandardTextDocument( this, hostProject, documentKey, getFolderNames, sourceCodeKind, _textUndoHistoryRegistry, _fileChangeService, openTextBuffer, id, updatedOnDiskHandler, openedHandler, closingHandler); // Add this to our document map _documentMap.Add(documentKey, document); if (openTextBuffer != null) { AddCookieOpenDocumentPair_NoLock(foundCookie, documentKey); } } return(document); }
private void TryProcessOpenForDocCookie_NoLock(uint docCookie) { string moniker = _runningDocumentTable.GetDocumentMoniker(docCookie); _runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out var itemid); var shimTextBuffer = _runningDocumentTable.GetDocumentData(docCookie) as IVsTextBuffer; if (shimTextBuffer != null) { var hasAssociatedRoslynDocument = false; foreach (var project in _projectContainer.GetProjects()) { var documentKey = new DocumentKey(project, moniker); if (_documentMap.ContainsKey(documentKey)) { hasAssociatedRoslynDocument = true; var textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(shimTextBuffer); // If we already have an ITextBuffer for this document, then we can open it now. // Otherwise, setup an event handler that will do it when the buffer loads. if (textBuffer != null) { // We might already have this docCookie marked as open an older document. This can happen // if we're in the middle of a rename but this class hasn't gotten the notification yet but // another listener for RDT events got it if (_docCookiesToOpenDocumentKeys.ContainsKey(docCookie)) { CloseDocuments_NoLock(docCookie, monikerToKeep: moniker); } if (hierarchy == project.Hierarchy) { // This is the current context NewBufferOpened_NoLock(docCookie, textBuffer, documentKey, isCurrentContext: true); } else { // This is a non-current linked context NewBufferOpened_NoLock(docCookie, textBuffer, documentKey, isCurrentContext: false); } } else { TextBufferDataEventsSink.HookupHandler(shimTextBuffer, onDocumentLoadCompleted: () => OnDocumentLoadCompleted(shimTextBuffer, documentKey, moniker)); } } } if (!hasAssociatedRoslynDocument && this._documentTrackingServiceOpt != null && !_docCookiesToNonRoslynDocumentBuffers.ContainsKey(docCookie)) { // Non-Roslyn document opened. var textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(shimTextBuffer); if (textBuffer != null) { OnNonRoslynBufferOpened_NoLock(textBuffer, docCookie); } else { TextBufferDataEventsSink.HookupHandler(shimTextBuffer, onDocumentLoadCompleted: () => OnDocumentLoadCompleted(shimTextBuffer, documentKeyOpt: null, moniker: moniker)); } } } else { // This is opening some other designer or property page. If it's tied to our IVsHierarchy, we should // let the workspace know foreach (var project in _projectContainer.GetProjects()) { if (hierarchy == project.Hierarchy) { _projectContainer.NotifyNonDocumentOpenedForProject(project); } } } }
/// <summary> /// Creates a <see cref="StandardTextDocument"/>. /// <para>Note: getFolderNames maps from a VSITEMID to the folders this document should be contained in.</para> /// </summary> public StandardTextDocument( DocumentProvider documentProvider, IVisualStudioHostProject project, DocumentKey documentKey, Func <uint, IReadOnlyList <string> > getFolderNames, SourceCodeKind sourceCodeKind, ITextUndoHistoryRegistry textUndoHistoryRegistry, IVsFileChangeEx fileChangeService, ITextBuffer openTextBuffer, DocumentId id, EventHandler updatedOnDiskHandler, EventHandler <bool> openedHandler, EventHandler <bool> closingHandler) { Contract.ThrowIfNull(documentProvider); this.Project = project; this.Id = id ?? DocumentId.CreateNewId(project.Id, documentKey.Moniker); _itemMoniker = documentKey.Moniker; var itemid = this.GetItemId(); this.Folders = itemid == (uint)VSConstants.VSITEMID.Nil ? SpecializedCollections.EmptyReadOnlyList <string>() : getFolderNames(itemid); _documentProvider = documentProvider; this.Key = documentKey; this.SourceCodeKind = sourceCodeKind; _textUndoHistoryRegistry = textUndoHistoryRegistry; _fileChangeTracker = new FileChangeTracker(fileChangeService, this.FilePath); _fileChangeTracker.UpdatedOnDisk += OnUpdatedOnDisk; _openTextBuffer = openTextBuffer; _snapshotTracker = new ReiteratedVersionSnapshotTracker(openTextBuffer); // The project system does not tell us the CodePage specified in the proj file, so // we use null to auto-detect. _doNotAccessDirectlyLoader = new FileTextLoader(documentKey.Moniker, defaultEncoding: null); // If we aren't already open in the editor, then we should create a file change notification if (openTextBuffer == null) { _fileChangeTracker.StartFileChangeListeningAsync(); } if (updatedOnDiskHandler != null) { UpdatedOnDisk += updatedOnDiskHandler; } if (openedHandler != null) { Opened += openedHandler; } if (closingHandler != null) { Closing += closingHandler; } }
private TextBufferDataEventsSink(DocumentProvider documentProvider, IVsTextBuffer textBuffer, DocumentKey documentKey) { _documentProvider = documentProvider; _textBuffer = textBuffer; _documentKey = documentKey; }
/// <summary> /// Helper method for creating and hooking up a <c>TextBufferDataEventsSink</c>. /// </summary> public static void HookupHandler(DocumentProvider documentProvider, IVsTextBuffer textBuffer, DocumentKey documentKey) { var eventHandler = new TextBufferDataEventsSink(documentProvider, textBuffer, documentKey); eventHandler._sink = ComEventSink.Advise <IVsTextBufferDataEvents>(textBuffer, eventHandler); }
private bool IsCurrentContext(DocumentKey documentKey) { AssertIsForeground(); var document = documentKey.HostProject.GetCurrentDocumentFromPath(documentKey.Moniker); return document != null && LinkedFileUtilities.IsCurrentContextHierarchy(document, _runningDocumentTable); }