From 6e9190b6a95186fa9cbad02db3ef38bd0dcb672a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 1 Apr 2012 14:09:09 +0200 Subject: [PATCH] Add InsertWithCursor implementation. --- .../CSharpBinding/Project/CSharpBinding.addin | 4 +- .../Project/CSharpBinding.csproj | 1 + .../Project/Src/Refactoring/InsertionPoint.cs | 237 ++++++++++++++++++ .../Src/Refactoring/SDRefactoringContext.cs | 2 +- .../Project/Src/Refactoring/SDScript.cs | 32 ++- .../Services/RefactoringService/TypeName.cs | 10 +- 6 files changed, 278 insertions(+), 8 deletions(-) create mode 100644 src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/InsertionPoint.cs diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin index 2061021bd5..1560138150 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin @@ -173,10 +173,10 @@ class = "ICSharpCode.NRefactory.CSharp.Refactoring.CreateDelegateAction" /> - + + IssueOptions.xaml Code diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/InsertionPoint.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/InsertionPoint.cs new file mode 100644 index 0000000000..92b155bd64 --- /dev/null +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/InsertionPoint.cs @@ -0,0 +1,237 @@ +// +// InsertionPoint.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using ICSharpCode.NRefactory; +using ICSharpCode.NRefactory.Editor; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.SharpDevelop.Editor; + +namespace CSharpBinding.Refactoring +{ + public enum NewLineInsertion + { + None, + Eol, + BlankLine + } + + public class InsertionPoint + { + public TextLocation Location { + get; + set; + } + + public NewLineInsertion LineBefore { get; set; } + public NewLineInsertion LineAfter { get; set; } + + public InsertionPoint (TextLocation location, NewLineInsertion lineBefore, NewLineInsertion lineAfter) + { + this.Location = location; + this.LineBefore = lineBefore; + this.LineAfter = lineAfter; + } + + public override string ToString () + { + return string.Format ("[InsertionPoint: Location={0}, LineBefore={1}, LineAfter={2}]", Location, LineBefore, LineAfter); + } + + void InsertNewLine (IDocument document, NewLineInsertion insertion, ref int offset) + { + string eolMarker = DocumentUtilitites.GetLineTerminator(document, 1); + string str = null; + switch (insertion) { + case NewLineInsertion.Eol: + str = eolMarker; + break; + case NewLineInsertion.BlankLine: + str = eolMarker + eolMarker; + break; + default: + return; + } + + document.Insert (offset, str); + offset += str.Length; + } + + public void Insert (IDocument document, string text) + { + int offset = document.GetOffset (Location); + using (var undo = document.OpenUndoGroup ()) { + text = DocumentUtilitites.NormalizeNewLines(text, document, Location.Line); + + var line = document.GetLineByOffset (offset); + int insertionOffset = line.Offset + Location.Column - 1; + offset = insertionOffset; + InsertNewLine (document, LineBefore, ref offset); + + document.Insert (offset, text); + offset += text.Length; + InsertNewLine (document, LineAfter, ref offset); + } + } + + public static List GetInsertionPoints (IDocument document, IUnresolvedTypeDefinition type) + { + if (type == null) + throw new ArgumentNullException ("type"); + + // update type from parsed document, since this is always newer. + //type = parsedDocument.GetInnermostTypeDefinition (type.GetLocation ()) ?? type; + + List result = new List (); + int offset = document.GetOffset (type.Region.Begin); + if (offset < 0) + return result; + while (offset < document.TextLength && document.GetCharAt (offset) != '{') { + offset++; + } + + var realStartLocation = document.GetLocation (offset); + result.Add (GetInsertionPosition (document, realStartLocation.Line, realStartLocation.Column)); + result [0].LineBefore = NewLineInsertion.None; + + foreach (var member in type.Members) { + TextLocation domLocation = member.BodyRegion.End; + if (domLocation.Line <= 0) { + domLocation = member.Region.End; + } + result.Add (GetInsertionPosition (document, domLocation.Line, domLocation.Column)); + } + result [result.Count - 1].LineAfter = NewLineInsertion.None; + CheckStartPoint (document, result [0], result.Count == 1); + if (result.Count > 1) { + result.RemoveAt (result.Count - 1); + NewLineInsertion insertLine; + var lineBefore = GetLineOrNull(document, type.BodyRegion.EndLine - 1); + if (lineBefore != null && lineBefore.Length == GetLineIndent(document, lineBefore).Length) { + insertLine = NewLineInsertion.None; + } else { + insertLine = NewLineInsertion.Eol; + } + // search for line start + var line = document.GetLineByNumber (type.BodyRegion.EndLine); + int col = type.BodyRegion.EndColumn - 1; + while (col > 1 && char.IsWhiteSpace (document.GetCharAt (line.Offset + col - 2))) + col--; + result.Add (new InsertionPoint (new TextLocation (type.BodyRegion.EndLine, col), insertLine, NewLineInsertion.Eol)); + CheckEndPoint (document, result [result.Count - 1], result.Count == 1); + } + + /*foreach (var region in parsedDocument.UserRegions.Where (r => type.BodyRegion.IsInside (r.Region.Begin))) { + result.Add (new InsertionPoint (new DocumentLocation (region.Region.BeginLine + 1, 1), NewLineInsertion.Eol, NewLineInsertion.Eol)); + result.Add (new InsertionPoint (new DocumentLocation (region.Region.EndLine, 1), NewLineInsertion.Eol, NewLineInsertion.Eol)); + result.Add (new InsertionPoint (new DocumentLocation (region.Region.EndLine + 1, 1), NewLineInsertion.Eol, NewLineInsertion.Eol)); + }*/ + result.Sort ((left, right) => left.Location.CompareTo (right.Location)); + return result; + } + + static void CheckEndPoint (IDocument doc, InsertionPoint point, bool isStartPoint) + { + var line = GetLineOrNull(doc, point.Location.Line); + if (line == null) + return; + + if (GetLineIndent (doc, line).Length + 1 < point.Location.Column) + point.LineBefore = NewLineInsertion.BlankLine; + if (point.Location.Column < line.Length + 1) + point.LineAfter = NewLineInsertion.Eol; + } + + static void CheckStartPoint (IDocument doc, InsertionPoint point, bool isEndPoint) + { + var line = GetLineOrNull(doc, point.Location.Line); + if (line == null) + return; + if (GetLineIndent (doc, line).Length + 1 == point.Location.Column) { + int lineNr = point.Location.Line; + while (lineNr > 1 && GetLineIndent(doc, lineNr - 1).Length == doc.GetLineByNumber (lineNr - 1).Length) { + lineNr--; + } + line = GetLineOrNull(doc, lineNr); + point.Location = new TextLocation (lineNr, GetLineIndent (doc, line).Length + 1); + } + + if (GetLineIndent (doc, line).Length + 1 < point.Location.Column) + point.LineBefore = NewLineInsertion.Eol; + if (point.Location.Column < line.Length + 1) + point.LineAfter = isEndPoint ? NewLineInsertion.Eol : NewLineInsertion.BlankLine; + } + + static string GetLineIndent(IDocument doc, int lineNumber) + { + return GetLineIndent(doc, GetLineOrNull(doc, lineNumber)); + } + + static string GetLineIndent(IDocument doc, IDocumentLine line) + { + if (line == null) + return string.Empty; + else + return DocumentUtilitites.GetWhitespaceAfter(doc, line.Offset); + } + + static InsertionPoint GetInsertionPosition (IDocument doc, int line, int column) + { + int bodyEndOffset = doc.GetOffset (line, column) + 1; + var curLine = GetLineOrNull(doc, line); + if (curLine != null) { + if (bodyEndOffset < curLine.EndOffset) { + // case1: positition is somewhere inside the start line + return new InsertionPoint (new TextLocation (line, column + 1), NewLineInsertion.Eol, NewLineInsertion.BlankLine); + } + } + + // -> if position is at line end check next line + var nextLine = GetLineOrNull (doc, line + 1); + if (nextLine == null) // check for 1 line case. + return new InsertionPoint (new TextLocation (line, column + 1), NewLineInsertion.BlankLine, NewLineInsertion.BlankLine); + + for (int i = nextLine.Offset; i < nextLine.EndOffset; i++) { + char ch = doc.GetCharAt (i); + if (!char.IsWhiteSpace (ch)) { + // case2: next line contains non ws chars. + return new InsertionPoint (new TextLocation (line + 1, 1), NewLineInsertion.Eol, NewLineInsertion.BlankLine); + } + } + // case3: whitespace line + return new InsertionPoint (new TextLocation (line + 1, 1), NewLineInsertion.Eol, NewLineInsertion.None); + } + + static IDocumentLine GetLineOrNull(IDocument doc, int lineNumber) + { + if (lineNumber >= 1 && lineNumber <= doc.LineCount) + return doc.GetLineByNumber(lineNumber); + else + return null; + } + } +} diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs index f2ea4fda66..c515b3017f 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs @@ -59,7 +59,7 @@ namespace CSharpBinding.Refactoring { if (editor == null) throw new InvalidOperationException("Cannot start a script in IsAvailable()."); - return new SDScript(editor, this.TextEditorOptions); + return new SDScript(editor, this); } public override TextLocation Location { diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs index 66f1a9045d..ad8417e815 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using CSharpBinding.Parser; using ICSharpCode.AvalonEdit.Document; using ICSharpCode.NRefactory; @@ -23,10 +24,12 @@ namespace CSharpBinding.Refactoring { readonly ITextEditor editor; readonly TextSegmentCollection textSegmentCollection; + readonly SDRefactoringContext context; - public SDScript(ITextEditor editor, TextEditorOptions options) : base(editor.Document, new CSharpFormattingOptions(), options) + public SDScript(ITextEditor editor, SDRefactoringContext context) : base(editor.Document, new CSharpFormattingOptions(), context.TextEditorOptions) { this.editor = editor; + this.context = context; this.textSegmentCollection = new TextSegmentCollection((TextDocument)editor.Document); } @@ -46,8 +49,7 @@ namespace CSharpBinding.Refactoring //var startLocation = editor.Document.GetLocation(offset); //var endLocation = editor.Document.GetLocation(offset + length); //var node = parseInfo.CompilationUnit.GetNodeContaining(startLocation, endLocation); - // TODO: pass TextEditorOptions - var formatter = new AstFormattingVisitor(new CSharpFormattingOptions(), editor.Document); + var formatter = new AstFormattingVisitor(new CSharpFormattingOptions(), editor.Document, context.TextEditorOptions); parseInfo.CompilationUnit.AcceptVisitor(formatter); var segment = GetSegment(node); formatter.ApplyChanges(segment.Offset, segment.Length); @@ -74,6 +76,30 @@ namespace CSharpBinding.Refactoring // TODO } + public override void InsertWithCursor(string operation, AstNode node, InsertPosition defaultPosition) + { + AstNode contextNode = context.GetNode(); + if (contextNode == null) + return; + var resolver = context.GetResolverStateBefore(contextNode); + InsertWithCursor(operation, node, resolver.CurrentTypeDefinition); + } + + public override void InsertWithCursor(string operation, AstNode node, ITypeDefinition parentType) + { + if (parentType == null) + return; + var currentPart = parentType.Parts.FirstOrDefault(p => p.ParsedFile != null && string.Equals(p.ParsedFile.FileName, editor.FileName, StringComparison.OrdinalIgnoreCase)); + if (currentPart != null) { + var insertionPoints = InsertionPoint.GetInsertionPoints(editor.Document, currentPart); + if (insertionPoints.Count > 0) { + int indentLevel = GetIndentLevelAt(editor.Document.GetOffset(insertionPoints[0].Location)); + var output = OutputNode(indentLevel, node); + insertionPoints[0].Insert(editor.Document, output.Text); + } + } + } + public override void Dispose() { base.Dispose(); diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/TypeName.cs b/src/Main/Base/Project/Src/Services/RefactoringService/TypeName.cs index 1d28a20ed3..b5736b5f33 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/TypeName.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/TypeName.cs @@ -19,12 +19,14 @@ namespace ICSharpCode.SharpDevelop.Refactoring public readonly string AssemblyName; public readonly string Namespace; public readonly string Name; + public readonly int TypeParameterCount; - public TypeName(string assemblyName, string @namespace, string name) + public TypeName(string assemblyName, string @namespace, string name, int typeParameterCount) { this.AssemblyName = assemblyName; this.Namespace = @namespace; this.Name = name; + this.TypeParameterCount = typeParameterCount; } public TypeName(ITypeDefinition typeDefinition) @@ -35,6 +37,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring for (ITypeDefinition parent = typeDefinition.DeclaringTypeDefinition; parent != null; parent = parent.DeclaringTypeDefinition) { this.Name = parent.Name + "." + this.Name; } + this.TypeParameterCount = typeDefinition.TypeParameterCount; } public override string ToString() @@ -44,6 +47,8 @@ namespace ICSharpCode.SharpDevelop.Refactoring fullName = Name; else fullName = Namespace + "." + Name; + if (TypeParameterCount > 0) + fullName = fullName + "`" + TypeParameterCount.ToString(); if (string.IsNullOrEmpty(AssemblyName)) return fullName; else @@ -57,7 +62,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring public bool Equals(TypeName other) { - return this.AssemblyName == other.AssemblyName && this.Namespace == other.Namespace && this.Name == other.Name; + return this.AssemblyName == other.AssemblyName && this.Namespace == other.Namespace && this.Name == other.Name && this.TypeParameterCount == other.TypeParameterCount; } public override int GetHashCode() @@ -70,6 +75,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring hashCode += 1000000009 * Namespace.GetHashCode(); if (Name != null) hashCode += 1000000021 * Name.GetHashCode(); + hashCode += TypeParameterCount; } return hashCode; }