diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs index fcbc9bd162..f6d4b3685c 100755 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs @@ -17,17 +17,19 @@ using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Documents; using System.Windows.Input; - using ICSharpCode.AvalonEdit.AddIn.Options; using ICSharpCode.AvalonEdit.AddIn.Snippets; using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Rendering; +using ICSharpCode.NRefactory; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Editor.AvalonEdit; using ICSharpCode.SharpDevelop.Editor.Commands; +using ICSharpCode.SharpDevelop.Refactoring; +using Ast = ICSharpCode.NRefactory.Ast; namespace ICSharpCode.AvalonEdit.AddIn { @@ -359,9 +361,11 @@ namespace ICSharpCode.AvalonEdit.AddIn void TextViewMouseDown(object sender, MouseButtonEventArgs e) { - // close existing popup immediately on text editor mouse down + // close existing debugger popup immediately on text editor mouse down TryCloseExistingPopup(false); + if (options.CtrlClickGoToDefinition && e.ChangedButton == MouseButton.Left && Keyboard.Modifiers == ModifierKeys.Control) { + // Ctrl+Click Go to definition var position = GetPositionFromPoint(e.GetPosition(this)); if (position == null) return; @@ -372,6 +376,45 @@ namespace ICSharpCode.AvalonEdit.AddIn } #endregion + protected override void OnKeyUp(KeyEventArgs e) + { + base.OnKeyUp(e); + if (e.Handled) return; + if (e.Key == Key.W && Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) { + // Select AST Node + var editorLang = EditorContext.GetEditorLanguage(this.Adapter); if (editorLang == null) return; + var parser = ParserFactory.CreateParser(editorLang.Value, new StringReader(this.Text)); + parser.ParseMethodBodies = true; + parser.Parse(); + var parsedCU = parser.CompilationUnit; if (parsedCU == null) return; + //var caretLocation = new Location(this.Adapter.Caret.Column, this.Adapter.Caret.Line); + var selectionStart = this.Adapter.Document.OffsetToPosition(this.SelectionStart); + var selectionEnd = this.Adapter.Document.OffsetToPosition(this.SelectionStart + this.SelectionLength); + foreach (var node in parsedCU.Children) { + // fix StartLocation / EndLocation + node.AcceptVisitor(new ICSharpCode.NRefactory.Visitors.SetRegionInclusionVisitor(), null); + } + Ast.INode currentNode = parsedCU.Children.Select( + n => EditorContext.FindInnermostNodeContainingSelection(n, selectionStart, selectionEnd)).Where(n => n != null).FirstOrDefault(); + if (currentNode == null) return; + + if (currentNode.StartLocation == selectionStart && currentNode.EndLocation == selectionEnd) { + // if whole node already selected, expand selection to parent + currentNode = currentNode.Parent; + if (currentNode == null) + return; + } + int startOffset, endOffset; + try { + startOffset = this.Adapter.Document.PositionToOffset(currentNode.StartLocation.Line, currentNode.StartLocation.Column); + endOffset = this.Adapter.Document.PositionToOffset(currentNode.EndLocation.Line, currentNode.EndLocation.Column); + } catch(ArgumentOutOfRangeException) { + return; + } + this.Select(startOffset, endOffset - startOffset); + } + } + public void JumpTo(int line, int column) { // closes Debugger popup on debugger step diff --git a/src/Libraries/NRefactory/Project/NRefactory.csproj b/src/Libraries/NRefactory/Project/NRefactory.csproj index 9e9585c33b..50ec66ad40 100644 --- a/src/Libraries/NRefactory/Project/NRefactory.csproj +++ b/src/Libraries/NRefactory/Project/NRefactory.csproj @@ -128,6 +128,7 @@ + diff --git a/src/Libraries/NRefactory/Project/Src/Visitors/SetRegionInclusionVisitor.cs b/src/Libraries/NRefactory/Project/Src/Visitors/SetRegionInclusionVisitor.cs new file mode 100644 index 0000000000..9721264467 --- /dev/null +++ b/src/Libraries/NRefactory/Project/Src/Visitors/SetRegionInclusionVisitor.cs @@ -0,0 +1,66 @@ +// +// +// +// +// $Revision: $ +// +using System; +using System.Collections.Generic; +using ICSharpCode.NRefactory.Ast; + +namespace ICSharpCode.NRefactory.Visitors +{ + /// + /// Sets StartLocation and EndLocation (region) of every node to union of regions of its children. + /// Parsers don't do this by default, + /// e.g. "a.Foo()" is InvocationExpression, its region includes only the "()" and it has a child MemberReferenceExpression, with region ".Foo". + /// + public class SetRegionInclusionVisitor : NodeTrackingAstVisitor + { + Stack parentNodes = new Stack(); + + public SetRegionInclusionVisitor() + { + parentNodes.Push(null); + } + + protected override void BeginVisit(INode node) + { + base.BeginVisit(node); + + // Only push nodes on the stack which have valid position information. + if (node != null && + node.StartLocation.X >= 1 && node.StartLocation.Y >= 1 && + node.EndLocation.X >= 1 && node.EndLocation.Y >= 1) { + + if (node is PropertyDeclaration) { + // PropertyDeclaration has correctly set BodyStart and BodyEnd by the parser, + // but it has no subnode "body", just 2 children GetRegion and SetRegion which don't cover + // the whole (BodyStart, BodyEnd) region => We have to handle PropertyDeclaration as a special case. + node.EndLocation = ((PropertyDeclaration)node).BodyEnd; + } + + this.parentNodes.Push(node); + } + } + + protected override void EndVisit(INode node) + { + base.EndVisit(node); + + // Only remove those nodes which have actually been pushed before. + if (this.parentNodes.Count > 0 && INode.ReferenceEquals(this.parentNodes.Peek(), node)) { + // remove this node + this.parentNodes.Pop(); + // fix region of parent + var parent = this.parentNodes.Peek(); + if (parent == null) + return; + if (node.StartLocation < parent.StartLocation) + parent.StartLocation = node.StartLocation; + if (node.EndLocation > parent.EndLocation) + parent.EndLocation = node.EndLocation; + } + } + } +} 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 6583227a12..3e96406139 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/EditorContext.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/EditorContext.cs @@ -62,7 +62,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring 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. + // This is equivalent to pretending that ; don't exist, and actually it's not such a bad idea. CaretColumn -= 1; } @@ -76,7 +76,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring this.CurrentElement = FindInnermostNodeAtLocation(this.CurrentMemberAST, new Location(CaretColumn, CaretLine)); - //DebugLog(); +// DebugLog(); } void DebugLog() @@ -131,41 +131,56 @@ namespace ICSharpCode.SharpDevelop.Refactoring if (memberDecl == null) return null; if (memberDecl is MethodDeclaration) { - return FindInnermostNodeInBlock(((MethodDeclaration)memberDecl).Body, position); + return FindInnermostNode(((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); + return FindInnermostNode(propertyDecl.GetRegion.Block, position); } if (propertyDecl.HasSetRegion && position >= propertyDecl.SetRegion.StartLocation && position <= propertyDecl.SetRegion.EndLocation) { - return FindInnermostNodeInBlock(propertyDecl.SetRegion.Block, position); + return FindInnermostNode(propertyDecl.SetRegion.Block, position); } } return null; } - INode FindInnermostNodeInBlock(BlockStatement node, Location position) + public static INode FindInnermostNode(INode node, Location position) { if (node == null) return null; - var findInnermostVisitor = new FindInnermostNodeVisitor(position); + var findInnermostVisitor = new FindInnermostNodeByRangeVisitor(position); node.AcceptVisitor(findInnermostVisitor, null); return findInnermostVisitor.InnermostNode; } - class FindInnermostNodeVisitor : NodeTrackingAstVisitor + public static INode FindInnermostNodeContainingSelection(INode node, Location start, Location end) { - public Location CaretLocation { get; private set; } + if (node == null) + return null; + var findInnermostVisitor = new FindInnermostNodeByRangeVisitor(start, end); + node.AcceptVisitor(findInnermostVisitor, null); + return findInnermostVisitor.InnermostNode; + } + + class FindInnermostNodeByRangeVisitor : NodeTrackingAstVisitor + { + public Location RangeStart { get; private set; } + public Location RangeEnd { get; private set; } public INode InnermostNode { get; private set; } - public FindInnermostNodeVisitor(Location caretLocation) + public FindInnermostNodeByRangeVisitor(Location caretPosition) : this(caretPosition, caretPosition) + { + } + + public FindInnermostNodeByRangeVisitor(Location selectionStart, Location selectionEnd) { - this.CaretLocation = caretLocation; + this.RangeStart = selectionStart; + this.RangeEnd = selectionEnd; } protected override void BeginVisit(INode node) { - if (node.StartLocation <= CaretLocation && node.EndLocation >= CaretLocation) { + if (node.StartLocation <= RangeStart && node.EndLocation >= RangeEnd) { // the node visited last will be the innermost this.InnermostNode = node; } @@ -218,7 +233,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring return null; } - SupportedLanguage? GetEditorLanguage(ITextEditor editor) + public static SupportedLanguage? GetEditorLanguage(ITextEditor editor) { if (editor == null || editor.Language == null) return null; @@ -233,8 +248,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring INode GetCurrentMemberAST(ITextEditor editor) { try { - var resolver = GetNRefactoryResolver(editor); - resolver.Initialize(ParserService.GetParseInformation(editor.FileName), CaretLine, CaretColumn); + var resolver = GetInitializedNRefactoryResolver(editor, this.CaretLine, this.CaretColumn); return resolver.ParseCurrentMember(editor.Document.Text); } catch { @@ -242,13 +256,15 @@ namespace ICSharpCode.SharpDevelop.Refactoring } } - NRefactoryResolver GetNRefactoryResolver(ITextEditor editor) + NRefactoryResolver GetInitializedNRefactoryResolver(ITextEditor editor, int caretLine, int caretColumn) { if (editor == null || editor.Language == null) return null; try { - return new NRefactoryResolver(editor.Language.Properties); + var resolver = new NRefactoryResolver(editor.Language.Properties); + resolver.Initialize(ParserService.GetParseInformation(editor.FileName), caretLine, caretColumn); + return resolver; } catch(NotSupportedException) {