PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/Tools/IronStudio/IronStudioCore/IronStudioCore/Repl/ReplWindow.cs

http://github.com/IronLanguages/main
C# | 1582 lines | 1173 code | 239 blank | 170 comment | 238 complexity | 5fa4dd4d31e14bdba64f5c6dc0e16f7f MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception

Large files files are truncated, but you can click here to view the full file

  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation.
  4. *
  5. * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
  6. * copy of the license can be found in the License.html file at the root of this distribution. If
  7. * you cannot locate the Apache License, Version 2.0, please send an email to
  8. * ironpy@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. * by the terms of the Apache License, Version 2.0.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. * ***************************************************************************/
  14. using System;
  15. using System.Collections.Generic;
  16. using System.Diagnostics;
  17. using System.Linq;
  18. using System.Runtime.Remoting;
  19. using System.Threading;
  20. using System.Windows;
  21. using System.Windows.Controls;
  22. using System.Windows.Input;
  23. using System.Windows.Media;
  24. using System.Windows.Threading;
  25. using Microsoft.IronStudio.Core.Repl;
  26. using Microsoft.IronStudio.Repl;
  27. using Microsoft.Scripting.Utils;
  28. using Microsoft.VisualStudio.ComponentModelHost;
  29. using Microsoft.VisualStudio.Language.Intellisense;
  30. using Microsoft.VisualStudio.Text;
  31. using Microsoft.VisualStudio.Text.Classification;
  32. using Microsoft.VisualStudio.Text.Editor;
  33. using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
  34. using Microsoft.VisualStudio.Text.Operations;
  35. using Microsoft.VisualStudio.Utilities;
  36. namespace Microsoft.IronStudio.Library.Repl {
  37. public class ReplWindow : IReplWindow, IReplWindowCommands, IReplPromptProvider, IMixedBuffer, IDisposable {
  38. private DockPanel _panel;
  39. private ComboBox _scopebox;
  40. private AutoResetEvent _inputEvent = new AutoResetEvent(false);
  41. private bool _useSmartUpDown;
  42. private bool _isRunning;
  43. private bool _showOutput;
  44. private IClassifier _classifier;
  45. private IIntellisenseSessionStack _sessionStack;
  46. private ITrackingPoint _inputPoint;
  47. private ITrackingPoint _outputPoint;
  48. private Action<ReplSpan> _processResult;
  49. private Stopwatch _sw;
  50. private string _unCommittedInput;
  51. private DispatcherTimer _executionTimer;
  52. private Cursor _oldCursor;
  53. private List<IReplCommand> _commands;
  54. private IWpfTextViewHost _textViewHost;
  55. private IEditorOperations _editorOperations;
  56. private readonly IComponentModel/*!*/ _componentModel;
  57. private readonly IReplEvaluator/*!*/ _evaluator;
  58. private readonly IContentType/*!*/ _contentType;
  59. private readonly string/*!*/ _title;
  60. private readonly List<ITrackingSpan>/*!*/ _inputSpans;
  61. private readonly List<int>/*!*/ _errorInputs;
  62. private readonly List<PendingInput>/*!*/ _pendingInput;
  63. private readonly List<string>/*!*/ _demoCommands;
  64. private readonly History/*!*/ _history; // TODO: history provider should be loaded via MEF?
  65. // We use one or two regions to protect span [0, input start) from modifications:
  66. private readonly IReadOnlyRegion[]/*!*/ _readOnlyRegions;
  67. private readonly string/*!*/ _commandPrefix;
  68. private readonly string/*!*/ _prompt;
  69. private struct PendingInput {
  70. public readonly string Text;
  71. public readonly bool HasNewline;
  72. public PendingInput(string text, bool hasNewline) {
  73. Text = text;
  74. HasNewline = hasNewline;
  75. }
  76. }
  77. #region Construction
  78. public ReplWindow(IComponentModel/*!*/ model, IReplEvaluator/*!*/ evaluator, IContentType/*!*/ contentType, string/*!*/ title) {
  79. ContractUtils.RequiresNotNull(evaluator, "evaluator");
  80. ContractUtils.RequiresNotNull(contentType, "contentType");
  81. ContractUtils.RequiresNotNull(title, "title");
  82. ContractUtils.RequiresNotNull(model, "model");
  83. _componentModel = model;
  84. _evaluator = evaluator;
  85. _contentType = contentType;
  86. _title = title;
  87. _commandPrefix = _evaluator.CommandPrefix;
  88. _prompt = _evaluator.Prompt;
  89. ContractUtils.Requires(_commandPrefix != null && _prompt != null);
  90. _readOnlyRegions = new IReadOnlyRegion[2];
  91. _pendingInput = new List<PendingInput>();
  92. _inputSpans = new List<ITrackingSpan>();
  93. _errorInputs = new List<int>();
  94. _history = new History();
  95. _demoCommands = new List<string>();
  96. SetupOutput();
  97. }
  98. public void Initialize() {
  99. _textViewHost = CreateTextViewHost();
  100. var textView = _textViewHost.TextView;
  101. textView.Options.SetOptionValue(DefaultTextViewHostOptions.HorizontalScrollBarId, false);
  102. textView.Options.SetOptionValue(DefaultTextViewHostOptions.LineNumberMarginId, false);
  103. textView.Options.SetOptionValue(DefaultTextViewHostOptions.OutliningMarginId, false);
  104. textView.Options.SetOptionValue(DefaultTextViewHostOptions.GlyphMarginId, _evaluator.DisplayPromptInMargin);
  105. textView.Options.SetOptionValue(DefaultTextViewOptions.WordWrapStyleId, WordWrapStyles.WordWrap);
  106. _editorOperations = GetEditorOperations(textView);
  107. StartEvaluator();
  108. _commands = CreateCommands();
  109. CreateUI();
  110. Evaluator.TextViewCreated(this, textView);
  111. textView.TextBuffer.Properties.AddProperty(typeof(IReplEvaluator), _evaluator);
  112. //
  113. // WARNING: We must set following properties only after everything is inititalized.
  114. // If we set them earlier we would expose uninitialized repl window properties.
  115. //
  116. textView.TextBuffer.Properties.AddProperty(typeof(IMixedBuffer), this);
  117. textView.TextBuffer.Properties.AddProperty(typeof(IReplWindowCommands), this);
  118. // the margin publishes itself in the properties upon creation:
  119. // textView.TextBuffer.Properties.TryGetProperty(typeof(ReplMargin), out _margin);
  120. //if (_evaluator.DisplayPromptInMargin) {
  121. // _margin = _textViewHost.GetTextViewMargin(PredefinedMarginNames.Glyph);
  122. //}
  123. PrepareForInput();
  124. ApplyProtection();
  125. }
  126. private List<IReplCommand>/*!*/ CreateCommands() {
  127. var commands = new List<IReplCommand>();
  128. var commandTypes = new HashSet<Type>();
  129. foreach (var command in _componentModel.GetExtensions<IReplCommand>()) {
  130. // avoid duplicate commands
  131. if (commandTypes.Contains(command.GetType())) {
  132. continue;
  133. } else {
  134. commandTypes.Add(command.GetType());
  135. }
  136. commands.Add(command);
  137. }
  138. return commands;
  139. }
  140. private void CreateUI() {
  141. // background: new SolidColorBrush(Color.FromArgb(0, 188, 199, 216));
  142. _panel = new DockPanel();
  143. ToolBarTray tray = new ToolBarTray();
  144. ToolBar toolBar = new ToolBar();
  145. tray.ToolBars.Add(toolBar);
  146. tray.Background = new SolidColorBrush(Color.FromArgb(255, 173, 185, 205));
  147. toolBar.Background = new SolidColorBrush(Color.FromArgb(255, 188, 199, 216));
  148. IMultipleScopeEvaluator multiScopeEval = Evaluator as IMultipleScopeEvaluator;
  149. if (multiScopeEval != null && multiScopeEval.EnableMultipleScopes) {
  150. AddScopeBox(toolBar, multiScopeEval);
  151. }
  152. foreach (var command in _commands) {
  153. AddToolBarButton(toolBar, command);
  154. }
  155. //_toolbar.Children.Add(comboBox);
  156. DockPanel.SetDock(tray, Dock.Top);
  157. _panel.Children.Add(tray);
  158. _panel.Children.Add((UIElement)_textViewHost);
  159. }
  160. #endregion
  161. #region To Be Moved Into Contracts?
  162. public event Action ExecutionFinished;
  163. public IEditorOperations EditorOperations {
  164. get {
  165. return _editorOperations;
  166. }
  167. }
  168. public IComponentModel ComponentModel {
  169. get {
  170. return _componentModel;
  171. }
  172. }
  173. public ITextBuffer/*!*/ TextBuffer {
  174. get { return CurrentView.TextBuffer; }
  175. }
  176. public IClassifier Classifier {
  177. get {
  178. if (_classifier == null) {
  179. var aggregator = ComponentModel.GetService<IClassifierAggregatorService>();
  180. _classifier = aggregator.GetClassifier(TextBuffer);
  181. }
  182. return _classifier;
  183. }
  184. }
  185. public ITextSnapshot CurrentSnapshot {
  186. get { return TextBuffer.CurrentSnapshot; }
  187. }
  188. public string CurrentLine {
  189. get {
  190. return Caret.Position.BufferPosition.GetContainingLine().GetText();
  191. }
  192. }
  193. protected virtual IWpfTextViewHost CreateTextViewHost() {
  194. var textBufferFactoryService = ComponentModel.GetService<ITextBufferFactoryService>();
  195. var textBuffer = textBufferFactoryService.CreateTextBuffer(ContentType);
  196. // we need to set IReplProptProvider property before TextViewHost is instantiated so that ReplPromptTaggerProvider can bind to it
  197. if (Evaluator.DisplayPromptInMargin) {
  198. textBuffer.Properties.AddProperty(typeof(IReplPromptProvider), this);
  199. }
  200. ITextEditorFactoryService textEditorFactoryService = ComponentModel.GetService<ITextEditorFactoryService>();
  201. ITextViewRoleSet roles = GetReplRoles();
  202. var textView = textEditorFactoryService.CreateTextView(textBuffer, roles);
  203. return textEditorFactoryService.CreateTextViewHost(textView, false);
  204. }
  205. protected ITextViewRoleSet/*!*/ GetReplRoles() {
  206. var textEditorFactoryService = ComponentModel.GetService<ITextEditorFactoryService>();
  207. return textEditorFactoryService.CreateTextViewRoleSet(
  208. PredefinedTextViewRoles.Analyzable,
  209. PredefinedTextViewRoles.Editable,
  210. PredefinedTextViewRoles.Interactive,
  211. PredefinedTextViewRoles.Zoomable,
  212. PredefinedTextViewRoles.Document,
  213. CoreConstants.ReplTextViewRole
  214. );
  215. }
  216. #endregion
  217. #region IReplWindow
  218. /// <summary>
  219. /// See IReplWindow
  220. /// </summary>
  221. public ITextCaret Caret {
  222. get {
  223. return CurrentView.Caret;
  224. }
  225. }
  226. /// <summary>
  227. /// See IReplWindow
  228. /// </summary>
  229. public FrameworkElement/*!*/ Content {
  230. get {
  231. return _panel;
  232. }
  233. }
  234. /// <summary>
  235. /// Content type provided by the evaluator.
  236. /// </summary>
  237. public IContentType/*!*/ ContentType {
  238. get {
  239. return _contentType;
  240. }
  241. }
  242. /// <summary>
  243. /// See IReplWindow
  244. /// </summary>
  245. public IWpfTextView/*!*/ CurrentView {
  246. get {
  247. return _textViewHost.TextView;
  248. }
  249. }
  250. /// <summary>
  251. /// See IReplWindow
  252. /// </summary>
  253. public IReplEvaluator/*!*/ Evaluator {
  254. get {
  255. return _evaluator;
  256. }
  257. }
  258. /// <summary>
  259. /// See IReplWindow
  260. /// </summary>
  261. public bool ShowOutput {
  262. get {
  263. return _showOutput;
  264. }
  265. set {
  266. Evaluator.FlushOutput();
  267. _showOutput = value;
  268. }
  269. }
  270. /// <summary>
  271. /// See IReplWindow
  272. /// </summary>
  273. public string/*!*/ Title {
  274. get {
  275. return _title;
  276. }
  277. }
  278. /// <summary>
  279. /// See IReplWindow
  280. /// </summary>
  281. public void ClearScreen() {
  282. if (!CheckAccess()) {
  283. Dispatcher.Invoke(new Action(() => ClearScreen()));
  284. return;
  285. }
  286. RemoveProtection();
  287. _inputSpans.Clear();
  288. _errorInputs.Clear();
  289. _inputPoint = null;
  290. using (var edit = TextBuffer.CreateEdit()) {
  291. edit.Delete(0, CurrentSnapshot.Length);
  292. edit.Apply();
  293. }
  294. PrepareForInput();
  295. ApplyProtection();
  296. }
  297. /// <summary>
  298. /// See IReplWindow
  299. /// </summary>
  300. public void Focus() {
  301. var textView = CurrentView;
  302. IInputElement input = textView as IInputElement;
  303. if (input != null) {
  304. Keyboard.Focus(input);
  305. }
  306. }
  307. /// <summary>
  308. /// See IReplWindow
  309. /// </summary>
  310. public void PasteText(string text) {
  311. if (!CheckAccess()) {
  312. Dispatcher.BeginInvoke(new Action(() => PasteText(text)));
  313. return;
  314. }
  315. if (_isRunning) {
  316. return;
  317. }
  318. if (text.IndexOf('\n') >= 0) {
  319. if (IsCaretProtected) {
  320. _editorOperations.MoveToEndOfDocument(false);
  321. }
  322. // TODO: Use a language service to parse out individual commands rather
  323. // than pretending each line has been typed in manually
  324. _pendingInput.AddRange(SplitLines(text));
  325. ProcessPendingInput();
  326. } else {
  327. if (_editorOperations.SelectedText != "") {
  328. _editorOperations.Delete();
  329. }
  330. _editorOperations.InsertText(text);
  331. }
  332. }
  333. /// <summary>
  334. /// See IReplWindow
  335. /// </summary>
  336. public void Reset() {
  337. WriteLine("Resetting execution engine");
  338. Evaluator.Reset();
  339. }
  340. public void AbortCommand() {
  341. Evaluator.AbortCommand();
  342. EnsureNewLine();
  343. PrepareForInput();
  344. }
  345. /// <summary>
  346. /// See IReplWindow
  347. /// </summary>
  348. public void WriteLine(string text) {
  349. Write(text + _textViewHost.TextView.Options.GetNewLineCharacter());
  350. }
  351. #endregion
  352. #region IReplWindowCommands
  353. /// <summary>
  354. /// See IReplWindowCommands
  355. /// </summary>
  356. public void BreakLine() {
  357. AutoIndent.HandleReturn(this);
  358. }
  359. /// <summary>
  360. /// See IReplWindowCommands
  361. /// </summary>
  362. public void Cancel() {
  363. ClearCurrentInput();
  364. }
  365. /// <summary>
  366. /// See IReplWindowCommands
  367. /// </summary>
  368. public void SmartUpArrow() {
  369. UIThread(() => {
  370. if (!((IIntellisenseCommandTarget)this.SessionStack).ExecuteKeyboardCommand(IntellisenseKeyboardCommand.Up)) {
  371. // uparrow and downarrow at the end of input or with empty input rotate history
  372. // with multi-line input, uparrow and downarrow move around in text
  373. if (!_isRunning && CaretAtEnd && UseSmartUpDown) {
  374. HistoryPrevious();
  375. } else {
  376. _editorOperations.MoveLineUp(false);
  377. }
  378. }
  379. });
  380. }
  381. public bool UseSmartUpDown {
  382. get {
  383. return _useSmartUpDown;
  384. }
  385. set {
  386. _useSmartUpDown = value;
  387. }
  388. }
  389. public void HistoryPrevious() {
  390. var found = _history.FindPrevious("");
  391. if (found != null) {
  392. StoreUncommittedInput();
  393. SelectHistoryItem(found);
  394. }
  395. }
  396. private bool IsSingleLineInput {
  397. get {
  398. return ActiveInput.Split('\n').Length == 1;
  399. }
  400. }
  401. /// <summary>
  402. /// See IReplWindowCommands
  403. /// </summary>
  404. public void SmartDownArrow() {
  405. UIThread(() => {
  406. if (!((IIntellisenseCommandTarget)this.SessionStack).ExecuteKeyboardCommand(IntellisenseKeyboardCommand.Down)) {
  407. if (!_isRunning && CaretAtEnd && UseSmartUpDown) {
  408. HistoryNext();
  409. } else {
  410. _editorOperations.MoveLineDown(false);
  411. }
  412. }
  413. });
  414. }
  415. public void HistoryNext() {
  416. var found = _history.FindNext("");
  417. if (found != null) {
  418. StoreUncommittedInput();
  419. SelectHistoryItem(found);
  420. } else {
  421. InsertUncommittedInput();
  422. }
  423. }
  424. /// <summary>
  425. /// See IReplWindowCommands
  426. /// </summary>
  427. public void Home(bool extendSelection) {
  428. UIThread(() => {
  429. var caret = Caret;
  430. var currentInput = MakeInputSpan();
  431. if (currentInput != null) {
  432. var start = currentInput.GetSpan(CurrentSnapshot).Start;
  433. int lineNumber = CurrentSnapshot.GetLineNumberFromPosition(start.Position);
  434. if (CurrentSnapshot.GetLineNumberFromPosition(caret.Position.BufferPosition) != lineNumber) {
  435. _editorOperations.MoveToStartOfLine(extendSelection);
  436. } else if (extendSelection) {
  437. VirtualSnapshotPoint anchor = CurrentView.Selection.AnchorPoint;
  438. caret.MoveTo(start);
  439. CurrentView.Selection.Select(anchor.TranslateTo(CurrentView.TextSnapshot), CurrentView.Caret.Position.VirtualBufferPosition);
  440. } else {
  441. CurrentView.Selection.Clear();
  442. caret.MoveTo(start);
  443. }
  444. } else {
  445. _editorOperations.MoveToStartOfLine(extendSelection);
  446. }
  447. });
  448. }
  449. /// <summary>
  450. /// See IReplWindowCommands
  451. /// </summary>
  452. public bool PasteClipboard() {
  453. return UIThread(() => {
  454. // TODO:
  455. //var handler = TryGetSho();
  456. //if (handler != null) {
  457. // var data = handler();
  458. // if (data) {
  459. // var varname = Evaluator.InsertData(data, "__data");
  460. // PasteText(varname);
  461. // ExecuteText();
  462. // return true;
  463. // }
  464. //}
  465. if (Clipboard.ContainsText()) {
  466. PasteText(Clipboard.GetText());
  467. } else if (Clipboard.ContainsImage()) {
  468. var image = Clipboard.GetImage();
  469. var varname = Evaluator.InsertData(image, "__data");
  470. PasteText(varname);
  471. } else {
  472. return false;
  473. }
  474. return true;
  475. });
  476. }
  477. /// <summary>
  478. /// See IReplWindowCommands
  479. /// </summary>
  480. public bool SelectAll() {
  481. return UIThread(() => {
  482. var ts = CurrentView.Selection;
  483. var region = GetInputSpanContainingCaret();
  484. if (region != null && (ts.SelectedSpans.Count == 0 || region != ts.SelectedSpans[0])) {
  485. ts.Select(region.Value, true);
  486. return true;
  487. }
  488. return false;
  489. });
  490. }
  491. #endregion
  492. #region IMixedBuffer
  493. /// <summary>
  494. /// See IMixedBuffer
  495. /// </summary>
  496. public SnapshotSpan[]/*!*/ GetLanguageSpans(ITextSnapshot/*!*/ snapshot) {
  497. var result = new List<SnapshotSpan>();
  498. foreach (var span in GetAllSpans(snapshot)) {
  499. if (!span.WasCommand) {
  500. // All the input spans have their CRLFs stripped off. Add to the span to compensate.
  501. if (span.Input.End.Position + 2 < span.Input.Snapshot.Length) {
  502. result.Add(new SnapshotSpan(span.Input.Start, span.Input.End + 2));
  503. } else {
  504. result.Add(span.Input);
  505. }
  506. }
  507. }
  508. #if DEBUG
  509. // we should never include redundant spans
  510. for (int i = 1; i < result.Count; i++) {
  511. Debug.Assert(result[i].Start.Position > result[i - 1].Start.Position);
  512. }
  513. #endif
  514. return result.ToArray();
  515. }
  516. /// <summary>
  517. /// See IMixedBuffer
  518. /// </summary>
  519. public SnapshotSpan? GetLanguageSpanForLine(ITextSnapshot snapshot, int line) {
  520. return UIThread(() => {
  521. // check if the current input is a relevant span
  522. var currentInput = MakeInputSpan(false, snapshot);
  523. if (currentInput != null) {
  524. var res = GetSpanFromInput(snapshot, line, currentInput);
  525. if (res != null) {
  526. return res;
  527. }
  528. }
  529. // then check the previous inputs
  530. foreach (var curInput in _inputSpans) {
  531. var res = GetSpanFromInput(snapshot, line, curInput);
  532. if (res != null) {
  533. return res;
  534. }
  535. }
  536. return null;
  537. });
  538. }
  539. #endregion
  540. #region IReplProptProvider Members
  541. bool IReplPromptProvider.HasPromptForLine(ITextSnapshot/*!*/ snapshot, int lineNumber) {
  542. return UIThread(() => {
  543. if (!_isRunning &&
  544. (_inputPoint != null && snapshot.GetLineNumberFromPosition(_inputPoint.GetPosition(snapshot)) == lineNumber ||
  545. _inputPoint == null && lineNumber == snapshot.LineCount - 1)) {
  546. return true;
  547. }
  548. // TODO: bin search?
  549. foreach (var input in _inputSpans) {
  550. if (snapshot.GetLineNumberFromPosition(input.GetStartPoint(snapshot)) == lineNumber) {
  551. return true;
  552. }
  553. }
  554. return false;
  555. });
  556. }
  557. public event Action<SnapshotSpan> PromptChanged;
  558. string/*!*/ IReplPromptProvider.Prompt {
  559. get { return _prompt; }
  560. }
  561. Control/*!*/ IReplPromptProvider.HostControl {
  562. get { return _textViewHost.HostControl; }
  563. }
  564. #endregion
  565. #region IDisposable
  566. /// <summary>
  567. /// See IDisposable
  568. /// </summary>
  569. public void Dispose() {
  570. Dispose(true);
  571. }
  572. #endregion
  573. #region Protected Methods
  574. protected virtual void Dispose(bool disposing) {
  575. if (disposing) {
  576. Evaluator.Dispose();
  577. _commands = null;
  578. }
  579. }
  580. #endregion
  581. #region Internal Methods
  582. internal bool CanExecuteInput() {
  583. var input = ActiveInput;
  584. if (input.Trim().Length == 0) {
  585. // Always allow "execution" of a blank line.
  586. // This will just close the current prompt and start a new one
  587. return true;
  588. }
  589. // Ignore any whitespace past the insertion point when determining
  590. // whether or not we're at the end of the input
  591. var pt = RelativeInsertionPoint;
  592. var atEnd = (pt == input.Length) || (pt >= 0 && input.Substring(pt).Trim().Length == 0);
  593. if (!atEnd) {
  594. return false;
  595. }
  596. // A command is never multi-line, so always try to execute something which looks like a command
  597. if (input.StartsWith(_commandPrefix)) {
  598. return true;
  599. }
  600. return Evaluator.CanExecuteText(input);
  601. }
  602. #endregion
  603. #region Private Methods
  604. private Dispatcher Dispatcher {
  605. get {
  606. return ((FrameworkElement)CurrentView).Dispatcher;
  607. }
  608. }
  609. private int CurrentInsertionPoint {
  610. get { return Caret.Position.BufferPosition.Position; }
  611. }
  612. private bool CaretAtEnd {
  613. get { return CurrentInsertionPoint == CurrentSnapshot.Length; }
  614. }
  615. private string ActiveInput {
  616. get {
  617. var s = CurrentSnapshot;
  618. var p = _inputPoint.GetPosition(s);
  619. return s.GetText(p, s.Length - p);
  620. }
  621. }
  622. private int RelativeInsertionPoint {
  623. get {
  624. var p = _inputPoint.GetPosition(CurrentSnapshot);
  625. return CurrentInsertionPoint - p;
  626. }
  627. }
  628. private IIntellisenseSessionStack SessionStack {
  629. get {
  630. if (_sessionStack == null) {
  631. IIntellisenseSessionStackMapService stackMapService = ComponentModel.GetService<IIntellisenseSessionStackMapService>();
  632. _sessionStack = stackMapService.GetStackForTextView(CurrentView);
  633. }
  634. return _sessionStack;
  635. }
  636. }
  637. private bool IsCaretProtected {
  638. get {
  639. return _readOnlyRegions[0] != null &&
  640. Caret.Position.BufferPosition.Position < _readOnlyRegions[0].Span.GetStartPoint(CurrentView.TextBuffer.CurrentSnapshot).Position;
  641. }
  642. }
  643. private bool CaretInFirstInputLine {
  644. get {
  645. var pos = Caret.Position.BufferPosition.Position;
  646. var line = Caret.Position.BufferPosition.GetContainingLine().LineNumber;
  647. var currentInput = MakeInputSpan();
  648. if (currentInput == null) {
  649. return false;
  650. }
  651. var span = currentInput.GetSpan(CurrentSnapshot);
  652. return (span.Start.GetContainingLine().LineNumber == line &&
  653. pos >= span.Start.Position);
  654. }
  655. }
  656. private bool CaretInLastInputLine {
  657. get {
  658. var pos = Caret.Position.BufferPosition.Position;
  659. var line = Caret.Position.BufferPosition.GetContainingLine().LineNumber;
  660. var currentInput = MakeInputSpan();
  661. if (currentInput == null) {
  662. return false;
  663. }
  664. var span = currentInput.GetSpan(CurrentSnapshot);
  665. return (span.End.GetContainingLine().LineNumber == line);
  666. }
  667. }
  668. public bool CaretInInputRegion {
  669. get {
  670. var currentInput = MakeInputSpan(false, null);
  671. if (currentInput == null) {
  672. return false;
  673. }
  674. var span = currentInput.GetSpan(CurrentSnapshot);
  675. return Caret.Position.BufferPosition.Position >= span.Start.Position && Caret.Position.BufferPosition.Position <= span.End.Position;
  676. }
  677. }
  678. private string InputTextToCaret {
  679. get {
  680. var s = CurrentSnapshot;
  681. var p = _inputPoint.GetPosition(s);
  682. var sl = Caret.Position.BufferPosition.Position - p;
  683. return s.GetText(p, sl);
  684. }
  685. }
  686. private bool CheckAccess() {
  687. return Dispatcher.CheckAccess();
  688. }
  689. public void ExecuteText() {
  690. ExecuteText(null);
  691. }
  692. private void AppendInput(string text) {
  693. if (!CheckAccess()) {
  694. Dispatcher.BeginInvoke(new Action(() => AppendInput(text)));
  695. return;
  696. }
  697. using (var edit = TextBuffer.CreateEdit()) {
  698. edit.Insert(edit.Snapshot.Length, text);
  699. edit.Apply();
  700. }
  701. Caret.EnsureVisible();
  702. }
  703. private void EnsureNewLine() {
  704. if (!CheckAccess()) {
  705. Dispatcher.BeginInvoke(new Action(() => EnsureNewLine()));
  706. return;
  707. }
  708. AppendInput(_textViewHost.TextView.Options.GetNewLineCharacter());
  709. if (!CaretAtEnd) {
  710. _editorOperations.MoveToEndOfDocument(false);
  711. }
  712. }
  713. private ITrackingSpan MakeInputSpan() {
  714. return MakeInputSpan(false, null);
  715. }
  716. private ITrackingSpan MakeInputSpan(bool excludeCrLf, ITextSnapshot snapshot) {
  717. if (_isRunning || _inputPoint == null) {
  718. return null;
  719. }
  720. snapshot = snapshot ?? CurrentSnapshot;
  721. int pos = _inputPoint.GetPosition(snapshot);
  722. int len = snapshot.Length - pos;
  723. if (excludeCrLf && len > 1) {
  724. // remove the line feed including any trailing linespace
  725. var text = snapshot.GetText(pos, len);
  726. var endTrimmed = text.TrimEnd(' ');
  727. string newLine = _textViewHost.TextView.Options.GetNewLineCharacter();
  728. if (endTrimmed.Length > newLine.Length && String.Compare(endTrimmed, endTrimmed.Length - newLine.Length, newLine, 0, newLine.Length) == 0) {
  729. len -= newLine.Length + (text.Length - endTrimmed.Length);
  730. }
  731. }
  732. return snapshot.CreateTrackingSpan(pos, len, SpanTrackingMode.EdgeExclusive);
  733. }
  734. private void Write(string text) {
  735. PerformWrite(() => {
  736. if (_outputPoint == null || !_showOutput) {
  737. return;
  738. }
  739. using (var edit = TextBuffer.CreateEdit()) {
  740. int insertPos = _outputPoint.GetPosition(CurrentSnapshot);
  741. edit.Insert(insertPos, text);
  742. edit.Apply();
  743. }
  744. Caret.EnsureVisible();
  745. });
  746. }
  747. private void WriteOutput(object sender, Output output) {
  748. if (!CheckAccess()) {
  749. Dispatcher.BeginInvoke(new Action(() => WriteOutput(sender, output)));
  750. return;
  751. }
  752. if (output.Object != null) {
  753. if (TryShowObject(output.Object, output.Text)) {
  754. return;
  755. }
  756. WriteLine(output.Text);
  757. } else {
  758. Write(output.Text);
  759. }
  760. }
  761. private string ReadInput() {
  762. // shouldn't be called on the UI thread because we'll hang
  763. Debug.Assert(!CheckAccess());
  764. bool wasRunning = _isRunning;
  765. // TODO: What do we do if we weren't running?
  766. if (_isRunning) {
  767. RemoveProtection();
  768. _isRunning = false;
  769. }
  770. Dispatcher.Invoke(new Action(() => {
  771. _outputPoint = CurrentSnapshot.CreateTrackingPoint(CurrentSnapshot.Length, PointTrackingMode.Positive);
  772. _inputPoint = CurrentSnapshot.CreateTrackingPoint(CurrentSnapshot.Length, PointTrackingMode.Negative);
  773. }));
  774. _inputEvent.WaitOne();
  775. if (wasRunning) {
  776. ApplyProtection();
  777. _isRunning = true;
  778. }
  779. return _textViewHost.TextView.Options.GetNewLineCharacter();
  780. }
  781. private List<ITrackingSpan> GetInputSpans() {
  782. var currentInput = MakeInputSpan();
  783. if (currentInput == null) {
  784. return _inputSpans;
  785. }
  786. var result = new List<ITrackingSpan>(_inputSpans);
  787. result.Add(currentInput);
  788. return result;
  789. }
  790. private SnapshotSpan? GetInputSpanContainingCaret() {
  791. var bufferPos = Caret.Position.BufferPosition;
  792. var pos = bufferPos.Position;
  793. var s = bufferPos.Snapshot;
  794. foreach (var inputSpan in GetInputSpans()) {
  795. var span = inputSpan.GetSpan(s);
  796. if (pos >= span.Start.Position && pos <= span.End.Position) {
  797. return span;
  798. }
  799. }
  800. return null;
  801. }
  802. private bool ProcessPendingInput() {
  803. if (!CheckAccess()) {
  804. return (bool)(Dispatcher.Invoke(new Action(() => ProcessPendingInput())));
  805. }
  806. while (_pendingInput.Count > 0) {
  807. var line = _pendingInput[0];
  808. _pendingInput.RemoveAt(0);
  809. AppendInput(line.Text);
  810. _editorOperations.MoveToEndOfDocument(false);
  811. if (line.HasNewline) {
  812. if (TryExecuteInput()) {
  813. return true;
  814. }
  815. EnsureNewLine();
  816. }
  817. }
  818. return false;
  819. }
  820. private bool RemoveProtection() {
  821. bool wasProtected = false;
  822. using (var edit = TextBuffer.CreateReadOnlyRegionEdit()) {
  823. foreach (var region in _readOnlyRegions) {
  824. if (region != null) {
  825. edit.RemoveReadOnlyRegion(region);
  826. wasProtected = true;
  827. }
  828. }
  829. edit.Apply();
  830. _readOnlyRegions[0] = _readOnlyRegions[1] = null;
  831. }
  832. return wasProtected;
  833. }
  834. private void ApplyProtection() {
  835. SpanTrackingMode trackingMode;
  836. EdgeInsertionMode insertionMode;
  837. int end;
  838. if (_isRunning) {
  839. trackingMode = SpanTrackingMode.EdgeInclusive;
  840. insertionMode = EdgeInsertionMode.Deny;
  841. end = CurrentSnapshot.Length;
  842. } else {
  843. trackingMode = SpanTrackingMode.EdgeExclusive;
  844. insertionMode = EdgeInsertionMode.Allow;
  845. end = _inputPoint.GetPosition(CurrentSnapshot);
  846. }
  847. using (var edit = TextBuffer.CreateReadOnlyRegionEdit()) {
  848. IReadOnlyRegion region0 = edit.CreateReadOnlyRegion(new Span(0, end), trackingMode, insertionMode);
  849. // Create a second read-only region to prevent insert at start of buffer:
  850. IReadOnlyRegion region1 = (end > 0) ? edit.CreateReadOnlyRegion(new Span(0, 0), SpanTrackingMode.EdgeExclusive, EdgeInsertionMode.Deny) : null;
  851. edit.Apply();
  852. _readOnlyRegions[0] = region0;
  853. _readOnlyRegions[1] = region1;
  854. }
  855. }
  856. private bool ExecuteReplCommand(string commandLine) {
  857. commandLine = commandLine.Trim();
  858. IReplCommand commandFn = null;
  859. string args, command = null;
  860. if (commandLine.Length == 0 || commandLine == "help") {
  861. ShowReplHelp();
  862. return true;
  863. } else if (commandLine.Substring(0, 1) == _commandPrefix) { // TODO ??
  864. // REPL-level comment; do nothing
  865. return true;
  866. } else {
  867. command = commandLine.Split(' ')[0];
  868. args = commandLine.Substring(command.Length).Trim();
  869. commandFn = _commands.Find(x => x.Command == command);
  870. }
  871. if (commandFn == null) {
  872. WriteLine(String.Format("Unknown command '{0}', use \"{1}help\" for help", command, _commandPrefix));
  873. return false;
  874. }
  875. try {
  876. // commandFn is either an Action or Action<string>
  877. commandFn.Execute(this, args);
  878. return true;
  879. } catch (Exception e) {
  880. WriteLine(Evaluator.FormatException(new ObjectHandle(e)));
  881. return false;
  882. }
  883. }
  884. public bool TryExecuteInput() {
  885. bool tryIt = CanExecuteInput();
  886. if (tryIt) {
  887. ExecuteText();
  888. }
  889. return tryIt;
  890. }
  891. private void PerformWrite(Action action) {
  892. if (!CheckAccess()) {
  893. Dispatcher.Invoke(new Action(() => PerformWrite(action)));
  894. return;
  895. }
  896. bool wasProtected = RemoveProtection();
  897. try {
  898. action();
  899. } finally {
  900. if (wasProtected) {
  901. ApplyProtection();
  902. }
  903. }
  904. }
  905. /// <summary>
  906. /// Execute and then call the callback function with the result text.
  907. /// </summary>
  908. /// <param name="processResult"></param>
  909. internal void ExecuteText(Action<ReplSpan> processResult) {
  910. PerformWrite(() => {
  911. // Ensure that the REPL doesn't try to execute if it is already
  912. // executing. If this invariant can no longer be maintained more of
  913. // the code in this method will need to be bullet-proofed
  914. if (_isRunning) {
  915. return;
  916. }
  917. var text = ActiveInput;
  918. var span = CreateInputSpan(text);
  919. _isRunning = true;
  920. Debug.Assert(_processResult == null);
  921. _processResult = processResult;
  922. // Following method assumes that _isRunning will be cleared before
  923. // the following method is called again.
  924. StartCursorTimer();
  925. _sw = Stopwatch.StartNew();
  926. if (text.Trim().Length == 0) {
  927. // Special case to avoid round-trip when remoting
  928. FinishExecute(true, null);
  929. } else if (text.StartsWith(_commandPrefix)) {
  930. _history.Last.Command = true;
  931. var status = ExecuteReplCommand(text.Substring(_commandPrefix.Length));
  932. FinishExecute(status, null);
  933. } else if (!Evaluator.ExecuteText(ActiveInput, FinishExecute)) {
  934. _inputSpans.Add(span);
  935. FinishExecute(false, null);
  936. } else {
  937. _inputSpans.Add(span);
  938. }
  939. });
  940. }
  941. private ITrackingSpan CreateInputSpan(string text) {
  942. CurrentView.Selection.Clear();
  943. var s = CurrentSnapshot;
  944. _outputPoint = s.CreateTrackingPoint(s.Length, PointTrackingMode.Positive);
  945. var span = MakeInputSpan(true, s);
  946. if (text.Length > 0) {
  947. _history.Add(text.TrimEnd());
  948. }
  949. return span;
  950. }
  951. private void FinishExecute(bool success, ObjectHandle exception) {
  952. PerformWrite(() => {
  953. _sw.Stop();
  954. var handler = ExecutionFinished;
  955. if (handler != null) {
  956. handler();
  957. }
  958. if (_history.Last != null) {
  959. _history.Last.Duration = _sw.Elapsed.Seconds;
  960. }
  961. if (!success) {
  962. if (exception != null) {
  963. AddFileHyperlink(exception);
  964. }
  965. if (_history.Last != null) {
  966. _history.Last.Failed = true;
  967. }
  968. _errorInputs.Add(_inputSpans.Count);
  969. }
  970. PrepareForInput();
  971. var processResult = _processResult;
  972. if (processResult != null) {
  973. _processResult = null;
  974. var spans = GetAllSpans(CurrentSnapshot);
  975. var lastSpan = spans[spans.Length - 2];
  976. processResult(lastSpan);
  977. }
  978. });
  979. }
  980. private static SnapshotSpan? GetSpanFromInput(ITextSnapshot snapshot, int line, ITrackingSpan curInput) {
  981. var span = curInput.GetSpan(snapshot);
  982. var start = span.Start;
  983. var startLine = start.GetContainingLine();
  984. if (startLine.LineNumber == line) {
  985. // we are the 1st line of a user entered code block
  986. return span.Intersection(snapshot.GetLineFromPosition(span.Start.Position).Extent);
  987. } else if (startLine.LineNumber < line) {
  988. // we may be part of a multi-line block
  989. var end = span.End;
  990. var endLine = end.GetContainingLine();
  991. if (endLine.LineNumber == line) {
  992. // we are the last line of a user entered code block
  993. return span.Overlap(snapshot.GetLineFromPosition(end.Position).Extent);
  994. } else if (endLine.LineNumber > line) {
  995. // we are in the middle of a user entered block, return the entire line
  996. return snapshot.GetLineFromLineNumber(line).Extent;
  997. }
  998. }
  999. return null;
  1000. }
  1001. public void ExecuteOrPasteSelected() {
  1002. if (CaretInInputRegion) {
  1003. EnsureNewLine();
  1004. ExecuteText();
  1005. } else {
  1006. string input = GetCurrentSelectedInput(TextBuffer.CurrentSnapshot);
  1007. if (input != null) {
  1008. _editorOperations.MoveToEndOfDocument(false);
  1009. PasteText(input);
  1010. }
  1011. }
  1012. }
  1013. private string GetCurrentSelectedInput(ITextSnapshot snapshot) {
  1014. List<ITrackingSpan> inputs = GetInputSpans(snapshot);
  1015. var curPosition = Caret.Position.BufferPosition.Position;
  1016. foreach (var input in inputs) {
  1017. if (curPosition >= input.GetStartPoint(snapshot).Position &&
  1018. curPosition <= input.GetEndPoint(snapshot).Position) {
  1019. return input.GetText(snapshot);
  1020. }
  1021. }
  1022. return null;
  1023. }
  1024. private ReplSpan[] GetAllSpans(ITextSnapshot s) {
  1025. // For thread safety, clone _inputSpans on the UI thread.
  1026. // TODO: review the rest of the intellisense interface for threadsafety
  1027. List<ITrackingSpan> inputs = GetInputSpans(s);
  1028. SnapshotSpan? last = null;
  1029. var result = new List<ReplSpan>();
  1030. for (int i = 0; i < inputs.Count; i++) {
  1031. var input = inputs[i].GetSpan(s);
  1032. if (last != null) {
  1033. var eoo = input.Start - 1 - (_evaluator.DisplayPromptInMargin ? 0 : _prompt.Length);
  1034. var start = eoo;
  1035. var end = last.Value.End;
  1036. end += Math.Min(2, end.Snapshot.Length - end.Position);
  1037. if (start.Position > end.Position) {
  1038. start = end;
  1039. end = eoo;
  1040. }
  1041. var output = new SnapshotSpan(start, end);
  1042. bool wasCommand = last.Value.GetText().StartsWith(_commandPrefix); // TODO: get less text?
  1043. bool wasException = _errorInputs.Contains(i);
  1044. result.Add(new ReplSpan(wasCommand, wasException, last.Value, output));
  1045. }
  1046. last = input;
  1047. }
  1048. if (last != null) {
  1049. bool finalWasCommand = last.Value.GetText().StartsWith(_commandPrefix);
  1050. result.Add(new ReplSpan(finalWasCommand, false, last.Value, null));
  1051. }
  1052. return result.ToArray();
  1053. }
  1054. private List<ITrackingSpan> GetInputSpans(ITextSnapshot s) {
  1055. List<ITrackingSpan> inputs = null;
  1056. UIThread(() => {
  1057. inputs = new List<ITrackingSpan>(_inputSpans);
  1058. var currentInput = MakeInputSpan(false, s);
  1059. if (currentInput != null) {
  1060. inputs.Add(currentInput);
  1061. }
  1062. });
  1063. return inputs;
  1064. }
  1065. private void SetupOutput() {
  1066. _outputPoint = null;
  1067. _showOutput = true;
  1068. }
  1069. private bool TryShowObject(object obj, string textRepr) {
  1070. Image image = null;
  1071. var imageSource = obj as ImageSource;
  1072. if (imageSource != null) {
  1073. image = new Image();
  1074. image.Source = imageSource;
  1075. }
  1076. return false;
  1077. }
  1078. private void ShowReplHelp() {
  1079. var cmdnames = new List<IReplCommand>(_commands.Where(x => x.Command != null));
  1080. cmdnames.Sort((x, y) => String.Compare(x.Command, y.Command));
  1081. const string helpFmt = " {0,-16} {1}";
  1082. WriteLine(string.Format(helpFmt, "help", "Show a list of REPL commands"));
  1083. foreach (var cmd in cmdnames) {
  1084. WriteLine(string.Format(helpFmt, cmd.Command, cmd.Description));
  1085. }
  1086. }
  1087. private void AddFileHyperlink(ObjectHandle exception) {
  1088. // TODO:
  1089. }
  1090. private void PrepareForInput() {
  1091. Evaluator.FlushOutput();
  1092. int saved = CurrentSnapshot.Length;
  1093. if (!_evaluator.DisplayPromptInMargin) {
  1094. using (var edit = TextBuffer.CreateEdit()) {
  1095. // remove any leading white space which may have been inserted by auto-indent support
  1096. var containingLine = Caret.Position.BufferPosition.GetContainingLine();
  1097. if (Caret.Position.BufferPosition.Position > containingLine.Start) {
  1098. int length = Caret.Position.BufferPosition.Position - containingLine.Start.Position;
  1099. edit.Delete(new Span(containingLine.Start.Position, length));
  1100. saved -= length;
  1101. }
  1102. edit.Insert(saved, _prompt + " ");
  1103. edit.Apply();
  1104. }
  1105. }
  1106. Caret.EnsureVisible();
  1107. _outputPoint = CurrentSnapshot.CreateTrackingPoint(saved, PointTrackingMode.Positive);
  1108. _inputPoint = CurrentSnapshot.CreateTrackingPoint(CurrentSnapshot.Length, PointTrackingMode.Negative);
  1109. ResetCursor();
  1110. _isRunning = false;
  1111. _unCommittedInput = null;
  1112. ProcessPendingInput();
  1113. // we need to update margin prompt after the new _inputPoint is set:
  1114. if (_evaluator.DisplayPromptInMargin) {
  1115. var promptChanged = PromptChanged;
  1116. if (promptChanged != null) {
  1117. promptChanged(new SnapshotSpan(CurrentSnapshot, new Span(CurrentSnapshot.Length, 0)));
  1118. }
  1119. }
  1120. }
  1121. private void ResetCursor() {
  1122. if (_executionTimer != null) {
  1123. _executionTimer.Stop();
  1124. }
  1125. if (_oldCursor != null) {
  1126. ((ContentControl)CurrentView).Cursor = _oldCursor;
  1127. }
  1128. /*if (_oldCaretBrush != null) {
  1129. CurrentView.Caret.RegularBrush = _oldCaretBrush;
  1130. }*/
  1131. _oldCursor = null;
  1132. //_oldCaretBrush = null;
  1133. _executionTimer = null;
  1134. }
  1135. private void StartCursorTimer() {
  1136. // Save the old value of the caret brush so it can be restored
  1137. // after execution has finished
  1138. //_oldCaretBrush = CurrentView.Caret.RegularBrush;
  1139. // Set the caret's brush to transparent so it isn't shown blinking
  1140. // while code is executing in the REPL
  1141. //CurrentView.Caret.RegularBrush = Brushes.Transparent;
  1142. var timer = new DispatcherTimer();
  1143. timer.Tick += SetRunningCursor;
  1144. timer.Interval = new TimeSpan(0, 0, 0, 0, 250);
  1145. _executionTimer = timer;
  1146. timer.Start();
  1147. }
  1148. private void SetRunningCursor(object sender, EventArgs e) {
  1149. var view = (ContentControl)CurrentView;
  1150. // Save the old value of the cursor so it can be restored
  1151. // after execution has finished
  1152. _oldCursor = view.Cur

Large files files are truncated, but you can click here to view the full file