index.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /*
  2. * Copyright 2018-2022 Elyra Authors
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import { ScriptEditorWidgetFactory, ScriptEditor } from '@elyra/script-editor';
  17. import { pyIcon } from '@elyra/ui-components';
  18. import {
  19. JupyterFrontEnd,
  20. JupyterFrontEndPlugin,
  21. ILayoutRestorer
  22. } from '@jupyterlab/application';
  23. import { WidgetTracker, ICommandPalette } from '@jupyterlab/apputils';
  24. import { CodeEditor, IEditorServices } from '@jupyterlab/codeeditor';
  25. import {
  26. IDocumentWidget,
  27. DocumentRegistry,
  28. DocumentWidget
  29. } from '@jupyterlab/docregistry';
  30. import { IFileBrowserFactory } from '@jupyterlab/filebrowser';
  31. import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor';
  32. import { ILauncher } from '@jupyterlab/launcher';
  33. import { IMainMenu } from '@jupyterlab/mainmenu';
  34. import { ISettingRegistry } from '@jupyterlab/settingregistry';
  35. import { JSONObject } from '@lumino/coreutils';
  36. import { PythonEditor } from './PythonEditor';
  37. const PYTHON_FACTORY = 'Python Editor';
  38. const PYTHON = 'python';
  39. const PYTHON_EDITOR_NAMESPACE = 'elyra-python-editor-extension';
  40. const commandIDs = {
  41. createNewPythonEditor: 'script-editor:create-new-python-editor',
  42. openDocManager: 'docmanager:open',
  43. newDocManager: 'docmanager:new-untitled'
  44. };
  45. /**
  46. * Initialization data for the python-editor-extension extension.
  47. */
  48. const extension: JupyterFrontEndPlugin<void> = {
  49. id: PYTHON_EDITOR_NAMESPACE,
  50. autoStart: true,
  51. requires: [
  52. IEditorServices,
  53. IEditorTracker,
  54. ICommandPalette,
  55. ISettingRegistry,
  56. IFileBrowserFactory
  57. ],
  58. optional: [ILayoutRestorer, IMainMenu, ILauncher],
  59. activate: (
  60. app: JupyterFrontEnd,
  61. editorServices: IEditorServices,
  62. editorTracker: IEditorTracker,
  63. palette: ICommandPalette,
  64. settingRegistry: ISettingRegistry,
  65. browserFactory: IFileBrowserFactory,
  66. restorer: ILayoutRestorer | null,
  67. menu: IMainMenu | null,
  68. launcher: ILauncher | null
  69. ) => {
  70. console.log('Elyra - python-editor extension is activated!');
  71. const factory = new ScriptEditorWidgetFactory({
  72. editorServices,
  73. factoryOptions: {
  74. name: PYTHON_FACTORY,
  75. fileTypes: [PYTHON],
  76. defaultFor: [PYTHON]
  77. },
  78. instanceCreator: (
  79. options: DocumentWidget.IOptions<
  80. FileEditor,
  81. DocumentRegistry.ICodeModel
  82. >
  83. ): ScriptEditor => new PythonEditor(options)
  84. });
  85. app.docRegistry.addFileType({
  86. name: PYTHON,
  87. displayName: 'Python File',
  88. extensions: ['.py'],
  89. pattern: '.*\\.py$',
  90. mimeTypes: ['text/x-python'],
  91. icon: pyIcon
  92. });
  93. const { restored } = app;
  94. /**
  95. * Track PythonEditor widget on page refresh
  96. */
  97. const tracker = new WidgetTracker<ScriptEditor>({
  98. namespace: PYTHON_EDITOR_NAMESPACE
  99. });
  100. let config: CodeEditor.IConfig = { ...CodeEditor.defaultConfig };
  101. if (restorer) {
  102. // Handle state restoration
  103. void restorer.restore(tracker, {
  104. command: commandIDs.openDocManager,
  105. args: widget => ({
  106. path: widget.context.path,
  107. factory: PYTHON_FACTORY
  108. }),
  109. name: widget => widget.context.path
  110. });
  111. }
  112. /**
  113. * Update the setting values. Adapted from fileeditor-extension.
  114. */
  115. const updateSettings = (settings: ISettingRegistry.ISettings): void => {
  116. config = {
  117. ...CodeEditor.defaultConfig,
  118. ...(settings.get('editorConfig').composite as JSONObject)
  119. };
  120. // Trigger a refresh of the rendered commands
  121. app.commands.notifyCommandChanged();
  122. };
  123. /**
  124. * Update the settings of the current tracker instances. Adapted from fileeditor-extension.
  125. */
  126. const updateTracker = (): void => {
  127. tracker.forEach(widget => {
  128. updateWidget(widget);
  129. });
  130. };
  131. /**
  132. * Update the settings of a widget. Adapted from fileeditor-extension.
  133. */
  134. const updateWidget = (widget: ScriptEditor): void => {
  135. if (!editorTracker.has(widget)) {
  136. (editorTracker as WidgetTracker<IDocumentWidget<FileEditor>>).add(
  137. widget
  138. );
  139. }
  140. const editor = widget.content.editor;
  141. Object.keys(config).forEach((keyStr: string) => {
  142. const key = keyStr as keyof CodeEditor.IConfig;
  143. editor.setOption(key, config[key]);
  144. });
  145. };
  146. // Fetch the initial state of the settings. Adapted from fileeditor-extension.
  147. Promise.all([
  148. settingRegistry.load('@jupyterlab/fileeditor-extension:plugin'),
  149. restored
  150. ])
  151. .then(([settings]) => {
  152. updateSettings(settings);
  153. updateTracker();
  154. settings.changed.connect(() => {
  155. updateSettings(settings);
  156. updateTracker();
  157. });
  158. })
  159. .catch((reason: Error) => {
  160. console.error(reason.message);
  161. updateTracker();
  162. });
  163. app.docRegistry.addWidgetFactory(factory);
  164. factory.widgetCreated.connect((sender, widget) => {
  165. void tracker.add(widget);
  166. // Notify the widget tracker if restore data needs to update
  167. widget.context.pathChanged.connect(() => {
  168. void tracker.save(widget);
  169. });
  170. updateWidget(widget);
  171. });
  172. // Handle the settings of new widgets. Adapted from fileeditor-extension.
  173. tracker.widgetAdded.connect((sender, widget) => {
  174. updateWidget(widget);
  175. });
  176. /**
  177. * Create new python editor from launcher and file menu
  178. */
  179. // Add a python launcher
  180. if (launcher) {
  181. launcher.add({
  182. command: commandIDs.createNewPythonEditor,
  183. category: 'Elyra',
  184. rank: 4
  185. });
  186. }
  187. if (menu) {
  188. // Add new python editor creation to the file menu
  189. menu.fileMenu.newMenu.addGroup(
  190. [{ command: commandIDs.createNewPythonEditor, args: { isMenu: true } }],
  191. 92
  192. );
  193. }
  194. // Function to create a new untitled python file, given the current working directory
  195. const createNew = (cwd: string): Promise<any> => {
  196. return app.commands
  197. .execute(commandIDs.newDocManager, {
  198. path: cwd,
  199. type: 'file',
  200. ext: '.py'
  201. })
  202. .then(model => {
  203. return app.commands.execute(commandIDs.openDocManager, {
  204. path: model.path,
  205. factory: PYTHON_FACTORY
  206. });
  207. });
  208. };
  209. // Add a command to create new Python editor
  210. app.commands.addCommand(commandIDs.createNewPythonEditor, {
  211. label: args =>
  212. args['isPalette'] ? 'New Python Editor' : 'Python Editor',
  213. caption: 'Create a new Python Editor',
  214. icon: args => (args['isPalette'] ? undefined : pyIcon),
  215. execute: args => {
  216. const cwd = args['cwd'] || browserFactory.defaultBrowser.model.path;
  217. return createNew(cwd as string);
  218. }
  219. });
  220. palette.addItem({
  221. command: commandIDs.createNewPythonEditor,
  222. args: { isPalette: true },
  223. category: 'Elyra'
  224. });
  225. }
  226. };
  227. export default extension;