From 9f5c8720a8673f1840f8afbe545a471695d90907 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 28 May 2013 17:08:22 +0200 Subject: [PATCH] trying to implement RenameContextAction for C# --- .../CSharpBinding/Project/CSharpBinding.addin | 1 + .../Project/CSharpBinding.csproj | 2 + .../Project/Src/CSharpLanguageBinding.cs | 49 +--- .../Project/Src/CSharpTextEditorExtension.cs | 41 ++++ .../Src/Refactoring/CSharpCodeGenerator.cs | 221 +++++++++++++++++- .../Src/Refactoring/RenameContextAction.cs | 181 ++++++++++++++ .../Project/Refactoring/ICodeGenerator.cs | 117 ++++++++++ .../LanguageBinding/DefaultLanguageBinding.cs | 8 + .../LanguageBinding/ILanguageBinding.cs | 14 ++ .../FindReferenceService.cs | 77 +++++- 10 files changed, 662 insertions(+), 49 deletions(-) create mode 100644 src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpTextEditorExtension.cs create mode 100644 src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/RenameContextAction.cs diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin index 4824542069..a81b51169a 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin @@ -309,5 +309,6 @@ + diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj index 10c84081e3..ddfb47d90d 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj @@ -72,6 +72,7 @@ + @@ -85,6 +86,7 @@ + diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpLanguageBinding.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpLanguageBinding.cs index e8e769d38d..6e2c5b2696 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpLanguageBinding.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpLanguageBinding.cs @@ -2,17 +2,12 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; -using System.Collections.Generic; -using System.ComponentModel.Design; -using CSharpBinding.FormattingStrategy; -using CSharpBinding.Refactoring; -using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.AvalonEdit.Rendering; -using ICSharpCode.Core; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Refactoring; +using CSharpBinding.FormattingStrategy; +using CSharpBinding.Refactoring; namespace CSharpBinding { @@ -21,6 +16,14 @@ namespace CSharpBinding /// public class CSharpLanguageBinding : DefaultLanguageBinding { + public override string Name { + get { return "C#"; } + } + + public override StringComparer IdentifierComparer { + get { return StringComparer.Ordinal; } + } + public override IFormattingStrategy FormattingStrategy { get { return new CSharpFormattingStrategy(); } } @@ -33,36 +36,4 @@ namespace CSharpBinding get { return new CSharpCodeGenerator(); } } } - - public class CSharpTextEditorExtension : ITextEditorExtension - { - ITextEditor editor; - IssueManager inspectionManager; - IList contextActionProviders; - - public void Attach(ITextEditor editor) - { - this.editor = editor; - inspectionManager = new IssueManager(editor); - //codeManipulation = new CodeManipulation(editor); - - if (!editor.ContextActionProviders.IsReadOnly) { - contextActionProviders = AddInTree.BuildItems("/SharpDevelop/ViewContent/TextEditor/C#/ContextActions", null); - editor.ContextActionProviders.AddRange(contextActionProviders); - } - } - - public void Detach() - { - //codeManipulation.Dispose(); - if (inspectionManager != null) { - inspectionManager.Dispose(); - inspectionManager = null; - } - if (contextActionProviders != null) { - editor.ContextActionProviders.RemoveAll(contextActionProviders.Contains); - } - this.editor = null; - } - } } diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpTextEditorExtension.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpTextEditorExtension.cs new file mode 100644 index 0000000000..f1f0d8b52d --- /dev/null +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpTextEditorExtension.cs @@ -0,0 +1,41 @@ +// 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 ICSharpCode.Core; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Refactoring; +using CSharpBinding.Refactoring; + +namespace CSharpBinding +{ + public class CSharpTextEditorExtension : ITextEditorExtension + { + ITextEditor editor; + IssueManager inspectionManager; + IList contextActionProviders; + public void Attach(ITextEditor editor) + { + this.editor = editor; + inspectionManager = new IssueManager(editor); + //codeManipulation = new CodeManipulation(editor); + if (!editor.ContextActionProviders.IsReadOnly) { + contextActionProviders = AddInTree.BuildItems("/SharpDevelop/ViewContent/TextEditor/C#/ContextActions", null); + editor.ContextActionProviders.AddRange(contextActionProviders); + } + } + public void Detach() + { + //codeManipulation.Dispose(); + if (inspectionManager != null) { + inspectionManager.Dispose(); + inspectionManager = null; + } + if (contextActionProviders != null) { + editor.ContextActionProviders.RemoveAll(contextActionProviders.Contains); + } + this.editor = null; + } + } +} + diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CSharpCodeGenerator.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CSharpCodeGenerator.cs index 7e3725f42f..5610706a34 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CSharpCodeGenerator.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CSharpCodeGenerator.cs @@ -2,26 +2,27 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading; -using System.Windows.Media.Animation; -using ICSharpCode.AvalonEdit.Document; using ICSharpCode.Core; -using ICSharpCode.Core.Presentation; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp.Refactoring; using ICSharpCode.NRefactory.CSharp.Resolver; -using ICSharpCode.NRefactory.CSharp.TypeSystem; using ICSharpCode.NRefactory.Editor; +using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Project; using ICSharpCode.SharpDevelop.Refactoring; +using CSharpBinding.Parser; namespace CSharpBinding.Refactoring { @@ -30,12 +31,12 @@ namespace CSharpBinding.Refactoring /// public class CSharpCodeGenerator : DefaultCodeGenerator { - public void AddAttribute(IEntity target, IAttribute attribute) + public override void AddAttribute(IEntity target, IAttribute attribute) { AddAttribute(target.Region, attribute); } - public void AddAssemblyAttribute(IProject targetProject, IAttribute attribute) + public override void AddAssemblyAttribute(IProject targetProject, IAttribute attribute) { // FIXME : will fail if there are no assembly attributes ICompilation compilation = SD.ParserService.GetCompilation(targetProject); @@ -45,12 +46,12 @@ namespace CSharpBinding.Refactoring AddAttribute(target.Region, attribute, "assembly"); } - public void AddReturnTypeAttribute(IMethod target, IAttribute attribute) + public override void AddReturnTypeAttribute(IMethod target, IAttribute attribute) { AddAttribute(target.Region, attribute, "return"); } - public void InsertEventHandler(ITypeDefinition target, string name, IEvent eventDefinition, bool jumpTo) + public override void InsertEventHandler(ITypeDefinition target, string name, IEvent eventDefinition, bool jumpTo) { IUnresolvedTypeDefinition match = null; @@ -110,5 +111,209 @@ namespace CSharpBinding.Refactoring script.InsertBefore(node, attr); } } + + public override bool IsValidIdentifier(string identifier) + { + if (string.IsNullOrWhiteSpace(identifier)) + return false; + if (char.IsDigit(identifier.First())) + return false; + return identifier.All(IsSupportedCharacter); + } + + bool IsSupportedCharacter(char ch) + { + if (ch == '_') return true; + switch (char.GetUnicodeCategory(ch)) { + case UnicodeCategory.UppercaseLetter: + case UnicodeCategory.LowercaseLetter: + case UnicodeCategory.TitlecaseLetter: + case UnicodeCategory.ModifierLetter: + case UnicodeCategory.OtherLetter: + case UnicodeCategory.NonSpacingMark: + case UnicodeCategory.SpacingCombiningMark: + case UnicodeCategory.DecimalDigitNumber: + case UnicodeCategory.LetterNumber: + case UnicodeCategory.ConnectorPunctuation: + case UnicodeCategory.Format: + return true; + default: + return false; + } + } + + public override string EscapeIdentifier(string identifier) + { + if (!IsValidIdentifier(identifier)) + throw new ArgumentException("Cannot escape this identifier!"); + identifier = EscapeIdentifierInternal(identifier); + if (IsKeyword(identifier)) + return "@" + identifier; + return identifier; + } + + string EscapeIdentifierInternal(string identifier) + { + return string.Concat(identifier.Select(EscapeCharacter)); + } + + string EscapeCharacter(char ch) + { + if (char.IsLetterOrDigit(ch)) + return ch.ToString(); + return string.Format("\\x{0:000X}", ch); + } + + #region Keywords + static readonly string[] keywords = new string [] { + "abstract", "as", "base", "bool", "break", + "byte", "case", "catch", "char", "checked", + "class", "const", "continue", "decimal", "default", + "delegate", "do", "double", "else", "enum", + "event", "explicit", "extern", "false", "finally", + "fixed", "float", "for", "foreach", "goto", + "if", "implicit", "in", "int", "interface", + "internal", "is", "lock", "long", "namespace", + "new", "null", "object", "operator", "out", + "override", "params", "private", "protected", "public", + "readonly", "ref", "return", "sbyte", "sealed", + "short", "sizeof", "stackalloc", "static", "string", + "struct", "switch", "this", "throw", "true", + "try", "typeof", "uint", "ulong", "unchecked", + "unsafe", "ushort", "using", "virtual", "void", + "volatile", "while" + }; + + static readonly string[] contextualKeywords = new string[] { + "add", "async", "await", "get", "partial", + "remove", "set", "where", "yield", "from", + "select", "group", "into", "orderby", "join", + "let", "on", "equals", "by", "ascending", + "descending", "dynamic", "var", "global" + }; + + public override bool IsKeyword(string identifier, bool treatContextualKeywordsAsIdentifiers = true) + { + if (contextualKeywords.Any(keyword => keyword.Equals(identifier, StringComparison.Ordinal))) + return !treatContextualKeywordsAsIdentifiers; + if (keywords.Any(keyword => keyword.Equals(identifier, StringComparison.Ordinal))) + return true; + return false; + } + #endregion + + public override void RenameSymbol(RenameReferenceContext context, string newName) + { + newName = EscapeIdentifier(newName); + if (context.HasConflicts) throw new InvalidOperationException(); + ITextEditorProvider provider = SD.FileService.OpenFile(context.RenameTarget.FileName) as ITextEditorProvider; + if (provider == null) return; + var editor = provider.TextEditor; + int offset = context.RenameTarget.StartOffset; + int length = context.RenameTarget.Length; + editor.Document.Replace(offset, length, newName); + } + + enum ConflictResolution { RequireThis, RequireFullName, NoResolution } + + public override IEnumerable FindRenamingConflicts(Reference context, string newName) + { + newName = EscapeIdentifier(newName); + + ICompilation compilation = SD.ParserService.GetCompilationForFile(context.FileName); + ITextSource fileContent = new ParseableFileContentFinder().Create(context.FileName); + CSharpFullParseInformation pi = SD.ParserService.Parse(context.FileName, fileContent) as CSharpFullParseInformation; + + if (pi == null) yield break; + + AstNode node = pi.SyntaxTree.GetNodeContaining(context.StartLocation, context.EndLocation); + while (node.Parent is Expression) + node = node.Parent; + node = node.Clone(); + var identifierNode = FindIdentifier(node, context.StartLocation, context.EndLocation); + node = SetName(identifierNode, newName); + var writer = new StringWriter(); + var visitor = new CSharpOutputVisitor(writer, FormattingOptionsFactory.CreateSharpDevelop()); + node.AcceptVisitor(visitor); + + string expression = writer.ToString(); + + ResolveResult rr = SD.ParserService.ResolveSnippet(context.FileName, context.StartLocation, fileContent, + expression, compilation, default(CancellationToken)); + + if (!rr.IsError) { + if (rr is LocalResolveResult) { + IVariable culprit = ((LocalResolveResult)rr).Variable; +// if (rr.GetType() == context.ResolveResult.GetType()) + yield return new CSharpConflict(context, culprit, ConflictResolution.NoResolution); +// else +// yield return new CSharpConflict(context, culprit, ConflictResolution.RequireThis); + } else if (rr is MemberResolveResult) { + IMember culprit = ((MemberResolveResult)rr).Member; +// if (rr.GetType() == context.ResolveResult.GetType()) + yield return new CSharpConflict(context, culprit, ConflictResolution.NoResolution); +// else +// yield return new CSharpConflict(context, culprit, ConflictResolution.RequireThis); + } else if (rr is TypeResolveResult) { + IEntity culprit = rr.Type.GetDefinition(); +// if (rr.GetType() == context.ResolveResult.GetType()) + yield return new CSharpConflict(context, culprit, ConflictResolution.NoResolution); +// else +// yield return new CSharpConflict(context, culprit, ConflictResolution.RequireThis); + } else { + yield return new UnknownConflict(); + } + } + } + + Identifier FindIdentifier(AstNode node, TextLocation startLocation, TextLocation endLocation) + { + var id = node.GetAdjacentNodeAt(startLocation, n => n is Identifier) as Identifier; + return id; + } + + AstNode SetName(AstNode old, string newName) + { + ((Identifier)old).Name = newName; + return old; + } + + class CSharpConflict : Conflict + { + ConflictResolution resolution; + Reference target; + + public CSharpConflict(Reference target, IVariable variable, ConflictResolution resolution) + : base(variable) + { + this.target = target; + this.resolution = resolution; + } + + public CSharpConflict(Reference target, IEntity entity, ConflictResolution resolution) + : base(entity) + { + this.target = target; + this.resolution = resolution; + } + + public override void Solve() + { + switch (resolution) { + case ConflictResolution.RequireThis: + break; + case ConflictResolution.RequireFullName: + break; + case ConflictResolution.NoResolution: + throw new InvalidOperationException(); + default: + throw new ArgumentOutOfRangeException(); + } + } + + public override bool IsSolvableConflict { + get { return resolution != ConflictResolution.NoResolution; } + } + } } } diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/RenameContextAction.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/RenameContextAction.cs new file mode 100644 index 0000000000..1a4ae2c519 --- /dev/null +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/RenameContextAction.cs @@ -0,0 +1,181 @@ +// 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.Linq; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.Core; +using ICSharpCode.NRefactory; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.CSharp.Refactoring; +using ICSharpCode.NRefactory.Editor; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Editor.Search; +using ICSharpCode.SharpDevelop.Parser; +using ICSharpCode.SharpDevelop.Project; +using ICSharpCode.SharpDevelop.Refactoring; +using CSharpBinding.Parser; + +namespace CSharpBinding.Refactoring +{ + [ContextAction("Rename", Description = "Renames a symbol and all references to it.")] + public class RenameContextAction : ContextAction + { + public override async Task IsAvailableAsync(EditorRefactoringContext context, CancellationToken cancellationToken) + { + var rr = await context.GetCurrentSymbolAsync(); + if (rr == null || rr.GetDefinitionRegion().IsEmpty) return false; + if (rr is MemberResolveResult) { + // check whether all members in the hierarchy are defined in code... + IEntity thisMember = GetSurrogateEntity(((MemberResolveResult)rr).Member); + switch (thisMember.EntityType) { + case EntityType.TypeDefinition: + case EntityType.Field: + return !thisMember.Region.IsEmpty; + break; + case EntityType.Property: + case EntityType.Event: + case EntityType.Method: + return !FindReferenceService.FindAllMembers((IMember)thisMember) + .Distinct() + .Any(m => m.Region.IsEmpty); + case EntityType.Indexer: + return false; + case EntityType.Operator: + return false; + default: + throw new NotSupportedException(); + } + } + + return true; + } + + IEntity GetSurrogateEntity(IMember member) + { + if (member.EntityType == EntityType.Accessor) + return ((IMethod)member).AccessorOwner; + if (member.EntityType == EntityType.Constructor || member.EntityType == EntityType.Destructor) + return member.DeclaringTypeDefinition; + return member; + } + + public override async void Execute(EditorRefactoringContext context) + { + var rr = await context.GetCurrentSymbolAsync(); + List references = null; + string oldName = null; + string text = null; + bool isLocal = rr is LocalResolveResult; + IVariable variable = null; + IEntity entity = null; + IProgressMonitor monitor = new DummyProgressMonitor(); + + if (isLocal) { + variable = ((LocalResolveResult)rr).Variable; + oldName = variable.Name; + text = "${res:SharpDevelop.Refactoring.RenameMemberText}"; + references = await FindReferenceService.FindLocalReferences(variable, monitor).ToListAsync(); + } else if (rr is TypeResolveResult) { + ITypeDefinition td = rr.Type.GetDefinition(); + if (td == null) return; + entity = td; + oldName = td.Name; + text = "${res:SharpDevelop.Refactoring.RenameClassText}"; + references = await FindReferenceService.FindReferences(td, monitor).ToListAsync(); + } else if (rr is MemberResolveResult) { + entity = GetSurrogateEntity(((MemberResolveResult)rr).Member); + oldName = entity.Name; + if (entity is IMember) { + text = "${res:SharpDevelop.Refactoring.RenameMemberText}"; + references = await FindReferenceService.FindReferencesToHierarchy((IMember)entity, monitor).ToListAsync(); + } else { + text = "${res:SharpDevelop.Refactoring.RenameClassText}"; + references = await FindReferenceService.FindReferences(entity, monitor).ToListAsync(); + } + } + + if (references == null || oldName == null) return; + string newName = SD.MessageService.ShowInputBox("${res:SharpDevelop.Refactoring.Rename}", text, oldName); + if (newName == null) return; + // if both identifiers are the same (in the language the symbol was defined in) + // => abort rename + ILanguageBinding definitionLanguage = SD.LanguageService + .GetLanguageByFileName(new FileName(rr.GetDefinitionRegion().FileName)); + if (definitionLanguage.IdentifierComparer.Compare(oldName, newName) == 0) + return; + + // check if identifier is valid in all target languages: + string lastExtension = null; + ILanguageBinding language = null; + bool isRenamePossible = true; + + Dictionary conflicts = new Dictionary(); + + foreach (var file in references) { + string ext = file.FileName.GetExtension(); + if (language == null || !string.Equals(ext, lastExtension, StringComparison.OrdinalIgnoreCase)) { + lastExtension = ext; + language = SD.LanguageService.GetLanguageByExtension(ext); + } + + if (!language.CodeGenerator.IsValidIdentifier(newName)) { + isRenamePossible = false; + SD.MessageService.ShowErrorFormatted("The symbol '{0}' cannot be renamed, because its new name '{1}' would be invalid in {2}, please choose a different name and try again.", oldName, newName, language.Name); + break; + } + + foreach (Reference reference in file.Matches.OfType()) { + var currentConflicts = language.CodeGenerator.FindRenamingConflicts(reference, newName).ToArray(); + if (currentConflicts.Any(conflict => !conflict.IsSolvableConflict)) { + isRenamePossible = false; + SD.MessageService.ShowErrorFormatted("The symbol '{0}' cannot be renamed, because its new name '{1}' would cause an unsolvable conflict in {2}, at line {3}, column {4}, please choose a different name and try again.", oldName, newName, reference.FileName, reference.StartLocation.Line, reference.StartLocation.Column); + break; + } + conflicts.Add(reference, currentConflicts); + } + } + + // TODO : ask if user wants to rename corresponding IField/IProperty/IEvent as well... + + if (!isRenamePossible) return; + + lastExtension = null; + language = null; + + foreach (var reference in references.SelectMany(file => file.Matches).OfType()) { + string ext = reference.FileName.GetExtension(); + if (language == null || !string.Equals(ext, lastExtension, StringComparison.OrdinalIgnoreCase)) { + lastExtension = ext; + language = SD.LanguageService.GetLanguageByExtension(ext); + } + + IList currentConflicts = conflicts.GetOrDefault(reference); + if (currentConflicts == null) + currentConflicts = EmptyList.Instance; + + var renamingContext = new RenameReferenceContext(reference, currentConflicts); + + if (isLocal) + renamingContext.OldVariable = variable; + else + renamingContext.OldEntity = entity; + + if (renamingContext != null) + language.CodeGenerator.RenameSymbol(renamingContext, newName); + } + } + + public override string DisplayName { + get { return "Rename symbol"; } + } + } +} diff --git a/src/Main/Base/Project/Refactoring/ICodeGenerator.cs b/src/Main/Base/Project/Refactoring/ICodeGenerator.cs index c53774be57..1e13d73d8c 100644 --- a/src/Main/Base/Project/Refactoring/ICodeGenerator.cs +++ b/src/Main/Base/Project/Refactoring/ICodeGenerator.cs @@ -2,7 +2,10 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; +using System.Linq; using ICSharpCode.NRefactory; +using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop.Project; @@ -21,6 +24,95 @@ namespace ICSharpCode.SharpDevelop.Refactoring string GetPropertyName(string fieldName); string GetParameterName(string fieldName); string GetFieldName(string propertyName); + + /// + /// Determines whether the given identifier is valid in the language or can be used if escaped properly. + /// + bool IsValidIdentifier(string identifier); + + /// + /// Escapes the identifier if needed. + /// + string EscapeIdentifier(string identifier); + + /// + /// Returns whether the given string is a keyword or not. + /// + bool IsKeyword(string identifier, bool treatContextualKeywordsAsIdentifiers = true); + + void RenameSymbol(RenameReferenceContext context, string newName); + IEnumerable FindRenamingConflicts(Reference symbol, string newName); + } + + public abstract class Conflict + { + public IVariable ConflictingVariable { get; private set; } + public IEntity ConflictingEntity { get; private set; } + + // TODO : Please add something like ISymbol => (IVariable, IEntity) + public bool IsLocalConflict { + get { + return ConflictingVariable != null; + } + } + + public abstract bool IsSolvableConflict { + get; + } + + public abstract void Solve(); + + protected Conflict() + { + } + + protected Conflict(IVariable variable) + { + this.ConflictingVariable = variable; + } + + protected Conflict(IEntity entity) + { + this.ConflictingEntity = entity; + } + } + + public class UnknownConflict : Conflict + { + public override bool IsSolvableConflict { + get { + return false; + } + } + + public override void Solve() + { + throw new NotSupportedException(); + } + } + + public class RenameReferenceContext + { + public IReadOnlyList Conflicts { get; private set; } + public Reference RenameTarget { get; private set; } + + // TODO : Please add something like ISymbol => (IVariable, IEntity) + public IVariable OldVariable { get; set; } + public IEntity OldEntity { get; set; } + + public bool IsLocal { + get { return OldVariable != null; } + } + + public bool HasConflicts { + get { return Conflicts.Any(); } + } + + public RenameReferenceContext(Reference target, IList conflicts) + { + this.RenameTarget = target; + this.Conflicts = conflicts.AsReadOnly(); + } } public class DefaultCodeGenerator : ICodeGenerator @@ -81,5 +173,30 @@ namespace ICSharpCode.SharpDevelop.Refactoring else return newName; } + + public virtual bool IsValidIdentifier(string identifier) + { + return false; + } + + public virtual string EscapeIdentifier(string identifier) + { + throw new NotSupportedException("Feature not supported!"); + } + + public virtual bool IsKeyword(string identifier, bool treatContextualKeywordsAsIdentifiers = true) + { + return false; + } + + public virtual void RenameSymbol(RenameReferenceContext context, string newName) + { + throw new NotSupportedException("Feature not supported!"); + } + + public virtual IEnumerable FindRenamingConflicts(Reference context, string newName) + { + throw new NotSupportedException("Feature not supported!"); + } } } diff --git a/src/Main/Base/Project/Src/Services/LanguageBinding/DefaultLanguageBinding.cs b/src/Main/Base/Project/Src/Services/LanguageBinding/DefaultLanguageBinding.cs index 2e401ba247..1d44d917e3 100644 --- a/src/Main/Base/Project/Src/Services/LanguageBinding/DefaultLanguageBinding.cs +++ b/src/Main/Base/Project/Src/Services/LanguageBinding/DefaultLanguageBinding.cs @@ -11,6 +11,14 @@ namespace ICSharpCode.SharpDevelop { public static readonly DefaultLanguageBinding DefaultInstance = new DefaultLanguageBinding(); + public virtual string Name { + get { return ""; } + } + + public virtual StringComparer IdentifierComparer { + get { return StringComparer.Ordinal; } + } + public virtual IFormattingStrategy FormattingStrategy { get { return DefaultFormattingStrategy.DefaultInstance; diff --git a/src/Main/Base/Project/Src/Services/LanguageBinding/ILanguageBinding.cs b/src/Main/Base/Project/Src/Services/LanguageBinding/ILanguageBinding.cs index 3c6cccf241..b3f9bdb825 100644 --- a/src/Main/Base/Project/Src/Services/LanguageBinding/ILanguageBinding.cs +++ b/src/Main/Base/Project/Src/Services/LanguageBinding/ILanguageBinding.cs @@ -12,6 +12,20 @@ namespace ICSharpCode.SharpDevelop /// public interface ILanguageBinding { + /// + /// Gets the display name for the language. + /// + string Name { + get; + } + + /// + /// Gets the comparer used to compare two identifiers in this language. + /// + StringComparer IdentifierComparer { + get; + } + /// /// Provides access to the formatting strategy for this language. /// diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs b/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs index 0073cad9f8..d075165540 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using ICSharpCode.Core; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; +using ICSharpCode.NRefactory.Utils; using ICSharpCode.SharpDevelop.Editor.Search; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Parser; @@ -22,7 +24,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring /// public static class FindReferenceService { - #region FindReferences +// #region FindReferences static IEnumerable GetProjectsThatCouldReferenceEntity(IEntity entity) { ISolution solution = ProjectService.OpenSolution; @@ -96,6 +98,57 @@ namespace ICSharpCode.SharpDevelop.Refactoring progressMonitor); } + public static Task FindReferencesToHierarchyAsync(IMember member, IProgressMonitor progressMonitor, Action callback) + { + if (member == null) + throw new ArgumentNullException("member"); + if (progressMonitor == null) + throw new ArgumentNullException("progressMonitor"); + if (callback == null) + throw new ArgumentNullException("callback"); + return Task.WhenAll(FindAllMembers(member).Distinct().Select(m => FindReferencesAsync(m, progressMonitor, callback))); + } + + public static IEnumerable FindAllMembers(IMember member) + { + yield return member; + foreach (var ancestor in InheritanceHelper.GetBaseMembers(member, true)) { + yield return ancestor; + // TODO : produces duplicates + foreach (var derived in FindDerivedMembers(ancestor)) + yield return derived; + } + foreach (var derived in FindDerivedMembers(member)) + yield return derived; + } + + static IEnumerable FindDerivedMembers(IMember member) + { + ITypeDefinition definition = member.DeclaringTypeDefinition; + if (definition == null) yield break; + var tree = BuildDerivedTypesGraph(definition).ConvertToDerivedTypeTree(); + var derivedMembers = TreeTraversal.PreOrder(tree, node => node.Children) + .Select(n => FindDerivedMember(member, n)); + foreach (var otherMember in derivedMembers.Where(m => m != null)) + yield return otherMember; + } + + static IMember FindDerivedMember(IMember member, ITreeNode node) + { +// if (endPoint != null && (node.Content.Equals(endPoint) || node.Content.GetAllBaseTypeDefinitions().Any(td => endPoint.Equals(td)))) +// return null; + var importedMember = node.Content.Compilation.Import(member); + if (importedMember == null) return null; + return InheritanceHelper.GetDerivedMember(importedMember, node.Content); + } + + public static IObservable FindReferencesToHierarchy(IMember member, IProgressMonitor progressMonitor) + { + return ReactiveExtensions.CreateObservable( + (monitor, callback) => FindReferencesToHierarchyAsync(member, monitor, callback), + progressMonitor); + } + /// /// Finds references to a local variable. /// @@ -120,7 +173,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring (monitor, callback) => FindLocalReferencesAsync(variable, monitor).ContinueWith(t => callback(t.Result)), progressMonitor); } - #endregion +// #endregion #region FindDerivedTypes /// @@ -182,6 +235,26 @@ namespace ICSharpCode.SharpDevelop.Refactoring } } + /// + /// Builds a graph of type definitions. + /// + public static TypeGraphNode BuildFullTypeGraph(ITypeDefinition type) + { + if (type == null) + throw new ArgumentNullException("baseType"); + var solutionSnapshot = GetSolutionSnapshot(type.Compilation); + var compilations = GetProjectsThatCouldReferenceEntity(type).Select(p => solutionSnapshot.GetCompilation(p)); + var graph = BuildTypeInheritanceGraph(compilations); + TypeGraphNode node; + if (graph.TryGetValue(new AssemblyQualifiedTypeName(type), out node)) { + // only derived types were requested, so don't return the base types + // (this helps the GC to collect the unused parts of the graph more quickly) + return node; + } else { + return new TypeGraphNode(type); + } + } + /// /// Builds a graph of all type definitions in the specified set of project contents. ///