index.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. ILayoutRestorer, JupyterLab, JupyterLabPlugin
  5. } from '@jupyterlab/application';
  6. import {
  7. ICommandPalette, IMainMenu, InstanceTracker
  8. } from '@jupyterlab/apputils';
  9. import {
  10. ILauncher
  11. } from '@jupyterlab/launcher';
  12. import {
  13. ServiceManager
  14. } from '@jupyterlab/services';
  15. import {
  16. Terminal, ITerminalTracker
  17. } from '@jupyterlab/terminal';
  18. import {
  19. Menu
  20. } from '@phosphor/widgets';
  21. /**
  22. * The command IDs used by the terminal plugin.
  23. */
  24. namespace CommandIDs {
  25. export
  26. const createNew = 'terminal:create-new';
  27. export
  28. const open = 'terminal:open';
  29. export
  30. const refresh = 'terminal:refresh';
  31. export
  32. const increaseFont = 'terminal:increase-font';
  33. export
  34. const decreaseFont = 'terminal:decrease-font';
  35. export
  36. const toggleTheme = 'terminal:toggle-theme';
  37. };
  38. /**
  39. * The class name for the terminal icon in the default theme.
  40. */
  41. const TERMINAL_ICON_CLASS = 'jp-TerminalIcon';
  42. /**
  43. * The default terminal extension.
  44. */
  45. const plugin: JupyterLabPlugin<ITerminalTracker> = {
  46. activate,
  47. id: 'jupyter.extensions.terminal',
  48. provides: ITerminalTracker,
  49. requires: [
  50. IMainMenu, ICommandPalette, ILayoutRestorer
  51. ],
  52. optional: [ILauncher],
  53. autoStart: true
  54. };
  55. /**
  56. * Export the plugin as default.
  57. */
  58. export default plugin;
  59. /**
  60. * Activate the terminal plugin.
  61. */
  62. function activate(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette, restorer: ILayoutRestorer, launcher: ILauncher | null): ITerminalTracker {
  63. const { commands, serviceManager } = app;
  64. const category = 'Terminal';
  65. const namespace = 'terminal';
  66. const tracker = new InstanceTracker<Terminal>({ namespace });
  67. // Bail if there are no terminals available.
  68. if (!serviceManager.terminals.isAvailable()) {
  69. console.log('Disabling terminals plugin because they are not available on the server');
  70. return tracker;
  71. }
  72. // Handle state restoration.
  73. restorer.restore(tracker, {
  74. command: CommandIDs.createNew,
  75. args: widget => ({ name: widget.session.name }),
  76. name: widget => widget.session && widget.session.name
  77. });
  78. // Update the command registry when the terminal state changes.
  79. tracker.currentChanged.connect(() => {
  80. if (tracker.size <= 1) {
  81. commands.notifyCommandChanged(CommandIDs.refresh);
  82. }
  83. });
  84. addCommands(app, serviceManager, tracker);
  85. // Add command palette and menu items.
  86. let menu = new Menu({ commands });
  87. menu.title.label = category;
  88. [
  89. CommandIDs.createNew,
  90. CommandIDs.refresh,
  91. CommandIDs.increaseFont,
  92. CommandIDs.decreaseFont,
  93. CommandIDs.toggleTheme
  94. ].forEach(command => {
  95. palette.addItem({ command, category });
  96. if (command !== CommandIDs.createNew) {
  97. menu.addItem({ command });
  98. }
  99. });
  100. mainMenu.addMenu(menu, {rank: 40});
  101. // Add a launcher item if the launcher is available.
  102. if (launcher) {
  103. launcher.add({
  104. displayName: 'Terminal',
  105. category: 'Other',
  106. rank: 0,
  107. iconClass: TERMINAL_ICON_CLASS,
  108. callback: () => {
  109. return commands.execute(CommandIDs.createNew);
  110. }
  111. });
  112. }
  113. app.contextMenu.addItem({command: CommandIDs.refresh, selector: '.jp-Terminal', rank: 1});
  114. return tracker;
  115. }
  116. /**
  117. * Add the commands for the terminal.
  118. */
  119. export
  120. function addCommands(app: JupyterLab, services: ServiceManager, tracker: InstanceTracker<Terminal>) {
  121. let { commands, shell } = app;
  122. /**
  123. * Whether there is an active terminal.
  124. */
  125. function hasWidget(): boolean {
  126. return tracker.currentWidget !== null;
  127. }
  128. // Add terminal commands.
  129. commands.addCommand(CommandIDs.createNew, {
  130. label: 'New Terminal',
  131. caption: 'Start a new terminal session',
  132. execute: args => {
  133. let name = args ? args['name'] as string : '';
  134. let term = new Terminal();
  135. term.title.closable = true;
  136. term.title.icon = TERMINAL_ICON_CLASS;
  137. term.title.label = '...';
  138. shell.addToMainArea(term);
  139. let promise = name ?
  140. services.terminals.connectTo(name)
  141. : services.terminals.startNew();
  142. return promise.then(session => {
  143. term.session = session;
  144. tracker.add(term);
  145. shell.activateById(term.id);
  146. return term;
  147. }).catch(() => { term.dispose(); });
  148. }
  149. });
  150. commands.addCommand(CommandIDs.open, {
  151. execute: args => {
  152. const name = args['name'] as string;
  153. // Check for a running terminal with the given name.
  154. const widget = tracker.find(value => {
  155. return value.session && value.session.name === name || false;
  156. });
  157. if (widget) {
  158. shell.activateById(widget.id);
  159. } else {
  160. // Otherwise, create a new terminal with a given name.
  161. return commands.execute(CommandIDs.createNew, { name });
  162. }
  163. }
  164. });
  165. commands.addCommand(CommandIDs.refresh, {
  166. label: 'Refresh Terminal',
  167. caption: 'Refresh the current terminal session',
  168. execute: () => {
  169. let current = tracker.currentWidget;
  170. if (!current) {
  171. return;
  172. }
  173. shell.activateById(current.id);
  174. return current.refresh().then(() => {
  175. if (current) {
  176. current.activate();
  177. }
  178. });
  179. },
  180. isEnabled: () => tracker.currentWidget !== null
  181. });
  182. commands.addCommand('terminal:increase-font', {
  183. label: 'Increase Terminal Font Size',
  184. execute: () => {
  185. let options = Terminal.defaultOptions;
  186. if (options.fontSize < 72) {
  187. options.fontSize++;
  188. tracker.forEach(widget => { widget.fontSize = options.fontSize; });
  189. }
  190. },
  191. isEnabled: hasWidget
  192. });
  193. commands.addCommand('terminal:decrease-font', {
  194. label: 'Decrease Terminal Font Size',
  195. execute: () => {
  196. let options = Terminal.defaultOptions;
  197. if (options.fontSize > 9) {
  198. options.fontSize--;
  199. tracker.forEach(widget => { widget.fontSize = options.fontSize; });
  200. }
  201. },
  202. isEnabled: hasWidget
  203. });
  204. commands.addCommand('terminal:toggle-theme', {
  205. label: 'Toggle Terminal Theme',
  206. caption: 'Switch Terminal Theme',
  207. execute: () => {
  208. tracker.forEach(widget => {
  209. if (widget.theme === 'dark') {
  210. widget.theme = 'light';
  211. } else {
  212. widget.theme = 'dark';
  213. }
  214. });
  215. },
  216. isEnabled: hasWidget
  217. });
  218. }