handler.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { CodeEditor } from '@jupyterlab/codeeditor';
  4. import { IDataConnector, Text, Debouncer } from '@jupyterlab/coreutils';
  5. import { MimeModel, IRenderMimeRegistry } from '@jupyterlab/rendermime';
  6. import { ReadonlyJSONObject } from '@phosphor/coreutils';
  7. import { IDisposable } from '@phosphor/disposable';
  8. import { ISignal, Signal } from '@phosphor/signaling';
  9. import { IInspector } from './tokens';
  10. /**
  11. * An object that handles code inspection.
  12. */
  13. export class InspectionHandler implements IDisposable, IInspector.IInspectable {
  14. /**
  15. * Construct a new inspection handler for a widget.
  16. */
  17. constructor(options: InspectionHandler.IOptions) {
  18. this._connector = options.connector;
  19. this._rendermime = options.rendermime;
  20. this._debouncer = new Debouncer(this.onEditorChange.bind(this), 250);
  21. }
  22. /**
  23. * A signal emitted when the inspector should clear all items.
  24. */
  25. get cleared(): ISignal<InspectionHandler, void> {
  26. return this._cleared;
  27. }
  28. /**
  29. * A signal emitted when the handler is disposed.
  30. */
  31. get disposed(): ISignal<InspectionHandler, void> {
  32. return this._disposed;
  33. }
  34. /**
  35. * A signal emitted when an inspector value is generated.
  36. */
  37. get inspected(): ISignal<InspectionHandler, IInspector.IInspectorUpdate> {
  38. return this._inspected;
  39. }
  40. /**
  41. * The editor widget used by the inspection handler.
  42. */
  43. get editor(): CodeEditor.IEditor | null {
  44. return this._editor;
  45. }
  46. set editor(newValue: CodeEditor.IEditor | null) {
  47. if (newValue === this._editor) {
  48. return;
  49. }
  50. // Remove all of our listeners.
  51. Signal.disconnectReceiver(this);
  52. let editor = (this._editor = newValue);
  53. if (editor) {
  54. // Clear the inspector in preparation for a new editor.
  55. this._cleared.emit(void 0);
  56. // Call onEditorChange to cover the case where the user changes
  57. // the active cell
  58. this.onEditorChange();
  59. editor.model.selections.changed.connect(this._onChange, this);
  60. editor.model.value.changed.connect(this._onChange, this);
  61. }
  62. }
  63. /**
  64. * Indicates whether the handler makes API inspection requests or stands by.
  65. *
  66. * #### Notes
  67. * The use case for this attribute is to limit the API traffic when no
  68. * inspector is visible.
  69. */
  70. get standby(): boolean {
  71. return this._standby;
  72. }
  73. set standby(value: boolean) {
  74. this._standby = value;
  75. }
  76. /**
  77. * Get whether the inspection handler is disposed.
  78. *
  79. * #### Notes
  80. * This is a read-only property.
  81. */
  82. get isDisposed(): boolean {
  83. return this._isDisposed;
  84. }
  85. /**
  86. * Dispose of the resources used by the handler.
  87. */
  88. dispose(): void {
  89. if (this.isDisposed) {
  90. return;
  91. }
  92. this._isDisposed = true;
  93. this._disposed.emit(void 0);
  94. Signal.clearData(this);
  95. }
  96. /**
  97. * Handle a text changed signal from an editor.
  98. *
  99. * #### Notes
  100. * Update the hints inspector based on a text change.
  101. */
  102. protected onEditorChange(): void {
  103. // If the handler is in standby mode, bail.
  104. if (this._standby) {
  105. return;
  106. }
  107. const editor = this.editor;
  108. if (!editor) {
  109. return;
  110. }
  111. const text = editor.model.value.text;
  112. const position = editor.getCursorPosition();
  113. const offset = Text.jsIndexToCharIndex(editor.getOffsetAt(position), text);
  114. const update: IInspector.IInspectorUpdate = { content: null };
  115. const pending = ++this._pending;
  116. void this._connector
  117. .fetch({ offset, text })
  118. .then(reply => {
  119. // If handler has been disposed or a newer request is pending, bail.
  120. if (this.isDisposed || pending !== this._pending) {
  121. this._inspected.emit(update);
  122. return;
  123. }
  124. const { data } = reply;
  125. const mimeType = this._rendermime.preferredMimeType(data);
  126. if (mimeType) {
  127. const widget = this._rendermime.createRenderer(mimeType);
  128. const model = new MimeModel({ data });
  129. void widget.renderModel(model);
  130. update.content = widget;
  131. }
  132. this._inspected.emit(update);
  133. })
  134. .catch(reason => {
  135. // Since almost all failures are benign, fail silently.
  136. this._inspected.emit(update);
  137. });
  138. }
  139. /**
  140. * Handle changes to the editor state, debouncing.
  141. */
  142. private _onChange(): void {
  143. void this._debouncer.invoke();
  144. }
  145. private _cleared = new Signal<InspectionHandler, void>(this);
  146. private _connector: IDataConnector<
  147. InspectionHandler.IReply,
  148. void,
  149. InspectionHandler.IRequest
  150. >;
  151. private _disposed = new Signal<this, void>(this);
  152. private _editor: CodeEditor.IEditor | null = null;
  153. private _inspected = new Signal<this, IInspector.IInspectorUpdate>(this);
  154. private _isDisposed = false;
  155. private _pending = 0;
  156. private _rendermime: IRenderMimeRegistry;
  157. private _standby = true;
  158. private _debouncer: Debouncer;
  159. }
  160. /**
  161. * A namespace for inspection handler statics.
  162. */
  163. export namespace InspectionHandler {
  164. /**
  165. * The instantiation options for an inspection handler.
  166. */
  167. export interface IOptions {
  168. /**
  169. * The connector used to make inspection requests.
  170. *
  171. * #### Notes
  172. * The only method of this connector that will ever be called is `fetch`, so
  173. * it is acceptable for the other methods to be simple functions that return
  174. * rejected promises.
  175. */
  176. connector: IDataConnector<IReply, void, IRequest>;
  177. /**
  178. * The mime renderer for the inspection handler.
  179. */
  180. rendermime: IRenderMimeRegistry;
  181. }
  182. /**
  183. * A reply to an inspection request.
  184. */
  185. export interface IReply {
  186. /**
  187. * The MIME bundle data returned from an inspection request.
  188. */
  189. data: ReadonlyJSONObject;
  190. /**
  191. * Any metadata that accompanies the MIME bundle returning from a request.
  192. */
  193. metadata: ReadonlyJSONObject;
  194. }
  195. /**
  196. * The details of an inspection request.
  197. */
  198. export interface IRequest {
  199. /**
  200. * The cursor offset position within the text being inspected.
  201. */
  202. offset: number;
  203. /**
  204. * The text being inspected.
  205. */
  206. text: string;
  207. }
  208. }