plugin.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. 'use strict';
  4. import {
  5. FileBrowserWidget, FileHandler
  6. } from 'jupyter-js-filebrowser';
  7. import {
  8. IContentsModel
  9. } from 'jupyter-js-services';
  10. import {
  11. IAppShell, ICommandPalette, ICommandRegistry
  12. } from 'phosphide';
  13. import {
  14. DelegateCommand
  15. } from 'phosphor-command';
  16. import {
  17. Container, Token
  18. } from 'phosphor-di';
  19. import {
  20. Property
  21. } from 'phosphor-properties';
  22. import {
  23. TabPanel
  24. } from 'phosphor-tabs';
  25. import {
  26. Widget
  27. } from 'phosphor-widget';
  28. import {
  29. IFileBrowserWidget
  30. } from '../index';
  31. import {
  32. IFileOpener, IFileHandler
  33. } from './index';
  34. /**
  35. * Register the plugin contributions.
  36. */
  37. export
  38. function resolve(container: Container): Promise<void> {
  39. return container.resolve({
  40. requires: [IAppShell, IFileOpener, IFileBrowserWidget, ICommandPalette, ICommandRegistry],
  41. create: (appShell, opener, browser, palette, registry) => {
  42. let newFileCommandItem = {
  43. id: 'jupyter-plugins:new-text-file',
  44. command: new DelegateCommand(() => {
  45. browser.newUntitled('file', '.txt').then((contents: IContentsModel) => {
  46. opener.open(contents.path);
  47. browser.refresh();
  48. });
  49. })
  50. }
  51. let newNotebookCommandItem = {
  52. id: 'jupyter-plugins:new-notebook',
  53. command: new DelegateCommand(() => {
  54. browser.newUntitled('notebook').then((contents: IContentsModel) => {
  55. opener.open(contents.path);
  56. browser.refresh();
  57. });
  58. })
  59. }
  60. registry.add([newFileCommandItem, newNotebookCommandItem]);
  61. let paletteItems = [{
  62. id: 'jupyter-plugins:new-text-file',
  63. title: 'Text File',
  64. caption: ''
  65. }, {
  66. id: 'jupyter-plugins:new-notebook',
  67. title: 'Notebook',
  68. caption: ''
  69. }];
  70. let section = {
  71. text: 'New...',
  72. items: paletteItems
  73. }
  74. palette.add([section]);
  75. FileBrowserWidget.widgetFactory = () => {
  76. let model = browser.model;
  77. let item = model.items[model.selected[0]];
  78. return opener.open(item.path);
  79. }
  80. }
  81. });
  82. }
  83. export
  84. function register(container: Container): void {
  85. container.register(IFileOpener, {
  86. requires: [IAppShell, IFileBrowserWidget],
  87. create: (shell, browser) => {
  88. return new FileOpener(shell, browser);
  89. }
  90. });
  91. }
  92. /**
  93. * An implementation on an IFileOpener.
  94. */
  95. class FileOpener implements IFileOpener {
  96. /**
  97. * Construct a new file opener.
  98. */
  99. constructor(appShell: IAppShell, browser: IFileBrowserWidget) {
  100. this._appShell = appShell;
  101. browser.openRequested.connect(this._openRequested,
  102. this);
  103. }
  104. /**
  105. * Register a file handler.
  106. */
  107. register(handler: IFileHandler): void {
  108. this._handlers.push(handler);
  109. }
  110. /**
  111. * Register a default file handler.
  112. */
  113. registerDefault(handler: IFileHandler): void {
  114. if (this._defaultHandler !== null) {
  115. throw Error('Default handler already registered');
  116. }
  117. this._handlers.push(handler);
  118. this._defaultHandler = handler;
  119. }
  120. /**
  121. * Open a file and add it to the application shell.
  122. */
  123. open(path: string): Widget {
  124. if (this._handlers.length === 0) {
  125. return;
  126. }
  127. let ext = '.' + path.split('.').pop();
  128. let handlers: IFileHandler[] = [];
  129. // Look for matching file extensions.
  130. for (let h of this._handlers) {
  131. if (h.fileExtensions.indexOf(ext) !== -1) handlers.push(h);
  132. }
  133. // If there was only one match, use it.
  134. if (handlers.length === 1) {
  135. return this._open(handlers[0], path);
  136. // If there were no matches, use default handler.
  137. } else if (handlers.length === 0) {
  138. if (this._defaultHandler !== null) {
  139. return this._open(this._defaultHandler, path);
  140. } else {
  141. throw new Error(`Could not open file '${path}'`);
  142. }
  143. // There are more than one possible handlers.
  144. } else {
  145. // TODO: Ask the user to choose one.
  146. return this._open(handlers[0], path);
  147. }
  148. }
  149. /**
  150. * Handle an `openRequested` signal by invoking the appropriate handler.
  151. */
  152. private _openRequested(browser: FileBrowserWidget, path: string): void {
  153. this.open(path);
  154. }
  155. /**
  156. * Open a file and add it to the application shell and give it focus.
  157. */
  158. private _open(handler: IFileHandler, path: string): Widget {
  159. let widget = handler.open(path);
  160. if (!widget.isAttached) {
  161. this._appShell.addToMainArea(widget);
  162. }
  163. let parent = widget.parent;
  164. while (parent) {
  165. if (parent instanceof TabPanel) {
  166. if ((parent as TabPanel).childIndex(widget) !== -1) {
  167. (parent as TabPanel).currentWidget = widget;
  168. return widget;
  169. }
  170. }
  171. parent = parent.parent;
  172. }
  173. return widget;
  174. }
  175. private _handlers: IFileHandler[] = [];
  176. private _appShell: IAppShell = null;
  177. private _defaultHandler: IFileHandler = null;
  178. }