Browse Source

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
pull/1/head
Martin Koníček 16 years ago
parent
commit
cbc1b162e6
  1. 12
      src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin
  2. 3
      src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj
  3. 107
      src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentAction.cs
  4. 50
      src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNotNull.cs
  5. 43
      src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNull.cs
  6. 39
      src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/Extensions.cs
  7. 10
      src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/GenerateMember.cs
  8. 6
      src/AddIns/Misc/SharpRefactoring/Project/Src/GenerateCode.cs
  9. 1
      src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
  10. 40
      src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextAction.cs
  11. 10
      src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsService.cs
  12. 41
      src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/EditorContext.cs
  13. 1
      src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextAction.cs
  14. 2
      src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextActionsProvider.cs
  15. 1
      src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/CtrlSpaceResolveHelper.cs

12
src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin

@ -40,11 +40,13 @@ @@ -40,11 +40,13 @@
<Class id="switch" class="SharpRefactoring.SwitchSnippetProvider" />
</Path>
<Path name="/SharpDevelop/ViewContent/AvalonEdit/ContextActionProviders">
<Class id="addUsing" class="SharpRefactoring.ContextActions.AddUsingProvider" />
<Class id="implementInterface" class="SharpRefactoring.ContextActions.ImplementInterfaceProvider" />
<Class id="implementAbstractClass" class="SharpRefactoring.ContextActions.ImplementAbstractClassProvider" />
<Class id="generateMember" class="SharpRefactoring.ContextActions.GenerateMemberProvider" />
<Path name="/SharpDevelop/ViewContent/AvalonEdit/ContextActions">
<Class class="SharpRefactoring.ContextActions.AddUsingProvider" />
<Class class="SharpRefactoring.ContextActions.ImplementInterfaceProvider" />
<Class class="SharpRefactoring.ContextActions.ImplementAbstractClassProvider" />
<Class class="SharpRefactoring.ContextActions.GenerateMemberProvider" />
<Class class="SharpRefactoring.ContextActions.CheckAssignmentNull" />
<Class class="SharpRefactoring.ContextActions.CheckAssignmentNotNull" />
</Path>
<Path name="/SharpDevelop/ViewContent/TextEditor/OverrideCompletionHandler">

3
src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj

@ -71,6 +71,9 @@ @@ -71,6 +71,9 @@
<Compile Include="Configuration\AssemblyInfo.cs" />
<Compile Include="Src\ClassRefactoringSubmenuBuilder.cs" />
<Compile Include="Src\ContextActions\AddUsing.cs" />
<Compile Include="Src\ContextActions\CheckAssignmentAction.cs" />
<Compile Include="Src\ContextActions\CheckAssignmentNull.cs" />
<Compile Include="Src\ContextActions\CheckAssignmentNotNull.cs" />
<Compile Include="Src\ContextActions\DelegateAction.cs" />
<Compile Include="Src\ContextActions\Extensions.cs" />
<Compile Include="Src\ContextActions\FindTypeDeclarationsVisitor.cs" />

107
src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentAction.cs

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Martin Konicek" email="martin.konicek@gmail.com"/>
// <version>$Revision: $</version>
// </file>
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
{
/// <summary>
/// Description of CheckAssignmentAction.
/// </summary>
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<AssignmentExpression>());
if (variableName != null)
return variableName;
variableName = GetVariableNameFromVariableDeclaration(context.GetContainingElement<LocalVariableDeclaration>());
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<AssignmentExpression>();
if (assignment != null)
return DomRegion.FromLocation(assignment.StartLocation, assignment.EndLocation);
var declaration = context.GetContainingElement<LocalVariableDeclaration>();
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);
}
}
}

50
src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNotNull.cs

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Martin Konicek" email="martin.konicek@gmail.com"/>
// <version>$Revision: $</version>
// </file>
using System;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Refactoring;
namespace SharpRefactoring.ContextActions
{
/// <summary>
/// Description of CheckAssignmentNotNull.
/// </summary>
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);
}
}
}

