From cbc1b162e629c1e0a3a55fb46ce5fb6e5f04a2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kon=C3=AD=C4=8Dek?= Date: Thu, 5 Aug 2010 01:17:36 +0000 Subject: [PATCH] Added 2 context actions - Check null, Check not null. They are offered after assignments to local variables. In case they annoy you: soon it will be possible to turn off any action easily right from the actions popup. Fixed minor bug in CtrlSpaceResolveHelper.GetResultFromDeclarationLine which showed up e.g. when right clicking keyword "as" in the editor. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@6369 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Project/SharpRefactoring.addin | 12 +- .../Project/SharpRefactoring.csproj | 3 + .../ContextActions/CheckAssignmentAction.cs | 107 ++++++++++++++++++ .../ContextActions/CheckAssignmentNotNull.cs | 50 ++++++++ .../Src/ContextActions/CheckAssignmentNull.cs | 43 +++++++ .../Project/Src/ContextActions/Extensions.cs | 39 +++++++ .../Src/ContextActions/GenerateMember.cs | 10 +- .../Project/Src/GenerateCode.cs | 6 +- .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../ContextActions/ContextAction.cs | 40 +++++++ .../ContextActions/ContextActionsService.cs | 10 +- .../ContextActions/EditorContext.cs | 43 +++---- .../ContextActions/IContextAction.cs | 1 + .../ContextActions/IContextActionsProvider.cs | 2 +- .../Project/Src/CtrlSpaceResolveHelper.cs | 1 + 15 files changed, 332 insertions(+), 36 deletions(-) create mode 100644 src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentAction.cs create mode 100644 src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNotNull.cs create mode 100644 src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNull.cs create mode 100644 src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextAction.cs diff --git a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin index 68907a0a71..75e3e4f3b4 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin +++ b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin @@ -40,11 +40,13 @@ - - - - - + + + + + + + diff --git a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj index 807c8e6638..5e5162181f 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj +++ b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj @@ -71,6 +71,9 @@ + + + diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentAction.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentAction.cs new file mode 100644 index 0000000000..14d973f207 --- /dev/null +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentAction.cs @@ -0,0 +1,107 @@ +// +// +// +// +// $Revision: $ +// +using System; +using ICSharpCode.NRefactory.Ast; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Dom; +using ICSharpCode.SharpDevelop.Dom.Refactoring; +using ICSharpCode.SharpDevelop.Refactoring; + +namespace SharpRefactoring.ContextActions +{ + /// + /// Description of CheckAssignmentAction. + /// + public abstract class CheckAssignmentAction : ContextAction + { + protected string VariableName { get; private set; } + + protected CodeGenerator CodeGenerator { get; private set; } + + protected DomRegion ElementRegion { get; private set; } + + protected string GetVariableName(EditorContext context) + { + // a = Foo() : AssignmentExpression.Left == IdentifierExpression(*identifier*) + // var a = Foo() : VariableDeclaration(*name*).Initializer != empty + + var variableName = GetVariableNameFromAssignment(context.GetContainingElement()); + if (variableName != null) + return variableName; + variableName = GetVariableNameFromVariableDeclaration(context.GetContainingElement()); + if (variableName != null) + return variableName; + + return null; + } + + protected DomRegion GetStatementRegion(EditorContext context) + { + // a = Foo() : AssignmentExpression.Left == IdentifierExpression(*identifier*) + // var a = Foo() : VariableDeclaration(*name*).Initializer != empty + + var assignment = context.GetContainingElement(); + if (assignment != null) + return DomRegion.FromLocation(assignment.StartLocation, assignment.EndLocation); + var declaration = context.GetContainingElement(); + if (declaration != null) + return DomRegion.FromLocation(declaration.StartLocation, declaration.EndLocation); + + return DomRegion.Empty; + } + + string GetVariableNameFromAssignment(AssignmentExpression assignment) + { + if (assignment == null) + return null; + var identifier = assignment.Left as IdentifierExpression; + if (identifier == null) + return null; + if (assignment.Right is ObjectCreateExpression) + // // don't offer action for "a = new Foo()" + return null; + return identifier.Identifier; + } + + string GetVariableNameFromVariableDeclaration(LocalVariableDeclaration declaration) + { + if (declaration == null) + return null; + if (declaration.Variables.Count != 1) + return null; + VariableDeclaration varDecl = declaration.Variables[0]; + if (!varDecl.Initializer.IsNull && + // don't offer action for "var a = new Foo()" + !(varDecl.Initializer is ObjectCreateExpression)) + return varDecl.Name; + return null; + } + + CodeGenerator GetCodeGenerator(EditorContext context) + { + var parseInfo = ParserService.GetParseInformation(context.Editor.FileName); + if (parseInfo == null) + return null; + return parseInfo.CompilationUnit.Language.CodeGenerator; + } + + public IReturnType GetResolvedType(ResolveResult symbol) + { + if (symbol != null) + return symbol.ResolvedType; + return null; + } + + public override bool IsEnabled(EditorContext context) + { + this.VariableName = GetVariableName(context); + this.CodeGenerator = GetCodeGenerator(context); + this.ElementRegion = GetStatementRegion(context); + return !string.IsNullOrEmpty(this.VariableName) && (this.CodeGenerator != null); + } + } +} diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNotNull.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNotNull.cs new file mode 100644 index 0000000000..74be619e10 --- /dev/null +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNotNull.cs @@ -0,0 +1,50 @@ +// +// +// +// +// $Revision: $ +// +using System; +using ICSharpCode.NRefactory.Ast; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Refactoring; + +namespace SharpRefactoring.ContextActions +{ + /// + /// Description of CheckAssignmentNotNull. + /// + public class CheckAssignmentNotNull : CheckAssignmentAction + { + public override string Title { + get { return "Check for not null"; } + } + + string caretMarker = "<<>>"; + + public override void Execute(EditorContext context) + { + var ifStatement = GenerateAstToInsert(this.VariableName); + + var editor = context.Editor; + string indent = DocumentUtilitites.GetWhitespaceAfter(editor.Document, editor.Document.GetLineStartOffset(this.ElementRegion.GetStart())); + string code = this.CodeGenerator.GenerateCode(ifStatement, indent); + int insertOffset = editor.Document.GetLineEndOffset(this.ElementRegion.GetEnd()); + using (var undoGroup = editor.Document.OpenUndoGroup()) { + editor.Document.Insert(insertOffset, code); + var caretPos = editor.Document.Text.IndexOf(caretMarker, insertOffset); + editor.Caret.Offset = caretPos; + editor.Document.RemoveRestOfLine(caretPos); + } + } + + AbstractNode GenerateAstToInsert(string variableName) + { + var block = new BlockStatement(); + block.AddChild(new ExpressionStatement(new IdentifierExpression(caretMarker))); + return new IfElseStatement( + new BinaryOperatorExpression(new IdentifierExpression(variableName), BinaryOperatorType.InEquality, new PrimitiveExpression(null)), + block); + } + } +} diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNull.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNull.cs new file mode 100644 index 0000000000..a50c1aedd0 --- /dev/null +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNull.cs @@ -0,0 +1,43 @@ +// +// +// +// +// $Revision: $ +// +using System; +using System.Collections.Generic; +using ICSharpCode.NRefactory.Ast; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Refactoring; + +namespace SharpRefactoring.ContextActions +{ + /// + /// Offers inserting "if (a == null) return;" after "var a = *expr*" + /// + public class CheckAssignmentNull : CheckAssignmentAction + { + public override string Title { + get { return "Check for null"; } + } + + public override void Execute(EditorContext context) + { + var ifStatement = GenerateAstToInsert(this.VariableName); + + var editor = context.Editor; + string indent = DocumentUtilitites.GetWhitespaceAfter(editor.Document, editor.Document.GetLineStartOffset(this.ElementRegion.GetStart())); + string code = this.CodeGenerator.GenerateCode(ifStatement, indent); + int insertOffset = editor.Document.GetLineEndOffset(this.ElementRegion.GetEnd()); + editor.Document.Insert(insertOffset, code); + editor.Caret.Offset = insertOffset + code.Length - 1; + } + + AbstractNode GenerateAstToInsert(string variableName) + { + return new IfElseStatement( + new BinaryOperatorExpression(new IdentifierExpression(variableName), BinaryOperatorType.Equality, new PrimitiveExpression(null)), + new ReturnStatement(Expression.Null)); + } + } +} diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/Extensions.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/Extensions.cs index ef69a01a07..f7e9a026de 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/Extensions.cs +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/Extensions.cs @@ -7,10 +7,13 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; + +using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.Ast; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Dom.Refactoring; +using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Refactoring; namespace SharpRefactoring.ContextActions @@ -47,5 +50,41 @@ namespace SharpRefactoring.ContextActions } } } + + public static Location GetStart(this DomRegion region) + { + return new Location(region.BeginColumn, region.BeginLine); + } + + public static Location GetEnd(this DomRegion region) + { + return new Location(region.EndColumn, region.EndLine); + } + + public static int PositionToOffset(this IDocument document, Location location) + { + return document.PositionToOffset(location.Line, location.Column); + } + + /// + /// Gets offset for the start of line at which given location is. + /// + public static int GetLineStartOffset(this IDocument document, Location location) + { + return document.GetLineForOffset(document.PositionToOffset(location)).Offset; + } + + public static int GetLineEndOffset(this IDocument document, Location location) + { + var line = document.GetLineForOffset(document.PositionToOffset(location)); + return line.Offset + line.TotalLength; + } + + public static void RemoveRestOfLine(this IDocument document, int offset) + { + var line = document.GetLineForOffset(offset); + int lineEndOffset = line.Offset + line.Length; + document.Remove(offset, lineEndOffset - offset); + } } } diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/GenerateMember.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/GenerateMember.cs index 4a5a594c48..4377b907af 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/GenerateMember.cs +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/GenerateMember.cs @@ -18,13 +18,13 @@ namespace SharpRefactoring.ContextActions /// public class GenerateMemberProvider : IContextActionsProvider { - public IEnumerable GetAvailableActions(EditorContext editorContext) + public IEnumerable GetAvailableActions(EditorContext context) { - if (string.IsNullOrEmpty(editorContext.CurrentExpression.Expression)) { + if (string.IsNullOrEmpty(context.CurrentExpression.Expression)) { yield break; } - if (editorContext.CurrentExpression.Region != null && - editorContext.CurrentExpression.Region.EndLine > editorContext.CurrentExpression.Region.BeginLine) { + if (context.CurrentExpression.Region != null && + context.CurrentExpression.Region.EndLine > context.CurrentExpression.Region.BeginLine) { // do not yield the action for 2-line expressions like this, which are actually 2 different expressions // variable.(*caret*) // CallFooMethod(); @@ -33,7 +33,7 @@ namespace SharpRefactoring.ContextActions // 123); yield break; } - var generateCodeAction = GenerateCode.GetContextAction(editorContext.CurrentSymbol, editorContext); + var generateCodeAction = GenerateCode.GetContextAction(context); if (generateCodeAction != null) yield return generateCodeAction; } diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/GenerateCode.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/GenerateCode.cs index 964bbf2954..5f49d27125 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/Src/GenerateCode.cs +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/GenerateCode.cs @@ -30,10 +30,10 @@ namespace SharpRefactoring /// /// If given symbol is Unknown ResolveResult, returns action that can generate code for this missing symbol. /// - public static GenerateCodeContextAction GetContextAction(ResolveResult symbol, EditorContext context) + public static GenerateCodeContextAction GetContextAction(EditorContext context) { - if (symbol is UnknownMethodResolveResult) { - UnknownMethodResolveResult unknownMethodCall = (UnknownMethodResolveResult)symbol; + if (context.CurrentSymbol is UnknownMethodResolveResult) { + UnknownMethodResolveResult unknownMethodCall = (UnknownMethodResolveResult)context.CurrentSymbol; Ast.Expression expression = context.GetContainingElement(); if (expression == null) return null; diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 32fcf36896..78f4855a9b 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -331,6 +331,7 @@ + ContextActionsBulbControl.xaml Code diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextAction.cs b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextAction.cs new file mode 100644 index 0000000000..760aaa07a3 --- /dev/null +++ b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextAction.cs @@ -0,0 +1,40 @@ +// +// +// +// +// $Revision: $ +// +using System; +using System.Collections.Generic; + +namespace ICSharpCode.SharpDevelop.Refactoring +{ + /// + /// Base class for implementing custom context actions. + /// + public abstract class ContextAction : IContextActionsProvider, IContextAction + { + public abstract string Title { get; } + + public abstract bool IsEnabled(EditorContext context); + + public abstract void Execute(EditorContext context); + + public IEnumerable GetAvailableActions(EditorContext context) + { + this.context = context; + if (this.IsEnabled(context)) + yield return this; + } + + EditorContext context; + public void Execute() + { + Execute(this.context); + } + + public virtual string Id { + get { return this.GetType().FullName; } + } + } +} diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsService.cs b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsService.cs index e04b6c0b70..03e29b68c9 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsService.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsService.cs @@ -6,8 +6,8 @@ // using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; - using ICSharpCode.Core; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Refactoring; @@ -31,7 +31,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring private ContextActionsService() { - this.providers = AddInTree.BuildItems("/SharpDevelop/ViewContent/AvalonEdit/ContextActionProviders", null, false); + this.providers = AddInTree.BuildItems("/SharpDevelop/ViewContent/AvalonEdit/ContextActions", null, false); } /// @@ -43,13 +43,19 @@ namespace ICSharpCode.SharpDevelop.Refactoring yield break; var parseTask = ParserService.BeginParseCurrentViewContent(); parseTask.Wait(); + + var sw = new Stopwatch(); sw.Start(); var editorContext = new EditorContext(editor); + long elapsedEditorContextMs = sw.ElapsedMilliseconds; + // could run providers in parallel foreach (var provider in this.providers) { foreach (var action in provider.GetAvailableActions(editorContext)) { yield return action; } } + ICSharpCode.Core.LoggingService.Debug(string.Format("Context actions elapsed {0}ms ({1}ms in EditorContext)", + sw.ElapsedMilliseconds, elapsedEditorContextMs)); } } } diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/EditorContext.cs b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/EditorContext.cs index 272779af0a..6583227a12 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/EditorContext.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/EditorContext.cs @@ -66,12 +66,8 @@ namespace ICSharpCode.SharpDevelop.Refactoring CaretColumn -= 1; } - Stopwatch sw = new Stopwatch(); - sw.Start(); - this.CurrentExpression = GetExpressionAtCaret(editor); this.CurrentSymbol = ResolveExpression(editor); - long elapsedResolveMs = sw.ElapsedMilliseconds; this.CurrentLine = editor.Document.GetLine(CaretLine); this.CurrentLineAST = GetCurrentLineAst(this.CurrentLine, editor); @@ -80,21 +76,28 @@ namespace ICSharpCode.SharpDevelop.Refactoring this.CurrentElement = FindInnermostNodeAtLocation(this.CurrentMemberAST, new Location(CaretColumn, CaretLine)); -// ICSharpCode.Core.LoggingService.Debug(string.Format( -// @" -// Context actions (elapsed {5} ms ({6} ms in Resolver)): -// ExprAtCaret: {0} -// ---------------------- -// SymbolAtCaret: {1} -// ---------------------- -// CurrentLineAST: {2} -// ---------------------- -// AstNodeAtCaret: {3} -// ---------------------- -// CurrentMemberAST: {4} -// ----------------------", -// CurrentExpression, CurrentSymbol, CurrentLineAST, CurrentElement, CurrentMemberAST == null ? "" : CurrentMemberAST.ToString().TakeStartEllipsis(100), -// sw.ElapsedMilliseconds, elapsedResolveMs)); + //DebugLog(); + } + + void DebugLog() + { + ICSharpCode.Core.LoggingService.Debug(string.Format( + @" + + Context actions : + ExprAtCaret: {0} + ---------------------- + SymbolAtCaret: {1} + ---------------------- + CurrentLineAST: {2} + ---------------------- + AstNodeAtCaret: {3} + ---------------------- + CurrentMemberAST: {4} + ----------------------", + CurrentExpression, CurrentSymbol, CurrentLineAST, + CurrentElement == null ? "" : CurrentElement.ToString().TakeStartEllipsis(400), + CurrentMemberAST == null ? "" : CurrentMemberAST.ToString().TakeStartEllipsis(400))); } public TNode GetCurrentElement() where TNode : class, INode @@ -173,7 +176,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring // class FindOutermostNodeVisitor : NodeTrackingAstVisitor where TNode : class, INode // { // public TNode FoundNode { get; private set; } -// +// // protected override void BeginVisit(INode node) // { // if (node is TNode && FoundNode == null) { diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextAction.cs b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextAction.cs index 721dab7e24..42f53a2fd8 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextAction.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextAction.cs @@ -15,6 +15,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring public interface IContextAction { string Title { get; } + //string Id { get; } void Execute(); } } diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextActionsProvider.cs b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextActionsProvider.cs index 0a3e484fda..0036c55ba7 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextActionsProvider.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextActionsProvider.cs @@ -19,6 +19,6 @@ namespace ICSharpCode.SharpDevelop.Refactoring /// /// Gets actions available for current line of the editor. /// - IEnumerable GetAvailableActions(EditorContext editorAST); + IEnumerable GetAvailableActions(EditorContext context); } } diff --git a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/CtrlSpaceResolveHelper.cs b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/CtrlSpaceResolveHelper.cs index 38b34b7f3a..9a19b5e8b1 100644 --- a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/CtrlSpaceResolveHelper.cs +++ b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/CtrlSpaceResolveHelper.cs @@ -213,6 +213,7 @@ namespace ICSharpCode.SharpDevelop.Dom public static ResolveResult GetResultFromDeclarationLine(IClass callingClass, IMethodOrProperty callingMember, int caretLine, int caretColumn, ExpressionResult expressionResult) { string expression = expressionResult.Expression; + if (expression == null) return null; if (callingClass == null) return null; int pos = expression.IndexOf('('); if (pos >= 0) {