/IdeIntegration/Vs2010Integration/LanguageService/GherkinLanguageService.cs

https://github.com/dablo/SpecFlow · C# · 242 lines · 176 code · 48 blank · 18 comment · 46 complexity · 2bbe65d6bd7de1c15d4c7b326c37fb17 MD5 · raw file

  1. using System;
  2. using System.Linq;
  3. using System.Threading;
  4. using Microsoft.VisualStudio.Text;
  5. using TechTalk.SpecFlow.Vs2010Integration.Tracing;
  6. using TechTalk.SpecFlow.Vs2010Integration.Utils;
  7. namespace TechTalk.SpecFlow.Vs2010Integration.LanguageService
  8. {
  9. /// <summary>
  10. /// Class controlling all Gherkin (feature file) related operation in the Visual Studio editor for a given file.
  11. /// </summary>
  12. public class GherkinLanguageService : IDisposable
  13. {
  14. private readonly IProjectScope projectScope;
  15. private readonly IVisualStudioTracer visualStudioTracer;
  16. private bool isDisposed = false;
  17. private IGherkinFileScope lastGherkinFileScope = null;
  18. private ITextSnapshot lastRegisteredSnapshot = null;
  19. public IProjectScope ProjectScope
  20. {
  21. get { return projectScope; }
  22. }
  23. public GherkinLanguageService(IProjectScope projectScope, IVisualStudioTracer visualStudioTracer)
  24. {
  25. this.projectScope = projectScope;
  26. this.visualStudioTracer = visualStudioTracer;
  27. AnalyzingEnabled = projectScope.GherkinScopeAnalyzer != null;
  28. visualStudioTracer.Trace("Language service created", "GherkinLanguageService");
  29. }
  30. public void Initialize(ITextBuffer textBuffer)
  31. {
  32. // do initial parsing
  33. TextBufferChanged(GherkinTextBufferChange.CreateEntireBufferChange(textBuffer.CurrentSnapshot));
  34. projectScope.GherkinDialectServicesChanged += GherkinDialectServicesChanged;
  35. }
  36. private void GherkinDialectServicesChanged(object sender, EventArgs eventArgs)
  37. {
  38. if (lastGherkinFileScope == null || lastGherkinFileScope.TextSnapshot == null)
  39. return;
  40. TextBufferChanged(GherkinTextBufferChange.CreateEntireBufferChange(lastGherkinFileScope.TextSnapshot.TextBuffer.CurrentSnapshot));
  41. }
  42. public bool AnalyzingEnabled { get; set; }
  43. /// <summary>
  44. /// Notifies the subscribers about a change in the (parsed) file scope.
  45. /// </summary>
  46. public event EventHandler<GherkinFileScopeChange> FileScopeChanged;
  47. /// <summary>
  48. /// Registers a change in the text buffer for the language service. The processing of the change is asynchronous, so it does not block the caller.
  49. /// </summary>
  50. public void TextBufferChanged(GherkinTextBufferChange change)
  51. {
  52. if (isDisposed)
  53. throw new ObjectDisposedException("GherkinLanguageService");
  54. lastRegisteredSnapshot = change.ResultTextSnapshot;
  55. var task = new ParsingTask(this, change);
  56. projectScope.GherkinProcessingScheduler.EnqueueParsingRequest(task);
  57. // task.Apply(); // synchronous execution
  58. }
  59. private class ParsingTask : IGherkinProcessingTask
  60. {
  61. private readonly GherkinLanguageService languageService;
  62. private readonly GherkinTextBufferChange change;
  63. public ParsingTask(GherkinLanguageService languageService, GherkinTextBufferChange change)
  64. {
  65. this.languageService = languageService;
  66. this.change = change;
  67. }
  68. public void Apply()
  69. {
  70. if (languageService.isDisposed)
  71. return;
  72. var lastGherkinFileScope = languageService.GetFileScope(waitForResult: false);
  73. var scopeChange = languageService.ProjectScope.GherkinTextBufferParser.Parse(change, lastGherkinFileScope);
  74. languageService.RegisterScopeChange(scopeChange);
  75. languageService.EnqueueAnalyzingRequest(scopeChange);
  76. }
  77. public IGherkinProcessingTask Merge(IGherkinProcessingTask other)
  78. {
  79. if (other is PingTask)
  80. return this;
  81. ParsingTask otherParsingTask = other as ParsingTask;
  82. if (otherParsingTask == null || languageService != otherParsingTask.languageService)
  83. return null;
  84. return new ParsingTask(
  85. languageService, GherkinTextBufferChange.Merge(change, otherParsingTask.change));
  86. }
  87. }
  88. private void RegisterScopeChange(GherkinFileScopeChange scopeChange)
  89. {
  90. lastGherkinFileScope = scopeChange.GherkinFileScope;
  91. TriggerScopeChange(scopeChange);
  92. }
  93. private class AnalyzingTask : IGherkinProcessingTask
  94. {
  95. private readonly GherkinLanguageService languageService;
  96. private readonly GherkinFileScopeChange change;
  97. public AnalyzingTask(GherkinLanguageService languageService, GherkinFileScopeChange change)
  98. {
  99. this.languageService = languageService;
  100. this.change = change;
  101. }
  102. public void Apply()
  103. {
  104. if (languageService.isDisposed)
  105. return;
  106. var newScopeChange = languageService.ProjectScope.GherkinScopeAnalyzer.Analyze(change);
  107. if (newScopeChange != change)
  108. languageService.TriggerScopeChange(newScopeChange);
  109. }
  110. public IGherkinProcessingTask Merge(IGherkinProcessingTask other)
  111. {
  112. if (other is PingTask)
  113. return this;
  114. AnalyzingTask otherAnalyzingTask = other as AnalyzingTask;
  115. if (otherAnalyzingTask == null || languageService != otherAnalyzingTask.languageService)
  116. return null;
  117. if (otherAnalyzingTask.change.EntireScopeChanged)
  118. return otherAnalyzingTask;
  119. if (change.EntireScopeChanged)
  120. return new AnalyzingTask(languageService, GherkinFileScopeChange.CreateEntireScopeChange(otherAnalyzingTask.change.GherkinFileScope));
  121. return new AnalyzingTask(languageService, Merge(change, otherAnalyzingTask.change));
  122. }
  123. private static GherkinFileScopeChange Merge(GherkinFileScopeChange change1, GherkinFileScopeChange change2)
  124. {
  125. var ramainingChanged1Blocks = change1.ChangedBlocks.Intersect(change2.GherkinFileScope.GetAllBlocks()).ToArray();
  126. var firstChanged1 = ramainingChanged1Blocks.FirstOrDefault();
  127. var lastChanged1 = ramainingChanged1Blocks.LastOrDefault();
  128. var firstChanged2 = change2.ChangedBlocks.First();
  129. var lastChanged2 = change2.ChangedBlocks.Last();
  130. var firstChanged = firstChanged1 != null && firstChanged1.GetStartLine() < firstChanged2.GetStartLine() ? firstChanged1 : firstChanged2;
  131. var lastChanged = lastChanged1 != null && lastChanged1.GetEndLine() > lastChanged2.GetEndLine() ? lastChanged1 : lastChanged2;
  132. var changedBlocks = change2.GherkinFileScope.GetAllBlocks().SkipFromItemInclusive(firstChanged).TakeUntilItemInclusive(lastChanged);
  133. var shiftedBlocks = change1.ShiftedBlocks.Any() || change2.ShiftedBlocks.Any() ?
  134. change2.GherkinFileScope.GetAllBlocks().SkipFromItemExclusive(lastChanged) :
  135. Enumerable.Empty<IGherkinFileBlock>();
  136. return new GherkinFileScopeChange(change2.GherkinFileScope, false, false, changedBlocks, shiftedBlocks);
  137. }
  138. }
  139. private void EnqueueAnalyzingRequest(GherkinFileScopeChange scopeChange)
  140. {
  141. if (!AnalyzingEnabled)
  142. return;
  143. var task = new AnalyzingTask(this, scopeChange);
  144. projectScope.GherkinProcessingScheduler.EnqueueAnalyzingRequest(task);
  145. // task.Apply(); // synchronous execution
  146. }
  147. private void TriggerScopeChange(GherkinFileScopeChange scopeChange)
  148. {
  149. if (FileScopeChanged != null)
  150. FileScopeChanged(this, scopeChange);
  151. }
  152. /// <summary>
  153. /// Receives a parsed file scope for the buffer.
  154. /// </summary>
  155. /// <param name="waitForLatest">If true, the caller is blocked until the most recent scope is produced.</param>
  156. /// <param name="waitForParsingSnapshot"></param>
  157. /// <returns>The parsed file scope.</returns>
  158. public IGherkinFileScope GetFileScope(bool waitForLatest = false, ITextSnapshot waitForParsingSnapshot = null, bool waitForResult = true)
  159. {
  160. if (isDisposed)
  161. throw new ObjectDisposedException("GherkinLanguageService");
  162. if (!waitForResult && lastGherkinFileScope == null)
  163. return null;
  164. if (lastGherkinFileScope == null)
  165. waitForLatest = true;
  166. if (waitForLatest)
  167. {
  168. while (lastRegisteredSnapshot == null)
  169. {
  170. Thread.Sleep(TimeSpan.FromMilliseconds(50));
  171. }
  172. waitForParsingSnapshot = lastRegisteredSnapshot;
  173. }
  174. if (waitForParsingSnapshot != null)
  175. {
  176. int counter = 0;
  177. while (counter < 10 &&
  178. (lastGherkinFileScope == null || lastGherkinFileScope.TextSnapshot != waitForParsingSnapshot))
  179. {
  180. Thread.Sleep(TimeSpan.FromMilliseconds(50));
  181. counter++;
  182. }
  183. }
  184. return lastGherkinFileScope;
  185. }
  186. public void Dispose()
  187. {
  188. visualStudioTracer.Trace("Language service disposed", "GherkinLanguageService");
  189. isDisposed = true;
  190. projectScope.GherkinDialectServicesChanged -= GherkinDialectServicesChanged;
  191. lastGherkinFileScope = null;
  192. }
  193. }
  194. }