43
src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/CheckAssignmentNull.cs

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Martin Konicek" email="martin.konicek@gmail.com"/>
// <version>$Revision: $</version>
// </file>
using System;
using System.Collections.Generic;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Refactoring;
namespace SharpRefactoring.ContextActions
{
/// <summary>
/// Offers inserting "if (a == null) return;" after "var a = *expr*"
/// </summary>
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));
}
}
}

39
src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/Extensions.cs

@ -7,10 +7,13 @@ @@ -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 @@ -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);
}
/// <summary>
/// Gets offset for the start of line at which given location is.
/// </summary>
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);
}
}
}

10
src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/GenerateMember.cs

@ -18,13 +18,13 @@ namespace SharpRefactoring.ContextActions @@ -18,13 +18,13 @@ namespace SharpRefactoring.ContextActions
/// </summary>
public class GenerateMemberProvider : IContextActionsProvider
{
public IEnumerable<IContextAction> GetAvailableActions(EditorContext editorContext)
public IEnumerable<IContextAction> 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 @@ -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;
}

6
src/AddIns/Misc/SharpRefactoring/Project/Src/GenerateCode.cs

@ -30,10 +30,10 @@ namespace SharpRefactoring @@ -30,10 +30,10 @@ namespace SharpRefactoring
/// <summary>
/// If given symbol is Unknown ResolveResult, returns action that can generate code for this missing symbol.
/// </summary>
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<Ast.InvocationExpression>();
if (expression == null)
return null;

1
src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj

@ -331,6 +331,7 @@ @@ -331,6 +331,7 @@
<Compile Include="Src\Services\ParserService\ParserService.cs" />
<Compile Include="Src\Services\ProjectService\CompileModifiedProjectsOnly.cs" />
<Compile Include="Src\Services\ProjectService\SolutionConfigurationEventHandler.cs" />
<Compile Include="Src\Services\RefactoringService\ContextActions\ContextAction.cs" />
<Compile Include="Src\Services\RefactoringService\ContextActions\ContextActionsBulbControl.xaml.cs">
<DependentUpon>ContextActionsBulbControl.xaml</DependentUpon>
<SubType>Code</SubType>

40
src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextAction.cs

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Martin Konicek" email="martin.konicek@gmail.com"/>
// <version>$Revision: $</version>
// </file>
using System;
using System.Collections.Generic;
namespace ICSharpCode.SharpDevelop.Refactoring
{
/// <summary>
/// Base class for implementing custom context actions.
/// </summary>
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<IContextAction> 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; }
}
}
}

10
src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsService.cs

@ -6,8 +6,8 @@ @@ -6,8 +6,8 @@
// </file>
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 @@ -31,7 +31,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring
private ContextActionsService()
{
this.providers = AddInTree.BuildItems<IContextActionsProvider>("/SharpDevelop/ViewContent/AvalonEdit/ContextActionProviders", null, false);
this.providers = AddInTree.BuildItems<IContextActionsProvider>("/SharpDevelop/ViewContent/AvalonEdit/ContextActions", null, false);
}
/// <summary>
@ -43,13 +43,19 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -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));
}
}
}

41
src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/EditorContext.cs

@ -66,12 +66,8 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -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 @@ -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<TNode>() where TNode : class, INode

1
src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextAction.cs

@ -15,6 +15,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -15,6 +15,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring
public interface IContextAction
{
string Title { get; }
//string Id { get; }
void Execute();
}
}

2
src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/IContextActionsProvider.cs

@ -19,6 +19,6 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -19,6 +19,6 @@ namespace ICSharpCode.SharpDevelop.Refactoring
/// <summary>
/// Gets actions available for current line of the editor.
/// </summary>
IEnumerable<IContextAction> GetAvailableActions(EditorContext editorAST);
IEnumerable<IContextAction> GetAvailableActions(EditorContext context);
}
}

1
src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/CtrlSpaceResolveHelper.cs

@ -213,6 +213,7 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -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) {

Loading…
Cancel
Save