kernelselector.ts 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. Kernel, Session
  5. } from '@jupyterlab/services';
  6. import {
  7. IterableOrArrayLike, each
  8. } from '@phosphor/algorithm';
  9. import {
  10. Widget
  11. } from '@phosphor/widgets';
  12. import {
  13. Dialog, showDialog
  14. } from '@jupyterlab/apputils';
  15. import {
  16. DocumentRegistry
  17. } from './registry';
  18. /**
  19. * An interface for selecting a new kernel.
  20. */
  21. export
  22. interface IKernelSelection {
  23. /**
  24. * The name of the current session.
  25. */
  26. name: string;
  27. /**
  28. * The kernel spec information.
  29. */
  30. specs: Kernel.ISpecModels;
  31. /**
  32. * The current running sessions.
  33. */
  34. sessions: IterableOrArrayLike<Session.IModel>;
  35. /**
  36. * The desired kernel language.
  37. */
  38. preferredLanguage: string;
  39. /**
  40. * The optional existing kernel model.
  41. */
  42. kernel?: Kernel.IModel;
  43. /**
  44. * The host widget to be selected after the dialog is shown.
  45. */
  46. host?: Widget;
  47. }
  48. /**
  49. * An interface for populating a kernel selector.
  50. */
  51. export
  52. interface IPopulateOptions {
  53. /**
  54. * The Kernel specs.
  55. */
  56. specs: Kernel.ISpecModels;
  57. /**
  58. * The current running sessions.
  59. */
  60. sessions: IterableOrArrayLike<Session.IModel>;
  61. /**
  62. * The optional existing kernel model.
  63. */
  64. kernel?: Kernel.IModel;
  65. /**
  66. * The preferred kernel name.
  67. */
  68. preferredKernel?: string;
  69. /**
  70. * The preferred kernel language.
  71. */
  72. preferredLanguage?: string;
  73. }
  74. /**
  75. * Bring up a dialog to select a kernel.
  76. */
  77. export
  78. function selectKernel(options: IKernelSelection): Promise<Kernel.IModel> {
  79. let { specs, kernel, sessions, preferredLanguage } = options;
  80. // Create the dialog body.
  81. let body = document.createElement('div');
  82. let text = document.createElement('label');
  83. text.innerHTML = `Select kernel for: "${options.name}"`;
  84. body.appendChild(text);
  85. let selector = document.createElement('select');
  86. body.appendChild(selector);
  87. // Get the current sessions, populate the kernels, and show the dialog.
  88. populateKernels(selector, { specs, sessions, preferredLanguage, kernel });
  89. let select = Dialog.okButton({ label: 'SELECT' });
  90. return showDialog({
  91. title: 'Select Kernel',
  92. body,
  93. buttons: [Dialog.cancelButton(), select]
  94. }).then(result => {
  95. // Change the kernel if a kernel was selected.
  96. if (result.accept) {
  97. return JSON.parse(selector.value) as Kernel.IModel;
  98. }
  99. return void 0;
  100. });
  101. }
  102. /**
  103. * Change the kernel on a context.
  104. */
  105. export
  106. function selectKernelForContext(context: DocumentRegistry.Context, manager: Session.IManager, host?: Widget): Promise<void> {
  107. return manager.ready.then(() => {
  108. let options: IKernelSelection = {
  109. name: context.path.split('/').pop(),
  110. specs: manager.specs,
  111. sessions: manager.running(),
  112. preferredLanguage: context.model.defaultKernelLanguage,
  113. kernel: context.kernel ? context.kernel.model : null,
  114. };
  115. return selectKernel(options);
  116. }).then(kernel => {
  117. if (host) {
  118. host.activate();
  119. }
  120. if (kernel && (kernel.id || kernel.name)) {
  121. context.changeKernel(kernel);
  122. } else if (kernel && !kernel.id && !kernel.name) {
  123. context.changeKernel();
  124. }
  125. });
  126. }
  127. /**
  128. * Get the appropriate kernel name.
  129. */
  130. export
  131. function findKernel(kernelName: string, language: string, specs: Kernel.ISpecModels): string {
  132. if (kernelName === 'unknown') {
  133. return specs.default;
  134. }
  135. // Look for an exact match.
  136. for (let specName in specs.kernelspecs) {
  137. if (specName === kernelName) {
  138. return kernelName;
  139. }
  140. }
  141. // Next try to match the language name.
  142. if (language === 'unknown') {
  143. return specs.default;
  144. }
  145. for (let specName in specs.kernelspecs) {
  146. let kernelLanguage = specs.kernelspecs[specName].language;
  147. if (language === kernelLanguage) {
  148. console.log('No exact match found for ' + specName +
  149. ', using kernel ' + specName + ' that matches ' +
  150. 'language=' + language);
  151. return specName;
  152. }
  153. }
  154. // Finally, use the default kernel.
  155. if (kernelName) {
  156. console.log(`No matching kernel found for ${kernelName}, ` +
  157. `using default kernel ${specs.default}`);
  158. }
  159. return specs.default;
  160. }
  161. /**
  162. * Populate a kernel dropdown list.
  163. *
  164. * @param node - The host node.
  165. *
  166. * @param options - The options used to populate the kernels.
  167. *
  168. * #### Notes
  169. * Populates the list with separated sections:
  170. * - Kernels matching the preferred language (display names).
  171. * - "None" signifying no kernel.
  172. * - The remaining kernels.
  173. * - Sessions matching the preferred language (file names).
  174. * - The remaining sessions.
  175. * If no preferred language is given or no kernels are found using
  176. * the preferred language, the default kernel is used in the first
  177. * section. Kernels are sorted by display name. Sessions display the
  178. * base name of the file with an ellipsis overflow and a tooltip with
  179. * the explicit session information.
  180. */
  181. export
  182. function populateKernels(node: HTMLSelectElement, options: IPopulateOptions): void {
  183. // Clear any existing options.
  184. while (node.firstChild) {
  185. node.removeChild(node.firstChild);
  186. }
  187. let maxLength = 10;
  188. let { preferredKernel, preferredLanguage, sessions, specs, kernel } = options;
  189. let existing = kernel ? kernel.id : void 0;
  190. // Create mappings of display names and languages for kernel name.
  191. let displayNames: { [key: string]: string } = Object.create(null);
  192. let languages: { [key: string]: string } = Object.create(null);
  193. for (let name in specs.kernelspecs) {
  194. let spec = specs.kernelspecs[name];
  195. displayNames[name] = spec.display_name;
  196. maxLength = Math.max(maxLength, displayNames[name].length);
  197. languages[name] = spec.language;
  198. }
  199. // Handle a kernel by name.
  200. let names: string[] = [];
  201. if (preferredKernel && preferredKernel in specs.kernelspecs) {
  202. names.push(name);
  203. }
  204. // Handle a preferred kernel language in order of display name.
  205. let preferred = document.createElement('optgroup');
  206. preferred.label = 'Start Preferred Kernel';
  207. if (preferredLanguage && specs) {
  208. for (let name in specs.kernelspecs) {
  209. if (languages[name] === preferredLanguage) {
  210. names.push(name);
  211. }
  212. }
  213. names.sort((a, b) => displayNames[a].localeCompare(displayNames[b]));
  214. for (let name of names) {
  215. preferred.appendChild(optionForName(name, displayNames[name]));
  216. }
  217. }
  218. // Use the default kernel if no preferred language or none were found.
  219. if (!names.length) {
  220. let name = specs.default;
  221. preferred.appendChild(optionForName(name, displayNames[name]));
  222. }
  223. if (preferred.firstChild) {
  224. node.appendChild(preferred);
  225. }
  226. // Add an option for no kernel
  227. node.appendChild(optionForNone());
  228. let other = document.createElement('optgroup');
  229. other.label = 'Start Other Kernel';
  230. // Add the rest of the kernel names in alphabetical order.
  231. let otherNames: string[] = [];
  232. for (let name in specs.kernelspecs) {
  233. if (names.indexOf(name) !== -1) {
  234. continue;
  235. }
  236. otherNames.push(name);
  237. }
  238. otherNames.sort((a, b) => displayNames[a].localeCompare(displayNames[b]));
  239. for (let name of otherNames) {
  240. other.appendChild(optionForName(name, displayNames[name]));
  241. }
  242. // Add a separator option if there were any other names.
  243. if (otherNames.length) {
  244. node.appendChild(other);
  245. }
  246. // Add the sessions using the preferred language first.
  247. let matchingSessions: Session.IModel[] = [];
  248. let otherSessions: Session.IModel[] = [];
  249. each(sessions, session => {
  250. if (preferredLanguage &&
  251. languages[session.kernel.name] === preferredLanguage &&
  252. session.kernel.id !== existing) {
  253. matchingSessions.push(session);
  254. } else if (session.kernel.id !== existing) {
  255. otherSessions.push(session);
  256. }
  257. });
  258. let matching = document.createElement('optgroup');
  259. matching.label = 'Use Kernel from Preferred Session';
  260. node.appendChild(matching);
  261. if (matchingSessions.length) {
  262. matchingSessions.sort((a, b) => {
  263. return a.notebook.path.localeCompare(b.notebook.path);
  264. });
  265. each(matchingSessions, session => {
  266. let name = displayNames[session.kernel.name];
  267. matching.appendChild(optionForSession(session, name, maxLength));
  268. });
  269. }
  270. let otherSessionsNode = document.createElement('optgroup');
  271. otherSessionsNode.label = 'Use Kernel from Other Session';
  272. node.appendChild(otherSessionsNode);
  273. if (otherSessions.length) {
  274. otherSessions.sort((a, b) => {
  275. return a.notebook.path.localeCompare(b.notebook.path);
  276. });
  277. each(otherSessions, session => {
  278. let name = displayNames[session.kernel.name] || session.kernel.name;
  279. otherSessionsNode.appendChild(optionForSession(session, name, maxLength));
  280. });
  281. }
  282. node.selectedIndex = 0;
  283. }
  284. /**
  285. * Create an option element for a kernel name.
  286. */
  287. function optionForName(name: string, displayName: string): HTMLOptionElement {
  288. let option = document.createElement('option');
  289. option.text = displayName;
  290. option.value = JSON.stringify({ name });
  291. return option;
  292. }
  293. /**
  294. * Create an option for no kernel.
  295. */
  296. function optionForNone(): HTMLOptGroupElement {
  297. let group = document.createElement('optgroup');
  298. group.label = 'Use No Kernel';
  299. let option = document.createElement('option');
  300. option.text = 'No Kernel';
  301. option.value = JSON.stringify({id: null, name: null});
  302. group.appendChild(option);
  303. return group;
  304. }
  305. /**
  306. * Create an option element for a session.
  307. */
  308. function optionForSession(session: Session.IModel, displayName: string, maxLength: number): HTMLOptionElement {
  309. let option = document.createElement('option');
  310. let sessionName = session.notebook.path.split('/').pop();
  311. const CONSOLE_REGEX = /^console-(\d)+-[0-9a-f]+$/;
  312. if (CONSOLE_REGEX.test(sessionName)) {
  313. sessionName = `Console ${sessionName.match(CONSOLE_REGEX)[1]}`;
  314. }
  315. if (sessionName.length > maxLength) {
  316. sessionName = sessionName.slice(0, maxLength - 3) + '...';
  317. }
  318. option.text = sessionName;
  319. option.value = JSON.stringify({ id: session.kernel.id });
  320. option.title = `Path: ${session.notebook.path}\n` +
  321. `Kernel Name: ${displayName}\n` +
  322. `Kernel Id: ${session.kernel.id}`;
  323. return option;
  324. }