PageRenderTime 48ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Services/Plugins/src/External/Nuget/src/VsConsole/Console/Console/ConsoleDispatcher.cs

#
C# | 414 lines | 306 code | 71 blank | 37 comment | 44 complexity | ace429ea2ce1c6865bb82ae219f69ad4 MD5 | raw file
Possible License(s): BSD-3-Clause, CC-BY-SA-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, GPL-2.0, Apache-2.0
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using System.Windows.Media;
  8. using Microsoft.VisualStudio.Text;
  9. using NuGet;
  10. namespace NuGetConsole.Implementation.Console {
  11. internal interface IPrivateConsoleDispatcher : IConsoleDispatcher, IDisposable {
  12. event EventHandler<EventArgs<Tuple<SnapshotSpan, bool>>> ExecuteInputLine;
  13. void PostInputLine(InputLine inputLine);
  14. void PostKey(VsKeyInfo key);
  15. void CancelWaitKey();
  16. }
  17. /// <summary>
  18. /// This class handles input line posting and command line dispatching/execution.
  19. /// </summary>
  20. internal class ConsoleDispatcher : IPrivateConsoleDispatcher {
  21. private readonly BlockingCollection<VsKeyInfo> _keyBuffer = new BlockingCollection<VsKeyInfo>();
  22. private CancellationTokenSource _cancelWaitKeySource;
  23. private bool _isExecutingReadKey;
  24. /// <summary>
  25. /// The IPrivateWpfConsole instance this dispatcher works with.
  26. /// </summary>
  27. private IPrivateWpfConsole WpfConsole { get; set; }
  28. /// <summary>
  29. /// Child dispatcher based on host type. Its creation is postponed to Start(), so that
  30. /// a WpfConsole's dispatcher can be accessed while inside a host construction.
  31. /// </summary>
  32. private Dispatcher _dispatcher;
  33. public event EventHandler StartCompleted;
  34. public event EventHandler StartWaitingKey;
  35. public ConsoleDispatcher(IPrivateWpfConsole wpfConsole) {
  36. UtilityMethods.ThrowIfArgumentNull(wpfConsole);
  37. this.WpfConsole = wpfConsole;
  38. }
  39. public bool IsExecutingCommand {
  40. get {
  41. return (_dispatcher != null) && _dispatcher.IsExecuting;
  42. }
  43. }
  44. public void PostKey(VsKeyInfo key) {
  45. if (key == null) {
  46. throw new ArgumentNullException("key");
  47. }
  48. _keyBuffer.Add(key);
  49. }
  50. public bool IsExecutingReadKey {
  51. get { return _isExecutingReadKey; }
  52. }
  53. public bool IsKeyAvailable {
  54. get {
  55. // In our BlockingCollection<T> producer/consumer this is
  56. // not critical so no need for locking.
  57. return _keyBuffer.Count > 0;
  58. }
  59. }
  60. public void CancelWaitKey() {
  61. if (_isExecutingReadKey && !_cancelWaitKeySource.IsCancellationRequested) {
  62. _cancelWaitKeySource.Cancel();
  63. }
  64. }
  65. public VsKeyInfo WaitKey() {
  66. try {
  67. // raise the StartWaitingKey event on main thread
  68. RaiseEventSafe(StartWaitingKey);
  69. // set/reset the cancellation token
  70. _cancelWaitKeySource = new CancellationTokenSource();
  71. _isExecutingReadKey = true;
  72. // blocking call
  73. VsKeyInfo key = _keyBuffer.Take(_cancelWaitKeySource.Token);
  74. return key;
  75. }
  76. catch (OperationCanceledException) {
  77. return null;
  78. }
  79. finally {
  80. _isExecutingReadKey = false;
  81. }
  82. }
  83. private void RaiseEventSafe(EventHandler handler) {
  84. if (handler != null) {
  85. Microsoft.VisualStudio.Shell.ThreadHelper.Generic.Invoke(() => handler(this, EventArgs.Empty));
  86. }
  87. }
  88. public bool IsStartCompleted { get; private set; }
  89. #region IConsoleDispatcher
  90. public void Start() {
  91. // Only Start once
  92. if (_dispatcher == null) {
  93. IHost host = WpfConsole.Host;
  94. if (host == null) {
  95. throw new InvalidOperationException("Can't start Console dispatcher. Host is null.");
  96. }
  97. if (host is IAsyncHost) {
  98. _dispatcher = new AsyncHostConsoleDispatcher(this);
  99. }
  100. else {
  101. _dispatcher = new SyncHostConsoleDispatcher(this);
  102. }
  103. Task.Factory.StartNew(
  104. // gives the host a chance to do initialization works before the console starts accepting user inputs
  105. () => host.Initialize(WpfConsole)
  106. ).ContinueWith(
  107. task => {
  108. if (task.IsFaulted) {
  109. var exception = ExceptionUtility.Unwrap(task.Exception);
  110. WriteError(exception.Message);
  111. }
  112. if (host.IsCommandEnabled) {
  113. Microsoft.VisualStudio.Shell.ThreadHelper.Generic.Invoke(_dispatcher.Start);
  114. }
  115. RaiseEventSafe(StartCompleted);
  116. IsStartCompleted = true;
  117. },
  118. TaskContinuationOptions.NotOnCanceled
  119. );
  120. }
  121. }
  122. private void WriteError(string message) {
  123. if (WpfConsole != null) {
  124. WpfConsole.Write(message + Environment.NewLine, Colors.Red, null);
  125. }
  126. }
  127. public void ClearConsole() {
  128. Debug.Assert(_dispatcher != null);
  129. if (_dispatcher != null) {
  130. _dispatcher.ClearConsole();
  131. }
  132. }
  133. #endregion
  134. #region IPrivateConsoleDispatcher
  135. public event EventHandler<EventArgs<Tuple<SnapshotSpan, bool>>> ExecuteInputLine;
  136. void OnExecute(SnapshotSpan inputLineSpan, bool isComplete) {
  137. ExecuteInputLine.Raise(this, Tuple.Create(inputLineSpan, isComplete));
  138. }
  139. public void PostInputLine(InputLine inputLine) {
  140. Debug.Assert(_dispatcher != null);
  141. if (_dispatcher != null) {
  142. _dispatcher.PostInputLine(inputLine);
  143. }
  144. }
  145. #endregion
  146. private abstract class Dispatcher {
  147. protected ConsoleDispatcher ParentDispatcher { get; private set; }
  148. protected IPrivateWpfConsole WpfConsole { get; private set; }
  149. private bool _isExecuting;
  150. public bool IsExecuting {
  151. get {
  152. return _isExecuting;
  153. }
  154. protected set {
  155. _isExecuting = value;
  156. WpfConsole.SetExecutionMode(_isExecuting);
  157. }
  158. }
  159. protected Dispatcher(ConsoleDispatcher parentDispatcher) {
  160. ParentDispatcher = parentDispatcher;
  161. WpfConsole = parentDispatcher.WpfConsole;
  162. }
  163. /// <summary>
  164. /// Process a input line.
  165. /// </summary>
  166. /// <param name="inputLine"></param>
  167. protected Tuple<bool, bool> Process(InputLine inputLine) {
  168. SnapshotSpan inputSpan = inputLine.SnapshotSpan;
  169. if (inputLine.Flags.HasFlag(InputLineFlag.Echo)) {
  170. WpfConsole.BeginInputLine();
  171. if (inputLine.Flags.HasFlag(InputLineFlag.Execute)) {
  172. WpfConsole.WriteLine(inputLine.Text);
  173. inputSpan = WpfConsole.EndInputLine(true).Value;
  174. }
  175. else {
  176. WpfConsole.Write(inputLine.Text);
  177. }
  178. }
  179. if (inputLine.Flags.HasFlag(InputLineFlag.Execute)) {
  180. string command = inputLine.Text;
  181. bool isExecuted = WpfConsole.Host.Execute(WpfConsole, command, null);
  182. WpfConsole.InputHistory.Add(command);
  183. ParentDispatcher.OnExecute(inputSpan, isExecuted);
  184. return Tuple.Create(true, isExecuted);
  185. }
  186. return Tuple.Create(false, false);
  187. }
  188. public void PromptNewLine() {
  189. WpfConsole.Write(WpfConsole.Host.Prompt + (char)32); // 32 is the space
  190. WpfConsole.BeginInputLine();
  191. }
  192. public void ClearConsole() {
  193. // When inputting commands
  194. if (WpfConsole.InputLineStart != null) {
  195. WpfConsole.Host.Abort(); // Clear constructing multi-line command
  196. WpfConsole.Clear();
  197. PromptNewLine();
  198. }
  199. else {
  200. WpfConsole.Clear();
  201. }
  202. }
  203. public abstract void Start();
  204. public abstract void PostInputLine(InputLine inputLine);
  205. }
  206. /// <summary>
  207. /// This class dispatches inputs for synchronous hosts.
  208. /// </summary>
  209. private class SyncHostConsoleDispatcher : Dispatcher {
  210. public SyncHostConsoleDispatcher(ConsoleDispatcher parentDispatcher)
  211. : base(parentDispatcher) {
  212. }
  213. public override void Start() {
  214. PromptNewLine();
  215. }
  216. public override void PostInputLine(InputLine inputLine) {
  217. IsExecuting = true;
  218. try {
  219. if (Process(inputLine).Item1) {
  220. PromptNewLine();
  221. }
  222. }
  223. finally {
  224. IsExecuting = false;
  225. }
  226. }
  227. }
  228. /// <summary>
  229. /// This class dispatches inputs for asynchronous hosts.
  230. /// </summary>
  231. private class AsyncHostConsoleDispatcher : Dispatcher {
  232. private Queue<InputLine> _buffer;
  233. private Marshaler _marshaler;
  234. public AsyncHostConsoleDispatcher(ConsoleDispatcher parentDispatcher)
  235. : base(parentDispatcher) {
  236. _marshaler = new Marshaler(this);
  237. }
  238. private bool IsStarted {
  239. get {
  240. return _buffer != null;
  241. }
  242. }
  243. public override void Start() {
  244. if (IsStarted) {
  245. // Can only start once... ConsoleDispatcher is already protecting this.
  246. throw new InvalidOperationException();
  247. }
  248. _buffer = new Queue<InputLine>();
  249. IAsyncHost asyncHost = WpfConsole.Host as IAsyncHost;
  250. if (asyncHost == null) {
  251. // ConsoleDispatcher is already checking this.
  252. throw new InvalidOperationException();
  253. }
  254. asyncHost.ExecuteEnd += _marshaler.AsyncHost_ExecuteEnd;
  255. PromptNewLine();
  256. }
  257. public override void PostInputLine(InputLine inputLine) {
  258. // The editor should be completely readonly unless started.
  259. Debug.Assert(IsStarted);
  260. if (IsStarted) {
  261. _buffer.Enqueue(inputLine);
  262. ProcessInputs();
  263. }
  264. }
  265. private void ProcessInputs() {
  266. if (IsExecuting) {
  267. return;
  268. }
  269. if (_buffer.Count > 0) {
  270. InputLine inputLine = _buffer.Dequeue();
  271. Tuple<bool, bool> executeState = Process(inputLine);
  272. if (executeState.Item1) {
  273. IsExecuting = true;
  274. if (!executeState.Item2) {
  275. // If NOT really executed, processing the same as ExecuteEnd event
  276. OnExecuteEnd();
  277. }
  278. }
  279. }
  280. }
  281. private void OnExecuteEnd() {
  282. if (IsStarted) {
  283. // Filter out noise. A host could execute private commands.
  284. Debug.Assert(IsExecuting);
  285. IsExecuting = false;
  286. PromptNewLine();
  287. ProcessInputs();
  288. }
  289. }
  290. /// <summary>
  291. /// This private Marshaler marshals async host event to main thread so that the dispatcher
  292. /// doesn't need to worry about threading.
  293. /// </summary>
  294. private class Marshaler : Marshaler<AsyncHostConsoleDispatcher> {
  295. public Marshaler(AsyncHostConsoleDispatcher impl)
  296. : base(impl) {
  297. }
  298. public void AsyncHost_ExecuteEnd(object sender, EventArgs e) {
  299. Invoke(() => _impl.OnExecuteEnd());
  300. }
  301. }
  302. }
  303. protected virtual void Dispose(bool disposing) {
  304. if (disposing) {
  305. _keyBuffer.Dispose();
  306. _cancelWaitKeySource.Dispose();
  307. }
  308. }
  309. void IDisposable.Dispose() {
  310. try {
  311. Dispose(true);
  312. }
  313. finally {
  314. GC.SuppressFinalize(this);
  315. }
  316. }
  317. ~ConsoleDispatcher() {
  318. Dispose(false);
  319. }
  320. }
  321. [Flags]
  322. internal enum InputLineFlag {
  323. Echo = 1,
  324. Execute = 2
  325. }
  326. internal class InputLine {
  327. public SnapshotSpan SnapshotSpan { get; private set; }
  328. public string Text { get; private set; }
  329. public InputLineFlag Flags { get; private set; }
  330. public InputLine(string text, bool execute) {
  331. this.Text = text;
  332. this.Flags = InputLineFlag.Echo;
  333. if (execute) {
  334. this.Flags |= InputLineFlag.Execute;
  335. }
  336. }
  337. public InputLine(SnapshotSpan snapshotSpan) {
  338. this.SnapshotSpan = snapshotSpan;
  339. this.Text = snapshotSpan.GetText();
  340. this.Flags = InputLineFlag.Execute;
  341. }
  342. }
  343. }