Skip to content

Commit 7d20c0a

Browse files
xor-xornnicandro
authored andcommitted
Sort available kernelspecs; guess with ^; use guessing in jupyter-run-repl
1 parent 0a7055d commit 7d20c0a

File tree

4 files changed

+62
-18
lines changed

4 files changed

+62
-18
lines changed

jupyter-kernelspec.el

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@
4545
"Get the available kernelspecs.
4646
Return an alist mapping kernel names to (DIRECTORY . PLIST) pairs
4747
where DIRECTORY is the resource directory of the kernel and PLIST
48-
is its kernelspec plist. The alist is formed by parsing the
49-
output of the shell command
48+
is its kernelspec plist. The alist is formed by parsing and
49+
sorting the output of the shell command
5050
51-
jupyter kernelspec list
51+
jupyter kernelspec list --json
5252
5353
By default the available kernelspecs are cached. To force an
5454
update of the cached kernelspecs, give a non-nil value to
@@ -64,14 +64,16 @@ each DIRECTORY will be a remote file name."
6464
(or (jupyter-command "kernelspec" "list" "--json")
6565
(error "Can't obtain kernelspecs from jupyter shell command")))
6666
:kernelspecs)))
67-
(puthash
68-
host (cl-loop
69-
for (name spec) on specs by #'cddr
70-
for dir = (concat (unless (equal host "local") host)
71-
(plist-get spec :resource_dir))
72-
collect (cons (substring (symbol-name name) 1)
73-
(cons dir (plist-get spec :spec))))
74-
jupyter--kernelspecs)))))
67+
(puthash host
68+
(sort (cl-loop
69+
for (name spec) on specs by #'cddr
70+
for dir = (concat (unless (equal host "local") host)
71+
(plist-get spec :resource_dir))
72+
collect (cons (substring (symbol-name name) 1)
73+
(cons dir (plist-get spec :spec))))
74+
(lambda (x y)
75+
(string< (car x) (car y))))
76+
jupyter--kernelspecs)))))
7577

7678
(defun jupyter-get-kernelspec (name &optional refresh)
7779
"Get the kernelspec for a kernel named NAME.
@@ -103,12 +105,12 @@ Optional argument REFRESH has the same meaning as in
103105
(or specs (jupyter-available-kernelspecs refresh))))
104106

105107
(defun jupyter-guess-kernelspec (name &optional specs refresh)
106-
"Return the first kernelspec matching NAME.
108+
"Return the first kernelspec starting with NAME.
107109
Raise an error if no kernelspec could be found.
108110
109111
SPECS and REFRESH have the same meaning as in
110112
`jupyter-find-kernelspecs'."
111-
(or (car (jupyter-find-kernelspecs name specs refresh))
113+
(or (car (jupyter-find-kernelspecs (format "^%s" name) specs refresh))
112114
(error "No valid kernelspec for kernel name (%s)" name)))
113115

114116
(defun jupyter-completing-read-kernelspec (&optional specs refresh)

jupyter-repl.el

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2097,7 +2097,7 @@ completing all of the above.")
20972097
;;;###autoload
20982098
(defun jupyter-run-repl (kernel-name &optional repl-name associate-buffer client-class display)
20992099
"Run a Jupyter REPL connected to a kernel with name, KERNEL-NAME.
2100-
KERNEL-NAME will be passed to `jupyter-find-kernelspecs' and the
2100+
KERNEL-NAME will be passed to `jupyter-guess-kernelspec' and the
21012101
first kernel found will be used to start the new kernel.
21022102
21032103
With a prefix argument give a new REPL-NAME for the REPL.
@@ -2128,10 +2128,6 @@ command on the host."
21282128
t nil t))
21292129
(or client-class (setq client-class 'jupyter-repl-client))
21302130
(jupyter-error-if-not-client-class-p client-class 'jupyter-repl-client)
2131-
(unless (called-interactively-p 'interactive)
2132-
(or (when-let* ((name (caar (jupyter-find-kernelspecs kernel-name))))
2133-
(setq kernel-name name))
2134-
(error "No kernel found for prefix (%s)" kernel-name)))
21352131
;; For `jupyter-start-new-kernel', we don't require this at top-level since
21362132
;; there are many ways to interact with a kernel, e.g. through a notebook
21372133
;; server, and we don't want to load any unnecessary files.

test/jupyter-test.el

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,6 +1912,24 @@ next(x"))))))
19121912
font-lock-extend-region-functions)))
19131913
(font-lock-ensure)))))
19141914

1915+
(ert-deftest jupyter-available-kernelspecs-sorting ()
1916+
:tags '(repl)
1917+
(jupyter-test-with-some-kernelspecs '("foo_qux" "qux" "bar_qux")
1918+
(let ((result (mapcar #'car (jupyter-available-kernelspecs t))))
1919+
(should (equal result (sort (copy-sequence result) #'string<))))))
1920+
1921+
(ert-deftest jupyter-run-repl-issue-371 ()
1922+
:tags '(repl)
1923+
(jupyter-test-with-some-kernelspecs '("foo_qux" "qux" "bar_qux")
1924+
(let ((client))
1925+
(unwind-protect
1926+
(progn
1927+
(setq client (jupyter-run-repl "qux"))
1928+
(should (equal (plist-get (jupyter-session-conn-info (oref client session))
1929+
:kernel_name)
1930+
"qux")))
1931+
(jupyter-test-kill-buffer (oref client buffer))))))
1932+
19151933
;;; `org-mode'
19161934

19171935
(defvar org-babel-jupyter-resource-directory nil)

test/test-helper.el

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,36 @@ For `url-retrieve', the callback will be called with a nil status."
365365
(progn ,@body)
366366
(jupyter-api-shutdown-kernel ,server ,id))))))
367367

368+
(defmacro jupyter-test-with-some-kernelspecs (names &rest body)
369+
"Execute BODY in the context where extra kernelspecs with NAMES are available.
370+
371+
Those kernelspecs will be created in a temporary dir, which will
372+
be presented to Jupyter process via JUPYTER_PATH environemnt
373+
variable."
374+
(declare (indent 1) (debug (listp body)))
375+
`(unwind-protect
376+
(let ((jupyter-extra-dir (make-temp-file "jupyter-extra-dir" 'directory)))
377+
(jupyter-test-create-some-kernelspecs ,names jupyter-extra-dir)
378+
(setenv "JUPYTER_PATH" jupyter-extra-dir)
379+
,@body)
380+
(setenv "JUPYTER_PATH")))
381+
368382
;;; Functions
369383

384+
(defun jupyter-test-create-some-kernelspecs (kernel-names data-dir)
385+
"In DATA-DIR, create kernelspecs according to KERNEL-NAMES list.
386+
387+
The only difference between them will be their names."
388+
(let ((argv (vector "python" "-m" "ipykernel_launcher" "-f" "{connection_file}"))
389+
(save-silently t))
390+
(dolist (name kernel-names)
391+
(let ((kernel-dir (format "%s/kernels/%s" data-dir name)))
392+
(make-directory kernel-dir t)
393+
(append-to-file (json-serialize
394+
`(:argv ,argv :display_name ,name :language "python"))
395+
nil
396+
(format "%s/kernel.json" kernel-dir))))))
397+
370398
(defun jupyter-test-ipython-kernel-version (spec)
371399
"Return the IPython kernel version string corresponding to SPEC.
372400
Assumes that SPEC is a kernelspec for a Python kernel and

0 commit comments

Comments
 (0)