diff options
author | Aaron Bockover <abock@microsoft.com> | 2019-07-03 23:10:19 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-07-03 23:10:19 +0300 |
commit | a5e6fa4c3fb65cebad4d0863dcf876913f3f6313 (patch) | |
tree | 28c8e7c25d9b2ecf6e224325721edf35e1655e47 /main | |
parent | 6fa0553335974757a356ef756094dd3502834dae (diff) | |
parent | 107025544efe812599ebcd5bcfdcc823825e7256 (diff) |
Merge pull request #8123 from mono/backport-pr-7882-to-release-8.2
[release-8.2] Fixes VSTS 915954: "Rename File to {NewClassName}.cs" code action does not work
Diffstat (limited to 'main')
7 files changed, 161 insertions, 6 deletions
diff --git a/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor/TextViewContent.cs b/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor/TextViewContent.cs index ba0fc0e72e..24bd8b38eb 100644 --- a/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor/TextViewContent.cs +++ b/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor/TextViewContent.cs @@ -236,8 +236,6 @@ namespace MonoDevelop.TextEditor if (TextDocument == null) return; - UpdateTextBufferRegistration (); - warnOverwrite = false; if (editorConfigContext != null) { @@ -261,6 +259,8 @@ namespace MonoDevelop.TextEditor //if (this.WorkbenchWindow?.Document != null) // textEditor.InitializeExtensionChain (this.WorkbenchWindow.Document); + UpdateTextBufferRegistration (); + UpdateTextEditorOptions (null, null); } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Documents/DocumentController.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Documents/DocumentController.cs index 759a537ed4..4299e121d5 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Documents/DocumentController.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Documents/DocumentController.cs @@ -672,7 +672,9 @@ namespace MonoDevelop.Ide.Gui.Documents // Get the list of nodes for which an extension has been created - var allExtensions = extensionChain.GetAllExtensions ().OfType<DocumentControllerExtension> ().ToList (); + var allExtensions = (extensionChain.GetAllExtensions () ?? Array.Empty<DocumentControllerExtension>()) + .OfType<DocumentControllerExtension> ().ToList (); + var loadedNodes = allExtensions.Where (ex => ex.SourceExtensionNode != null) .Select (ex => ex.SourceExtensionNode.Data.NodeId).ToList (); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Documents/TextBufferFileModel.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Documents/TextBufferFileModel.cs index 59f3193212..2941b70049 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Documents/TextBufferFileModel.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Documents/TextBufferFileModel.cs @@ -107,6 +107,10 @@ namespace MonoDevelop.Ide.Gui.Documents protected override void OnSetText (string text) { // OnLoad is always called before anything else, so the document should be ready + if (textDocument == null) { + return; + } + var edit = textDocument.TextBuffer.CreateEdit (); edit.Replace (0, textDocument.TextBuffer.CurrentSnapshot.Length, text); edit.Apply (); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Shell/DefaultWorkbench.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Shell/DefaultWorkbench.cs index 8ee60288a4..f7373dd586 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Shell/DefaultWorkbench.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Shell/DefaultWorkbench.cs @@ -1065,7 +1065,7 @@ namespace MonoDevelop.Ide.Gui bool IsInFullViewMode { get { - return dock.CurrentLayout != null && dock.CurrentLayout.EndsWith(fullViewModeTag, StringComparison.Ordinal); + return dock?.CurrentLayout?.EndsWith(fullViewModeTag, StringComparison.Ordinal) ?? false; } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/RoslynDocumentExtension.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/RoslynDocumentExtension.cs index c54d16a77a..d5153082dd 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/RoslynDocumentExtension.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/RoslynDocumentExtension.cs @@ -136,6 +136,7 @@ namespace MonoDevelop.Ide.Gui TryEditorInitialization (); UpdateTextBufferRegistration (); + SubscribeControllerEvents (); } public void TryEditorInitialization () @@ -482,6 +483,7 @@ namespace MonoDevelop.Ide.Gui if (doc != null) return Task.CompletedTask; } + if (Editor == null) { UnsubscribeAnalysisDocument (); UpdateTextBufferRegistration (); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs index aba2c748c8..7f59e41ec5 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs @@ -995,6 +995,7 @@ namespace MonoDevelop.Ide.TypeSystem // as a result, we can assume that the things it calls are _also_ main thread only
Runtime.CheckMainThread ();
lock (projectModifyLock) {
+ FileService.FreezeEvents (); freezeProjectModify = true;
try {
var ret = base.TryApplyChanges (newSolution, progressTracker);
@@ -1024,6 +1025,7 @@ namespace MonoDevelop.Ide.TypeSystem tryApplyState_documentTextChangedTasks.Clear ();
tryApplyState_changedProjects.Clear ();
freezeProjectModify = false;
+ FileService.ThawEvents (); }
}
}
@@ -1040,6 +1042,7 @@ namespace MonoDevelop.Ide.TypeSystem case ApplyChangesKind.RemoveMetadataReference:
case ApplyChangesKind.AddProjectReference:
case ApplyChangesKind.RemoveProjectReference:
+ case ApplyChangesKind.ChangeDocumentInfo: return true;
default:
return false;
@@ -1295,8 +1298,124 @@ namespace MonoDevelop.Ide.TypeSystem }
}
- #endregion
+ protected override void ApplyDocumentInfoChanged (DocumentId id, DocumentInfo info) + { + var currentSolution = CurrentSolution; + var document = currentSolution.GetDocument (id); + FailIfDocumentInfoChangesNotSupported (document, info); + + // abort if the name is the same, as there's nothing to do + if (document.Name == info.Name + || string.IsNullOrEmpty (info.Name) + || string.IsNullOrEmpty (info.FilePath)) + return; + + MonoDevelop.Projects.Project mdProject = null; + + if (id.ProjectId != null) { + var project = currentSolution.GetProject (id.ProjectId); + mdProject = GetMonoProject (project); + if (mdProject == null) { + LoggingService.LogWarning ("Couldn't find project for newly file file {0} (Project {1}).", + info.Name, info.Id.ProjectId); + + return; + } + } + + var guiDoc = documentManager + .Documents + .FirstOrDefault (d => d.IsFile + && document.FilePath.Equals (d.FilePath, FilePath.PathComparison)); + + DispatchService.PumpingWait (() => guiDoc.IsDirty ? guiDoc.Save () : Task.CompletedTask); + + // TODO: the Visual Studio Windows implementation of this also adds an undo unit to the + // global undo manager which we don't currently support. + + var newName = NameGenerator.GenerateUniqueName ( + Path.GetFileNameWithoutExtension (info.Name), + Path.GetExtension (info.Name), + nx => !mdProject.Files.Any (p => string.Equals (p.Name, nx, FilePath.PathComparison))); + + var mdFile = mdProject.GetProjectFile (document.FilePath); + if (mdFile == null) { + LoggingService.LogWarning ($"{document.FilePath} was not found in project in ApplyDocumentInfo"); + return; + } + + DispatchService.PumpingWait (() => { + return Runtime.RunInMainThread (() => { + + FileService.RenameFile (document.FilePath, newName); + + var childrenToRename = GetDependentFilesToRename (mdFile, newName); + if (childrenToRename != null) { + // we also need to rename children! + foreach (var child in childrenToRename) { + FileService.RenameFile (child.File.FilePath, child.NewName); + } + } + }); + }); + + tryApplyState_changedProjects.Add (mdProject); + } + + static List<(MonoDevelop.Projects.ProjectFile File, string NewName)> GetDependentFilesToRename ( + MonoDevelop.Projects.ProjectFile file, + string newName) + { + if (!file.HasChildren) + return null; + + List<(MonoDevelop.Projects.ProjectFile File, string NewName)> files = null; + + string oldName = file.FilePath.FileName; + foreach (MonoDevelop.Projects.ProjectFile child in file.DependentChildren) { + string oldChildName = child.FilePath.FileName; + if (oldChildName.StartsWith (oldName, StringComparison.CurrentCultureIgnoreCase)) { + string childNewName = newName + oldChildName.Substring (oldName.Length); + + if (files == null) + files = new List<(MonoDevelop.Projects.ProjectFile projectFile, string name)> (); + files.Add ((child, childNewName)); + } + } + return files; + } + + static void FailIfDocumentInfoChangesNotSupported (Document document, DocumentInfo updatedInfo) + { + if (document.SourceCodeKind != updatedInfo.SourceCodeKind) { + throw new InvalidOperationException ( + $"This Workspace does not support changing a document's {nameof (document.SourceCodeKind)}."); + } + + if (document.FilePath != updatedInfo.FilePath) { + throw new InvalidOperationException ( + $"This Workspace does not support changing a document's {nameof (document.FilePath)}."); + } + + if (document.Id != updatedInfo.Id) { + throw new InvalidOperationException ( + $"This Workspace does not support changing a document's {nameof (document.Id)}."); + } + + if (document.Folders != updatedInfo.Folders) { + throw new InvalidOperationException ( + $"This Workspace does not support changing a document's {nameof (document.Folders)}."); + } + + if (document.State.Attributes.IsGenerated != updatedInfo.IsGenerated) { + throw new InvalidOperationException ( + $"This Workspace does not support changing a document's {nameof (document.State.Attributes.IsGenerated)} state."); + } + } + + #endregion + internal Document GetDocument (DocumentId documentId, CancellationToken cancellationToken = default (CancellationToken))
{
var project = CurrentSolution.GetProject (documentId.ProjectId);
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/DispatchService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/DispatchService.cs index 17cc0257b7..8cbdec1fef 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/DispatchService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/DispatchService.cs @@ -196,7 +196,35 @@ namespace MonoDevelop.Ide Gdk.Threads.Leave(); } - + + /// <summary> + /// This method executes an async method on the main thread, but does not + /// technically block the calling thread. It does however "loop" and pumps + /// events in the mean time. This is a workaround for times when some of our + /// model (which is aync) is called by an external party (i.e. Roslyn) which + /// does not support async methods and potentially causes a deadlock. + /// </summary> + /// <param name="asyncMethod"></param> + /// <remarks>See: https://github.com/mono/monodevelop/pull/7823</remarks> + internal static void PumpingWait (Func<Task> asyncMethod) + { + // This method can be called by Roslyn or the editor in a context which is not the GTK UI context + // that MonoDevelop uses. In that case, before starting async an operation that may queue + // task continuations into the current context, we switch to the GTK context, so that + // whatever is queued will be dispatched when we run RunPendingEvents. + var oldContext = SynchronizationContext.Current; + try { + SynchronizationContext.SetSynchronizationContext (Runtime.MainSynchronizationContext); + Task task = asyncMethod (); + // Can't wait for the task to finish synchronously since doing so would deadlock the UI thread. + while (task != null && !task.IsCompleted) { + DispatchService.RunPendingEvents (30); + } + } finally { + SynchronizationContext.SetSynchronizationContext (oldContext); + } + } + #region Animations /// <summary> |