plugin.ts 7.6 KB

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