33 changed files with 488 additions and 547 deletions
@ -1,264 +0,0 @@ |
|||||||
// 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.Text; |
|
||||||
using ICSharpCode.Core; |
|
||||||
using ICSharpCode.SharpDevelop; |
|
||||||
using ICSharpCode.SharpDevelop.Dom; |
|
||||||
using ICSharpCode.SharpDevelop.Dom.CSharp; |
|
||||||
using ICSharpCode.SharpDevelop.Editor; |
|
||||||
using ICSharpCode.SharpDevelop.Editor.CodeCompletion; |
|
||||||
using System.Windows.Input; |
|
||||||
|
|
||||||
namespace CSharpBinding |
|
||||||
{ |
|
||||||
public class EventHandlerCompletionItemProvider : AbstractCompletionItemProvider |
|
||||||
{ |
|
||||||
string expression; |
|
||||||
ResolveResult resolveResult; |
|
||||||
IReturnType resolvedReturnType; |
|
||||||
IClass resolvedClass; |
|
||||||
|
|
||||||
public EventHandlerCompletionItemProvider(string expression, ResolveResult resolveResult) |
|
||||||
{ |
|
||||||
this.expression = expression; |
|
||||||
this.resolveResult = resolveResult; |
|
||||||
this.resolvedReturnType = resolveResult.ResolvedType; |
|
||||||
this.resolvedClass = resolvedReturnType.GetUnderlyingClass(); |
|
||||||
} |
|
||||||
|
|
||||||
public override ICompletionItemList GenerateCompletionList(ITextEditor editor) |
|
||||||
{ |
|
||||||
DefaultCompletionItemList result = new DefaultCompletionItemList(); |
|
||||||
result.InsertSpace = true; |
|
||||||
|
|
||||||
// delegate { }
|
|
||||||
result.Items.Add(new DelegateCompletionItem("delegate { };", 3, |
|
||||||
"${res:CSharpBinding.InsertAnonymousMethod}")); |
|
||||||
|
|
||||||
CSharpAmbience ambience = new CSharpAmbience(); |
|
||||||
// get eventHandler type name incl. type argument list
|
|
||||||
ambience.ConversionFlags = ConversionFlags.ShowParameterNames | ConversionFlags.ShowTypeParameterList | ConversionFlags.UseFullyQualifiedTypeNames; |
|
||||||
string eventHandlerFullyQualifiedTypeName = ambience.Convert(resolvedReturnType); |
|
||||||
ambience.ConversionFlags = ConversionFlags.ShowParameterNames | ConversionFlags.ShowTypeParameterList; |
|
||||||
string eventHandlerTypeName = ambience.Convert(resolvedReturnType); |
|
||||||
|
|
||||||
// retrieve Invoke method from resolvedReturnType instead of resolvedClass to get a method where
|
|
||||||
// type arguments are substituted.
|
|
||||||
IMethod invoke = resolvedReturnType.GetMethods().Find(delegate(IMethod m) { return m.Name == "Invoke"; }); |
|
||||||
StringBuilder parameterString = new StringBuilder(); |
|
||||||
if (invoke != null) { |
|
||||||
|
|
||||||
// build the parameter string
|
|
||||||
for (int i = 0; i < invoke.Parameters.Count; ++i) { |
|
||||||
if (i > 0) { |
|
||||||
parameterString.Append(", "); |
|
||||||
} |
|
||||||
|
|
||||||
parameterString.Append(ambience.Convert(invoke.Parameters[i])); |
|
||||||
} |
|
||||||
|
|
||||||
// delegate(object sender, EventArgs e) { };
|
|
||||||
StringBuilder anonMethodWithParametersBuilder = |
|
||||||
new StringBuilder("delegate(").Append(parameterString.ToString()).Append(") { };"); |
|
||||||
result.Items.Add(new DelegateCompletionItem(anonMethodWithParametersBuilder.ToString(), 3, |
|
||||||
"${res:CSharpBinding.InsertAnonymousMethodWithParameters}")); |
|
||||||
|
|
||||||
// new EventHandler(ClassName_EventName);
|
|
||||||
IClass callingClass = resolveResult.CallingClass; |
|
||||||
bool inStatic = false; |
|
||||||
if (resolveResult.CallingMember != null) |
|
||||||
inStatic = resolveResult.CallingMember.IsStatic; |
|
||||||
|
|
||||||
// ...build the new handler name...
|
|
||||||
string newHandlerName = BuildHandlerName(); |
|
||||||
if (newHandlerName == null) { |
|
||||||
MemberResolveResult mrr = resolveResult as MemberResolveResult; |
|
||||||
IEvent eventMember = (mrr != null ? mrr.ResolvedMember as IEvent : null); |
|
||||||
newHandlerName = |
|
||||||
((callingClass != null) ? callingClass.Name : "callingClass") |
|
||||||
+ "_" |
|
||||||
+ ((eventMember != null) ? eventMember.Name : "eventMember"); |
|
||||||
} |
|
||||||
|
|
||||||
// ...build the completion text...
|
|
||||||
StringBuilder newHandlerTextBuilder = new StringBuilder("new ").Append(eventHandlerTypeName).Append("("); |
|
||||||
newHandlerTextBuilder.Append(newHandlerName).Append(");"); |
|
||||||
|
|
||||||
// ...build the optional new method text...
|
|
||||||
StringBuilder newHandlerCodeBuilder = new StringBuilder(); |
|
||||||
newHandlerCodeBuilder.AppendLine().AppendLine(); |
|
||||||
if (inStatic) |
|
||||||
newHandlerCodeBuilder.Append("static "); |
|
||||||
newHandlerCodeBuilder.Append(ambience.Convert(invoke.ReturnType)).Append(" ").Append(newHandlerName); |
|
||||||
newHandlerCodeBuilder.Append("(").Append(parameterString.ToString()).AppendLine(")"); |
|
||||||
newHandlerCodeBuilder.AppendLine("{"); |
|
||||||
newHandlerCodeBuilder.AppendLine("throw new NotImplementedException();"); |
|
||||||
newHandlerCodeBuilder.Append("}"); |
|
||||||
|
|
||||||
// ...and add it to the completionData.
|
|
||||||
result.Items.Add(new NewEventHandlerCompletionItem( |
|
||||||
newHandlerTextBuilder.ToString(), |
|
||||||
2+newHandlerName.Length, |
|
||||||
newHandlerName.Length, |
|
||||||
"new " + eventHandlerFullyQualifiedTypeName + |
|
||||||
"(" + newHandlerName + StringParser.Parse(")\n${res:CSharpBinding.GenerateNewHandlerInstructions}\n") |
|
||||||
+ CodeCompletionItem.ConvertDocumentation(resolvedClass.Documentation), |
|
||||||
resolveResult, |
|
||||||
newHandlerCodeBuilder.ToString() |
|
||||||
)); |
|
||||||
|
|
||||||
if (callingClass != null) { |
|
||||||
foreach (IMethod method in callingClass.DefaultReturnType.GetMethods()) { |
|
||||||
if (inStatic && !method.IsStatic) |
|
||||||
continue; |
|
||||||
if (!method.IsAccessible(callingClass, true)) |
|
||||||
continue; |
|
||||||
if (method.Parameters.Count != invoke.Parameters.Count) |
|
||||||
continue; |
|
||||||
// check return type compatibility:
|
|
||||||
if (!MemberLookupHelper.ConversionExists(method.ReturnType, invoke.ReturnType)) |
|
||||||
continue; |
|
||||||
bool ok = true; |
|
||||||
for (int i = 0; i < invoke.Parameters.Count; i++) { |
|
||||||
if (!MemberLookupHelper.ConversionExists(invoke.Parameters[i].ReturnType, method.Parameters[i].ReturnType)) { |
|
||||||
ok = false; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
if (ok) { |
|
||||||
result.Items.Add(new CodeCompletionItem(method)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
result.SortItems(); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
string BuildHandlerName() |
|
||||||
{ |
|
||||||
if (expression != null) |
|
||||||
expression = expression.Trim(); |
|
||||||
if (string.IsNullOrEmpty(expression)) |
|
||||||
return null; |
|
||||||
if (!(char.IsLetter(expression[0]) || expression[0] == '_')) |
|
||||||
return null; |
|
||||||
StringBuilder handlerNameBuilder = new StringBuilder(); |
|
||||||
for (int i = 0; i < expression.Length; i++) { |
|
||||||
if (char.IsLetterOrDigit(expression[i]) || expression[i] == '_') { |
|
||||||
handlerNameBuilder.Append(expression[i]); |
|
||||||
} else if (expression[i] == '.') { |
|
||||||
if (ICSharpCode.NRefactory.Parser.CSharp.Keywords.IsNonIdentifierKeyword(handlerNameBuilder.ToString())) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
handlerNameBuilder.Append('_'); |
|
||||||
} else { |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
return handlerNameBuilder.ToString(); |
|
||||||
} |
|
||||||
|
|
||||||
sealed class DelegateCompletionItem : DefaultCompletionItem |
|
||||||
{ |
|
||||||
int cursorOffset; |
|
||||||
|
|
||||||
public DelegateCompletionItem(string text, int cursorOffset, string documentation) |
|
||||||
: base(text) |
|
||||||
{ |
|
||||||
this.cursorOffset = cursorOffset; |
|
||||||
this.Description = StringParser.Parse(documentation); |
|
||||||
this.Image = ClassBrowserIconService.Delegate; |
|
||||||
} |
|
||||||
|
|
||||||
public override void Complete(CompletionContext context) |
|
||||||
{ |
|
||||||
base.Complete(context); |
|
||||||
context.Editor.Caret.Column -= cursorOffset; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
sealed class NewEventHandlerCompletionItem : DefaultCompletionItem |
|
||||||
{ |
|
||||||
int selectionBeginOffset; |
|
||||||
int selectionLength; |
|
||||||
ResolveResult resolveResult; |
|
||||||
string newHandlerCode; |
|
||||||
|
|
||||||
ITextEditor editor; |
|
||||||
|
|
||||||
public NewEventHandlerCompletionItem(string text, int selectionBeginOffset, int selectionLength, string documentation, ResolveResult resolveResult, string newHandlerCode) |
|
||||||
: base(text) |
|
||||||
{ |
|
||||||
this.selectionBeginOffset = selectionBeginOffset; |
|
||||||
this.selectionLength = selectionLength; |
|
||||||
this.resolveResult = resolveResult; |
|
||||||
this.newHandlerCode = newHandlerCode; |
|
||||||
|
|
||||||
this.Description = StringParser.Parse(documentation); |
|
||||||
this.Image = ClassBrowserIconService.Delegate; |
|
||||||
} |
|
||||||
|
|
||||||
public override void Complete(CompletionContext context) |
|
||||||
{ |
|
||||||
base.Complete(context); |
|
||||||
|
|
||||||
// save a reference to the relevant textArea so that we can remove our event handlers after the next keystroke
|
|
||||||
editor = context.Editor; |
|
||||||
// select suggested name
|
|
||||||
editor.Caret.Column -= this.selectionBeginOffset; |
|
||||||
editor.Select(editor.Caret.Offset, this.selectionLength); |
|
||||||
|
|
||||||
// TODO: refactor ToolTip architecture to allow for showing a tooltip relative to the current caret position so that we can show our "press TAB to create this method" text as a text-based tooltip
|
|
||||||
|
|
||||||
// TODO: skip the auto-insert step if the method already exists, or change behavior so that it moves the caret inside the existing method.
|
|
||||||
|
|
||||||
// attach our keydown filter to catch the next character pressed
|
|
||||||
editor.SelectionChanged += EditorSelectionChanged; |
|
||||||
editor.KeyPress += EditorKeyPress; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void RemoveEventHandlers() |
|
||||||
{ |
|
||||||
if (editor != null) { |
|
||||||
editor.SelectionChanged -= EditorSelectionChanged; |
|
||||||
editor.KeyPress -= EditorKeyPress; |
|
||||||
editor = null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void EditorSelectionChanged(object sender, EventArgs e) |
|
||||||
{ |
|
||||||
RemoveEventHandlers(); |
|
||||||
} |
|
||||||
|
|
||||||
void EditorKeyPress(object sender, KeyEventArgs e) |
|
||||||
{ |
|
||||||
if (e.Key == Key.Tab || e.Key == Key.Enter || e.Key == Key.Return) { |
|
||||||
using (editor.Document.OpenUndoGroup()) { |
|
||||||
|
|
||||||
// is there a better way to calculate the optimal insertion point?
|
|
||||||
DomRegion region = resolveResult.CallingMember.BodyRegion; |
|
||||||
editor.Caret.Line = region.EndLine; |
|
||||||
editor.Caret.Column = region.EndColumn; |
|
||||||
|
|
||||||
editor.Document.Insert(editor.Caret.Offset, this.newHandlerCode); |
|
||||||
|
|
||||||
editor.Language.FormattingStrategy.IndentLines(editor, region.EndLine, editor.Caret.Line); |
|
||||||
|
|
||||||
IDocumentLine line = editor.Document.GetLine(editor.Caret.Line - 1); |
|
||||||
int indentationLength = DocumentUtilitites.GetWhitespaceAfter(editor.Document, line.Offset).Length; |
|
||||||
|
|
||||||
editor.Select(line.Offset + indentationLength, line.Length - indentationLength); |
|
||||||
} |
|
||||||
e.Handled = true; |
|
||||||
} |
|
||||||
// detatch our keydown filter to return to the normal processing state
|
|
||||||
RemoveEventHandlers(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,57 +0,0 @@ |
|||||||
// 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.NRefactory; |
|
||||||
using ICSharpCode.NRefactory.Ast; |
|
||||||
using ICSharpCode.NRefactory.Visitors; |
|
||||||
using ICSharpCode.SharpDevelop.Dom; |
|
||||||
|
|
||||||
namespace CSharpBinding |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Removes members that are in the specified range.
|
|
||||||
/// </summary>
|
|
||||||
sealed class RemoveMembersInRangeVisitor : AbstractAstTransformer |
|
||||||
{ |
|
||||||
DomRegion region; |
|
||||||
|
|
||||||
public RemoveMembersInRangeVisitor(DomRegion region) |
|
||||||
{ |
|
||||||
this.region = region; |
|
||||||
} |
|
||||||
|
|
||||||
bool RemoveIfInRange(INode node) |
|
||||||
{ |
|
||||||
if (node.StartLocation.IsEmpty) |
|
||||||
return false; |
|
||||||
if (region.IsInside(node.StartLocation.Line, node.StartLocation.Column)) { |
|
||||||
if (node.EndLocation.IsEmpty || region.IsInside(node.EndLocation.Line, node.EndLocation.Column)) { |
|
||||||
RemoveCurrentNode(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
public override object VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration, object data) |
|
||||||
{ |
|
||||||
return RemoveIfInRange(propertyDeclaration) ? null : base.VisitPropertyDeclaration(propertyDeclaration, data); |
|
||||||
} |
|
||||||
|
|
||||||
public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) |
|
||||||
{ |
|
||||||
return RemoveIfInRange(methodDeclaration) ? null : base.VisitMethodDeclaration(methodDeclaration, data); |
|
||||||
} |
|
||||||
|
|
||||||
public override object VisitFieldDeclaration(FieldDeclaration fieldDeclaration, object data) |
|
||||||
{ |
|
||||||
return RemoveIfInRange(fieldDeclaration) ? null : base.VisitFieldDeclaration(fieldDeclaration, data); |
|
||||||
} |
|
||||||
|
|
||||||
public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) |
|
||||||
{ |
|
||||||
return RemoveIfInRange(typeDeclaration) ? null : base.VisitTypeDeclaration(typeDeclaration, data); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue