diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs index a99852e7cf..77030adb9c 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs @@ -82,7 +82,10 @@ namespace ICSharpCode.AvalonEdit.AddIn.Snippets new CodeSnippet { Name = "switch", Description = "Switch statement", - Text = "switch (${condition}) {\n\tcase ${firstcase=0}:\n\t\t${Caret}\n\t\tbreak;\n\tdefault:\n\t\t${Selection}\n\t\tbreak;\n}" + // dynamic switch snippet (inserts switch body dependent on condition) + Text = "switch (${condition}) {\n${refactoring:switchbody}}" + // static switch snippet (always inserts the same, independent of condition) + //Text = "switch (${condition}) {\n\tcase ${firstcase=0}:\n\t\t${Caret}\n\t\tbreak;\n\tdefault:\n\t\t${Selection}\n\t\tbreak;\n}" }, new CodeSnippet { Name = "try", diff --git a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin index 796c6ebcaf..7c2e86ccf0 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin +++ b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.addin @@ -48,6 +48,7 @@ + diff --git a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj index 6e543107f9..e1c5959aad 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj +++ b/src/AddIns/Misc/SharpRefactoring/Project/SharpRefactoring.csproj @@ -107,6 +107,8 @@ + + diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/SwitchBodySnippetElement.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/SwitchBodySnippetElement.cs new file mode 100644 index 0000000000..dcde451ae4 --- /dev/null +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/SwitchBodySnippetElement.cs @@ -0,0 +1,218 @@ +// +// +// +// +// $Revision: $ +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ICSharpCode.AvalonEdit.Snippets; +using ICSharpCode.Core; +using ICSharpCode.NRefactory; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Dom; +using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver; +using ICSharpCode.SharpDevelop.Dom.Refactoring; +using ICSharpCode.SharpDevelop.Editor; + +namespace SharpRefactoring +{ + /// + /// The snippet element inserted inside the body curly braces. + /// When snippet interactive mode ends, resolves type of switch condition and generates switch cases. + /// + public class SwitchBodySnippetElement : SnippetElement//, IActiveElement would add this.Segment to know where to insert + { + InsertionContext context; + + /// + /// The ITextEditor in which this switch body element has been inserted. + /// + public ITextEditor Editor { + get { return context.TextArea.GetService(typeof(ITextEditor)) as ITextEditor; } + } + + /// + /// Called when this switch body element is inserted to the editor. + /// + public override void Insert(InsertionContext context) + { + this.context = context; + this.context.Deactivated += new EventHandler(InteractiveModeCompleted); + } + + /// + /// Main logic of switch spinnet. Called when user ends the snippet interactive mode. + /// Inserts switch body depending on the type of switch condition: + /// Inserts all cases if condition is enum, generic switch body with one case otherwise. + /// + void InteractiveModeCompleted(object sender, SnippetEventArgs e) + { + if (e.Reason != DeactivateReason.ReturnPressed) + return; + + int offset; + // conditionText is currently not needed, + // could be useful if NRefactory had a public method for resolving string expressions + // (NRefactoryResolver.ParseExpression is private) + string conditionText = GetSwitchConditionText(this.context, out offset); + IReturnType conditionType = ResolveConditionType(offset); + + int lineStart = this.Editor.Document.GetLineForOffset(offset).Offset; + // indent the whole switch body appropriately + string indent = DocumentUtilitites.GetWhitespaceAfter(this.Editor.Document, lineStart); + indent += '\t'; + // switch body starts at next line + int switchBodyOffset = GetNextLineStart(this.Editor.Document, offset); + + if (conditionType != null && IsEnum(conditionType)) { + GenerateEnumSwitchBodyCode(switchBodyOffset, conditionType, indent); + } else { + GenerateGenericSwitchBodyCode(switchBodyOffset, indent); + } + } + + /// + /// Inserts switch body for enum to the given offset. + /// + /// Whole text will be indented by this value. + void GenerateEnumSwitchBodyCode(int offset, IReturnType enumType, string indent) + { + string switchBody = GetSwitchBodyCode(enumType, indent, GetCodeGeneratorForCurrentFile()); + this.Editor.Document.Insert(offset, switchBody); + } + + /// + /// Inserts generic switch body to the given offset. + /// + /// Whole text will be indented by this value. + void GenerateGenericSwitchBodyCode(int offset, string indent) + { + string switchBody = GetGenericBodyCode(indent, GetCodeGeneratorForCurrentFile()); + this.Editor.Document.Insert(offset, switchBody); + } + + // TODO this should use CodeGenerator and build a BlockStatement to work for both C# and VB + string GetSwitchBodyCode(IReturnType enumType, string indent, CodeGenerator generator) + { + if (generator == null) + return string.Empty; + StringBuilder sb = new StringBuilder(); + foreach (var enumCase in GetEnumCases(enumType)) { + sb.AppendLine(string.Format(indent + "case {0}:", enumCase.Name)); + sb.AppendLine(indent + '\t'); + sb.AppendLine(indent + "\tbreak;"); + } + sb.AppendLine(indent + "default:"); + sb.AppendLine(string.Format(indent + "\tthrow new Exception(\"Invalid value for {0}\");", enumType.Name)); + return sb.ToString(); + } + + // TODO this should use CodeGenerator and build a BlockStatement to work for both C# and VB + string GetGenericBodyCode(string indent, CodeGenerator generator) + { + if (generator == null) + return string.Empty; + StringBuilder sb = new StringBuilder(); + sb.AppendLine(indent + "case :"); + sb.AppendLine(indent + '\t'); + sb.AppendLine(indent + "\tbreak;"); + sb.AppendLine(indent + "default:"); + sb.AppendLine(indent + '\t'); + sb.AppendLine(indent + "\tbreak;"); + return sb.ToString(); + } + + bool IsEnum(IReturnType type) + { + var typeClass = type.GetUnderlyingClass(); + if (typeClass == null) + // eg. MethodGroup type has no UnderlyingClass + return false; + return typeClass.BaseClass.DotNetName == "System.Enum"; + } + + /// + /// Gets enum values out of enum type. + /// + IEnumerable GetEnumCases(IReturnType enumType) + { + var typeClass = enumType.GetUnderlyingClass(); + if (typeClass == null) + // eg. MethodGroup type has no UnderlyingClass + yield break; + foreach (var enumValue in typeClass.AllMembers) { + yield return enumValue as DefaultField; + } + } + + /// + /// Assuming that interactive mode of 'switch' snippet has currently finished in context, + /// returns the switch condition that user entered. + /// + string GetSwitchConditionText(InsertionContext context, out int startOffset) + { + var snippetActiveElements = context.ActiveElements.ToList(); + if (snippetActiveElements.Count == 0) + throw new InvalidOperationException("Switch snippet should have at least one active element"); + var switchConditionElement = snippetActiveElements[0] as IReplaceableActiveElement; + if (switchConditionElement == null) + throw new InvalidOperationException("Switch snippet condition should be " + typeof(IReplaceableActiveElement).Name); + if (switchConditionElement.Segment == null) + throw new InvalidOperationException("Swith condition should have a start offset"); + startOffset = switchConditionElement.Segment.EndOffset - 1; + return switchConditionElement.Text; + } + + /// + /// Resolves the Dom.IReturnType of expression ending at offset, ie. the switch condition expression. + /// + IReturnType ResolveConditionType(int offset) + { + // this could be just solved by making NRefactoryResolver.ParseExpression public and passing the string + // - no need for offset and expressionFinder / ExpressionResult + ITextEditor textEditor = this.Editor; + if (textEditor == null) + return null; + IExpressionFinder expressionFinder = ParserService.GetExpressionFinder(textEditor.FileName); + if (expressionFinder == null) + return null; + ExpressionResult expressionResult = expressionFinder.FindFullExpression(textEditor.Document.Text, offset); + + Location location = textEditor.Document.OffsetToPosition(offset); + var result = ParserService.Resolve(expressionResult, location.Line, location.Column, textEditor.FileName, textEditor.Document.Text); + return result.ResolvedType; + + /* + // alternate way to ParserService.Resolve + var resolver = new NRefactoryResolver(textEditor.Language.Properties); + ResolveResult rr = resolver.Resolve(expressionResult, ParserService.GetParseInformation(textEditor.FileName), textEditor.Document.Text); + + return rr.ResolvedType;*/ + } + + /// + /// Returns CodeGenerator for C# or VB depending on the current file where snippet is being inserted. + /// + CodeGenerator GetCodeGeneratorForCurrentFile() + { + ParseInformation parseInfo = ParserService.GetParseInformation(this.Editor.FileName); + if (parseInfo != null) { + return parseInfo.CompilationUnit.Language.CodeGenerator; + } + return null; + } + + /// + /// Get start of the next line (that is, from offset, go one line down and to its start). + /// + int GetNextLineStart(IDocument document, int offset) + { + int nextLineNuber = document.GetLineForOffset(offset).LineNumber + 1; + return document.GetLine(nextLineNuber).Offset; + } + } +} diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/SwitchSnippetProvider.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/SwitchSnippetProvider.cs new file mode 100644 index 0000000000..5e9cbcc3d5 --- /dev/null +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/SwitchSnippetProvider.cs @@ -0,0 +1,30 @@ +// +// +// +// +// $Revision: $ +// +using System; +using ICSharpCode.AvalonEdit.Snippets; +using ICSharpCode.SharpDevelop.Editor.AvalonEdit; + +namespace SharpRefactoring +{ + /// + /// Description of SwitchSnippetProvider. + /// + public class SwitchSnippetProvider : ISnippetElementProvider + { + public SwitchSnippetProvider() + { + } + + public SnippetElement GetElement(string tag) + { + if (tag.Equals("refactoring:switchbody", StringComparison.InvariantCultureIgnoreCase)) + return new SwitchBodySnippetElement(); + + return null; + } + } +}