From e48d564c9bbd4467e17eb1980910bbb1c1d9a34c Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 10 Mar 2012 23:00:54 +0100 Subject: [PATCH] Change RefactoringContext/Script API. --- .../CSharpBinding/Project/CSharpBinding.addin | 2 +- .../Src/Refactoring/SDRefactoringContext.cs | 47 +-- .../Project/Src/Refactoring/SDScript.cs | 115 +++++--- .../Document/TextDocument.cs | 8 + .../Formatter/AstFormattingVisitor.cs | 76 ++--- .../ICSharpCode.NRefactory.CSharp.csproj | 7 - .../Refactoring/Action.cs | 52 ---- .../ContextAction/AddAnotherAccessor.cs | 2 +- .../ContextAction/CreateEventInvocator.cs | 2 +- .../Refactoring/ContextAction/CreateField.cs | 4 +- .../Refactoring/ContextAction/InvertIf.cs | 4 +- .../ContextAction/RemoveBackingStore.cs | 5 +- .../Refactoring/ContextAction/RemoveBraces.cs | 6 +- .../Refactoring/CreateLinkAction.cs | 44 --- .../Refactoring/FormatTextAction.cs | 39 --- .../Refactoring/IActionFactory.cs | 75 ----- .../Refactoring/IContextAction.cs | 2 +- .../Refactoring/NodeOutputAction.cs | 118 -------- .../Refactoring/NodeSelectionAction.cs | 40 --- .../Refactoring/RefactoringContext.cs | 121 +++++--- .../Refactoring/Script.cs | 277 ++++++++++++------ .../Refactoring/TextReplaceAction.cs | 134 --------- .../Resolver/CSharpAstResolver.cs | 93 +++--- .../Resolver/CSharpResolver.cs | 4 + .../Editor/IDocument.cs | 5 + .../Editor/ReadOnlyDocument.cs | 6 + .../Base/Project/Src/Util/ExtensionMethods.cs | 10 +- 27 files changed, 484 insertions(+), 814 deletions(-) delete mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Action.cs delete mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/CreateLinkAction.cs delete mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/FormatTextAction.cs delete mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IActionFactory.cs delete mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/NodeOutputAction.cs delete mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/NodeSelectionAction.cs delete mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/TextReplaceAction.cs diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin index 6cbab75ecc..d676838507 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin @@ -146,7 +146,7 @@ displayName = "Introduce local variable" class = "ICSharpCode.NRefactory.CSharp.Refactoring.CreateLocalVariable" /> = version; } - public override NodeOutputAction CreateNodeOutputAction(int offset, int removedChars, NodeOutput output) - { - return new SDScript.SDNodeOutputAction(document, offset, removedChars, output); - } - public override Script StartScript() { if (editor == null) throw new InvalidOperationException("Cannot start a script in IsAvailable()."); - return new SDScript(editor, this); + return new SDScript(editor, this.EolMarker); + } + + public override TextLocation Location { + get { return location; } } public override int SelectionStart { @@ -95,17 +89,6 @@ namespace CSharpBinding.Refactoring } } - public override ResolveResult Resolve(AstNode expression) - { - lock (resolver) - return resolver.Resolve(expression, cancellationToken); - } - - public override void ReplaceReferences(IMember member, MemberDeclaration replaceWidth) - { - throw new NotImplementedException(); - } - public override bool IsSomethingSelected { get { return selectionLength > 0; @@ -131,17 +114,11 @@ namespace CSharpBinding.Refactoring return document.GetLocation(offset); } - public override CSharpFormattingOptions FormattingOptions { - get { - return new CSharpFormattingOptions(); - } - } - public override string EolMarker { get { if (document == null) document = new ReadOnlyDocument(textSource); - return DocumentUtilitites.GetLineTerminator(document, 1); + return DocumentUtilitites.GetLineTerminator(document, location.Line); } } diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs index 31df894785..db855b390c 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs @@ -3,9 +3,12 @@ using System; using System.Collections.Generic; +using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp.Refactoring; using ICSharpCode.NRefactory.Editor; +using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop.Editor; namespace CSharpBinding.Refactoring @@ -15,69 +18,95 @@ namespace CSharpBinding.Refactoring /// sealed class SDScript : Script { + int indentationSize = 4; readonly ITextEditor editor; + readonly TextSegmentCollection textSegmentCollection = new TextSegmentCollection(); + readonly OffsetChangeMap offsetChangeMap = new OffsetChangeMap(); + readonly List actions = new List(); - public SDScript(ITextEditor editor, SDRefactoringContext context) : base(context) + public SDScript(ITextEditor editor, string eolMarker) : base(eolMarker, new CSharpFormattingOptions()) { this.editor = editor; } - public static void RunActions (IList actions, Script script) + public override int GetCurrentOffset(TextLocation originalDocumentLocation) { - for (int i = 0; i < actions.Count; i++) { - actions [i].Perform (script); - var replaceChange = actions [i] as TextReplaceAction; - if (replaceChange == null) - continue; - for (int j = 0; j < actions.Count; j++) { - if (i == j) - continue; - var change = actions [j] as TextReplaceAction; - if (change == null) - continue; - if (replaceChange.Offset >= 0 && change.Offset >= 0) { - if (replaceChange.Offset < change.Offset) { - change.Offset -= replaceChange.RemovedChars; - if (!string.IsNullOrEmpty (replaceChange.InsertedText)) - change.Offset += replaceChange.InsertedText.Length; - } else if (replaceChange.Offset < change.Offset + change.RemovedChars) { - change.RemovedChars = Math.Max (0, change.RemovedChars - replaceChange.RemovedChars); - change.Offset = replaceChange.Offset + (!string.IsNullOrEmpty (replaceChange.InsertedText) ? replaceChange.InsertedText.Length : 0); - } + int offset = editor.Document.GetOffset(originalDocumentLocation); + return offsetChangeMap.GetNewOffset(offset, AnchorMovementType.Default); + } + + public override int GetCurrentOffset(int originalDocumentOffset) + { + return offsetChangeMap.GetNewOffset(originalDocumentOffset, AnchorMovementType.Default); + } + + public override void Replace(int offset, int length, string newText) + { + var changeMapEntry = new OffsetChangeMapEntry(offset, length, newText.Length); + textSegmentCollection.UpdateOffsets(changeMapEntry); + offsetChangeMap.Add(changeMapEntry); + + actions.Add(delegate { editor.Document.Replace(offset, length, newText); }); + } + + protected override ISegment CreateTrackedSegment(int offset, int length) + { + var segment = new TextSegment(); + segment.StartOffset = offset; + segment.Length = length; + textSegmentCollection.Add(segment); + return segment; + } + + protected override int GetIndentLevelAt(int offset) + { + int oldOffset = offsetChangeMap.Invert().GetNewOffset(offset, AnchorMovementType.Default); + var line = editor.Document.GetLineByOffset(oldOffset); + int spaces = 0; + int indentationLevel = 0; + for (int i = line.Offset; i < offset; i++) { + char c = editor.Document.GetCharAt(i); + if (c == '\t') { + spaces = 0; + indentationLevel++; + } else if (c == ' ') { + spaces++; + if (spaces == indentationSize) { + spaces = 0; + indentationLevel++; } + } else { + break; } } + return indentationLevel; } - public override void Dispose () + public override void Rename(IEntity entity, string name) { - using (editor.Document.OpenUndoGroup ()) { - RunActions (changes, this); - } } public override void InsertWithCursor(string operation, AstNode node, InsertPosition defaultPosition) { - throw new NotImplementedException(); } - internal class SDNodeOutputAction : NodeOutputAction + public override void FormatText(int offset, int length) { - IDocument doc; - - public SDNodeOutputAction(IDocument doc, int offset, int removedChars, NodeOutput output) : base (offset, removedChars, output) - { - if (doc == null) - throw new ArgumentNullException ("doc"); - if (output == null) - throw new ArgumentNullException ("output"); - this.doc = doc; - } - - public override void Perform (Script script) - { - doc.Replace (Offset, RemovedChars, NodeOutput.Text); - } + } + + public override void Select(int offset, int length) + { + actions.Add(delegate { editor.Select(offset, length); }); + } + + public override void Link(params AstNode[] nodes) + { + } + + public override void Dispose() + { + foreach (var action in actions) + action(); } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs index 7a23404139..1661d0d148 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs @@ -345,6 +345,14 @@ namespace ICSharpCode.AvalonEdit.Document } } + /// + /// Creates an immutable snapshot of this document. + /// + public IDocument CreateDocumentSnapshot() + { + return new ReadOnlyDocument(this); + } + /// /// Creates a snapshot of the current text. /// Additionally, creates a checkpoint that allows tracking document changes. diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Formatter/AstFormattingVisitor.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Formatter/AstFormattingVisitor.cs index d4f7fef6ea..a73bc2aef5 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Formatter/AstFormattingVisitor.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Formatter/AstFormattingVisitor.cs @@ -37,8 +37,8 @@ namespace ICSharpCode.NRefactory.CSharp { CSharpFormattingOptions policy; IDocument document; - IActionFactory factory; - List changes = new List (); + Script script; + //List changes = new List (); Indent curIndent = new Indent (); public int IndentLevel { @@ -55,10 +55,6 @@ namespace ICSharpCode.NRefactory.CSharp set; } - public List Changes { - get { return this.changes; } - } - public bool CorrectBlankLines { get; set; @@ -71,16 +67,19 @@ namespace ICSharpCode.NRefactory.CSharp public string EolMarker { get; set; } - public AstFormattingVisitor (CSharpFormattingOptions policy, IDocument document, IActionFactory factory, - bool tabsToSpaces = false, int indentationSize = 4) + public AstFormattingVisitor (CSharpFormattingOptions policy, IDocument document, Script script, bool tabsToSpaces = false, int indentationSize = 4) { - if (factory == null) - throw new ArgumentNullException ("factory"); + if (policy == null) + throw new ArgumentNullException("policy"); + if (document == null) + throw new ArgumentNullException("document"); + if (script == null) + throw new ArgumentNullException("script"); this.policy = policy; this.document = document; + this.script = script; this.curIndent.TabsToSpaces = tabsToSpaces; this.curIndent.TabSize = indentationSize; - this.factory = factory; this.EolMarker = Environment.NewLine; CorrectBlankLines = true; } @@ -972,34 +971,35 @@ namespace ICSharpCode.NRefactory.CSharp void AddChange (int offset, int removedChars, string insertedText) { - if (changes.Any (c => c.Offset == offset && c.RemovedChars == removedChars - && c.InsertedText == insertedText)) - return; - string currentText = document.GetText (offset, removedChars); - if (currentText == insertedText) - return; - if (currentText.Any (c => !(char.IsWhiteSpace (c) || c == '\r' || c == '\t' || c == '{' || c == '}'))) - throw new InvalidOperationException ("Tried to remove non ws chars: '" + currentText + "'"); - foreach (var change in changes) { - if (change.Offset == offset) { - if (removedChars > 0 && insertedText == change.InsertedText) { - change.RemovedChars = removedChars; + script.Replace(offset, removedChars, insertedText); +// if (changes.Any (c => c.Offset == offset && c.RemovedChars == removedChars +// && c.InsertedText == insertedText)) +// return; +// string currentText = document.GetText (offset, removedChars); +// if (currentText == insertedText) +// return; +// if (currentText.Any (c => !(char.IsWhiteSpace (c) || c == '\r' || c == '\t' || c == '{' || c == '}'))) +// throw new InvalidOperationException ("Tried to remove non ws chars: '" + currentText + "'"); +// foreach (var change in changes) { +// if (change.Offset == offset) { +// if (removedChars > 0 && insertedText == change.InsertedText) { +// change.RemovedChars = removedChars; +//// change.InsertedText = insertedText; +// return; +// } +// if (!string.IsNullOrEmpty (change.InsertedText)) { +// change.InsertedText += insertedText; +// } else { // change.InsertedText = insertedText; - return; - } - if (!string.IsNullOrEmpty (change.InsertedText)) { - change.InsertedText += insertedText; - } else { - change.InsertedText = insertedText; - } - change.RemovedChars = System.Math.Max (removedChars, change.RemovedChars); - return; - } - } - //Console.WriteLine ("offset={0}, removedChars={1}, insertedText={2}", offset, removedChars, insertedText == null ? "" : insertedText.Replace ("\n", "\\n").Replace ("\r", "\\r").Replace ("\t", "\\t").Replace (" ", ".")); - //Console.WriteLine (Environment.StackTrace); - - changes.Add (factory.CreateTextReplaceAction (offset, removedChars, insertedText)); +// } +// change.RemovedChars = System.Math.Max (removedChars, change.RemovedChars); +// return; +// } +// } +// //Console.WriteLine ("offset={0}, removedChars={1}, insertedText={2}", offset, removedChars, insertedText == null ? "" : insertedText.Replace ("\n", "\\n").Replace ("\r", "\\r").Replace ("\t", "\\t").Replace (" ", ".")); +// //Console.WriteLine (Environment.StackTrace); +// +// changes.Add (factory.CreateTextReplaceAction (offset, removedChars, insertedText)); } public bool IsLineIsEmptyUpToEol (TextLocation startLocation) diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index ef5aa7d947..ba7894c277 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -243,7 +243,6 @@ - @@ -267,15 +266,9 @@ - - - - - - diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Action.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Action.cs deleted file mode 100644 index fa3cd7f703..0000000000 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Action.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// Change.cs -// -// Author: -// Mike Krüger -// -// Copyright (c) 2011 Mike Krüger -// -// 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; - -namespace ICSharpCode.NRefactory.CSharp.Refactoring -{ - /// - /// This is the base class for all refactoring operations that are performed. - /// - public abstract class Action - { - /// - /// Gets or sets the description. - /// - /// - /// A brief description of the refactoring change. - /// - public string Description { - get; - set; - } - - /// - /// Performs the change. - /// - public abstract void Perform (Script script); - } -} - diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/AddAnotherAccessor.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/AddAnotherAccessor.cs index 8b112a5cd9..f35f8ad068 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/AddAnotherAccessor.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/AddAnotherAccessor.cs @@ -57,7 +57,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring using (var script = context.StartScript ()) { script.InsertBefore (pdecl.RBraceToken, accessor); script.Select (accessorStatement); - script.FormatText (ctx => GetPropertyDeclaration (context)); + script.FormatText (pdecl); } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CreateEventInvocator.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CreateEventInvocator.cs index 0fa5fa5650..ee17410331 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CreateEventInvocator.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CreateEventInvocator.cs @@ -75,7 +75,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring ReturnType = new PrimitiveType ("void"), Modifiers = ICSharpCode.NRefactory.CSharp.Modifiers.Protected | ICSharpCode.NRefactory.CSharp.Modifiers.Virtual, Body = new BlockStatement () { - new VariableDeclarationStatement (context.CreateShortType (eventDeclaration.ReturnType), handlerName, new MemberReferenceExpression (new ThisReferenceExpression (), initializer.Name)), + new VariableDeclarationStatement (eventDeclaration.ReturnType.Clone (), handlerName, new MemberReferenceExpression (new ThisReferenceExpression (), initializer.Name)), new IfElseStatement () { Condition = new BinaryOperatorExpression (new IdentifierExpression (handlerName), BinaryOperatorType.InEquality, new PrimitiveExpression (null)), TrueStatement = new ExpressionStatement (new InvocationExpression (new IdentifierExpression (handlerName), arguments)) diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CreateField.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CreateField.cs index 831dfe121e..be506a7e51 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CreateField.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/CreateField.cs @@ -1,4 +1,4 @@ -// +// // CreateField.cs // // Author: @@ -63,7 +63,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring if (identifier.Parent is AssignmentExpression) { var assign = (AssignmentExpression)identifier.Parent; var other = assign.Left == identifier ? assign.Right : assign.Left; - return context.Resolve (other).Type.ConvertToAstType (); + return context.CreateShortType (context.Resolve (other).Type); } return null; } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/InvertIf.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/InvertIf.cs index 79a59c32ae..ccea15c562 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/InvertIf.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/InvertIf.cs @@ -1,4 +1,4 @@ -// +// // InvertIf.cs // // Author: @@ -46,7 +46,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring script.Replace (ifStatement.Condition, CSharpUtil.InvertCondition (ifStatement.Condition)); script.Replace (ifStatement.TrueStatement, ifStatement.FalseStatement); script.Replace (ifStatement.FalseStatement, ifStatement.TrueStatement); - script.FormatText (ctx => GetIfElseStatement (ctx)); + script.FormatText (ifStatement); } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/RemoveBackingStore.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/RemoveBackingStore.cs index 1e9282e691..8db470897e 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/RemoveBackingStore.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/RemoveBackingStore.cs @@ -43,16 +43,15 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring var property = context.GetNode (); var field = GetBackingField (context); - context.ReplaceReferences (field, property); - // create new auto property var newProperty = (PropertyDeclaration)property.Clone (); newProperty.Getter.Body = BlockStatement.Null; newProperty.Setter.Body = BlockStatement.Null; using (var script = context.StartScript ()) { - script.Remove (context.Unit.GetNodeAt (field.Region.BeginLine, field.Region.BeginColumn)); + script.Remove (context.RootNode.GetNodeAt (field.Region.BeginLine, field.Region.BeginColumn)); script.Replace (property, newProperty); + script.Rename (field, newProperty.Name); } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/RemoveBraces.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/RemoveBraces.cs index 9d78086cf4..5aad476947 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/RemoveBraces.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/ContextAction/RemoveBraces.cs @@ -1,4 +1,4 @@ -// +// // RemoveBraces.cs // // Author: @@ -42,7 +42,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring using (var script = context.StartScript ()) { script.Remove (block.LBraceToken); script.Remove (block.RBraceToken); - script.FormatText (ctx => ctx.Unit.GetNodeAt (block.Parent.StartLocation)); + script.FormatText (block.Parent); } } @@ -53,7 +53,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring return null; if (!(block.Parent is Statement)) return null; - if (block.Statements.Count () != 1) + if (block.Statements.Count != 1) return null; return block; } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/CreateLinkAction.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/CreateLinkAction.cs deleted file mode 100644 index 11212a1c9a..0000000000 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/CreateLinkAction.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -// CreateLinkAction.cs -// -// Author: -// Mike Krüger -// -// Copyright (c) 2011 Mike Krüger -// -// 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; - -namespace ICSharpCode.NRefactory.CSharp.Refactoring -{ - public abstract class CreateLinkAction : Action - { - public IEnumerable Linked { - get; - private set; - } - - public CreateLinkAction (IEnumerable linked) - { - this.Linked = linked; - } - } -} - diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/FormatTextAction.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/FormatTextAction.cs deleted file mode 100644 index 8b7fde6615..0000000000 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/FormatTextAction.cs +++ /dev/null @@ -1,39 +0,0 @@ -// -// FormatTextAction.cs -// -// Author: -// Mike Krüger -// -// Copyright (c) 2011 Mike Krüger -// -// 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; - -namespace ICSharpCode.NRefactory.CSharp.Refactoring -{ - public abstract class FormatTextAction : Action - { - public Func Callback { get; private set; } - - public FormatTextAction (Func callback) - { - this.Callback = callback; - } - } -} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IActionFactory.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IActionFactory.cs deleted file mode 100644 index f801a1c9b9..0000000000 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IActionFactory.cs +++ /dev/null @@ -1,75 +0,0 @@ -// -// IChangeFactory.cs -// -// Author: -// Mike Krüger -// -// Copyright (c) 2011 Mike Krüger -// -// 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; - -namespace ICSharpCode.NRefactory.CSharp.Refactoring -{ - /// - /// A factory that creates the changes. - /// - public interface IActionFactory - { - TextReplaceAction CreateTextReplaceAction (int offset, int removedChars, string insertedText); - NodeOutputAction CreateNodeOutputAction (int offset, int removedChars, NodeOutput output); - NodeSelectionAction CreateNodeSelectionAction (AstNode node); - FormatTextAction CreateFormatTextAction (Func callback); - CreateLinkAction CreateLinkAction (IEnumerable linkedNodes); - } - - public abstract class AbstractActionFactory : IActionFactory - { - #region IActionFactory implementation - public virtual TextReplaceAction CreateTextReplaceAction (int offset, int removedChars, string insertedText) - { - throw new NotImplementedException (); - } - - public virtual NodeOutputAction CreateNodeOutputAction (int offset, int removedChars, NodeOutput output) - { - throw new NotImplementedException (); - } - - public virtual NodeSelectionAction CreateNodeSelectionAction (AstNode node) - { - throw new NotImplementedException (); - } - - public virtual FormatTextAction CreateFormatTextAction (Func callback) - { - throw new NotImplementedException (); - } - - public virtual CreateLinkAction CreateLinkAction (IEnumerable linkedNodes) - { - throw new NotImplementedException (); - } - #endregion - - } - -} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IContextAction.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IContextAction.cs index 8c2b64ce23..586df389c2 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IContextAction.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/IContextAction.cs @@ -1,4 +1,4 @@ -// +// // ContextAction.cs // // Author: diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/NodeOutputAction.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/NodeOutputAction.cs deleted file mode 100644 index 911ec67ccd..0000000000 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/NodeOutputAction.cs +++ /dev/null @@ -1,118 +0,0 @@ -// -// NodeOutputChange.cs -// -// Author: -// Mike Krüger -// -// Copyright (c) 2011 Mike Krüger -// -// 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.Editor; - -namespace ICSharpCode.NRefactory.CSharp.Refactoring -{ - /// - /// This is the node that should be outputted at a position. - /// - public class NodeOutput - { - public readonly Dictionary NodeSegments = new Dictionary (); - - public string Text { - get; - set; - } - - public void Trim () - { - for (int i = 0; i < Text.Length; i++) { - char ch = Text [i]; - if (ch != ' ' && ch != '\t') { - if (i > 0) { - Text = Text.Substring (i); - foreach (var seg in NodeSegments.Values) { - seg.Offset -= i; - } - } - break; - } - } - } - - public class Segment : ISegment - { - public int Offset { - get; - internal set; - } - - public int Length { - get; - internal set; - } - - public int EndOffset { - get { - return Offset + Length; - } - } - - public Segment (int offset) - { - this.Offset = offset; - } - - public override string ToString () - { - return string.Format ("[NodeOutput.Segment: Offset={0}, Length={1}, EndOffset={2}]", Offset, Length, EndOffset); - } - } - } - - - /// - /// Outputs an Ast node at a specific offset. - /// - public abstract class NodeOutputAction : TextReplaceAction - { - public NodeOutput NodeOutput { - get; - private set; - } - - public override string InsertedText { - get { - return NodeOutput.Text; - } - set { - throw new NotSupportedException ("Changing text with this propery is not supported on NodeOutputChange."); - } - } - - public NodeOutputAction (int offset, int removedChars, NodeOutput output) : base (offset, removedChars) - { - if (output == null) - throw new ArgumentNullException ("output"); - this.NodeOutput = output; - } - } -} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/NodeSelectionAction.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/NodeSelectionAction.cs deleted file mode 100644 index b7df34b951..0000000000 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/NodeSelectionAction.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// SelectionAction.cs -// -// Author: -// Mike Krüger -// -// Copyright (c) 2011 Mike Krüger -// -// 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; - -namespace ICSharpCode.NRefactory.CSharp.Refactoring -{ - public abstract class NodeSelectionAction : Action - { - public AstNode AstNode { get; private set; } - - public NodeSelectionAction (AstNode astNode) - { - this.AstNode = astNode; - } - } -} - diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs index 450d950a68..f7b301d6ad 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs @@ -25,37 +25,51 @@ // THE SOFTWARE. using System; using System.Linq; +using System.Threading; using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.CSharp.TypeSystem; using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; namespace ICSharpCode.NRefactory.CSharp.Refactoring { - public abstract class RefactoringContext : AbstractActionFactory + public abstract class RefactoringContext { - public CompilationUnit Unit { - get; - protected set; + readonly CSharpAstResolver resolver; + readonly CancellationToken cancellationToken; + + public RefactoringContext(CSharpAstResolver resolver, CancellationToken cancellationToken) + { + this.resolver = resolver; + this.cancellationToken = cancellationToken; } - - public TextLocation Location { - get; - protected set; + + public CancellationToken CancellationToken { + get { return cancellationToken; } } - public abstract bool Supports(Version version); + public virtual AstNode RootNode { + get { return resolver.RootNode; } + } + + public abstract TextLocation Location { get; } + + public virtual bool Supports(Version version) + { + return true; + } public ICompilation Compilation { - get; - protected set; + get { return resolver.Compilation; } } - public abstract CSharpFormattingOptions FormattingOptions { - get; + public virtual AstType CreateShortType (IType fullType) + { + var csResolver = resolver.GetResolverStateBefore(GetNode()); + var builder = new TypeSystemAstBuilder(csResolver); + return builder.ConvertType(fullType); } - - public abstract AstType CreateShortType (IType fullType); public AstType CreateShortType (string ns, string name, int typeParameterCount = 0) { @@ -68,34 +82,38 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring return new MemberType (new SimpleType (ns), name); } - public virtual AstType CreateShortType (AstType fullType) - { - return CreateShortType (Resolve (fullType).Type); - } - -// public abstract IType GetDefinition (AstType resolvedType); - - public abstract void ReplaceReferences (IMember member, MemberDeclaration replaceWidth); - public AstNode GetNode () { - return Unit.GetNodeAt (Location); + return RootNode.GetNodeAt (Location); } public T GetNode () where T : AstNode { - return Unit.GetNodeAt (Location); + return RootNode.GetNodeAt (Location); } - public abstract Script StartScript (); - #region Text stuff - public abstract string EolMarker { get; } - public abstract bool IsSomethingSelected { get; } - public abstract string SelectedText { get; } - public abstract int SelectionStart { get; } - public abstract int SelectionEnd { get; } - public abstract int SelectionLength { get; } + public virtual string EolMarker { + get { return Environment.NewLine; } + } + + public virtual bool IsSomethingSelected { + get { return this.SelectionLength > 0; } + } + + public virtual string SelectedText { + get { return string.Empty; } + } + + public virtual int SelectionStart { + get { return 0; } + } + public virtual int SelectionEnd { + get { return 0; } + } + public virtual int SelectionLength { + get { return 0; } + } public abstract int GetOffset (TextLocation location); public int GetOffset (int line, int col) { @@ -106,10 +124,28 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring #endregion #region Resolving - public abstract ResolveResult Resolve (AstNode expression); + public ResolveResult Resolve (AstNode node) + { + return resolver.Resolve (node, cancellationToken); + } + + public IType ResolveType (AstType type) + { + return resolver.Resolve (type, cancellationToken).Type; + } + + public IType GetExpectedType (Expression expression) + { + return resolver.GetExpectedType(expression, cancellationToken); + } + + public Conversion GetConversion (Expression expression) + { + return resolver.GetConversion(expression, cancellationToken); + } #endregion - public string GetNameProposal (string name, bool camelCase = true) + public virtual string GetNameProposal (string name, bool camelCase = true) { string baseName = (camelCase ? char.ToLower (name [0]) : char.ToUpper (name [0])) + name.Substring (1); @@ -129,17 +165,8 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring { return baseName + (number > 0 ? (number + 1).ToString () : ""); } - } - - public static class RefactoringExtensions - { - #region ConvertTypes - public static ICSharpCode.NRefactory.CSharp.AstType ConvertToAstType (this IType type) - { - var builder = new TypeSystemAstBuilder (); - return builder.ConvertType (type); - } - #endregion + + public abstract Script StartScript(); } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs index 8500f04341..6db950abee 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs @@ -1,6 +1,6 @@ // // Script.cs -// +// // Author: // Mike Krüger // @@ -27,106 +27,175 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using ICSharpCode.NRefactory.Editor; +using ICSharpCode.NRefactory.TypeSystem; namespace ICSharpCode.NRefactory.CSharp.Refactoring { + /// + /// Class for creating change scripts. + /// 'Original document' = document without the change script applied. + /// 'Current document' = document with the change script (as far as it is already created) applies. + /// public abstract class Script : IDisposable { - public RefactoringContext Context { - get; - private set; - } - - protected readonly List changes = new List (); - - public IEnumerable Actions { - get { - return changes; + internal struct Segment : ISegment + { + readonly int offset; + readonly int length; + + public int Offset { + get { return offset; } + } + + public int Length { + get { return length; } + } + + public int EndOffset { + get { return Offset + Length; } + } + + public Segment (int offset, int length) + { + this.offset = offset; + this.length = length; + } + + public override string ToString () + { + return string.Format ("[Script.Segment: Offset={0}, Length={1}, EndOffset={2}]", Offset, Length, EndOffset); } } - public Script (RefactoringContext context) - { - if (context == null) - throw new ArgumentNullException ("context"); - this.Context = context; - } + readonly string eolMarker; + readonly CSharpFormattingOptions formattingOptions; + Dictionary segmentsForInsertedNodes = new Dictionary(); - public void Queue (Action change) + protected Script(string eolMarker, CSharpFormattingOptions formattingOptions) { - changes.Add (change); + if (eolMarker == null) + throw new ArgumentNullException("eolMarker"); + if (formattingOptions == null) + throw new ArgumentNullException("formattingOptions"); + this.eolMarker = eolMarker; + this.formattingOptions = formattingOptions; } - public void InsertText (int offset, string text) - { - Queue (Context.CreateTextReplaceAction (offset, 0, text)); - } + /// + /// Given an offset in the original document (at the start of script execution), + /// returns the offset in the current document. + /// + public abstract int GetCurrentOffset(int originalDocumentOffset); - public void InsertBefore (AstNode node, AstNode insertNode) - { - var startOffset = Context.GetOffset (node.StartLocation.Line, 1); - var output = OutputNode (GetIndentLevelAt (startOffset), insertNode); - - if (!(insertNode is Expression || insertNode is AstType)) - output.Text += Context.EolMarker; - - Queue (Context.CreateNodeOutputAction (startOffset, 0, output)); - } - - public void AddTo (BlockStatement bodyStatement, AstNode insertNode) + /// + /// Given an offset in the original document (at the start of script execution), + /// returns the offset in the current document. + /// + public abstract int GetCurrentOffset(TextLocation originalDocumentLocation); + + /// + /// Creates a tracked segment for the specified (offset,length)-segment. + /// Offset is interpreted to be an offset in the current document. + /// + /// + /// A segment that initially has the specified values, and updates + /// on every call. + /// + protected abstract ISegment CreateTrackedSegment(int offset, int length); + + protected ISegment GetSegment(AstNode node) { - var startOffset = Context.GetOffset (bodyStatement.LBraceToken.StartLocation) + 1; - var output = OutputNode (GetIndentLevelAt (startOffset), insertNode, true); - Queue (Context.CreateNodeOutputAction (startOffset, 0, output)); + ISegment segment; + if (segmentsForInsertedNodes.TryGetValue(node, out segment)) + return segment; + if (node.StartLocation.IsEmpty || node.EndLocation.IsEmpty) { + throw new InvalidOperationException("Trying to get the position of a node that is not part of the original document and was not inserted"); + } + int startOffset = GetCurrentOffset(node.StartLocation); + int endOffset = GetCurrentOffset(node.EndLocation); + return new Segment(startOffset, endOffset - startOffset); } - public void Link (params AstNode[] nodes) + /// + /// Replaces text. + /// + /// The starting offset of the text to be replaced. + /// The length of the text to be replaced. + /// The new text. + public abstract void Replace (int offset, int length, string newText); + + public void InsertText(int offset, string newText) { - Queue (Context.CreateLinkAction (nodes)); + Replace(offset, 0, newText); } - public void Link (IEnumerable nodes) - { - Queue (Context.CreateLinkAction (nodes)); + public CSharpFormattingOptions FormattingOptions { + get { return formattingOptions; } } public void Remove (AstNode node) { - var startOffset = Context.GetOffset (node.StartLocation); - var endOffset = Context.GetOffset (node.EndLocation); - Remove (startOffset, endOffset - startOffset); + var segment = GetSegment(node); + Replace(segment.Offset, segment.Length, string.Empty); } - void Remove (int offset, int length) + public void InsertBefore (AstNode node, AstNode insertNode) { - Queue (Context.CreateTextReplaceAction (offset, length, null)); + var startOffset = GetCurrentOffset (new TextLocation(node.StartLocation.Line, 1)); + var output = OutputNode (GetIndentLevelAt (startOffset), insertNode); + string text = output.Text; + if (!(insertNode is Expression || insertNode is AstType)) + text += eolMarker; + InsertText(startOffset, text); + output.RegisterTrackedSegments(this, startOffset); + } + + public void AddTo (BlockStatement bodyStatement, AstNode insertNode) + { + var startOffset = GetCurrentOffset (bodyStatement.LBraceToken.EndLocation); + var output = OutputNode (1 + GetIndentLevelAt (startOffset), insertNode, true); + InsertText (startOffset, output.Text); + output.RegisterTrackedSegments (this, startOffset); } - void Replace (int offset, int length, string text) + public virtual void Link (params AstNode[] nodes) { - Queue (Context.CreateTextReplaceAction (offset, length, text)); + // Default implementation: do nothing + // Derived classes are supposed to enter the text editor's linked state. } public void Replace (AstNode node, AstNode replaceWith) { - var startOffset = Context.GetOffset (node.StartLocation); - var endOffset = Context.GetOffset (node.EndLocation); + var segment = GetSegment (node); + int startOffset = segment.Offset; int level = 0; if (!(replaceWith is Expression) && !(replaceWith is AstType)) level = GetIndentLevelAt (startOffset); NodeOutput output = OutputNode (level, replaceWith); - output.Trim (); - Queue (Context.CreateNodeOutputAction (startOffset, endOffset - startOffset, output)); + output.TrimStart (); + Replace (startOffset, segment.Length, output.Text); + output.RegisterTrackedSegments(this, startOffset); } - - public void FormatText (Func callback) + + public void FormatText (AstNode node) { - Queue (Context.CreateFormatTextAction (callback)); + var segment = GetSegment(node); + FormatText(segment.Offset, segment.Length); } + public abstract void FormatText (int offset, int length); + public void Select (AstNode node) { - Queue (Context.CreateNodeSelectionAction (node)); + var segment = GetSegment(node); + Select(segment.Offset, segment.Length); + } + + public virtual void Select (int offset, int length) + { + // default implementation: do nothing + // Derived classes are supposed to set the text editor's selection } public enum InsertPosition { @@ -138,65 +207,101 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring public abstract void InsertWithCursor (string operation, AstNode node, InsertPosition defaultPosition); - protected int GetIndentLevelAt (int offset) + protected virtual int GetIndentLevelAt (int offset) { - var node = Context.Unit.GetNodeAt (Context.GetLocation (offset)); - int level = 0; - while (node != null) { - if (node is BlockStatement || node is TypeDeclaration || node is NamespaceDeclaration) - level++; - node = node.Parent; - } - return level; + return 0; } sealed class SegmentTrackingOutputFormatter : TextWriterOutputFormatter { - readonly NodeOutput result; + internal List> NewSegments = new List>(); + Stack startOffsets = new Stack(); readonly StringWriter stringWriter; - public SegmentTrackingOutputFormatter(NodeOutput result, StringWriter stringWriter) + public SegmentTrackingOutputFormatter(StringWriter stringWriter) : base(stringWriter) { - this.result = result; this.stringWriter = stringWriter; } public override void StartNode(AstNode node) { base.StartNode(node); - result.NodeSegments [node] = new NodeOutput.Segment (stringWriter.GetStringBuilder ().Length); + startOffsets.Push(stringWriter.GetStringBuilder ().Length); } public override void EndNode(AstNode node) { - var nodeSegment = result.NodeSegments [node]; - nodeSegment.Length = stringWriter.GetStringBuilder ().Length - nodeSegment.Offset; + int startOffset = startOffsets.Pop(); + int endOffset = stringWriter.GetStringBuilder ().Length; + NewSegments.Add(new KeyValuePair(node, new Segment(startOffset, endOffset - startOffset))); base.EndNode(node); } } - protected NodeOutput OutputNode (int indentLevel, AstNode node, bool startWithNewLine = false) + protected NodeOutput OutputNode(int indentLevel, AstNode node, bool startWithNewLine = false) { - var result = new NodeOutput (); var stringWriter = new StringWriter (); - var formatter = new SegmentTrackingOutputFormatter (result, stringWriter); + var formatter = new SegmentTrackingOutputFormatter (stringWriter); formatter.Indentation = indentLevel; - stringWriter.NewLine = Context.EolMarker; + stringWriter.NewLine = eolMarker; if (startWithNewLine) formatter.NewLine (); - var visitor = new CSharpOutputVisitor (formatter, Context.FormattingOptions); + var visitor = new CSharpOutputVisitor (formatter, formattingOptions); node.AcceptVisitor (visitor, null); - result.Text = stringWriter.ToString ().TrimEnd (); + string text = stringWriter.ToString().TrimEnd(); + if (node is FieldDeclaration) - result.Text += Context.EolMarker; - - return result; + text += eolMarker; + return new NodeOutput(text, formatter.NewSegments); } - - #region IDisposable implementation - public abstract void Dispose (); - #endregion + + protected class NodeOutput + { + string text; + List> newSegments; + int trimmedLength; + + internal NodeOutput(string text, List> newSegments) + { + this.text = text; + this.newSegments = newSegments; + } + + public string Text { + get { return text; } + } + + public void TrimStart() + { + for (int i = 0; i < text.Length; i++) { + char ch = text [i]; + if (ch != ' ' && ch != '\t') { + if (i > 0) { + text = text.Substring (i); + trimmedLength = i; + } + break; + } + } + } + + public void RegisterTrackedSegments(Script script, int insertionOffset) + { + foreach (var pair in newSegments) { + int offset = insertionOffset + pair.Value.Offset - trimmedLength; + ISegment trackedSegment = script.CreateTrackedSegment(offset, pair.Value.Length); + script.segmentsForInsertedNodes.Add(pair.Key, trackedSegment); + } + } + } + + /// + /// Performs a rename refactoring. + /// + public abstract void Rename(IEntity entity, string name); + + public abstract void Dispose(); } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/TextReplaceAction.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/TextReplaceAction.cs deleted file mode 100644 index bbe6234d28..0000000000 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Refactoring/TextReplaceAction.cs +++ /dev/null @@ -1,134 +0,0 @@ -// -// TextReplaceChange.cs -// -// Author: -// Mike Krüger -// -// Copyright (c) 2011 Mike Krüger -// -// 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; - -namespace ICSharpCode.NRefactory.CSharp.Refactoring -{ - /// - /// This is the base action for changes in a text document. - /// - public abstract class TextReplaceAction : Action - { - /// - /// Gets or sets the offset. - /// - /// - /// The offset of the replace. - /// - public int Offset { - get; - set; - } - - int removedChars; - /// - /// Gets or sets the numer of chars to removed. - /// - /// - /// The numer of chars to remove. - /// - /// - /// Is thrown when an argument passed to a method is invalid because it is outside the allowable range of values as - /// specified by the method. - /// - public int RemovedChars { - get { - return removedChars; - } - set { - if (value < 0) - throw new ArgumentOutOfRangeException ("RemovedChars", "needs to be >= 0"); - removedChars = value; - } - } - - /// - /// Gets or sets the inserted text. - /// - /// - /// The text to insert. - /// - public virtual string InsertedText { - get; - set; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The offset of the replace. - /// - /// - /// The numer of chars to remove. - /// - /// - /// Is thrown when an argument passed to a method is invalid because it is outside the allowable range of values as - /// specified by the method. - /// - protected TextReplaceAction (int offset, int removedChars) - { - if (removedChars < 0) - throw new ArgumentOutOfRangeException ("removedChars", "removedChars needs to be >= 0"); - if (offset < 0) - throw new ArgumentOutOfRangeException ("offset", "offset needs to be >= 0"); - this.removedChars = removedChars; - this.Offset = offset; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The offset of the replace. - /// - /// - /// The numer of chars to remove. - /// - /// - /// The text to insert. - /// - /// - /// Is thrown when an argument passed to a method is invalid because it is outside the allowable range of values as - /// specified by the method. - public TextReplaceAction (int offset, int removedChars, string insertedText) : this (offset, removedChars) - { - this.InsertedText = insertedText; - } - - /// - /// Returns a that represents the current . - /// - /// - /// A that represents the current . - /// - public override string ToString () - { - return string.Format ("[TextReplaceAction: Offset={0}, RemovedChars={1}, InsertedText={2}]", Offset, RemovedChars, InsertedText == null ? "" : InsertedText.Replace ("\t", "\\t").Replace ("\n", "\\n").Replace ("\r", "\\r")); - } - } -} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs index fcc31d9e9f..b1f485d670 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs @@ -28,6 +28,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// /// Resolves C# AST nodes. /// + /// This class is thread-safe. public class CSharpAstResolver { readonly CSharpResolver initialResolverState; @@ -120,17 +121,19 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (navigator == null) throw new ArgumentNullException("navigator"); - if (resolverInitialized) - throw new InvalidOperationException("Applying a navigator is only valid as the first operation on the CSharpAstResolver."); - - resolverInitialized = true; - resolveVisitor.cancellationToken = cancellationToken; - resolveVisitor.SetNavigator(navigator); - try { - resolveVisitor.Scan(rootNode); - } finally { - resolveVisitor.SetNavigator(null); - resolveVisitor.cancellationToken = CancellationToken.None; + lock (resolveVisitor) { + if (resolverInitialized) + throw new InvalidOperationException("Applying a navigator is only valid as the first operation on the CSharpAstResolver."); + + resolverInitialized = true; + resolveVisitor.cancellationToken = cancellationToken; + resolveVisitor.SetNavigator(navigator); + try { + resolveVisitor.Scan(rootNode); + } finally { + resolveVisitor.SetNavigator(null); + resolveVisitor.cancellationToken = CancellationToken.None; + } } } @@ -141,14 +144,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { if (node == null || node.IsNull || IsUnresolvableNode(node)) return ErrorResolveResult.UnknownError; - InitResolver(); - resolveVisitor.cancellationToken = cancellationToken; - try { - ResolveResult rr = resolveVisitor.GetResolveResult(node); - Debug.Assert(rr != null); - return rr; - } finally { - resolveVisitor.cancellationToken = CancellationToken.None; + lock (resolveVisitor) { + InitResolver(); + resolveVisitor.cancellationToken = cancellationToken; + try { + ResolveResult rr = resolveVisitor.GetResolveResult(node); + Debug.Assert(rr != null); + return rr; + } finally { + resolveVisitor.cancellationToken = CancellationToken.None; + } } } @@ -168,14 +173,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { if (node == null || node.IsNull) throw new ArgumentNullException("node"); - InitResolver(); - resolveVisitor.cancellationToken = cancellationToken; - try { - CSharpResolver resolver = resolveVisitor.GetResolverStateBefore(node); - Debug.Assert(resolver != null); - return resolver; - } finally { - resolveVisitor.cancellationToken = CancellationToken.None; + lock (resolveVisitor) { + InitResolver(); + resolveVisitor.cancellationToken = cancellationToken; + try { + CSharpResolver resolver = resolveVisitor.GetResolverStateBefore(node); + Debug.Assert(resolver != null); + return resolver; + } finally { + resolveVisitor.cancellationToken = CancellationToken.None; + } } } @@ -191,14 +198,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver node = node.Parent; if (node == null) return initialResolverState; - InitResolver(); - resolveVisitor.cancellationToken = cancellationToken; - try { - CSharpResolver resolver = resolveVisitor.GetResolverStateAfter(node); - Debug.Assert(resolver != null); - return resolver; - } finally { - resolveVisitor.cancellationToken = CancellationToken.None; + lock (resolveVisitor) { + InitResolver(); + resolveVisitor.cancellationToken = cancellationToken; + try { + CSharpResolver resolver = resolveVisitor.GetResolverStateAfter(node); + Debug.Assert(resolver != null); + return resolver; + } finally { + resolveVisitor.cancellationToken = CancellationToken.None; + } } } @@ -206,12 +215,14 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { if (expr == null || expr.IsNull) throw new ArgumentNullException("expr"); - InitResolver(); - resolveVisitor.cancellationToken = cancellationToken; - try { - return resolveVisitor.GetConversionWithTargetType(expr); - } finally { - resolveVisitor.cancellationToken = CancellationToken.None; + lock (resolveVisitor) { + InitResolver(); + resolveVisitor.cancellationToken = cancellationToken; + try { + return resolveVisitor.GetConversionWithTargetType(expr); + } finally { + resolveVisitor.cancellationToken = CancellationToken.None; + } } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs index 8ac05c8575..3fac874ea9 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs @@ -35,6 +35,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// /// Contains the main resolver logic. /// + /// + /// Despite being immutable, this class is not thread-safe. (due to caches) + /// TODO: fix this, it's very hard for NR users to tell whether two CSharpResolvers share the same caches + /// public class CSharpResolver { static readonly ResolveResult ErrorResult = ErrorResolveResult.UnknownError; diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/IDocument.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/IDocument.cs index 07057cdb34..ade9ea8081 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/IDocument.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/IDocument.cs @@ -27,6 +27,11 @@ namespace ICSharpCode.NRefactory.Editor /// public interface IDocument : ITextSource, IServiceProvider { + /// + /// Creates an immutable snapshot of this document. + /// + IDocument CreateDocumentSnapshot(); + /// /// Gets/Sets the text of the whole document.. /// diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/ReadOnlyDocument.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/ReadOnlyDocument.cs index 5120fd2e32..4912ccd2d9 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/ReadOnlyDocument.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Editor/ReadOnlyDocument.cs @@ -324,6 +324,12 @@ namespace ICSharpCode.NRefactory.Editor return textSource.CreateSnapshot(offset, length); } + /// + public IDocument CreateDocumentSnapshot() + { + return this; // ReadOnlyDocument is immutable + } + /// public System.IO.TextReader CreateReader() { diff --git a/src/Main/Base/Project/Src/Util/ExtensionMethods.cs b/src/Main/Base/Project/Src/Util/ExtensionMethods.cs index d6e8e81c77..f8f4a6b383 100644 --- a/src/Main/Base/Project/Src/Util/ExtensionMethods.cs +++ b/src/Main/Base/Project/Src/Util/ExtensionMethods.cs @@ -77,7 +77,15 @@ namespace ICSharpCode.SharpDevelop /// public static void FireAndForget(this Task task) { - task.ContinueWith(t => { if (t.Exception != null) Core.MessageService.ShowException(t.Exception); }); + task.ContinueWith( + t => { + if (t.Exception != null) { + if (t.Exception.InnerExceptions.Count == 1) + Core.MessageService.ShowException(t.Exception.InnerExceptions[0]); + else + Core.MessageService.ShowException(t.Exception); + } + }); } ///