diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin index 4824542069..47ffeea755 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin @@ -155,6 +155,17 @@ class="CSharpBinding.Refactoring.SearchForIssuesCommand"/> + + + + + + + + + + diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj index ebda626378..00027765d5 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj @@ -67,16 +67,25 @@ + + + + + + + InsertCtorDialog.xaml + + IssueOptions.xaml @@ -86,6 +95,10 @@ + + OverrideToStringMethodDialog.xaml + + @@ -95,7 +108,6 @@ Code - @@ -181,8 +193,10 @@ + + diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CodeManipulation.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CodeManipulation.cs index b33ee33e57..2213a738bb 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CodeManipulation.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CodeManipulation.cs @@ -3,16 +3,18 @@ using System; using System.Collections.Generic; using System.Linq; -/* + + +using System.Windows.Input; using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.Core; using ICSharpCode.NRefactory; -using ICSharpCode.NRefactory.Ast; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.Editor; +using ICSharpCode.NRefactory.PatternMatching; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Editor; -using ICSharpCode.SharpDevelop.Refactoring; -using Ast = ICSharpCode.NRefactory.Ast; namespace CSharpBinding { @@ -100,8 +102,8 @@ namespace CSharpBinding class Selection { - public Location Start { get; set; } - public Location End { get; set; } + public TextLocation Start { get; set; } + public TextLocation End { get; set; } } void CodeManipulationSelectionChanged(object sender, EventArgs e) @@ -134,46 +136,110 @@ namespace CSharpBinding // move selection - find outermost node in selection, swap selection with closest child of its parent to the selection void MoveStatement(ITextEditor editor, MoveStatementDirection direction) { - IList commentsBlankLines; + IList commentsBlankLines; var parsedCU = ParseDocument(editor, out commentsBlankLines); if (parsedCU == null) return; // Find the Statement or Definition containing caret -> Extend selection to Statement or Definition - INode currentStatement; + AstNode currentStatement; Selection statementSelection = ExtendSelection(editor, parsedCU, commentsBlankLines, out currentStatement, new Type[] { typeof(Statement), - typeof(MemberNode), - typeof(FieldDeclaration), - typeof(ConstructorDeclaration), - typeof(DestructorDeclaration) }); + typeof(EntityDeclaration) }); if (currentStatement == null) return; statementSelection = TryExtendSelectionToComments(editor.Document, statementSelection, commentsBlankLines); // Take its sibling if (currentStatement.Parent == null) return; - var siblings = currentStatement.Parent.Children; + var siblings = currentStatement.Parent.Children.Where(c => (c.Role.GetType() == currentStatement.Role.GetType())).ToList(); int currentStatementPos = siblings.IndexOf(currentStatement); + int swapIndex = currentStatementPos + (direction == MoveStatementDirection.Down ? 1 : -1); if (swapIndex < 0 || swapIndex >= siblings.Count) return; - INode swapSibling = siblings[swapIndex]; + AstNode swapSibling = siblings[swapIndex]; + + // Expand selection to full line, if there is more than one statement in it, and set swapped sibling appropriately + AstNode tempSwapSibling = currentStatement; + int tempSwapIndex = currentStatementPos; + while ((tempSwapIndex == currentStatementPos) || (tempSwapSibling.StartLocation.Line == statementSelection.End.Line)) { + tempSwapIndex++; + if (tempSwapSibling.EndLocation > statementSelection.End) + statementSelection.End = tempSwapSibling.EndLocation; + if (tempSwapIndex >= siblings.Count) { + if (direction == MoveStatementDirection.Down) + return; + else + break; + } + tempSwapSibling = siblings[tempSwapIndex]; + if (direction == MoveStatementDirection.Down) { + swapSibling = tempSwapSibling; + swapIndex = tempSwapIndex; + } + } + tempSwapSibling = currentStatement; + tempSwapIndex = currentStatementPos; + while ((tempSwapIndex == currentStatementPos) || (tempSwapSibling.EndLocation.Line == statementSelection.Start.Line)) { + tempSwapIndex--; + if (tempSwapSibling.StartLocation < statementSelection.Start) + statementSelection.Start = tempSwapSibling.StartLocation; + if (tempSwapIndex < 0) { + if (direction == MoveStatementDirection.Up) + return; + else + break; + } + tempSwapSibling = siblings[tempSwapIndex]; + if (direction == MoveStatementDirection.Up) { + swapSibling = tempSwapSibling; + swapIndex = tempSwapIndex; + } + } + + Selection swapSiblingSelection = ExtendSelectionToComments(editor.Document, swapSibling.StartLocation, swapSibling.EndLocation, commentsBlankLines); + if (swapSiblingSelection == null) + swapSiblingSelection = new Selection() { Start = swapSibling.StartLocation, End = swapSibling.EndLocation }; + + // Expand swapSiblingSelection, too, if there are > 1 statements in line + tempSwapSibling = swapSibling; + tempSwapIndex = swapIndex; + if (direction == MoveStatementDirection.Down) { + while ((tempSwapIndex == swapIndex) || (tempSwapSibling.StartLocation.Line == swapSiblingSelection.End.Line)) { + tempSwapIndex++; + if (tempSwapSibling.EndLocation > swapSiblingSelection.End) + swapSiblingSelection.End = tempSwapSibling.EndLocation; + if (tempSwapIndex >= siblings.Count) + break; + tempSwapSibling = siblings[tempSwapIndex]; + } + } else if (direction == MoveStatementDirection.Up) { + while ((tempSwapIndex == swapIndex) || (tempSwapSibling.EndLocation.Line == swapSiblingSelection.Start.Line)) { + tempSwapIndex--; + if (tempSwapSibling.StartLocation < swapSiblingSelection.Start) + swapSiblingSelection.Start = tempSwapSibling.StartLocation; + if (tempSwapIndex < 0) + break; + tempSwapSibling = siblings[tempSwapIndex]; + } + } + // Swap them string currentNodeText = editor.Document.GetText(statementSelection.Start, statementSelection.End); - SwapText(editor.Document, statementSelection.Start, statementSelection.End, swapSibling.StartLocation, swapSibling.EndLocation); + SwapText(editor.Document, statementSelection.Start, statementSelection.End, swapSiblingSelection.Start, swapSiblingSelection.End); // Move caret to the start of moved statement - Location upperLocation = new Location[] {statementSelection.Start, swapSibling.StartLocation}.Min(); + TextLocation upperLocation = new TextLocation[] {statementSelection.Start, swapSiblingSelection.Start}.Min(); if (direction == MoveStatementDirection.Up) - editor.Caret.Position = upperLocation; + editor.Caret.Location = upperLocation; else { // look where current statement ended up because it is hard to calculate it correctly - int currentMovedOffset = editor.Document.Text.IndexOf(currentNodeText, editor.Document.PositionToOffset(upperLocation)); + int currentMovedOffset = editor.Document.Text.IndexOf(currentNodeText, editor.Document.GetOffset(upperLocation)); editor.Caret.Offset = currentMovedOffset; } } - Selection TryExtendSelectionToComments(IDocument document, Selection selection, IList commentsBlankLines) + Selection TryExtendSelectionToComments(IDocument document, Selection selection, IList commentsBlankLines) { var extendedToComments = ExtendSelectionToComments(document, selection, commentsBlankLines); if (extendedToComments != null) @@ -183,13 +249,13 @@ namespace CSharpBinding public void ExtendSelection() { - INode selectedNode = null; - IList commentsBlankLines; + AstNode selectedNode = null; + IList commentsBlankLines; var parsedCU = ParseDocument(editor, out commentsBlankLines); if (parsedCU == null) return; ISegment oldSelection = new TextSegment { StartOffset = editor.SelectionStart, Length = editor.SelectionLength }; - Selection extendedSelection = ExtendSelection(editor, parsedCU, commentsBlankLines, out selectedNode, new Type[] { typeof(INode) }); // any node type + Selection extendedSelection = ExtendSelection(editor, parsedCU, commentsBlankLines, out selectedNode, new Type[] { typeof(AstNode) }); // any node type SelectText(extendedSelection, editor); @@ -198,7 +264,7 @@ namespace CSharpBinding LoggingService.Debug("pushed: " + oldSelection); } else { LoggingService.Debug("not accepted: " + oldSelection); - } + } } public void ShrinkSelection() @@ -211,14 +277,14 @@ namespace CSharpBinding } // could work to extend selection to set of adjacent statements separated by blank lines - Selection ExtendSelection(ITextEditor editor, SyntaxTree parsedCU, IList commentsBlankLines, out INode selectedResultNode, Type[] interestingNodeTypes) + Selection ExtendSelection(ITextEditor editor, SyntaxTree parsedCU, IList commentsBlankLines, out AstNode selectedResultNode, Type[] interestingNodeTypes) { selectedResultNode = null; - var selectionStart = editor.Document.OffsetToPosition(editor.SelectionStart); - var selectionEnd = editor.Document.OffsetToPosition(editor.SelectionStart + editor.SelectionLength); + var selectionStart = editor.Document.GetLocation(editor.SelectionStart); + var selectionEnd = editor.Document.GetLocation(editor.SelectionStart + editor.SelectionLength); - Ast.INode currentNode = parsedCU.Children.Select(n => EditorContext.FindInnermostNodeContainingSelection(n, selectionStart, selectionEnd)).FirstOrDefault(n => n != null); + AstNode currentNode = parsedCU.GetNodeContaining(selectionStart, selectionEnd); if (currentNode == null) return null; if (!IsNodeTypeInteresting(currentNode, interestingNodeTypes)) { // ignore uninteresting nodes in the AST @@ -258,7 +324,7 @@ namespace CSharpBinding // 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(); + // var blankLines = commentsBlankLines.Where(s => s is BlankLine).Cast().ToList(); //if (SelectionContainsBlankLines(extendedSelectionStart, extendedLocationEnd, blankLines)) { if (false) { // blank line separators - implement later @@ -276,7 +342,7 @@ namespace CSharpBinding return new Selection { Start = selectionStart, End = selectionEnd }; } - Selection ExtendSelectionToComments(IDocument document, Selection selection, IList commentsBlankLines) + Selection ExtendSelectionToComments(IDocument document, Selection selection, IList commentsBlankLines) { if (selection == null) throw new ArgumentNullException("selection"); @@ -286,7 +352,7 @@ namespace CSharpBinding /// /// If there is a comment block immediately before selection, or behind selection on the same line, add it to selection. /// - Selection ExtendSelectionToComments(IDocument document, Location selectionStart, Location selectionEnd, IList commentsBlankLines) + Selection ExtendSelectionToComments(IDocument document, TextLocation selectionStart, TextLocation selectionEnd, IList commentsBlankLines) { var comments = commentsBlankLines.Where(s => s is Comment).Cast(); // add "var i = 5; // comments" comments @@ -302,38 +368,37 @@ namespace CSharpBinding /// /// If there is a comment block behind selection on the same line ("var i = 5; // comment"), add it to selection. /// - Selection ExtendSelectionToEndOfLineComments(IDocument document, Location selectionStart, Location selectionEnd, IEnumerable commentsBlankLines) + Selection ExtendSelectionToEndOfLineComments(IDocument document, TextLocation selectionStart, TextLocation selectionEnd, IEnumerable commentsBlankLines) { - var lineComment = commentsBlankLines.FirstOrDefault(c => c.StartPosition.Line == selectionEnd.Line && c.StartPosition >= selectionEnd); + var lineComment = commentsBlankLines.FirstOrDefault(c => c.StartLocation.Line == selectionEnd.Line && c.StartLocation >= selectionEnd); if (lineComment == null) { return null; } - bool isWholeLineSelected = IsWhitespaceBetween(document, new Location(1, selectionStart.Line), selectionStart); - if (!isWholeLineSelected) { - // whole line must be selected before we add the comment - return null; - } - // fix the end of comment set to next line incorrectly by the parser - int fixEndPos = document.PositionToOffset(lineComment.EndPosition) - 1; - return new Selection { Start = selectionStart, End = document.OffsetToPosition(fixEndPos) }; +// bool isWholeLineSelected = IsWhitespaceBetween(document, new TextLocation(selectionStart.Line, 1), selectionStart); +// if (!isWholeLineSelected) { +// // whole line must be selected before we add the comment +// return null; +// } + int endPos = document.GetOffset(lineComment.EndLocation); + return new Selection { Start = selectionStart, End = document.GetLocation(endPos) }; } /// /// If there is a comment block immediately before selection, add it to selection. /// - Selection ExtendSelectionToSeparateComments(IDocument document, Location selectionStart, Location selectionEnd, IEnumerable commentsBlankLines) + Selection ExtendSelectionToSeparateComments(IDocument document, TextLocation selectionStart, TextLocation selectionEnd, IEnumerable commentsBlankLines) { - var comments = commentsBlankLines.Where(c => c.CommentStartsLine).ToList(); - int commentIndex = comments.FindIndex(c => c.EndPosition <= selectionStart && IsWhitespaceBetween(document, c.EndPosition, selectionStart)); + var comments = commentsBlankLines.Where(c => (c is Comment) && ((Comment) c).StartsLine).ToList(); + int commentIndex = comments.FindIndex(c => c.EndLocation <= selectionStart && IsWhitespaceBetween(document, c.EndLocation, selectionStart)); if (commentIndex < 0) { return null; } var extendedSelection = new Selection { Start = selectionStart, End = selectionEnd }; // start at the selection and keep adding comments upwards as long as they are separated only by whitespace - while (commentIndex >= 0 && IsWhitespaceBetween(document, comments[commentIndex].EndPosition, extendedSelection.Start)) { + while (commentIndex >= 0 && IsWhitespaceBetween(document, comments[commentIndex].EndLocation, extendedSelection.Start)) { var comment = comments[commentIndex]; // Include the "//, /*, ///" since they are not included by the parser - extendedSelection.Start = ExtendLeft(comment.StartPosition, document, "///", "/*", "//") ; + extendedSelection.Start = ExtendLeft(comment.StartLocation, document, "///", "/*", "//") ; commentIndex--; } return extendedSelection; @@ -342,7 +407,7 @@ namespace CSharpBinding /// /// Searches for parent of interesting type. Skips uninteresting parents. /// - INode GetInterestingParent(INode node, Type[] interestingNodeTypes) + AstNode GetInterestingParent(AstNode node, Type[] interestingNodeTypes) { var parent = node.Parent; while(parent != null) { @@ -359,15 +424,15 @@ namespace CSharpBinding return interestingNodeTypes.Any(interestingType => interestingType.IsAssignableFrom(nodeType) || (nodeType == interestingType)); } - bool SelectionContainsBlankLines(Location selectionStart, Location selectionEnd, List blankLines) + bool SelectionContainsBlankLines(TextLocation selectionStart, TextLocation selectionEnd, List blankLines) { - return blankLines.Exists(b => b.StartPosition >= selectionStart && b.EndPosition <= selectionEnd); + return blankLines.Exists(b => b.StartLocation >= selectionStart && b.EndLocation <= selectionEnd); } - bool IsWhitespaceBetween(IDocument document, Location startPos, Location endPos) + bool IsWhitespaceBetween(IDocument document, TextLocation startPos, TextLocation endPos) { - int startOffset = document.PositionToOffset(startPos); - int endOffset = document.PositionToOffset(endPos); + int startOffset = document.GetOffset(startPos); + int endOffset = document.GetOffset(endPos); if (startOffset > endOffset) { throw new ArgumentException("Invalid range for (startPos, endPos)"); } @@ -378,14 +443,14 @@ namespace CSharpBinding /// If the text at startPos is preceded by any of the prefixes, moves the start backwards to include one prefix /// (the rightmost one). /// - Location ExtendLeft(Location startPos, IDocument document, params String[] prefixes) + TextLocation ExtendLeft(TextLocation startPos, IDocument document, params String[] prefixes) { - int startOffset = document.PositionToOffset(startPos); + int startOffset = document.GetOffset(startPos); foreach (string prefix in prefixes) { if (startOffset < prefix.Length) continue; string realPrefix = document.GetText(startOffset - prefix.Length, prefix.Length); if (realPrefix == prefix) { - return document.OffsetToPosition(startOffset - prefix.Length); + return document.GetLocation(startOffset - prefix.Length); } } // no prefixes -> do not extend @@ -393,42 +458,34 @@ namespace CSharpBinding } // could depend just on IDocument - SyntaxTree ParseDocument(ITextEditor editor, out IList parsedSpecials) + SyntaxTree 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()); + CompilerSettings compilerSettings = new CompilerSettings(); + var parser = new CSharpParser(); if (parser == null) return null; - parser.ParseMethodBodies = true; - parser.Lexer.SkipAllComments = false; - parser.Parse(); - var parsedCU = parser.SyntaxTree; - 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; + var syntaxTree = parser.Parse(editor.Document.CreateReader()); + if (syntaxTree == null) return null; + parsedSpecials = new List(syntaxTree.Descendants.OfType()); + return syntaxTree; } /// /// Swaps 2 ranges of text in a document. /// - void SwapText(IDocument document, Location start1, Location end1, Location start2, Location end2) + void SwapText(IDocument document, TextLocation start1, TextLocation end1, TextLocation start2, TextLocation end2) { if (start1 > start2) { - Location sw; + TextLocation sw; sw = start1; start1 = start2; start2 = sw; sw = end1; end1 = end2; end2 = sw; } if (end1 >= start2) throw new InvalidOperationException("Cannot swap overlaping segments"); - int offset1 = document.PositionToOffset(start1); - int len1 = document.PositionToOffset(end1) - offset1; - int offset2 = document.PositionToOffset(start2); - int len2 = document.PositionToOffset(end2) - offset2; + int offset1 = document.GetOffset(start1); + int len1 = document.GetOffset(end1) - offset1; + int offset2 = document.GetOffset(start2); + int len2 = document.GetOffset(end2) - offset2; string text1 = document.GetText(offset1, len1); string text2 = document.GetText(offset2, len2); @@ -445,8 +502,8 @@ namespace CSharpBinding return; int startOffset, endOffset; try { - startOffset = editor.Document.PositionToOffset(selection.Start); - endOffset = editor.Document.PositionToOffset(selection.End); + startOffset = editor.Document.GetOffset(selection.Start); + endOffset = editor.Document.GetOffset(selection.End); } catch (ArgumentOutOfRangeException) { return; } @@ -454,4 +511,3 @@ namespace CSharpBinding } } } -*/ diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionDataFactory.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionDataFactory.cs index a31ab8adde..1c1e8ff6f5 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionDataFactory.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionDataFactory.cs @@ -95,7 +95,10 @@ namespace CSharpBinding.Completion ICompletionData ICompletionDataFactory.CreateNewOverrideCompletionData(int declarationBegin, IUnresolvedTypeDefinition type, IMember m) { - return new OverrideCompletionData(declarationBegin, m, contextAtCaret); + if ((m.EntityType == EntityType.Method) && (m.Name == "ToString")) + return new OverrideToStringCompletionData(declarationBegin, m, contextAtCaret); + else + return new OverrideCompletionData(declarationBegin, m, contextAtCaret); } ICompletionData ICompletionDataFactory.CreateNewPartialCompletionData(int declarationBegin, IUnresolvedTypeDefinition type, IUnresolvedMember m) diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/OverrideCompletionData.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/OverrideCompletionData.cs index c955563ee1..286c76eaab 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/OverrideCompletionData.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/OverrideCompletionData.cs @@ -24,8 +24,8 @@ namespace CSharpBinding.Completion /// class OverrideCompletionData : EntityCompletionData { - readonly int declarationBegin; - readonly CSharpTypeResolveContext contextAtCaret; + protected readonly int declarationBegin; + protected readonly CSharpTypeResolveContext contextAtCaret; public OverrideCompletionData(int declarationBegin, IMember m, CSharpTypeResolveContext contextAtCaret) : base(m) @@ -43,6 +43,7 @@ namespace CSharpBinding.Completion base.Complete(context); return; } + TypeSystemAstBuilder b = new TypeSystemAstBuilder(new CSharpResolver(contextAtCaret)); b.ShowTypeParameterConstraints = false; b.GenerateBody = true; diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/OverrideToStringCompletionData.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/OverrideToStringCompletionData.cs new file mode 100644 index 0000000000..911ea59cfe --- /dev/null +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/OverrideToStringCompletionData.cs @@ -0,0 +1,148 @@ +// 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; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using ICSharpCode.AvalonEdit.Editing; +using ICSharpCode.AvalonEdit.Snippets; +using ICSharpCode.NRefactory.Editor; +using ICSharpCode.SharpDevelop.Parser; +using CSharpBinding.FormattingStrategy; +using CSharpBinding.Parser; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.CSharp.Refactoring; +using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.CSharp.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Editor.CodeCompletion; +using CSharpBinding.Refactoring; + +namespace CSharpBinding.Completion +{ + /// + /// Item for 'override' completion of "ToString()" methods. + /// + class OverrideToStringCompletionData : OverrideCompletionData + { + public OverrideToStringCompletionData(int declarationBegin, IMember m, CSharpTypeResolveContext contextAtCaret) + : base(declarationBegin, m, contextAtCaret) + { + } + + public override void Complete(CompletionContext context) + { + if (declarationBegin > context.StartOffset) { + base.Complete(context); + return; + } + + TypeSystemAstBuilder b = new TypeSystemAstBuilder(new CSharpResolver(contextAtCaret)); + b.ShowTypeParameterConstraints = false; + b.GenerateBody = true; + + var entityDeclaration = b.ConvertEntity(this.Entity); + entityDeclaration.Modifiers &= ~(Modifiers.Virtual | Modifiers.Abstract); + entityDeclaration.Modifiers |= Modifiers.Override; + + var body = entityDeclaration.GetChildByRole(Roles.Body); + Statement baseCallStatement = body.Children.OfType().FirstOrDefault(); + + if (!this.Entity.IsAbstract) { + // modify body to call the base method + if (this.Entity.EntityType == EntityType.Method) { + var baseCall = new BaseReferenceExpression().Invoke(this.Entity.Name, new Expression[] { }); + if (((IMethod)this.Entity).ReturnType.IsKnownType(KnownTypeCode.Void)) + baseCallStatement = new ExpressionStatement(baseCall); + else + baseCallStatement = new ReturnStatement(baseCall); + + // Clear body of inserted method + entityDeclaration.GetChildByRole(Roles.Body).Statements.Clear(); + } + } + + var document = context.Editor.Document; + StringWriter w = new StringWriter(); + var formattingOptions = FormattingOptionsFactory.CreateSharpDevelop(); + var segmentDict = SegmentTrackingOutputFormatter.WriteNode(w, entityDeclaration, formattingOptions, context.Editor.Options); + + using (document.OpenUndoGroup()) { + string newText = w.ToString().TrimEnd(); + document.Replace(declarationBegin, context.EndOffset - declarationBegin, newText); + var throwStatement = entityDeclaration.Descendants.FirstOrDefault(n => n is ThrowStatement); + if (throwStatement != null) { + var segment = segmentDict[throwStatement]; + context.Editor.Select(declarationBegin + segment.Offset, segment.Length); + } + CSharpFormatterHelper.Format(context.Editor, declarationBegin, newText.Length, formattingOptions); + + var refactoringContext = SDRefactoringContext.Create(context.Editor, CancellationToken.None); + var typeResolveContext = refactoringContext.GetTypeResolveContext(); + if (typeResolveContext == null) { + return; + } + var resolvedCurrent = typeResolveContext.CurrentTypeDefinition; + var entities = FindFieldsAndProperties(resolvedCurrent).ToList(); + if (entities.Any()) { + IEditorUIService uiService = context.Editor.GetService(typeof(IEditorUIService)) as IEditorUIService; + + ITextAnchor endAnchor = context.Editor.Document.CreateAnchor(context.Editor.Caret.Offset); + endAnchor.MovementType = AnchorMovementType.AfterInsertion; + + ITextAnchor startAnchor = context.Editor.Document.CreateAnchor(context.Editor.Caret.Offset); + startAnchor.MovementType = AnchorMovementType.BeforeInsertion; + + ITextAnchor insertionPos = context.Editor.Document.CreateAnchor(endAnchor.Offset); + insertionPos.MovementType = AnchorMovementType.BeforeInsertion; + + InsertionContext insertionContext = new InsertionContext(context.Editor.GetService(typeof(TextArea)) as TextArea, startAnchor.Offset); + + AbstractInlineRefactorDialog dialog = new OverrideToStringMethodDialog(insertionContext, context.Editor, startAnchor, insertionPos, entities, baseCallStatement); + dialog.Element = uiService.CreateInlineUIElement(insertionPos, dialog); + + insertionContext.RegisterActiveElement(new InlineRefactorSnippetElement(cxt => null, ""), dialog); + insertionContext.RaiseInsertionCompleted(EventArgs.Empty); + } + else { + if (baseCallStatement != null) { + // Add default base call + MethodDeclaration insertedOverrideMethod = refactoringContext.GetNode().PrevSibling as MethodDeclaration; + if (insertedOverrideMethod == null) + { + // We are not inside of a method declaration + return; + } + using (Script script = refactoringContext.StartScript()) { + script.AddTo(insertedOverrideMethod.Body, baseCallStatement); + } + } + } + } + } + + IEnumerable FindFieldsAndProperties(IType sourceType) + { + int i = 0; + + foreach (var f in sourceType.GetFields().Where(field => !field.IsConst + && field.IsStatic == sourceType.GetDefinition().IsStatic + && field.ReturnType != null)) { + yield return new PropertyOrFieldWrapper(f) { Index = i }; + i++; + } + + foreach (var p in sourceType.GetProperties().Where(prop => prop.CanGet && !prop.IsIndexer + && prop.IsStatic == sourceType.GetDefinition().IsStatic + && prop.ReturnType != null)) { + yield return new PropertyOrFieldWrapper(p) { Index = i }; + i++; + } + } + } +} diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/AbstractInlineRefactorDialog.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/AbstractInlineRefactorDialog.cs new file mode 100644 index 0000000000..6140d3af60 --- /dev/null +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/AbstractInlineRefactorDialog.cs @@ -0,0 +1,161 @@ +// 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.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Threading; + +using ICSharpCode.AvalonEdit.Snippets; +using ICSharpCode.Core.Presentation; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.Editor; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.SharpDevelop.Parser; +using CSharpBinding.Refactoring; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Editor; + +namespace CSharpBinding.Refactoring +{ + public abstract class AbstractInlineRefactorDialog : GroupBox, IOptionBindingContainer, IActiveElement + { + protected ITextAnchor anchor; + protected ITextAnchor insertionEndAnchor; + protected ITextEditor editor; + + protected SDRefactoringContext refactoringContext; + protected InsertionContext insertionContext; + + public IInlineUIElement Element { get; set; } + + protected AbstractInlineRefactorDialog(InsertionContext context, ITextEditor editor, ITextAnchor anchor) + { + if (context == null) + throw new ArgumentNullException("context"); + + this.anchor = insertionEndAnchor = anchor; + this.editor = editor; + this.insertionContext = context; + this.refactoringContext = SDRefactoringContext.Create(editor, CancellationToken.None); + + this.Background = SystemColors.ControlBrush; + } + + protected virtual void FocusFirstElement() + { + Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(delegate { this.MoveFocus(new TraversalRequest(FocusNavigationDirection.First)); })); + } + + protected abstract string GenerateCode(IUnresolvedTypeDefinition currentClass); + + protected virtual void OKButtonClick(object sender, RoutedEventArgs e) + { + ParseInformation parseInfo = SD.ParserService.GetCachedParseInformation(editor.FileName); + + if (optionBindings != null) { + foreach (OptionBinding binding in optionBindings) + binding.Save(); + } + + if (parseInfo != null) { + IUnresolvedTypeDefinition current = parseInfo.UnresolvedFile.GetInnermostTypeDefinition(anchor.Line, anchor.Column); + + using (editor.Document.OpenUndoGroup()) { + // GenerateCode could modify the document. + // So read anchor.Offset after code generation. + GenerateCode(current); + } + } + + Deactivate(); + } + + protected virtual void CancelButtonClick(object sender, RoutedEventArgs e) + { + Deactivate(); + } + + List optionBindings; + + public void AddBinding(OptionBinding binding) + { + if (optionBindings == null) + optionBindings = new List(); + + optionBindings.Add(binding); + } + + protected AstType ConvertType(IType type) + { + return refactoringContext.CreateShortType(type); + } + + bool IActiveElement.IsEditable { + get { return false; } + } + + ISegment IActiveElement.Segment { + get { return null; } + } + + void IActiveElement.OnInsertionCompleted() + { + OnInsertionCompleted(); + } + + protected virtual void OnInsertionCompleted() + { + FocusFirstElement(); + } + + void IActiveElement.Deactivate(SnippetEventArgs e) + { + if (e.Reason == DeactivateReason.Deleted) { + Deactivate(); + return; + } + + if (e.Reason == DeactivateReason.ReturnPressed) + OKButtonClick(null, null); + + if (e.Reason == DeactivateReason.EscapePressed) + CancelButtonClick(null, null); + + Deactivate(); + } + + bool deactivated; + + void Deactivate() + { + if (Element == null) + throw new InvalidOperationException("no IInlineUIElement set!"); + if (deactivated) + return; + + deactivated = true; + Element.Remove(); + + insertionContext.Deactivate(null); + } + + protected Key? GetAccessKeyFromButton(ContentControl control) + { + if (control == null) + return null; + string text = control.Content as string; + if (text == null) + return null; + int index = text.IndexOf('_'); + if (index < 0 || index > text.Length - 2) + return null; + char ch = text[index + 1]; + // works only for letter keys! + return (Key)new KeyConverter().ConvertFrom(ch.ToString()); + } + } +} diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CreateProperties.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CreateProperties.cs new file mode 100644 index 0000000000..91f9b03df0 --- /dev/null +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CreateProperties.cs @@ -0,0 +1,85 @@ +// 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.ComponentModel; +using System.Linq; +using System.Windows.Forms; + +using ICSharpCode.AvalonEdit.Snippets; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Dom; +using ICSharpCode.SharpDevelop.Dom.Refactoring; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Editor.AvalonEdit; +using SharpRefactoring.Gui; + +namespace CSharpBinding.Refactoring +{ + public class CreateProperties : ISnippetElementProvider + { + public SnippetElement GetElement(SnippetInfo snippetInfo) + { + if ("refactoring:propall".Equals(snippetInfo.Tag, StringComparison.OrdinalIgnoreCase)) + return new InlineRefactorSnippetElement(context => CreateDialog(context), "{" + snippetInfo.Tag + "}"); + + return null; + } + + internal static CreatePropertiesDialog CreateDialog(InsertionContext context) + { + ITextEditor textEditor = context.TextArea.GetService(typeof(ITextEditor)) as ITextEditor; + + if (textEditor == null) + return null; + + IEditorUIService uiService = textEditor.GetService(typeof(IEditorUIService)) as IEditorUIService; + + if (uiService == null) + return null; + + ParseInformation parseInfo = ParserService.GetParseInformation(textEditor.FileName); + + if (parseInfo == null) + return null; + + CodeGenerator generator = parseInfo.CompilationUnit.Language.CodeGenerator; + + // cannot use insertion position at this point, because it might not be + // valid, because we are still generating the elements. + // DOM is not updated + ICSharpCode.AvalonEdit.Document.TextLocation loc = context.Document.GetLocation(context.StartPosition); + + IClass current = parseInfo.CompilationUnit.GetInnermostClass(loc.Line, loc.Column); + + if (current == null) + return null; + + List parameters = FindFields(current).Where(f => !current.Properties.Any(p => p.Name == f.PropertyName)).ToList(); + + if (!parameters.Any()) + return null; + + ITextAnchor anchor = textEditor.Document.CreateAnchor(context.InsertionPosition); + anchor.MovementType = AnchorMovementType.BeforeInsertion; + + CreatePropertiesDialog dialog = new CreatePropertiesDialog(context, textEditor, anchor, current, parameters); + + dialog.Element = uiService.CreateInlineUIElement(anchor, dialog); + + return dialog; + } + + static IEnumerable FindFields(IClass sourceClass) + { + int i = 0; + + foreach (var f in sourceClass.Fields.Where(field => !field.IsConst + && field.ReturnType != null)) { + yield return new FieldWrapper(f) { Index = i }; + i++; + } + } + } +} diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CreatePropertiesCommand.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CreatePropertiesCommand.cs new file mode 100644 index 0000000000..c3aa3e8423 --- /dev/null +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CreatePropertiesCommand.cs @@ -0,0 +1,26 @@ +// 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 ICSharpCode.AvalonEdit.Editing; +using ICSharpCode.AvalonEdit.Snippets; +using ICSharpCode.SharpDevelop.Dom.Refactoring; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Editor.AvalonEdit; +using ICSharpCode.SharpDevelop.Refactoring; +using SharpRefactoring.Gui; + +namespace CSharpBinding.Refactoring +{ + public class CreatePropertiesCommand : AbstractRefactoringCommand + { + protected override void Run(ITextEditor textEditor, RefactoringProvider provider) + { + new Snippet { + Elements = { + new InlineRefactorSnippetElement(context => CreateProperties.CreateDialog(context), "") + } + }.Insert((TextArea)textEditor.GetService(typeof(TextArea))); + } + } +} diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CreatePropertiesDialog.xaml b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CreatePropertiesDialog.xaml new file mode 100644 index 0000000000..cb0462c44d --- /dev/null +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CreatePropertiesDialog.xaml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +