diff --git a/AddIns/ICSharpCode.SharpDevelop.addin b/AddIns/ICSharpCode.SharpDevelop.addin index f44fee2328..d6f4790b39 100755 --- a/AddIns/ICSharpCode.SharpDevelop.addin +++ b/AddIns/ICSharpCode.SharpDevelop.addin @@ -1726,7 +1726,7 @@ - + @@ -1734,7 +1734,7 @@ - + diff --git a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin index aabcb0eb88..bfadd7271d 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin +++ b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin @@ -18,14 +18,14 @@ diff --git a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj index b58b3ddf02..595e442e3e 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj +++ b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj @@ -69,7 +69,7 @@ Always - + @@ -106,6 +106,7 @@ + diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/ClassCodeGeneratorMenuBuilder.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/ClassRefactoringSubmenuBuilder.cs similarity index 59% rename from src/AddIns/Misc/SharpRefactoring/Project/Src/ClassCodeGeneratorMenuBuilder.cs rename to src/AddIns/Misc/SharpRefactoring/Project/Src/ClassRefactoringSubmenuBuilder.cs index 7c8e60c5e5..b987df3508 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/Src/ClassCodeGeneratorMenuBuilder.cs +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/ClassRefactoringSubmenuBuilder.cs @@ -6,8 +6,9 @@ // using System; -using System.Linq; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Windows.Forms; using ICSharpCode.Core; @@ -19,12 +20,22 @@ using ICSharpCode.SharpDevelop.Bookmarks; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Dom.Refactoring; using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Editor.Commands; using ICSharpCode.SharpDevelop.Gui.ClassBrowser; +using ICSharpCode.SharpDevelop.Project; using ICSharpCode.SharpDevelop.Refactoring; namespace SharpRefactoring { - public class ClassCodeGeneratorMenuBuilder : ISubmenuBuilder, IMenuItemBuilder + /// + /// Generates items the class submenu. + /// Paths: + /// /SharpDevelop/ViewContent/DefaultTextEditor/ClassBookmarkContextMenu, id=classCodeGenerators + /// /SharpDevelop/Pads/ClassBrowser/ClassContextMenu, id=classCodeGenerators + /// + /// This builder generates items to the same submenu as . + /// + public class ClassRefactoringSubmenuBuilder : ISubmenuBuilder, IMenuItemBuilder { public System.Collections.ICollection BuildItems(Codon codon, object owner) { @@ -35,28 +46,92 @@ namespace SharpRefactoring { List resultItems = new List(); - IClass c; - ClassNode classNode = owner as ClassNode; - if (classNode != null) { - c = classNode.Class; - } else { - ClassBookmark bookmark = (ClassBookmark)owner; - c = bookmark.Class; + IClass c = ClassBookmarkSubmenuBuilder.GetClass(owner); + if (c == null) { + return new ToolStripMenuItem[0]; } - LanguageProperties language = c.ProjectContent.Language; - if (language != LanguageProperties.CSharp) - return resultItems.ToArray(); - AddImplementAbstractClassCommands(c, resultItems); + if (language == LanguageProperties.CSharp) { + AddImplementAbstractClassCommands(c, resultItems); + } if (c.BaseTypes.Count > 0 && c.ClassType != ClassType.Interface && !FindReferencesAndRenameHelper.IsReadOnly(c)) { AddImplementInterfaceCommands(c, resultItems); } + if (!FindReferencesAndRenameHelper.IsReadOnly(c)) { + AddCorrectClassFileNameCommands(c, resultItems); + + AddRenameCommand(c, resultItems); + + if (language.RefactoringProvider.SupportsExtractInterface) { + AddExtractInterfaceCommand(c, resultItems); + } + } + return resultItems.ToArray(); } + void AddRenameCommand(IClass c, List resultItems) + { + var cmd = new MenuCommand("${res:SharpDevelop.Refactoring.RenameCommand}", Rename); + cmd.ShortcutKeys = MenuCommand.ParseShortcut("Control|R"); + cmd.Tag = c; + resultItems.Add(cmd); + } + + void AddExtractInterfaceCommand(IClass c, List resultItems) + { + var cmd = new MenuCommand("${res:SharpDevelop.Refactoring.ExtractInterfaceCommand}", ExtractInterface); + cmd.Tag = c; + resultItems.Add(cmd); + } + + void AddCorrectClassFileNameCommands(IClass c, List resultItems) + { + if (c.DeclaringType == null && + !c.BodyRegion.IsEmpty && + !c.Name.Equals(Path.GetFileNameWithoutExtension(c.CompilationUnit.FileName), + StringComparison.OrdinalIgnoreCase)) + { + // File name does not match class name + string correctFileName = Path.Combine(Path.GetDirectoryName(c.CompilationUnit.FileName), + c.Name + Path.GetExtension(c.CompilationUnit.FileName)); + if (FileUtility.IsValidPath(correctFileName) + && Path.IsPathRooted(correctFileName) + && !File.Exists(correctFileName)) + { + if (c.CompilationUnit.Classes.Count == 1) { + // Rename file to ## + var cmd = new MenuCommand( + StringParser.Parse("${res:SharpDevelop.Refactoring.RenameFileTo}", + new string[,] {{ "FileName", Path.GetFileName(correctFileName) }}), + delegate { + IProject p = (IProject)c.ProjectContent.Project; + RefactoringHelpers.RenameFile(p, c.CompilationUnit.FileName, correctFileName); + if (p != null) { + p.Save(); + } + }); + resultItems.Add(cmd); + } else { + var refactoringProvider = c.ProjectContent.Language.RefactoringProvider; + if (refactoringProvider.SupportsCreateNewFileLikeExisting && refactoringProvider.SupportsGetFullCodeRangeForType) { + // Move class to file ## + var cmd = new MenuCommand( + StringParser.Parse("${res:SharpDevelop.Refactoring.MoveClassToFile}", + new string[,] {{ "FileName", Path.GetFileName(correctFileName) }}), + delegate { + FindReferencesAndRenameHelper.MoveClassToFile(c, correctFileName); + }); + resultItems.Add(cmd); + } + } + } + } + } + void AddImplementInterfaceCommands(IClass c, List list) { CodeGenerator codeGen = c.ProjectContent.Language.CodeGenerator; @@ -129,7 +204,7 @@ namespace SharpRefactoring } } - #region Code generation + #region Implementation void ImplementAbstractClass(IDocument document, IClass target, IReturnType abstractClass) { CodeGenerator generator = target.ProjectContent.Language.CodeGenerator; @@ -142,6 +217,18 @@ namespace SharpRefactoring generator.InsertCodeAtEnd(target.BodyRegion, doc, generator.GetOverridingMethod(member, context)); } } + + void Rename(object sender, EventArgs e) + { + MenuCommand item = (MenuCommand)sender; + FindReferencesAndRenameHelper.RenameClass((IClass)item.Tag); + } + + void ExtractInterface(object sender, EventArgs e) + { + MenuCommand item = (MenuCommand)sender; + FindReferencesAndRenameHelper.ExtractInterface((IClass)item.Tag); + } #endregion #region Helpers diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/RefactoringHelpers.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/RefactoringHelpers.cs new file mode 100644 index 0000000000..98b3c78836 --- /dev/null +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/RefactoringHelpers.cs @@ -0,0 +1,46 @@ +// +// +// +// +// $Revision: $ +// +using System; +using System.IO; +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Project; + +namespace SharpRefactoring +{ + public class RefactoringHelpers + { + /// + /// Renames file as well as files it is dependent upon. + /// + public static void RenameFile(IProject p, string oldFileName, string newFileName) + { + FileService.RenameFile(oldFileName, newFileName, false); + if (p != null) { + string oldPrefix = Path.GetFileNameWithoutExtension(oldFileName) + "."; + string newPrefix = Path.GetFileNameWithoutExtension(newFileName) + "."; + foreach (ProjectItem item in p.Items) { + FileProjectItem fileItem = item as FileProjectItem; + if (fileItem == null) + continue; + string dependentUpon = fileItem.DependentUpon; + if (string.IsNullOrEmpty(dependentUpon)) + continue; + string directory = Path.GetDirectoryName(fileItem.FileName); + dependentUpon = Path.Combine(directory, dependentUpon); + if (FileUtility.IsEqualFileName(dependentUpon, oldFileName)) { + fileItem.DependentUpon = FileUtility.GetRelativePath(directory, newFileName); + string fileName = Path.GetFileName(fileItem.FileName); + if (fileName.StartsWith(oldPrefix)) { + RenameFile(p, fileItem.FileName, Path.Combine(directory, newPrefix + fileName.Substring(oldPrefix.Length))); + } + } + } + } + } + } +} diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index e73869ce9b..d830c39fb7 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -97,7 +97,7 @@ - + diff --git a/src/Main/Base/Project/Src/Editor/Commands/ClassBookmarkMenuBuilder.cs b/src/Main/Base/Project/Src/Editor/Commands/ClassBookmarkSubmenuBuilder.cs similarity index 60% rename from src/Main/Base/Project/Src/Editor/Commands/ClassBookmarkMenuBuilder.cs rename to src/Main/Base/Project/Src/Editor/Commands/ClassBookmarkSubmenuBuilder.cs index f774dcc478..add472a8e9 100644 --- a/src/Main/Base/Project/Src/Editor/Commands/ClassBookmarkMenuBuilder.cs +++ b/src/Main/Base/Project/Src/Editor/Commands/ClassBookmarkSubmenuBuilder.cs @@ -27,62 +27,18 @@ using ICSharpCode.SharpDevelop.Refactoring; namespace ICSharpCode.SharpDevelop.Editor.Commands { /// - /// Build context menu for class members in the text editor. + /// Build context submenu for class members in the text editor. + /// Paths: + /// /SharpDevelop/ViewContent/DefaultTextEditor/ClassBookmarkContextMenu, id=MenuBuilder + /// /SharpDevelop/Pads/ClassBrowser/ClassContextMenu, id=MenuBuilder /// - public class ClassBookmarkMenuBuilder : ISubmenuBuilder, IMenuItemBuilder + public class ClassBookmarkSubmenuBuilder : ISubmenuBuilder, IMenuItemBuilder { public System.Collections.ICollection BuildItems(Codon codon, object owner) { return BuildSubmenu(codon, owner).TranslateToWpf(); } - /// - /// Gets a specific part of the compound class. - /// - static IClass GetPart(IClass possibleCompound, string fileName) - { - CompoundClass compound = possibleCompound as CompoundClass; - if (compound == null) - return possibleCompound; - - IList parts = compound.Parts; - if (!string.IsNullOrEmpty(fileName)) { - // get the part with the requested file name - foreach (IClass part in parts) { - if (FileUtility.IsEqualFileName(fileName, part.CompilationUnit.FileName)) - return part; - } - } - - // Fallback: get the part with the shortest file name. - // This should prefer non-designer files over designer files. - IClass preferredClass = parts[0]; - for (int i = 1; i < parts.Count; i++) { - if (IsShorterFileName(parts[i].CompilationUnit.FileName, preferredClass.CompilationUnit.FileName)) - preferredClass = parts[i]; - } - return preferredClass; - } - - static bool IsShorterFileName(string a, string b) - { - // Fix forum-17295: compilation unit's file name might be null: prefer the non-null file - if (a == null) - return false; - if (b == null) - return true; - return a.Length < b.Length; - } - - static IClass GetCurrentPart(IClass possibleCompound) - { - IViewContent viewContent = WorkbenchSingleton.Workbench.ActiveViewContent; - if (viewContent != null) - return GetPart(possibleCompound, viewContent.PrimaryFileName); - else - return GetPart(possibleCompound, null); - } - public ToolStripItem[] BuildSubmenu(Codon codon, object owner) { MenuCommand cmd; @@ -106,58 +62,6 @@ namespace ICSharpCode.SharpDevelop.Editor.Commands List list = new List(); - // refactoring actions - if (!FindReferencesAndRenameHelper.IsReadOnly(c)) { - if (c.DeclaringType == null && - !c.BodyRegion.IsEmpty && - !c.Name.Equals(Path.GetFileNameWithoutExtension(c.CompilationUnit.FileName), - StringComparison.OrdinalIgnoreCase)) - { - // File name does not match class name - string correctFileName = Path.Combine(Path.GetDirectoryName(c.CompilationUnit.FileName), - c.Name + Path.GetExtension(c.CompilationUnit.FileName)); - if (FileUtility.IsValidPath(correctFileName) - && Path.IsPathRooted(correctFileName) - && !File.Exists(correctFileName)) - { - if (c.CompilationUnit.Classes.Count == 1) { - // Rename file to ## - cmd = new MenuCommand( - StringParser.Parse("${res:SharpDevelop.Refactoring.RenameFileTo}", - new string[,] {{ "FileName", Path.GetFileName(correctFileName) }}), - delegate { - IProject p = (IProject)c.ProjectContent.Project; - RenameFile(p, c.CompilationUnit.FileName, correctFileName); - if (p != null) { - p.Save(); - } - }); - list.Add(cmd); - } else if (language.RefactoringProvider.SupportsCreateNewFileLikeExisting && language.RefactoringProvider.SupportsGetFullCodeRangeForType) { - // Move class to file ## - cmd = new MenuCommand( - StringParser.Parse("${res:SharpDevelop.Refactoring.MoveClassToFile}", - new string[,] {{ "FileName", Path.GetFileName(correctFileName) }}), - delegate { - FindReferencesAndRenameHelper.MoveClassToFile(c, correctFileName); - }); - list.Add(cmd); - } - } - } - - cmd = new MenuCommand("${res:SharpDevelop.Refactoring.RenameCommand}", Rename); - cmd.ShortcutKeys = MenuCommand.ParseShortcut("Control|R"); - cmd.Tag = c; - list.Add(cmd); - - if (language.RefactoringProvider.SupportsExtractInterface) { - cmd = new MenuCommand("${res:SharpDevelop.Refactoring.ExtractInterfaceCommand}", ExtractInterface); - cmd.Tag = c; - list.Add(cmd); - } - } - // navigation actions if (c.BaseTypes.Count > 0) { list.Add(new MenuSeparator()); @@ -182,38 +86,6 @@ namespace ICSharpCode.SharpDevelop.Editor.Commands return list.ToArray(); } - static void RenameFile(IProject p, string oldFileName, string newFileName) - { - FileService.RenameFile(oldFileName, newFileName, false); - if (p != null) { - string oldPrefix = Path.GetFileNameWithoutExtension(oldFileName) + "."; - string newPrefix = Path.GetFileNameWithoutExtension(newFileName) + "."; - foreach (ProjectItem item in p.Items) { - FileProjectItem fileItem = item as FileProjectItem; - if (fileItem == null) - continue; - string dependentUpon = fileItem.DependentUpon; - if (string.IsNullOrEmpty(dependentUpon)) - continue; - string directory = Path.GetDirectoryName(fileItem.FileName); - dependentUpon = Path.Combine(directory, dependentUpon); - if (FileUtility.IsEqualFileName(dependentUpon, oldFileName)) { - fileItem.DependentUpon = FileUtility.GetRelativePath(directory, newFileName); - string fileName = Path.GetFileName(fileItem.FileName); - if (fileName.StartsWith(oldPrefix)) { - RenameFile(p, fileItem.FileName, Path.Combine(directory, newPrefix + fileName.Substring(oldPrefix.Length))); - } - } - } - } - } - - void ExtractInterface(object sender, EventArgs e) - { - MenuCommand item = (MenuCommand)sender; - FindReferencesAndRenameHelper.ExtractInterface((IClass)item.Tag); - } - void GoToBase(object sender, EventArgs e) { MenuCommand item = (MenuCommand)sender; @@ -227,12 +99,6 @@ namespace ICSharpCode.SharpDevelop.Editor.Commands } } - void Rename(object sender, EventArgs e) - { - MenuCommand item = (MenuCommand)sender; - FindReferencesAndRenameHelper.RenameClass((IClass)item.Tag); - } - void FindDerivedClasses(object sender, EventArgs e) { MenuCommand item = (MenuCommand)sender; @@ -264,5 +130,69 @@ namespace ICSharpCode.SharpDevelop.Editor.Commands IClass c = (IClass)item.Tag; FindReferencesAndRenameHelper.RunFindReferences(c); } + + + public static IClass GetClass(object menuOwner) + { + IClass c; + ClassNode classNode = menuOwner as ClassNode; + if (classNode != null) { + c = classNode.Class; + } else { + ClassBookmark bookmark = (ClassBookmark)menuOwner; + c = bookmark.Class; + } + ParserService.ParseCurrentViewContent(); + c = c.ProjectContent.GetClass(c.FullyQualifiedName, c.TypeParameters.Count, c.ProjectContent.Language, GetClassOptions.LookForInnerClass); + c = GetCurrentPart(c); + return c; + } + + static IClass GetCurrentPart(IClass possibleCompound) + { + IViewContent viewContent = WorkbenchSingleton.Workbench.ActiveViewContent; + if (viewContent != null) + return GetPart(possibleCompound, viewContent.PrimaryFileName); + else + return GetPart(possibleCompound, null); + } + + /// + /// Gets a specific part of the compound class. + /// + static IClass GetPart(IClass possibleCompound, string fileName) + { + CompoundClass compound = possibleCompound as CompoundClass; + if (compound == null) + return possibleCompound; + + IList parts = compound.Parts; + if (!string.IsNullOrEmpty(fileName)) { + // get the part with the requested file name + foreach (IClass part in parts) { + if (FileUtility.IsEqualFileName(fileName, part.CompilationUnit.FileName)) + return part; + } + } + + // Fallback: get the part with the shortest file name. + // This should prefer non-designer files over designer files. + IClass preferredClass = parts[0]; + for (int i = 1; i < parts.Count; i++) { + if (IsShorterFileName(parts[i].CompilationUnit.FileName, preferredClass.CompilationUnit.FileName)) + preferredClass = parts[i]; + } + return preferredClass; + } + + static bool IsShorterFileName(string a, string b) + { + // Fix forum-17295: compilation unit's file name might be null: prefer the non-null file + if (a == null) + return false; + if (b == null) + return true; + return a.Length < b.Length; + } } } diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringMenuBuilder.cs b/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringMenuBuilder.cs index ed3e6be77b..992e59990b 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringMenuBuilder.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringMenuBuilder.cs @@ -46,7 +46,10 @@ namespace ICSharpCode.SharpDevelop.Refactoring } /// - /// Build a menu with refactoring commands for the item that has been clicked on in the text editor. + /// Build refactoring commands for the item that has been clicked on in the text editor. + /// The commands are inserted to the top level of the context menu. + /// Path: + /// /SharpDevelop/ViewContent/TextEditor/ContextMenu, id=Refactoring /// public class RefactoringMenuBuilder : IMenuItemBuilder {