From d724328441fb6a4d8e9ec27d92310576a4a878d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kon=C3=AD=C4=8Dek?= Date: Wed, 4 Aug 2010 04:05:18 +0000 Subject: [PATCH] Rewrote EditorContext so that it will be more usable for implementing Context actions. It now provides AST Node at caret and its parents. Next will be to move Context actions to background thread. Found 2 bugs in IntroduceMethod (overloads and type arguments are ignored), will fix them. Fixed NullReferenceException in NRefactoryInsightWindowHandler.HighlightParameter (when typing "," after last method parameter). git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@6368 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Project/SharpRefactoring.csproj | 3 +- .../Project/Src/ContextActions/AddUsing.cs | 8 +- ...itor.cs => FindTypeDeclarationsVisitor.cs} | 0 .../Src/ContextActions/GenerateMember.cs | 18 +- .../Project/Src/Extensions.cs | 12 + .../Project/Src/GenerateCode.cs | 86 ++---- .../Src/MenuItemFactories/IntroduceMethod.cs | 49 ---- .../NRefactoryInsightWindowHandler.cs | 2 + .../Services/ParserService/ParserService.cs | 20 +- .../ContextActions/ContextActionsService.cs | 2 +- .../ContextActions/EditorContext.cs | 247 +++++++++++++----- .../Base/Project/Src/Util/ExtensionMethods.cs | 44 ++++ .../Src/Interfaces/ICompilationUnit.cs | 2 +- .../NRefactoryResolver/NRefactoryResolver.cs | 2 +- 14 files changed, 297 insertions(+), 198 deletions(-) rename src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/{FindNodesVisitor.cs => FindTypeDeclarationsVisitor.cs} (100%) delete mode 100644 src/AddIns/Misc/SharpRefactoring/Project/Src/MenuItemFactories/IntroduceMethod.cs diff --git a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj index 4b76a365b2..807c8e6638 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj +++ b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj @@ -73,7 +73,7 @@ - + @@ -108,7 +108,6 @@ - diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/AddUsing.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/AddUsing.cs index ac7653f0d5..dddc29481b 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/AddUsing.cs +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/AddUsing.cs @@ -18,13 +18,13 @@ namespace SharpRefactoring.ContextActions /// public class AddUsingProvider : IContextActionsProvider { - public IEnumerable GetAvailableActions(EditorContext editorAST) + public IEnumerable GetAvailableActions(EditorContext context) { - var currentLineAST = editorAST.CurrentLineAST; + var currentLineAST = context.CurrentLineAST; if (currentLineAST == null) yield break; - var symbol = editorAST.SymbolUnderCaret; - foreach (var contextAction in GetAddUsingContextActions(symbol, editorAST.Editor)) { + var symbol = context.CurrentSymbol; + foreach (var contextAction in GetAddUsingContextActions(symbol, context.Editor)) { yield return contextAction; } } diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/FindNodesVisitor.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/FindTypeDeclarationsVisitor.cs similarity index 100% rename from src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/FindNodesVisitor.cs rename to src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/FindTypeDeclarationsVisitor.cs diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/GenerateMember.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/GenerateMember.cs index 721c2f2ade..4a5a594c48 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/GenerateMember.cs +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/ContextActions/GenerateMember.cs @@ -6,8 +6,9 @@ // using System; using System.Collections.Generic; +using System.Windows; +using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Editor; -using ICSharpCode.SharpDevelop.Editor.AvalonEdit; using ICSharpCode.SharpDevelop.Refactoring; namespace SharpRefactoring.ContextActions @@ -19,7 +20,20 @@ namespace SharpRefactoring.ContextActions { public IEnumerable GetAvailableActions(EditorContext editorContext) { - var generateCodeAction = GenerateCode.GetContextAction(editorContext.SymbolUnderCaret, editorContext.Editor); + if (string.IsNullOrEmpty(editorContext.CurrentExpression.Expression)) { + yield break; + } + if (editorContext.CurrentExpression.Region != null && + editorContext.CurrentExpression.Region.EndLine > editorContext.CurrentExpression.Region.BeginLine) { + // do not yield the action for 2-line expressions like this, which are actually 2 different expressions + // variable.(*caret*) + // CallFooMethod(); + // this check is not correct for this case because it does not yield the action when it should: + // variable.Foo((*caret*) + // 123); + yield break; + } + var generateCodeAction = GenerateCode.GetContextAction(editorContext.CurrentSymbol, editorContext); if (generateCodeAction != null) yield return generateCodeAction; } diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/Extensions.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/Extensions.cs index bcbc829a98..715a81a7ad 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/Src/Extensions.cs +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/Extensions.cs @@ -47,5 +47,17 @@ namespace SharpRefactoring resolver.Initialize(ParserService.GetParseInformation(context.FileName), context.Caret.Line, context.Caret.Column); return resolver; } + + public static bool IsUserCode(this IReturnType rt) + { + if (rt == null) + return false; + return IsUserCode(rt.GetUnderlyingClass()); + } + + public static bool IsUserCode(this IClass c) + { + return (c != null && !c.BodyRegion.IsEmpty); + } } } diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/GenerateCode.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/GenerateCode.cs index eb247b6868..964bbf2954 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/Src/GenerateCode.cs +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/GenerateCode.cs @@ -30,12 +30,11 @@ namespace SharpRefactoring /// /// If given symbol is Unknown ResolveResult, returns action that can generate code for this missing symbol. /// - public static GenerateCodeContextAction GetContextAction(ResolveResult symbol, ITextEditor editor) + public static GenerateCodeContextAction GetContextAction(ResolveResult symbol, EditorContext context) { if (symbol is UnknownMethodResolveResult) { - - UnknownMethodResolveResult unknownMethodCall = symbol as UnknownMethodResolveResult; - Ast.Expression expression = GetExpressionInContext(unknownMethodCall, editor); + UnknownMethodResolveResult unknownMethodCall = (UnknownMethodResolveResult)symbol; + Ast.Expression expression = context.GetContainingElement(); if (expression == null) return null; @@ -45,58 +44,15 @@ namespace SharpRefactoring } catch (FormatException) { } - return new IntroduceMethodContextAction(unknownMethodCall, expression, editor) { - Title = title - }; + if (unknownMethodCall.Target.IsUserCode()) { + // Don't introduce method on non-modyfiable types + return new IntroduceMethodContextAction(unknownMethodCall, expression, context.Editor) { + Title = title + }; + } } return null; } - - internal static Ast.Expression GetExpressionInContext(UnknownMethodResolveResult rr, ITextEditor editor) - { - if (rr.Target == null || rr.Target.GetUnderlyingClass() == null) - return null; - - NRefactoryResolver resolver = Extensions.CreateResolverForContext(rr.CallingClass.ProjectContent.Language, editor); - Ast.INode node = resolver.ParseCurrentMember(editor.Document.Text); - - if (node == null) - return null; - - resolver.RunLookupTableVisitor(node); - InvocationExpressionLookupVisitor visitor = new InvocationExpressionLookupVisitor(editor); - node.AcceptVisitor(visitor, null); - return visitor.Expression; - } - - internal class InvocationExpressionLookupVisitor : AbstractAstVisitor - { - ITextEditor editor; - Ast.InvocationExpression expression; - - public Ast.InvocationExpression Expression { - get { return expression; } - } - - public InvocationExpressionLookupVisitor(ITextEditor editor) - { - this.editor = editor; - this.expression = null; - } - - public override object VisitInvocationExpression(Ast.InvocationExpression invocationExpression, object data) - { - int startOffset = editor.Document.PositionToOffset(invocationExpression.TargetObject.StartLocation.Line, invocationExpression.TargetObject.StartLocation.Column); - int endOffset = editor.Document.PositionToOffset(invocationExpression.EndLocation.Line, invocationExpression.EndLocation.Column); - - int offset = editor.Caret.Offset; - - if (offset >= startOffset && offset <= endOffset) - expression = invocationExpression; - - return base.VisitInvocationExpression(invocationExpression, data); - } - } } public abstract class GenerateCodeContextAction : IContextAction @@ -110,25 +66,27 @@ namespace SharpRefactoring public class IntroduceMethodContextAction : GenerateCodeContextAction { public UnknownMethodResolveResult UnknownMethodCall { get; private set; } - public Ast.Expression Expression { get; private set; } + public Ast.Expression InvocationExpr { get; private set; } public ITextEditor Editor { get; private set; } - public IntroduceMethodContextAction(UnknownMethodResolveResult symbol, Ast.Expression expression, ITextEditor editor) + public IntroduceMethodContextAction(UnknownMethodResolveResult symbol, Ast.Expression invocationExpr, ITextEditor editor) { if (symbol == null) throw new ArgumentNullException("rr"); - if (expression == null) + if (invocationExpr == null) throw new ArgumentNullException("ex"); if (editor == null) throw new ArgumentNullException("editor"); this.UnknownMethodCall = symbol; - this.Expression = expression; + this.InvocationExpr = invocationExpr; this.Editor = editor; } public override void Execute() { IClass targetClass = UnknownMethodCall.Target.GetUnderlyingClass(); + if (targetClass == null) + return; bool isNew = false; object result = null; @@ -141,7 +99,7 @@ namespace SharpRefactoring ?? targetClass; } - if (targetClass.BodyRegion.IsEmpty) { + if (!targetClass.IsUserCode()) { IntroduceMethodDialog dialog = new IntroduceMethodDialog(UnknownMethodCall.CallingClass); dialog.Owner = WorkbenchSingleton.MainWindow; @@ -152,10 +110,10 @@ namespace SharpRefactoring result = dialog.Result; } - ExecuteIntroduceMethod(UnknownMethodCall, Expression, Editor, isNew, result); + ExecuteIntroduceMethod(UnknownMethodCall, InvocationExpr, Editor, isNew, result); } - internal void ExecuteIntroduceMethod(UnknownMethodResolveResult rr, Ast.Expression expression, ITextEditor editor, bool isNew, object result) + internal void ExecuteIntroduceMethod(UnknownMethodResolveResult rr, Ast.Expression invocationExpr, ITextEditor editor, bool isNew, object result) { IClass targetClass = IsEqualClass(rr.CallingClass, rr.Target.GetUnderlyingClass()) ? rr.CallingClass : rr.Target.GetUnderlyingClass(); @@ -167,7 +125,7 @@ namespace SharpRefactoring ModifierEnum modifiers = ModifierEnum.None; - bool isExtension = targetClass.BodyRegion.IsEmpty; + bool isExtension = !targetClass.IsUserCode(); if (IsEqualClass(rr.CallingClass, targetClass)) { if (rr.CallingMember != null) @@ -190,11 +148,11 @@ namespace SharpRefactoring NRefactoryResolver resolver = Extensions.CreateResolverForContext(targetClass.ProjectContent.Language, editor); - IReturnType type = resolver.GetExpectedTypeFromContext(expression); + IReturnType type = resolver.GetExpectedTypeFromContext(invocationExpr); Ast.TypeReference typeRef = CodeGenerator.ConvertType(type, finder); if (typeRef.IsNull) { - if (expression.Parent is Ast.ExpressionStatement) + if (invocationExpr.Parent is Ast.ExpressionStatement) typeRef = new Ast.TypeReference("void", true); else typeRef = new Ast.TypeReference("object", true); @@ -204,7 +162,7 @@ namespace SharpRefactoring Name = rr.CallName, Modifier = CodeGenerator.ConvertModifier(modifiers, finder), TypeReference = typeRef, - Parameters = CreateParameters(rr, finder, expression as Ast.InvocationExpression).ToList(), + Parameters = CreateParameters(rr, finder, invocationExpr as Ast.InvocationExpression).ToList(), }; if (targetClass.ClassType != ClassType.Interface) diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/MenuItemFactories/IntroduceMethod.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/MenuItemFactories/IntroduceMethod.cs deleted file mode 100644 index 3fad0c0e67..0000000000 --- a/src/AddIns/Misc/SharpRefactoring/Project/Src/MenuItemFactories/IntroduceMethod.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// -// -// -// $Revision$ -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Controls; - -using ICSharpCode.Core; -using ICSharpCode.NRefactory.Visitors; -using ICSharpCode.SharpDevelop; -using ICSharpCode.SharpDevelop.Dom; -using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver; -using ICSharpCode.SharpDevelop.Dom.Refactoring; -using ICSharpCode.SharpDevelop.Editor; -using ICSharpCode.SharpDevelop.Gui; -using ICSharpCode.SharpDevelop.Refactoring; -using SharpRefactoring.Gui; -using Ast = ICSharpCode.NRefactory.Ast; - -namespace SharpRefactoring -{ - /// - /// Provides as editor context menu entry. - /// - public class IntroduceMethod : IRefactoringMenuItemFactory - { - public MenuItem Create(RefactoringMenuContext context) - { - if (context.ExpressionResult.Context == ExpressionContext.Attribute) - return null; - - var introduceCodeAction = GenerateCode.GetContextAction(context.ResolveResult, context.Editor); - if (introduceCodeAction == null) - return null; - - var item = new MenuItem() { - Header = introduceCodeAction.Title, - Icon = ClassBrowserIconService.GotoArrow.CreateImage() - }; - item.Click += delegate { introduceCodeAction.Execute(); }; - return item; - } - } -} diff --git a/src/Main/Base/Project/Src/Editor/CodeCompletion/NRefactoryInsightWindowHandler.cs b/src/Main/Base/Project/Src/Editor/CodeCompletion/NRefactoryInsightWindowHandler.cs index 7e35bfb831..09fb70f017 100644 --- a/src/Main/Base/Project/Src/Editor/CodeCompletion/NRefactoryInsightWindowHandler.cs +++ b/src/Main/Base/Project/Src/Editor/CodeCompletion/NRefactoryInsightWindowHandler.cs @@ -282,6 +282,8 @@ namespace ICSharpCode.SharpDevelop.Editor.CodeCompletion public void HighlightParameter(IInsightWindow window, int index) { + if (window == null) + return; var item = window.SelectedItem as MethodInsightItem; if (item != null) diff --git a/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs b/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs index fb2f4c277b..0b4e8e35d8 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs @@ -263,19 +263,23 @@ namespace ICSharpCode.SharpDevelop /// public static ResolveResult Resolve(int caretLine, int caretColumn, IDocument document, string fileName) { - IExpressionFinder expressionFinder = GetExpressionFinder(fileName); - if (expressionFinder == null) - return null; - if (caretColumn > document.GetLine(caretLine).Length) - return null; - string documentText = document.Text; - var expressionResult = expressionFinder.FindFullExpression(documentText, document.PositionToOffset(caretLine, caretColumn)); + var expressionResult = FindFullExpression(caretLine, caretColumn, document, fileName); string expression = (expressionResult.Expression ?? "").Trim(); if (expression.Length > 0) { - return Resolve(expressionResult, caretLine, caretColumn, fileName, documentText); + return Resolve(expressionResult, caretLine, caretColumn, fileName, document.Text); } else return null; } + + public static ExpressionResult FindFullExpression(int caretLine, int caretColumn, IDocument document, string fileName) + { + IExpressionFinder expressionFinder = GetExpressionFinder(fileName); + if (expressionFinder == null) + return ExpressionResult.Empty; + if (caretColumn > document.GetLine(caretLine).Length) + return ExpressionResult.Empty; + return expressionFinder.FindFullExpression(document.Text, document.PositionToOffset(caretLine, caretColumn)); + } public static ResolveResult Resolve(int offset, IDocument document, string fileName) { 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 cf911d370d..e04b6c0b70 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsService.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsService.cs @@ -35,7 +35,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring } /// - /// Gets actions available for current line of the editor. + /// Gets actions available for current caret position in the editor. /// public IEnumerable GetAvailableActions(ITextEditor editor) { 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 d9ca3286a1..272779af0a 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/EditorContext.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/EditorContext.cs @@ -5,8 +5,11 @@ // $Revision: $ // using System; +using System.Diagnostics; +using System.Linq; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.Ast; +using ICSharpCode.NRefactory.Visitors; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver; using ICSharpCode.SharpDevelop.Editor; @@ -21,7 +24,32 @@ namespace ICSharpCode.SharpDevelop.Refactoring public class EditorContext { public ITextEditor Editor { get; private set; } - SnippetParser SnippetParser { get; set; } + int CaretLine { get; set; } + int CaretColumn { get; set; } + + /// + /// Language independent. + /// + public ExpressionResult CurrentExpression { get; private set; } + /// + /// Language independent. + /// + public ResolveResult CurrentSymbol { get; private set; } + + public IDocumentLine CurrentLine { get; private set; } + /// + /// Only available for C# and VB. + /// + public INode CurrentLineAST { get; private set; } + /// + /// Only available for C# and VB. + /// + public INode CurrentMemberAST { get; private set; } + /// + /// Only available for C# and VB. + /// + public INode CurrentElement { get; private set; } + NRefactoryResolver Resolver { get; set; } public EditorContext(ITextEditor editor) @@ -29,81 +57,155 @@ namespace ICSharpCode.SharpDevelop.Refactoring if (editor == null) throw new ArgumentNullException("editor"); this.Editor = editor; - this.SnippetParser = GetSnippetParser(editor); - this.Resolver = GetResolver(editor); + this.CaretLine = editor.Caret.Line; + this.CaretColumn = editor.Caret.Column; + if (CaretColumn > 1 && editor.Document.GetText(editor.Document.PositionToOffset(CaretLine, CaretColumn - 1), 1) == ";") { + // If caret is just after ';', pretend that caret is before ';' + // (works well e.g. for this.Foo();(*caret*) - we want to get "this.Foo()") + // This is equivalent to pretending that ; don't exist, and actually is not such a bad idea. + 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); + + this.CurrentMemberAST = GetCurrentMemberAST(editor); + + 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)); } - // TODO make all reference types cached ResolveResult? - implement own Nullable - ResolveResult symbolUnderCaret; - public ResolveResult SymbolUnderCaret + public TNode GetCurrentElement() where TNode : class, INode { - get - { - if (symbolUnderCaret != null) - return symbolUnderCaret; - // workaround so that Resolve works when the caret is placed also at the end of the word - symbolUnderCaret = ParserService.Resolve(Editor.Caret.Line, Editor.Caret.Column > 1 ? Editor.Caret.Column - 1 : 1, Editor.Document, Editor.FileName); - if (symbolUnderCaret == null) - symbolUnderCaret = ParserService.Resolve(Editor.Caret.Line, Editor.Caret.Column, Editor.Document, Editor.FileName); - return symbolUnderCaret; - } + if (this.CurrentElement is TNode) + return (TNode)this.CurrentElement; + return null; } - IDocumentLine currentLine; - public IDocumentLine CurrentLine + public TNode GetContainingElement() where TNode : class, INode { - get + var node = this.CurrentElement; + while(node != null) { - if (currentLine != null) - return currentLine; - try - { - return (currentLine = Editor.Document.GetLine(Editor.Caret.Line)); - } - catch - { - return null; - } + if (node is TNode) + return (TNode)node; + node = node.Parent; } + return null; } - INode currentLineAST; - public INode CurrentLineAST +// public TNode GetInnerElement() where TNode : class, INode +// { +// var findChildVisitor = new FindOutermostNodeVisitor(); +// this.CurrentElement.AcceptVisitor(findChildVisitor, null); +// return findChildVisitor.FoundNode; +// } + + INode FindInnermostNodeAtLocation(INode memberDecl, Location position) { - get - { - if (currentLineAST != null) - return currentLineAST; - if (this.SnippetParser == null || this.CurrentLine == null) - return null; - try { - return (currentLineAST = SnippetParser.Parse(this.CurrentLine.Text)); + if (memberDecl == null) + return null; + if (memberDecl is MethodDeclaration) { + return FindInnermostNodeInBlock(((MethodDeclaration)memberDecl).Body, position); + } else if (memberDecl is PropertyDeclaration) { + var propertyDecl = (PropertyDeclaration)memberDecl; + if (propertyDecl.HasGetRegion && position >= propertyDecl.GetRegion.StartLocation && position <= propertyDecl.GetRegion.EndLocation) { + return FindInnermostNodeInBlock(propertyDecl.GetRegion.Block, position); } - catch { - return null; + if (propertyDecl.HasSetRegion && position >= propertyDecl.SetRegion.StartLocation && position <= propertyDecl.SetRegion.EndLocation) { + return FindInnermostNodeInBlock(propertyDecl.SetRegion.Block, position); } } + return null; } - INode currentMemberAST; - public INode CurrentMemberAST + INode FindInnermostNodeInBlock(BlockStatement node, Location position) { - get + if (node == null) + return null; + var findInnermostVisitor = new FindInnermostNodeVisitor(position); + node.AcceptVisitor(findInnermostVisitor, null); + return findInnermostVisitor.InnermostNode; + } + + class FindInnermostNodeVisitor : NodeTrackingAstVisitor + { + public Location CaretLocation { get; private set; } + public INode InnermostNode { get; private set; } + + public FindInnermostNodeVisitor(Location caretLocation) { - if (Resolver == null) - return null; - if (currentMemberAST != null) - return currentMemberAST; - try { - Resolver.Initialize(ParserService.GetParseInformation(Editor.FileName), Editor.Caret.Line, Editor.Caret.Column); - return (currentMemberAST = Resolver.ParseCurrentMember(Editor.Document.Text)); - } - catch { - return null; + this.CaretLocation = caretLocation; + } + + protected override void BeginVisit(INode node) + { + if (node.StartLocation <= CaretLocation && node.EndLocation >= CaretLocation) { + // the node visited last will be the innermost + this.InnermostNode = node; } + base.BeginVisit(node); } } + +// 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) { +// FoundNode = (TNode)node; +// } +// base.BeginVisit(node); +// } +// } + ResolveResult ResolveExpression(ITextEditor editor) + { + return ParserService.Resolve(this.CurrentExpression, CaretLine, CaretColumn, editor.FileName, editor.Document.Text); + } + + ExpressionResult GetExpressionAtCaret(ITextEditor editor) + { + ExpressionResult expr = ParserService.FindFullExpression(CaretLine, CaretColumn, editor.Document, editor.FileName); + // if no expression, look one character back (works better with method calls - Foo()(*caret*)) + if (string.IsNullOrWhiteSpace(expr.Expression) && CaretColumn > 1) + expr = ParserService.FindFullExpression(CaretLine, CaretColumn - 1, editor.Document, editor.FileName); + return expr; + } + + + INode GetCurrentLineAst(IDocumentLine currentLine, ITextEditor editor) + { + if (currentLine == null) + return null; + var snippetParser = GetSnippetParser(editor); + return snippetParser.Parse(currentLine.Text); + } + SnippetParser GetSnippetParser(ITextEditor editor) { var lang = GetEditorLanguage(editor); @@ -113,29 +215,42 @@ namespace ICSharpCode.SharpDevelop.Refactoring return null; } - NRefactoryResolver GetResolver(ITextEditor editor) + SupportedLanguage? GetEditorLanguage(ITextEditor editor) { if (editor == null || editor.Language == null) return null; - try - { - return new NRefactoryResolver(editor.Language.Properties); + if (editor.Language.Properties == LanguageProperties.CSharp) + return SupportedLanguage.CSharp; + if (editor.Language.Properties == LanguageProperties.VBNet) + return SupportedLanguage.VBNet; + return null; + } + + + INode GetCurrentMemberAST(ITextEditor editor) + { + try { + var resolver = GetNRefactoryResolver(editor); + resolver.Initialize(ParserService.GetParseInformation(editor.FileName), CaretLine, CaretColumn); + return resolver.ParseCurrentMember(editor.Document.Text); } - catch(NotSupportedException) - { + catch { return null; } } - SupportedLanguage? GetEditorLanguage(ITextEditor editor) + NRefactoryResolver GetNRefactoryResolver(ITextEditor editor) { if (editor == null || editor.Language == null) return null; - if (editor.Language.Properties == LanguageProperties.CSharp) - return SupportedLanguage.CSharp; - if (editor.Language.Properties == LanguageProperties.VBNet) - return SupportedLanguage.VBNet; - return null; + try + { + return new NRefactoryResolver(editor.Language.Properties); + } + catch(NotSupportedException) + { + return null; + } } } } diff --git a/src/Main/Base/Project/Src/Util/ExtensionMethods.cs b/src/Main/Base/Project/Src/Util/ExtensionMethods.cs index 51820afcd2..d84d521232 100644 --- a/src/Main/Base/Project/Src/Util/ExtensionMethods.cs +++ b/src/Main/Base/Project/Src/Util/ExtensionMethods.cs @@ -18,6 +18,8 @@ using System.Windows.Media; using System.Windows.Documents; using System.Windows.Forms; using ICSharpCode.Core.Presentation; +using ICSharpCode.SharpDevelop.Dom; +using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Gui; using WinForms = System.Windows.Forms; @@ -348,6 +350,29 @@ namespace ICSharpCode.SharpDevelop return s.Substring(0, s.Length - stringToRemove.Length); } + /// + /// Takes at most first characters from string. + /// String can be null. + /// + public static string TakeStart(this string s, int length) + { + if (string.IsNullOrEmpty(s) || length >= s.Length) + return s; + return s.Substring(0, length); + } + + /// + /// Takes at most first characters from string, and appends '...' if string is longer. + /// String can be null. + /// + public static string TakeStartEllipsis(this string s, int length) + { + if (string.IsNullOrEmpty(s) || length >= s.Length) + return s; + return s.Substring(0, length) + "..."; + } + + public static string Replace(this string original, string pattern, string replacement, StringComparison comparisonType) { if (original == null) @@ -458,5 +483,24 @@ namespace ICSharpCode.SharpDevelop if (itemToAdd != null) list.Add(itemToAdd); } + + public static ExpressionResult FindFullExpressionAtCaret(this ITextEditor editor) + { + if (editor == null) + throw new ArgumentNullException("editor"); + return ParserService.FindFullExpression(editor.Caret.Line, editor.Caret.Column, editor.Document, editor.FileName); + } + + public static ResolveResult ResolveSymbolAtCaret(this ITextEditor editor) + { + if (editor == null) + throw new ArgumentNullException("editor"); + return ParserService.Resolve(editor.Caret.Line, editor.Caret.Column, editor.Document, editor.FileName); + } + + public static bool IsEmpty(ExpressionResult expr) + { + return expr.Region.IsEmpty; + } } } diff --git a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Interfaces/ICompilationUnit.cs b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Interfaces/ICompilationUnit.cs index fd15dbc439..d7bc7210f8 100644 --- a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Interfaces/ICompilationUnit.cs +++ b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Interfaces/ICompilationUnit.cs @@ -70,7 +70,7 @@ namespace ICSharpCode.SharpDevelop.Dom } /// - /// Returns the innerst class in which the carret currently is, returns null + /// Returns the innermost class in which the carret currently is, returns null /// if the carret is outside any class boundaries. /// IClass GetInnermostClass(int caretLine, int caretColumn); diff --git a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/NRefactoryResolver.cs b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/NRefactoryResolver.cs index c6b17b5101..786be4b700 100644 --- a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/NRefactoryResolver.cs +++ b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/NRefactoryResolver.cs @@ -114,7 +114,7 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver language = NR.SupportedLanguage.VBNet; inferAllowed = false; } else { - throw new NotSupportedException("The language " + languageProperties.ToString() + " is not supported in the resolver"); + throw new NotSupportedException("The language " + languageProperties.ToString() + " is not supported in the NRefactoryResolver"); } }