plugin.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. each
  5. } from 'phosphor/lib/algorithm/iteration';
  6. import {
  7. AttachedProperty
  8. } from 'phosphor/lib/core/properties';
  9. import {
  10. FocusTracker
  11. } from 'phosphor/lib/ui/focustracker';
  12. import {
  13. Menu
  14. } from 'phosphor/lib/ui/menu';
  15. import {
  16. JupyterLab, JupyterLabPlugin
  17. } from '../application';
  18. import {
  19. IDocumentRegistry
  20. } from '../docregistry';
  21. import {
  22. EditorWidgetFactory, EditorWidget
  23. } from './widget';
  24. import {
  25. ICommandPalette
  26. } from '../commandpalette';
  27. import {
  28. IMainMenu
  29. } from '../mainmenu';
  30. import {
  31. IEditorTracker
  32. } from './index';
  33. import {
  34. DEFAULT_CODEMIRROR_THEME
  35. } from '../codemirror/widget';
  36. import 'codemirror/addon/edit/matchbrackets.js';
  37. import 'codemirror/addon/edit/closebrackets.js';
  38. import 'codemirror/addon/comment/comment.js';
  39. import 'codemirror/keymap/vim.js';
  40. /**
  41. * The class name for all main area portrait tab icons.
  42. */
  43. const PORTRAIT_ICON_CLASS = 'jp-MainAreaPortraitIcon';
  44. /**
  45. * The class name for the text editor icon from the default theme.
  46. */
  47. const EDITOR_ICON_CLASS = 'jp-ImageTextEditor';
  48. /**
  49. * The editor handler extension.
  50. */
  51. export
  52. const editorHandlerProvider: JupyterLabPlugin<IEditorTracker> = {
  53. id: 'jupyter.services.editor-handler',
  54. requires: [IDocumentRegistry, IMainMenu, ICommandPalette],
  55. provides: IEditorTracker,
  56. activate: activateEditorHandler,
  57. autoStart: true
  58. };
  59. /**
  60. * The map of command ids used by the editor.
  61. */
  62. const cmdIds = {
  63. lineNumbers: 'editor:line-numbers',
  64. lineWrap: 'editor:line-wrap',
  65. matchBrackets: 'editor:match-brackets',
  66. vimMode: 'editor:vim-mode',
  67. closeAll: 'editor:close-all',
  68. changeTheme: 'editor:change-theme',
  69. createConsole: 'editor:create-console',
  70. runCode: 'editor:run-code'
  71. };
  72. /**
  73. * Sets up the editor widget
  74. */
  75. function activateEditorHandler(app: JupyterLab, registry: IDocumentRegistry, mainMenu: IMainMenu, palette: ICommandPalette): IEditorTracker {
  76. let tracker = new FocusTracker<EditorWidget>();
  77. let widgetFactory = new EditorWidgetFactory();
  78. widgetFactory.widgetCreated.connect((sender, widget) => {
  79. widget.title.icon = `${PORTRAIT_ICON_CLASS} ${EDITOR_ICON_CLASS}`;
  80. tracker.add(widget);
  81. });
  82. registry.addWidgetFactory(widgetFactory,
  83. {
  84. fileExtensions: ['*'],
  85. displayName: 'Editor',
  86. modelName: 'text',
  87. defaultFor: ['*'],
  88. preferKernel: false,
  89. canStartKernel: false
  90. });
  91. mainMenu.addMenu(createMenu(app, tracker), {rank: 30});
  92. let commands = app.commands;
  93. commands.addCommand(cmdIds.lineNumbers, {
  94. execute: () => { toggleLineNums(tracker); },
  95. label: 'Toggle Line Numbers',
  96. });
  97. commands.addCommand(cmdIds.lineWrap, {
  98. execute: () => { toggleLineWrap(tracker); },
  99. label: 'Toggle Line Wrap',
  100. });
  101. commands.addCommand(cmdIds.matchBrackets, {
  102. execute: () => { toggleMatchBrackets(tracker); },
  103. label: 'Toggle Match Brackets',
  104. });
  105. commands.addCommand(cmdIds.vimMode, {
  106. execute: () => { toggleVim(tracker); },
  107. label: 'Toggle Vim Mode'
  108. });
  109. commands.addCommand(cmdIds.closeAll, {
  110. execute: () => { closeAllFiles(tracker); },
  111. label: 'Close all files'
  112. });
  113. commands.addCommand(cmdIds.createConsole, {
  114. execute: () => {
  115. let widget = tracker.currentWidget;
  116. if (!widget) {
  117. return;
  118. }
  119. let options: any = {
  120. path: widget.context.path,
  121. preferredLanguage: widget.context.model.defaultKernelLanguage
  122. };
  123. commands.execute('console:create', options).then(id => {
  124. sessionIdProperty.set(widget, id);
  125. });
  126. },
  127. label: 'Create Console for Editor'
  128. });
  129. commands.addCommand(cmdIds.runCode, {
  130. execute: () => {
  131. let widget = tracker.currentWidget;
  132. if (!widget) {
  133. return;
  134. }
  135. // Get the session id.
  136. let id = sessionIdProperty.get(widget);
  137. if (!id) {
  138. return;
  139. }
  140. // Get the selected code from the editor.
  141. let doc = widget.editor.getDoc();
  142. let code = doc.getSelection();
  143. if (!code) {
  144. let { line } = doc.getCursor();
  145. code = doc.getLine(line);
  146. }
  147. commands.execute('console:inject', { id, code });
  148. },
  149. label: 'Run Code',
  150. });
  151. [
  152. cmdIds.lineNumbers,
  153. cmdIds.lineWrap,
  154. cmdIds.matchBrackets,
  155. cmdIds.vimMode,
  156. cmdIds.closeAll,
  157. cmdIds.createConsole,
  158. cmdIds.runCode,
  159. ].forEach(command => palette.addItem({ command, category: 'Editor' }));
  160. return tracker;
  161. }
  162. /**
  163. * An attached property for the session id associated with an editor widget.
  164. */
  165. const sessionIdProperty = new AttachedProperty<EditorWidget, string>({ name: 'sessionId' });
  166. /**
  167. * Toggle editor line numbers
  168. */
  169. function toggleLineNums(tracker: IEditorTracker) {
  170. if (tracker.currentWidget) {
  171. let editor = tracker.currentWidget.editor;
  172. editor.setOption('lineNumbers', !editor.getOption('lineNumbers'));
  173. }
  174. }
  175. /**
  176. * Toggle editor line wrap
  177. */
  178. function toggleLineWrap(tracker: IEditorTracker) {
  179. if (tracker.currentWidget) {
  180. let editor = tracker.currentWidget.editor;
  181. editor.setOption('lineWrapping', !editor.getOption('lineWrapping'));
  182. }
  183. }
  184. /**
  185. * Toggle editor matching brackets
  186. */
  187. function toggleMatchBrackets(tracker: IEditorTracker) {
  188. if (tracker.currentWidget) {
  189. let editor = tracker.currentWidget.editor;
  190. editor.setOption('matchBrackets', !editor.getOption('matchBrackets'));
  191. }
  192. }
  193. /**
  194. * Toggle the editor's vim mode
  195. */
  196. function toggleVim(tracker: IEditorTracker) {
  197. each(tracker.widgets, widget => {
  198. widget.editor.setOption('keyMap',
  199. (widget.editor.getOption('keyMap')==='vim'
  200. ? 'default' : 'vim'));
  201. });
  202. }
  203. /**
  204. * Close all currently open text editor files
  205. */
  206. function closeAllFiles(tracker: IEditorTracker) {
  207. each(tracker.widgets, widget => {
  208. widget.close();
  209. });
  210. }
  211. /**
  212. * Create a menu for the editor.
  213. */
  214. function createMenu(app: JupyterLab, tracker: IEditorTracker): Menu {
  215. let { commands, keymap } = app;
  216. let settings = new Menu({ commands, keymap });
  217. let theme = new Menu({ commands, keymap });
  218. let menu = new Menu({ commands, keymap });
  219. menu.title.label = 'Editor';
  220. settings.title.label = 'Settings';
  221. theme.title.label = 'Theme';
  222. settings.addItem({ command: cmdIds.lineNumbers });
  223. settings.addItem({ command: cmdIds.lineWrap });
  224. settings.addItem({ command: cmdIds.matchBrackets });
  225. settings.addItem({ command: cmdIds.vimMode });
  226. commands.addCommand(cmdIds.changeTheme, {
  227. label: args => {
  228. return args['theme'] as string;
  229. },
  230. execute: args => {
  231. let name: string = args['theme'] as string || DEFAULT_CODEMIRROR_THEME;
  232. each(tracker.widgets, widget => {
  233. widget.editor.setOption('theme', name);
  234. });
  235. }
  236. });
  237. [
  238. 'jupyter', 'default', 'abcdef', 'base16-dark', 'base16-light',
  239. 'hopscotch', 'material', 'mbo', 'mdn-like', 'seti', 'the-matrix',
  240. 'xq-light', 'zenburn'
  241. ].forEach(name => theme.addItem({
  242. command: 'editor:change-theme',
  243. args: { theme: name }
  244. }));
  245. menu.addItem({ command: 'file-operations:new-text-file' });
  246. menu.addItem({ command: 'file-operations:save' });
  247. menu.addItem({ command: cmdIds.closeAll });
  248. menu.addItem({ type: 'separator' });
  249. menu.addItem({ type: 'submenu', menu: settings });
  250. menu.addItem({ type: 'submenu', menu: theme });
  251. return menu;
  252. }