PageRenderTime 42ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/EditorFeatures/CSharp/EventHookup/EventHookupSessionManager_EventHookupSession.cs

https://gitlab.com/sharadag/Roslyn
C# | 267 lines | 205 code | 39 blank | 23 comment | 25 complexity | c9c8fdd428de57dd879d24f03295099e MD5 | raw file
  1. // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  2. using System;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Microsoft.CodeAnalysis.CSharp;
  7. using Microsoft.CodeAnalysis.CSharp.Extensions;
  8. using Microsoft.CodeAnalysis.CSharp.Syntax;
  9. using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
  10. using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
  11. using Microsoft.CodeAnalysis.Internal.Log;
  12. using Microsoft.CodeAnalysis.LanguageServices;
  13. using Microsoft.CodeAnalysis.Shared.Extensions;
  14. using Microsoft.CodeAnalysis.Shared.TestHooks;
  15. using Microsoft.CodeAnalysis.Shared.Utilities;
  16. using Microsoft.CodeAnalysis.Text;
  17. using Microsoft.VisualStudio.Text;
  18. using Microsoft.VisualStudio.Text.Editor;
  19. using Roslyn.Utilities;
  20. namespace Microsoft.CodeAnalysis.Editor.CSharp.EventHookup
  21. {
  22. internal sealed partial class EventHookupSessionManager
  23. {
  24. /// <summary>
  25. /// A session begins when an '=' is typed after a '+' and requires determining whether the
  26. /// += is being used to add an event handler to an event. If it is, then we also determine
  27. /// a candidate name for the event handler.
  28. /// </summary>
  29. internal class EventHookupSession : ForegroundThreadAffinitizedObject
  30. {
  31. public readonly Task<string> GetEventNameTask;
  32. private readonly CancellationTokenSource _cancellationTokenSource;
  33. private readonly ITrackingPoint _trackingPoint;
  34. private readonly ITrackingSpan _trackingSpan;
  35. private readonly ITextView _textView;
  36. private readonly ITextBuffer _subjectBuffer;
  37. public event Action Dismissed = () => { };
  38. // For testing purposes only! Should always be null except in tests.
  39. internal Mutex TESTSessionHookupMutex = null;
  40. public ITrackingPoint TrackingPoint
  41. {
  42. get
  43. {
  44. AssertIsForeground();
  45. return _trackingPoint;
  46. }
  47. }
  48. public ITrackingSpan TrackingSpan
  49. {
  50. get
  51. {
  52. AssertIsForeground();
  53. return _trackingSpan;
  54. }
  55. }
  56. public ITextView TextView
  57. {
  58. get
  59. {
  60. AssertIsForeground();
  61. return _textView;
  62. }
  63. }
  64. public ITextBuffer SubjectBuffer
  65. {
  66. get
  67. {
  68. AssertIsForeground();
  69. return _subjectBuffer;
  70. }
  71. }
  72. public void Cancel()
  73. {
  74. AssertIsForeground();
  75. _cancellationTokenSource.Cancel();
  76. }
  77. public EventHookupSession(
  78. EventHookupSessionManager eventHookupSessionManager,
  79. EventHookupCommandHandler commandHandler,
  80. ITextView textView,
  81. ITextBuffer subjectBuffer,
  82. IAsynchronousOperationListener asyncListener,
  83. Mutex testSessionHookupMutex)
  84. {
  85. AssertIsForeground();
  86. _cancellationTokenSource = new CancellationTokenSource();
  87. _textView = textView;
  88. _subjectBuffer = subjectBuffer;
  89. this.TESTSessionHookupMutex = testSessionHookupMutex;
  90. var document = textView.TextSnapshot.GetOpenDocumentInCurrentContextWithChanges();
  91. if (document != null && document.Project.Solution.Workspace.CanApplyChange(ApplyChangesKind.ChangeDocument))
  92. {
  93. var position = textView.GetCaretPoint(subjectBuffer).Value.Position;
  94. _trackingPoint = textView.TextSnapshot.CreateTrackingPoint(position, PointTrackingMode.Negative);
  95. _trackingSpan = textView.TextSnapshot.CreateTrackingSpan(new Span(position, 1), SpanTrackingMode.EdgeInclusive);
  96. var asyncToken = asyncListener.BeginAsyncOperation(GetType().Name + ".Start");
  97. this.GetEventNameTask = Task.Factory.SafeStartNewFromAsync(
  98. () => DetermineIfEventHookupAndGetHandlerNameAsync(document, position, _cancellationTokenSource.Token),
  99. _cancellationTokenSource.Token,
  100. TaskScheduler.Default);
  101. var continuedTask = this.GetEventNameTask.SafeContinueWith(t =>
  102. {
  103. AssertIsForeground();
  104. if (t.Result != null)
  105. {
  106. commandHandler.EventHookupSessionManager.EventHookupFoundInSession(this);
  107. }
  108. },
  109. _cancellationTokenSource.Token,
  110. TaskContinuationOptions.OnlyOnRanToCompletion,
  111. ForegroundThreadAffinitizedObject.CurrentForegroundThreadData.TaskScheduler);
  112. continuedTask.CompletesAsyncOperation(asyncToken);
  113. }
  114. else
  115. {
  116. _trackingPoint = textView.TextSnapshot.CreateTrackingPoint(0, PointTrackingMode.Negative);
  117. _trackingSpan = textView.TextSnapshot.CreateTrackingSpan(new Span(), SpanTrackingMode.EdgeInclusive);
  118. this.GetEventNameTask = SpecializedTasks.Default<string>();
  119. eventHookupSessionManager.CancelAndDismissExistingSessions();
  120. }
  121. }
  122. private async Task<string> DetermineIfEventHookupAndGetHandlerNameAsync(Document document, int position, CancellationToken cancellationToken)
  123. {
  124. AssertIsBackground();
  125. // For test purposes only!
  126. if (TESTSessionHookupMutex != null)
  127. {
  128. TESTSessionHookupMutex.WaitOne();
  129. TESTSessionHookupMutex.ReleaseMutex();
  130. }
  131. using (Logger.LogBlock(FunctionId.EventHookup_Determine_If_Event_Hookup, cancellationToken))
  132. {
  133. var plusEqualsToken = await GetPlusEqualsTokenInsideAddAssignExpressionAsync(document, position, cancellationToken).ConfigureAwait(false);
  134. if (plusEqualsToken == null)
  135. {
  136. return null;
  137. }
  138. var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
  139. var eventSymbol = GetEventSymbol(semanticModel, plusEqualsToken.Value, cancellationToken);
  140. if (eventSymbol == null)
  141. {
  142. return null;
  143. }
  144. return GetEventHandlerName(eventSymbol, plusEqualsToken.Value, semanticModel, document.GetLanguageService<ISyntaxFactsService>());
  145. }
  146. }
  147. private async Task<SyntaxToken?> GetPlusEqualsTokenInsideAddAssignExpressionAsync(Document document, int position, CancellationToken cancellationToken)
  148. {
  149. AssertIsBackground();
  150. var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
  151. var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
  152. if (token.Kind() != SyntaxKind.PlusEqualsToken)
  153. {
  154. return null;
  155. }
  156. if (!token.Parent.IsKind(SyntaxKind.AddAssignmentExpression))
  157. {
  158. return null;
  159. }
  160. return token;
  161. }
  162. private IEventSymbol GetEventSymbol(SemanticModel semanticModel, SyntaxToken plusEqualsToken, CancellationToken cancellationToken)
  163. {
  164. AssertIsBackground();
  165. var parentToken = plusEqualsToken.Parent as AssignmentExpressionSyntax;
  166. if (parentToken == null)
  167. {
  168. return null;
  169. }
  170. var symbol = semanticModel.GetSymbolInfo(parentToken.Left, cancellationToken).Symbol;
  171. if (symbol == null)
  172. {
  173. return null;
  174. }
  175. return symbol as IEventSymbol;
  176. }
  177. private string GetEventHandlerName(IEventSymbol eventSymbol, SyntaxToken plusEqualsToken, SemanticModel semanticModel, ISyntaxFactsService syntaxFactsService)
  178. {
  179. AssertIsBackground();
  180. var basename = string.Format("{0}_{1}", GetNameObjectPart(eventSymbol, plusEqualsToken, semanticModel, syntaxFactsService), eventSymbol.Name);
  181. basename = basename.ToPascalCase(trimLeadingTypePrefix: false);
  182. var reservedNames = semanticModel.LookupSymbols(plusEqualsToken.SpanStart).Select(m => m.Name);
  183. return NameGenerator.EnsureUniqueness(basename, reservedNames);
  184. }
  185. /// <summary>
  186. /// Take another look at the LHS of the += node -- we need to figure out a default name
  187. /// for the event handler, and that's usually based on the object (which is usually a
  188. /// field of 'this', but not always) to which the event belongs. So, if the event is
  189. /// something like 'button1.Click' or 'this.listBox1.Select', we want the names
  190. /// 'button1' and 'listBox1' respectively. If the field belongs to 'this', then we use
  191. /// the name of this class, as we do if we can't make any sense out of the parse tree.
  192. /// </summary>
  193. private string GetNameObjectPart(IEventSymbol eventSymbol, SyntaxToken plusEqualsToken, SemanticModel semanticModel, ISyntaxFactsService syntaxFactsService)
  194. {
  195. AssertIsBackground();
  196. var parentToken = plusEqualsToken.Parent as AssignmentExpressionSyntax;
  197. var memberAccessExpression = parentToken.Left as MemberAccessExpressionSyntax;
  198. if (memberAccessExpression != null)
  199. {
  200. // This is expected -- it means the last thing is(probably) the event name. We
  201. // already have that in eventSymbol. What we need is the LHS of that dot.
  202. var lhs = memberAccessExpression.Expression;
  203. var lhsMemberAccessExpression = lhs as MemberAccessExpressionSyntax;
  204. if (lhsMemberAccessExpression != null)
  205. {
  206. // Okay, cool. The name we're after is in the RHS of this dot.
  207. return lhsMemberAccessExpression.Name.ToString();
  208. }
  209. var lhsNameSyntax = lhs as NameSyntax;
  210. if (lhsNameSyntax != null)
  211. {
  212. // Even easier -- the LHS of the dot is the name itself
  213. return lhsNameSyntax.ToString();
  214. }
  215. }
  216. // If we didn't find an object name above, then the object name is the name of this class.
  217. // Note: For generic, it's ok(it's even a good idea) to exclude type variables,
  218. // because the name is only used as a prefix for the method name.
  219. var typeDeclaration = syntaxFactsService.GetContainingTypeDeclaration(
  220. semanticModel.SyntaxTree.GetRoot(),
  221. plusEqualsToken.SpanStart) as BaseTypeDeclarationSyntax;
  222. return typeDeclaration != null
  223. ? typeDeclaration.Identifier.Text
  224. : eventSymbol.ContainingType.Name;
  225. }
  226. }
  227. }
  228. }