From 3eb705c21f5faef80afceb5737a5c60baf704744 Mon Sep 17 00:00:00 2001 From: mkonicek Date: Thu, 9 Dec 2010 16:08:31 +0100 Subject: [PATCH] Extend selection - refactoring. --- .../AvalonEdit.AddIn/AvalonEdit.AddIn.csproj | 2 +- .../AvalonEdit.AddIn/Src/CodeEditorView.cs | 2 +- ...ExtendSelection.cs => CodeManipulation.cs} | 120 ++++++++++++++---- 3 files changed, 97 insertions(+), 27 deletions(-) rename src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/{ExtendSelection.cs => CodeManipulation.cs} (55%) diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj index 7c07e15741..57b8aa69a1 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj @@ -94,7 +94,7 @@ - + diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs index 76b4e28da1..6b6ef4dccd 100755 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs @@ -393,7 +393,7 @@ namespace ICSharpCode.AvalonEdit.AddIn base.OnKeyUp(e); if (e.Handled) return; if (e.Key == Key.W && Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) { - ExtendSelection.Run(this.Adapter); + CodeManipulation.ExtendSelection(this.Adapter); } } #endregion diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/ExtendSelection.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeManipulation.cs similarity index 55% rename from src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/ExtendSelection.cs rename to src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeManipulation.cs index 8d98e315bd..631e722ef0 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/ExtendSelection.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeManipulation.cs @@ -1,10 +1,12 @@ // 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.IO; using System.Linq; using ICSharpCode.NRefactory; +using ICSharpCode.NRefactory.Ast; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Refactoring; @@ -15,36 +17,52 @@ namespace ICSharpCode.AvalonEdit.AddIn /// /// Description of ExtendSelection. /// - public class ExtendSelection + public class CodeManipulation { - public static void Run(ITextEditor editor) + enum MoveStatementDirection { Up, Down }; + + public static void MoveStatementUp(ITextEditor editor) { - var editorLang = EditorContext.GetEditorLanguage(editor); - if (editorLang == null) return; - var parser = ParserFactory.CreateParser(editorLang.Value, editor.Document.CreateReader()); - if (parser == null) return; - parser.ParseMethodBodies = true; - parser.Lexer.SkipAllComments = false; - parser.Parse(); - var parsedCU = parser.CompilationUnit; - if (parsedCU == null) return; + MoveStatement(editor, MoveStatementDirection.Up); + } + + public static void MoveStatementDown(ITextEditor editor) + { + MoveStatement(editor, MoveStatementDirection.Down); + } + + static void MoveStatement(ITextEditor editor, MoveStatementDirection direction) + { + // Find the Statement or Definition containing caret -> Extend selection to Statement or Definition + + // Take its sibling + // Swap them + } + + public static void ExtendSelection(ITextEditor editor) + { + ExtendSelection(editor, new Type[] { typeof(Statement) }); // any node type + } + + // could work to extend selection to set of adjacent statements - e.g. select 3 lines + static void ExtendSelection(ITextEditor editor, Type[] interestingNodeTypes) + { + IList commentsBlankLines; + var parsedCU = ParseDocument(editor, out commentsBlankLines); + var selectionStart = editor.Document.OffsetToPosition(editor.SelectionStart); var selectionEnd = editor.Document.OffsetToPosition(editor.SelectionStart + editor.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; // whole node already selected -> expand selection if (currentNode.StartLocation == selectionStart && currentNode.EndLocation == selectionEnd) { - var commentsBlankLines = parser.Lexer.SpecialTracker.CurrentSpecials; // if there is a comment block immediately before selection, or behind selection on the same line, add it to selection var comments = commentsBlankLines.Where(s => s is Comment).Cast().ToList(); - int commentIndex = comments.FindIndex(c => c.EndPosition.Line == currentNode.StartLocation.Line); + int commentIndex = comments.FindIndex(c => c.EndPosition.Line == selectionStart.Line); if (commentIndex >= 0 && IsWhitespaceBetween(editor.Document, comments[commentIndex].EndPosition, selectionStart)) { while (commentIndex >= 0 && comments[commentIndex].EndPosition.Line == selectionStart.Line) { @@ -56,20 +74,27 @@ namespace ICSharpCode.AvalonEdit.AddIn } } else { - // if the extended selection would contain blank lines, extend the selection only to the blank lines/comments on both sides (use siblings) - // if the selection contains blank lines or comments on both sides, dont do this - - - var parent = currentNode.Parent; + var parent = GetInterestingParent(currentNode, interestingNodeTypes); // it can happen that parent region exactly matches child region - in this case we need to advance even to the next parent // bc otherwise the selection would never move while (parent != null && parent.StartLocation == selectionStart && parent.EndLocation == selectionEnd) { - parent = parent.Parent; + parent = GetInterestingParent(parent, interestingNodeTypes); } if (parent == null) return; - selectionStart = parent.StartLocation; - selectionEnd = parent.EndLocation; + var extendedSelectionStart = parent.StartLocation; + var extendedLocationEnd = parent.EndLocation; + + // if the extended selection would contain blank lines, extend the selection only to the blank lines/comments on both sides (use siblings) + // if the selection contains blank lines or comments on both sides, dont do this + var blankLines = commentsBlankLines.Where(s => s is BlankLine).Cast().ToList(); + //if (SelectionContainsBlankLines(extendedSelectionStart, extendedLocationEnd, blankLines)) { + if (false) { // implement later + + } else { + selectionStart = extendedSelectionStart; + selectionEnd = extendedLocationEnd; + } } } else { // select current node @@ -86,11 +111,56 @@ namespace ICSharpCode.AvalonEdit.AddIn editor.Select(startOffset, endOffset - startOffset); } + /// + /// Searches for parent of interesting type. Skips uninteresting parents. + /// + static INode GetInterestingParent(INode node, Type[] interestingNodeTypes) + { + var parent = node.Parent; + while(parent != null) { + if (IsNodeTypeInteresting(parent.GetType(), interestingNodeTypes)) + return parent; + parent = parent.Parent; + } + return null; + } + + static bool IsNodeTypeInteresting(Type nodeType, Type[] interestingNodeTypes) + { + return interestingNodeTypes.Any(interestingType => interestingType.IsAssignableFrom(nodeType) || (nodeType == interestingType)); + } + + static bool SelectionContainsBlankLines(Location selectionStart, Location selectionEnd, List blankLines) + { + return blankLines.Exists(b => b.StartPosition >= selectionStart && b.EndPosition <= selectionEnd); + } + static bool IsWhitespaceBetween(IDocument document, Location startPos, Location endPos) { int startOffset = document.PositionToOffset(startPos); int endOffset = document.PositionToOffset(endPos); return string.IsNullOrWhiteSpace(document.GetText(startOffset, endOffset - startOffset)); } + + // could depend just on IDocument + static CompilationUnit ParseDocument(ITextEditor editor, out IList parsedSpecials) + { + parsedSpecials = null; + var editorLang = EditorContext.GetEditorLanguage(editor); + if (editorLang == null) return null; + var parser = ParserFactory.CreateParser(editorLang.Value, editor.Document.CreateReader()); + if (parser == null) return null; + parser.ParseMethodBodies = true; + parser.Lexer.SkipAllComments = false; + parser.Parse(); + var parsedCU = parser.CompilationUnit; + if (parsedCU == null) return null; + foreach (var node in parsedCU.Children) { + // fix StartLocation / EndLocation + node.AcceptVisitor(new ICSharpCode.NRefactory.Visitors.SetRegionInclusionVisitor(), null); + } + parsedSpecials = parser.Lexer.SpecialTracker.CurrentSpecials; + return parsedCU; + } } }