syntaxstatus.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import React from 'react';
  2. import { VDomRenderer, VDomModel } from '@jupyterlab/apputils';
  3. import { CodeEditor } from '@jupyterlab/codeeditor';
  4. import { IChangedArgs } from '@jupyterlab/coreutils';
  5. import {
  6. interactiveItem,
  7. Popup,
  8. showPopup,
  9. TextItem
  10. } from '@jupyterlab/statusbar';
  11. import { Mode } from '.';
  12. import { CommandRegistry } from '@lumino/commands';
  13. import { JSONObject } from '@lumino/coreutils';
  14. import { Menu } from '@lumino/widgets';
  15. /**
  16. * A namespace for `EditorSyntaxComponentStatics`.
  17. */
  18. namespace EditorSyntaxComponent {
  19. /**
  20. * The props for the `EditorSyntaxComponent`.
  21. */
  22. export interface IProps {
  23. /**
  24. * The current CodeMirror mode for an editor.
  25. */
  26. mode: string;
  27. /**
  28. * A function to execute on clicking the component.
  29. * By default we provide a function that opens a menu
  30. * for CodeMirror mode selection.
  31. */
  32. handleClick: () => void;
  33. }
  34. }
  35. /**
  36. * A pure function that returns a tsx component for an editor syntax item.
  37. *
  38. * @param props: the props for the component.
  39. *
  40. * @returns an editor syntax component.
  41. */
  42. function EditorSyntaxComponent(
  43. props: EditorSyntaxComponent.IProps
  44. ): React.ReactElement<EditorSyntaxComponent.IProps> {
  45. return <TextItem source={props.mode} onClick={props.handleClick} />;
  46. }
  47. /**
  48. * StatusBar item to change the language syntax highlighting of the file editor.
  49. */
  50. export class EditorSyntaxStatus extends VDomRenderer<EditorSyntaxStatus.Model> {
  51. /**
  52. * Construct a new VDomRenderer for the status item.
  53. */
  54. constructor(opts: EditorSyntaxStatus.IOptions) {
  55. super(new EditorSyntaxStatus.Model());
  56. this._commands = opts.commands;
  57. this.addClass(interactiveItem);
  58. this.title.caption = 'Change text editor syntax highlighting';
  59. }
  60. /**
  61. * Render the status item.
  62. */
  63. render() {
  64. if (!this.model) {
  65. return null;
  66. }
  67. return (
  68. <EditorSyntaxComponent
  69. mode={this.model.mode}
  70. handleClick={this._handleClick}
  71. />
  72. );
  73. }
  74. /**
  75. * Create a menu for selecting the mode of the editor.
  76. */
  77. private _handleClick = () => {
  78. const modeMenu = new Menu({ commands: this._commands });
  79. let command = 'codemirror:change-mode';
  80. if (this._popup) {
  81. this._popup.dispose();
  82. }
  83. Mode.getModeInfo()
  84. .sort((a, b) => {
  85. let aName = a.name || '';
  86. let bName = b.name || '';
  87. return aName.localeCompare(bName);
  88. })
  89. .forEach(spec => {
  90. if (spec.mode.indexOf('brainf') === 0) {
  91. return;
  92. }
  93. let args: JSONObject = {
  94. insertSpaces: true,
  95. name: spec.name!
  96. };
  97. modeMenu.addItem({
  98. command,
  99. args
  100. });
  101. });
  102. this._popup = showPopup({
  103. body: modeMenu,
  104. anchor: this,
  105. align: 'left'
  106. });
  107. };
  108. private _commands: CommandRegistry;
  109. private _popup: Popup | null = null;
  110. }
  111. /**
  112. * A namespace for EditorSyntax statics.
  113. */
  114. export namespace EditorSyntaxStatus {
  115. /**
  116. * A VDomModel for the current editor/mode combination.
  117. */
  118. export class Model extends VDomModel {
  119. /**
  120. * The current mode for the editor. If no editor is present,
  121. * returns the empty string.
  122. */
  123. get mode(): string {
  124. return this._mode;
  125. }
  126. /**
  127. * The current editor for the application editor tracker.
  128. */
  129. get editor(): CodeEditor.IEditor | null {
  130. return this._editor;
  131. }
  132. set editor(editor: CodeEditor.IEditor | null) {
  133. const oldEditor = this._editor;
  134. if (oldEditor !== null) {
  135. oldEditor.model.mimeTypeChanged.disconnect(this._onMIMETypeChange);
  136. }
  137. const oldMode = this._mode;
  138. this._editor = editor;
  139. if (this._editor === null) {
  140. this._mode = '';
  141. } else {
  142. const spec = Mode.findByMIME(this._editor.model.mimeType);
  143. this._mode = spec.name || spec.mode;
  144. this._editor.model.mimeTypeChanged.connect(this._onMIMETypeChange);
  145. }
  146. this._triggerChange(oldMode, this._mode);
  147. }
  148. /**
  149. * If the editor mode changes, update the model.
  150. */
  151. private _onMIMETypeChange = (
  152. mode: CodeEditor.IModel,
  153. change: IChangedArgs<string>
  154. ) => {
  155. const oldMode = this._mode;
  156. const spec = Mode.findByMIME(change.newValue);
  157. this._mode = spec.name || spec.mode;
  158. this._triggerChange(oldMode, this._mode);
  159. };
  160. /**
  161. * Trigger a rerender of the model.
  162. */
  163. private _triggerChange(oldState: string, newState: string) {
  164. if (oldState !== newState) {
  165. this.stateChanged.emit(void 0);
  166. }
  167. }
  168. private _mode: string = '';
  169. private _editor: CodeEditor.IEditor | null = null;
  170. }
  171. /**
  172. * Options for the EditorSyntax status item.
  173. */
  174. export interface IOptions {
  175. /**
  176. * The application command registry.
  177. */
  178. commands: CommandRegistry;
  179. }
  180. }