You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
546 lines
20 KiB
546 lines
20 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.IO; |
|
using System.Linq; |
|
|
|
using ICSharpCode.AvalonEdit.Document; |
|
using ICSharpCode.AvalonEdit.Highlighting; |
|
using ICSharpCode.Core; |
|
using ICSharpCode.NRefactory.Editor; |
|
using ICSharpCode.NRefactory.Semantics; |
|
using ICSharpCode.NRefactory.TypeSystem; |
|
using ICSharpCode.SharpDevelop.Editor; |
|
using ICSharpCode.SharpDevelop.Editor.Search; |
|
using ICSharpCode.SharpDevelop.Gui; |
|
using ICSharpCode.SharpDevelop.Parser; |
|
|
|
namespace ICSharpCode.SharpDevelop.Refactoring |
|
{ |
|
public static class FindReferencesAndRenameHelper |
|
{ |
|
#region Extract Interface |
|
/* Reimplement extract interface and put the code somewhere else - this isn't the place for C#-specific refactorings |
|
public static void ExtractInterface(IClass c) |
|
{ |
|
ExtractInterfaceOptions extractInterface = new ExtractInterfaceOptions(c); |
|
using (ExtractInterfaceDialog eid = new ExtractInterfaceDialog()) { |
|
extractInterface = eid.ShowDialog(extractInterface); |
|
if (extractInterface.IsCancelled) { |
|
return; |
|
} |
|
|
|
// do rename |
|
// MessageService.ShowMessageFormatted("Extracting interface", |
|
// @"Extracting {0} [{1}] from {2} into {3}", |
|
// extractInterface.NewInterfaceName, |
|
// extractInterface.FullyQualifiedName, |
|
// extractInterface.ClassEntity.Name, |
|
// extractInterface.NewFileName |
|
// ); |
|
} |
|
|
|
string newInterfaceFileName = Path.Combine(Path.GetDirectoryName(c.SyntaxTree.FileName), |
|
extractInterface.NewFileName); |
|
if (File.Exists(newInterfaceFileName)) { |
|
int confirmReplace = MessageService.ShowCustomDialog("Extract Interface", |
|
newInterfaceFileName+" already exists!", |
|
0, |
|
1, |
|
"${res:Global.ReplaceButtonText}", |
|
"${res:Global.AbortButtonText}"); |
|
if (confirmReplace != 0) { |
|
return; |
|
} |
|
} |
|
|
|
LanguageProperties language = c.ProjectContent.Language; |
|
string classFileName = c.SyntaxTree.FileName; |
|
string existingClassCode = ParserService.GetParseableFileContent(classFileName).Text; |
|
|
|
// build the new interface... |
|
string newInterfaceCode = language.RefactoringProvider.GenerateInterfaceForClass(extractInterface.NewInterfaceName, |
|
existingClassCode, |
|
extractInterface.ChosenMembers, |
|
c, extractInterface.IncludeComments); |
|
if (newInterfaceCode == null) |
|
return; |
|
|
|
// ...dump it to a file... |
|
IViewContent viewContent = FileService.GetOpenFile(newInterfaceFileName); |
|
ITextEditorProvider editable = viewContent as ITextEditorProvider; |
|
|
|
if (viewContent != null && editable != null) { |
|
// simply update it |
|
editable.TextEditor.Document.Text = newInterfaceCode; |
|
viewContent.PrimaryFile.SaveToDisk(); |
|
} else { |
|
// create it |
|
viewContent = FileService.NewFile(newInterfaceFileName, newInterfaceCode); |
|
viewContent.PrimaryFile.SaveToDisk(newInterfaceFileName); |
|
|
|
// ... and add it to the project |
|
IProject project = (IProject)c.ProjectContent.Project; |
|
if (project != null) { |
|
FileProjectItem projectItem = new FileProjectItem(project, ItemType.Compile); |
|
projectItem.FileName = newInterfaceFileName; |
|
ProjectService.AddProjectItem(project, projectItem); |
|
FileService.FireFileCreated(newInterfaceFileName, false); |
|
project.Save(); |
|
ProjectBrowserPad.RefreshViewAsync(); |
|
} |
|
} |
|
|
|
ISyntaxTree newSyntaxTree = ParserService.ParseFile(newInterfaceFileName).SyntaxTree; |
|
IClass newInterfaceDef = newSyntaxTree.Classes[0]; |
|
|
|
// finally, add the interface to the base types of the class that we're extracting from |
|
if (extractInterface.AddInterfaceToClass) { |
|
string modifiedClassCode = language.RefactoringProvider.AddBaseTypeToClass(existingClassCode, c, newInterfaceDef); |
|
if (modifiedClassCode == null) { |
|
return; |
|
} |
|
|
|
// TODO: replacing the whole text is not an option, we would loose all breakpoints/bookmarks. |
|
viewContent = FileService.OpenFile(classFileName); |
|
editable = viewContent as ITextEditorProvider; |
|
if (editable == null) { |
|
return; |
|
} |
|
editable.TextEditor.Document.Text = modifiedClassCode; |
|
} |
|
} |
|
*/ |
|
#endregion |
|
|
|
/* |
|
#region Rename Class |
|
public static void RenameClass(ITypeDefinition c) |
|
{ |
|
string newName = MessageService.ShowInputBox("${res:SharpDevelop.Refactoring.Rename}", "${res:SharpDevelop.Refactoring.RenameClassText}", c.Name); |
|
if (!FindReferencesAndRenameHelper.CheckName(newName, c.Name)) return; |
|
|
|
using (AsynchronousWaitDialog monitor = AsynchronousWaitDialog.ShowWaitDialog("${res:SharpDevelop.Refactoring.Rename}")) |
|
{ |
|
RenameClass(c, newName); |
|
} |
|
} |
|
|
|
public static void RenameClass(ITypeDefinition c, string newName) |
|
{ |
|
if (c == null) |
|
throw new ArgumentNullException("c"); |
|
if (newName == null) |
|
throw new ArgumentNullException("newName"); |
|
c = c.GetDefinition(); // get compound class if class is partial |
|
|
|
List<Reference> list = RefactoringService.FindReferences(c, null); |
|
if (list == null) return; |
|
|
|
// Add the class declaration(s) |
|
foreach (ITypeDefinition part in c.GetParts()) { |
|
AddDeclarationAsReference(list, part.SyntaxTree.FileName, part.Region, part.Name); |
|
} |
|
|
|
// Add the constructors |
|
foreach (IMethod m in c.Methods) { |
|
if (m.IsConstructor || (m.Name == "#dtor")) { |
|
AddDeclarationAsReference(list, m.DeclaringType.SyntaxTree.FileName, m.Region, c.Name); |
|
} |
|
} |
|
|
|
FindReferencesAndRenameHelper.RenameReferences(list, newName); |
|
} |
|
|
|
static void AddDeclarationAsReference(List<Reference> list, string fileName, DomRegion region, string name) |
|
{ |
|
if (fileName == null) |
|
return; |
|
ProvidedDocumentInformation documentInformation = FindReferencesAndRenameHelper.GetDocumentInformation(fileName); |
|
int offset = documentInformation.Document.PositionToOffset(region.BeginLine, region.BeginColumn); |
|
string text = documentInformation.Document.GetText(offset, Math.Min(name.Length + 30, documentInformation.Document.TextLength - offset - 1)); |
|
int offsetChange = -1; |
|
do { |
|
offsetChange = text.IndexOf(name, offsetChange + 1); |
|
if (offsetChange < 0 || offsetChange >= text.Length) |
|
return; |
|
} while (offsetChange + name.Length < text.Length |
|
&& char.IsLetterOrDigit(text[offsetChange + name.Length])); |
|
offset += offsetChange; |
|
foreach (Reference r in list) { |
|
if (r.Offset == offset) |
|
return; |
|
} |
|
list.Add(new Reference(fileName, offset, name.Length, name, null)); |
|
} |
|
#endregion |
|
|
|
#region Rename member |
|
public static void RenameMember(IMember member) |
|
{ |
|
string newName = MessageService.ShowInputBox("${res:SharpDevelop.Refactoring.Rename}", "${res:SharpDevelop.Refactoring.RenameMemberText}", member.Name); |
|
if (!FindReferencesAndRenameHelper.CheckName(newName, member.Name)) return; |
|
|
|
RenameMember(member, newName); |
|
} |
|
|
|
public static bool RenameMember(IMember member, string newName) |
|
{ |
|
List<Reference> list; |
|
using (AsynchronousWaitDialog monitor = AsynchronousWaitDialog.ShowWaitDialog("${res:SharpDevelop.Refactoring.Rename}")) |
|
{ |
|
list = RefactoringService.FindReferences(member, monitor); |
|
if (list == null) return false; |
|
FindReferencesAndRenameHelper.RenameReferences(list, newName); |
|
} |
|
|
|
if (member is IField) { |
|
IProperty property = FindProperty((IField)member); |
|
if (property != null) { |
|
string newPropertyName = member.DeclaringType.ProjectContent.Language.CodeGenerator.GetPropertyName(newName); |
|
if (newPropertyName != newName && newPropertyName != property.Name) { |
|
if (MessageService.AskQuestionFormatted("${res:SharpDevelop.Refactoring.Rename}", "${res:SharpDevelop.Refactoring.RenameFieldAndProperty}", property.FullyQualifiedName, newPropertyName)) { |
|
using (AsynchronousWaitDialog monitor = AsynchronousWaitDialog.ShowWaitDialog("${res:SharpDevelop.Refactoring.Rename}")) |
|
{ |
|
list = RefactoringService.FindReferences(property, monitor); |
|
if (list != null) { |
|
FindReferencesAndRenameHelper.RenameReferences(list, newPropertyName); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
internal static IProperty FindProperty(IField field) |
|
{ |
|
LanguageProperties language = field.DeclaringType.ProjectContent.Language; |
|
if (language.CodeGenerator == null) return null; |
|
string propertyName = language.CodeGenerator.GetPropertyName(field.Name); |
|
IProperty foundProperty = null; |
|
foreach (IProperty prop in field.DeclaringType.Properties) { |
|
if (language.NameComparer.Equals(propertyName, prop.Name)) { |
|
foundProperty = prop; |
|
break; |
|
} |
|
} |
|
return foundProperty; |
|
} |
|
#endregion |
|
*/ |
|
|
|
#region Common helper functions |
|
public static bool IsReadOnly(ITypeDefinition c) |
|
{ |
|
return c.IsSynthetic || c.Parts.All(p => p.UnresolvedFile == null); |
|
} |
|
|
|
[Obsolete("Use NavigationService.NavigateTo() instead")] |
|
public static void JumpToDefinition(IMember member) |
|
{ |
|
NavigationService.NavigateTo(member); |
|
} |
|
|
|
/* |
|
public static ITextEditor OpenDefinitionFile(IMember member, bool switchTo) |
|
{ |
|
IViewContent viewContent = null; |
|
ISyntaxTree cu = member.DeclaringType.SyntaxTree; |
|
if (cu != null) { |
|
string fileName = cu.FileName; |
|
if (fileName != null) { |
|
viewContent = FileService.OpenFile(fileName, switchTo); |
|
} |
|
} |
|
ITextEditorProvider tecp = viewContent as ITextEditorProvider; |
|
return (tecp == null) ? null : tecp.TextEditor; |
|
} |
|
|
|
public static ITextEditor JumpBehindDefinition(IMember member) |
|
{ |
|
IViewContent viewContent = null; |
|
ISyntaxTree cu = member.DeclaringType.SyntaxTree; |
|
if (cu != null) { |
|
string fileName = cu.FileName; |
|
if (fileName != null) { |
|
if (!member.Region.IsEmpty) { |
|
viewContent = FileService.JumpToFilePosition(fileName, member.Region.EndLine + 1, 1); |
|
} else { |
|
FileService.OpenFile(fileName); |
|
} |
|
} |
|
} |
|
ITextEditorProvider tecp = viewContent as ITextEditorProvider; |
|
return (tecp == null) ? null : tecp.TextEditor; |
|
} |
|
|
|
public static bool CheckName(string name, string oldName) |
|
{ |
|
if (name == null || name.Length == 0 || name == oldName) |
|
return false; |
|
if (!char.IsLetter(name, 0) && name[0] != '_') { |
|
MessageService.ShowError("${res:SharpDevelop.Refactoring.InvalidNameStart}"); |
|
return false; |
|
} |
|
for (int i = 1; i < name.Length; i++) { |
|
if (!char.IsLetterOrDigit(name, i) && name[i] != '_') { |
|
MessageService.ShowError("${res:SharpDevelop.Refactoring.InvalidName}"); |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
public struct Modification |
|
{ |
|
public IDocument Document; |
|
public int Offset; |
|
public int LengthDifference; |
|
|
|
public Modification(IDocument document, int offset, int lengthDifference) |
|
{ |
|
this.Document = document; |
|
this.Offset = offset; |
|
this.LengthDifference = lengthDifference; |
|
} |
|
} |
|
|
|
public static void ModifyDocument(List<Modification> modifications, IDocument doc, int offset, int length, string newName) |
|
{ |
|
using (doc.OpenUndoGroup()) { |
|
foreach (Modification m in modifications) { |
|
if (m.Document == doc) { |
|
if (m.Offset < offset) |
|
offset += m.LengthDifference; |
|
} |
|
} |
|
int lengthDifference = newName.Length - length; |
|
doc.Replace(offset, length, newName); |
|
if (lengthDifference != 0) { |
|
for (int i = 0; i < modifications.Count; ++i) { |
|
Modification m = modifications[i]; |
|
if (m.Document == doc) { |
|
if (m.Offset > offset) { |
|
m.Offset += lengthDifference; |
|
modifications[i] = m; // Modification is a value type |
|
} |
|
} |
|
} |
|
modifications.Add(new Modification(doc, offset, lengthDifference)); |
|
} |
|
} |
|
} |
|
*/ |
|
|
|
[Obsolete] |
|
public static void ShowAsSearchResults(string title, List<Reference> list) |
|
{ |
|
if (list == null) return; |
|
List<SearchResultMatch> results = new List<SearchResultMatch>(list.Count); |
|
ReadOnlyDocument document = null; |
|
ITextSource buffer = null; |
|
FileName fileName = null; |
|
IHighlighter highlighter = null; |
|
foreach (Reference r in list) { |
|
if (document == null || fileName != r.FileName) { |
|
if (highlighter != null) { |
|
highlighter.Dispose(); |
|
} |
|
fileName = r.FileName; |
|
buffer = SD.FileService.GetFileContent(r.FileName); |
|
document = new ReadOnlyDocument(buffer, r.FileName); |
|
highlighter = SD.EditorControlService.CreateHighlighter(document); |
|
} |
|
var start = r.StartLocation; |
|
var end = r.EndLocation; |
|
var startOffset = document.GetOffset(start); |
|
var endOffset = document.GetOffset(end); |
|
var builder = SearchResultsPad.CreateInlineBuilder(start, end, document, highlighter); |
|
var defaultTextColor = highlighter != null ? highlighter.DefaultTextColor : null; |
|
SearchResultMatch res = new SearchResultMatch(fileName, start, end, startOffset, endOffset - startOffset, builder, defaultTextColor); |
|
results.Add(res); |
|
} |
|
if (highlighter != null) { |
|
highlighter.Dispose(); |
|
} |
|
SearchResultsPad.Instance.ShowSearchResults(title, results); |
|
SearchResultsPad.Instance.BringToFront(); |
|
} |
|
|
|
/* |
|
sealed class FileView { |
|
public IViewContent ViewContent; |
|
public OpenedFile OpenedFile; |
|
} |
|
|
|
public static void RenameReferences(List<Reference> list, string newName) |
|
{ |
|
Dictionary<IDocument, FileView> modifiedDocuments = new Dictionary<IDocument, FileView>(); |
|
List<Modification> modifications = new List<Modification>(); |
|
foreach (Reference r in list) { |
|
IDocument document = null; |
|
IViewContent viewContent = null; |
|
|
|
OpenedFile file = FileService.GetOpenedFile(r.FileName); |
|
if (file != null) { |
|
viewContent = file.CurrentView; |
|
IFileDocumentProvider p = viewContent as IFileDocumentProvider; |
|
if (p != null) { |
|
document = p.GetDocumentForFile(file); |
|
} |
|
} |
|
|
|
if (document == null) { |
|
viewContent = FileService.OpenFile(r.FileName, false); |
|
IFileDocumentProvider p = viewContent as IFileDocumentProvider; |
|
if (p != null) { |
|
file = FileService.GetOpenedFile(r.FileName); |
|
System.Diagnostics.Debug.Assert(file != null, "OpenedFile not found after opening the file."); |
|
document = p.GetDocumentForFile(file); |
|
} |
|
} |
|
|
|
if (document == null) { |
|
LoggingService.Warn("RenameReferences: Could not get document for file '" + r.FileName + "'"); |
|
continue; |
|
} |
|
|
|
if (!modifiedDocuments.ContainsKey(document)) { |
|
modifiedDocuments.Add(document, new FileView() {ViewContent = viewContent, OpenedFile = file}); |
|
document.StartUndoableAction(); |
|
} |
|
|
|
ModifyDocument(modifications, document, r.Offset, r.Length, newName); |
|
} |
|
foreach (KeyValuePair<IDocument, FileView> entry in modifiedDocuments) { |
|
entry.Key.EndUndoableAction(); |
|
entry.Value.OpenedFile.MakeDirty(); |
|
if (entry.Value.ViewContent is IEditable) { |
|
ParserService.ParseViewContent(entry.Value.ViewContent); |
|
} else { |
|
ParserService.ParseFile(entry.Value.OpenedFile.FileName, entry.Key); |
|
} |
|
} |
|
} |
|
*/ |
|
|
|
/* TODO: these are refactorings and don't belong here |
|
public static void MoveClassToFile(IClass c, string newFileName) |
|
{ |
|
LanguageProperties language = c.ProjectContent.Language; |
|
string existingCode = ParserService.GetParseableFileContent(c.SyntaxTree.FileName).Text; |
|
DomRegion fullRegion = language.RefactoringProvider.GetFullCodeRangeForType(existingCode, c); |
|
if (fullRegion.IsEmpty) return; |
|
|
|
Action removeExtractedCodeAction; |
|
string newCode = ExtractCode(c, fullRegion, c.BodyRegion.BeginLine, out removeExtractedCodeAction); |
|
|
|
newCode = language.RefactoringProvider.CreateNewFileLikeExisting(existingCode, newCode); |
|
if (newCode == null) |
|
return; |
|
IViewContent viewContent = FileService.NewFile(newFileName, newCode); |
|
viewContent.PrimaryFile.SaveToDisk(newFileName); |
|
// now that the code is saved in the other file, remove it from the original document |
|
removeExtractedCodeAction(); |
|
|
|
IProject project = (IProject)c.ProjectContent.Project; |
|
if (project != null) { |
|
FileProjectItem projectItem = new FileProjectItem(project, ItemType.Compile); |
|
projectItem.FileName = newFileName; |
|
ProjectService.AddProjectItem(project, projectItem); |
|
FileService.FireFileCreated(newFileName, false); |
|
project.Save(); |
|
ProjectBrowserPad.RefreshViewAsync(); |
|
} |
|
} |
|
|
|
public static string ExtractCode(IClass c, DomRegion codeRegion, int indentationLine, out Action removeExtractedCodeAction) |
|
{ |
|
IDocument doc = GetDocument(c); |
|
if (indentationLine < 1) indentationLine = 1; |
|
if (indentationLine >= doc.TotalNumberOfLines) indentationLine = doc.TotalNumberOfLines; |
|
|
|
IDocumentLine segment = doc.GetLine(indentationLine); |
|
string mainLine = doc.GetText(segment.Offset, segment.Length); |
|
string indentation = mainLine.Substring(0, mainLine.Length - mainLine.TrimStart().Length); |
|
|
|
segment = doc.GetLine(codeRegion.BeginLine); |
|
int startOffset = segment.Offset; |
|
segment = doc.GetLine(codeRegion.EndLine); |
|
int endOffset = segment.Offset + segment.Length; |
|
|
|
StringReader reader = new StringReader(doc.GetText(startOffset, endOffset - startOffset)); |
|
removeExtractedCodeAction = delegate { |
|
doc.Remove(startOffset, endOffset - startOffset); |
|
}; |
|
|
|
// now remove indentation from extracted source code |
|
string line; |
|
StringBuilder b = new StringBuilder(); |
|
int endOfLastFilledLine = 0; |
|
while ((line = reader.ReadLine()) != null) { |
|
int startpos; |
|
for (startpos = 0; startpos < line.Length && startpos < indentation.Length; startpos++) { |
|
if (line[startpos] != indentation[startpos]) |
|
break; |
|
} |
|
if (startpos == line.Length) { |
|
// empty line |
|
if (b.Length > 0) { |
|
b.AppendLine(); |
|
} |
|
} else { |
|
b.Append(line, startpos, line.Length - startpos); |
|
b.AppendLine(); |
|
endOfLastFilledLine = b.Length; |
|
} |
|
} |
|
b.Length = endOfLastFilledLine; |
|
return b.ToString(); |
|
} |
|
|
|
public static IDocument GetDocument(IClass c) |
|
{ |
|
if (c is CompoundClass) |
|
throw new ArgumentException("Cannot get document from compound class - must pass a specific class part"); |
|
|
|
ITextEditorProvider tecp = FileService.OpenFile(c.SyntaxTree.FileName) as ITextEditorProvider; |
|
if (tecp == null) return null; |
|
return tecp.TextEditor.Document; |
|
} |
|
*/ |
|
#endregion |
|
|
|
|
|
#region Find references |
|
public static void RunFindReferences(IEntity entity) |
|
{ |
|
string entityName = (entity.DeclaringTypeDefinition != null ? entity.DeclaringTypeDefinition.Name + "." + entity.Name : entity.Name); |
|
var monitor = SD.StatusBar.CreateProgressMonitor(); |
|
var results = FindReferenceService.FindReferences(entity, monitor); |
|
SearchResultsPad.Instance.ShowSearchResults(StringParser.Parse("${res:SharpDevelop.Refactoring.ReferencesTo}", new StringTagPair("Name", entityName)), results); |
|
SearchResultsPad.Instance.BringToFront(); |
|
} |
|
|
|
public static void RunFindReferences(LocalResolveResult local) |
|
{ |
|
var monitor = SD.StatusBar.CreateProgressMonitor(); |
|
var results = FindReferenceService.FindLocalReferences(local.Variable, monitor); |
|
SearchResultsPad.Instance.ShowSearchResults(StringParser.Parse("${res:SharpDevelop.Refactoring.ReferencesTo}", new StringTagPair("Name", local.Variable.Name)), results); |
|
SearchResultsPad.Instance.BringToFront(); |
|
} |
|
|
|
// public static ICSharpCode.Core.WinForms.MenuCommand MakeFindReferencesMenuCommand(EventHandler handler) |
|
// { |
|
// return new ICSharpCode.Core.WinForms.MenuCommand("${res:SharpDevelop.Refactoring.FindReferencesCommand}", handler) { |
|
// ShortcutKeys = System.Windows.Forms.Keys.F12 |
|
// }; |
|
// } |
|
#endregion |
|
} |
|
} |
|
|
|
|