mirror of
https://github.com/vale981/emacs-ipython-notebook
synced 2025-03-06 01:21:38 -05:00

As emacs users we prefer and have the luxury of fuzzy file navigation via ido and projectile. From a notebook or notebooklist buffer, the commands `C-c C-f` ein:file-open `C-c C-o` ein:notebook-open offer an ido alternative to point and click navigation. To populate the ido lists, retrieving the content hierarchy is on by default. Two custom variables determine how wide and deep the content query probes (currently at 2 levels deep and 6 directories wide). Set both to zero to turn off. tkf half finished code to quickly go from local file buffers to notebook mode via `C-c C-z` or `C-c C-o`. This is now possible. EIN will start the server from a suitable parent directory of the visited file. Enable ido completion for `notebooklist-login`. Remove the albatross `ein-loaddefs.el` in favor of more standard `ein-autoloads.el` that is not git tracked. Convenience `make install` from git source (local alternative to melpa).
244 lines
10 KiB
EmacsLisp
244 lines
10 KiB
EmacsLisp
;;; ein-process.el --- Notebook list buffer
|
|
|
|
;; Copyright (C) 2018- John M. Miller
|
|
|
|
;; Authors: Takafumi Arakaki <aka.tkf at gmail.com>
|
|
;; John M. Miller <millejoh at mac.com>
|
|
|
|
;; This file is NOT part of GNU Emacs.
|
|
|
|
;; ein-process.el is free software: you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; ein-process.el is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with ein-process.el. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
;;; Code:
|
|
|
|
(eval-when-compile (require 'cl))
|
|
|
|
(require 'ein-core)
|
|
(require 'ein-jupyter)
|
|
(require 'ein-file)
|
|
(require 'ein-notebooklist)
|
|
(require 'f)
|
|
|
|
(defcustom ein:process-jupyter-regexp "\\(jupyter\\|ipython\\)\\(-\\|\\s-+\\)note"
|
|
"Regexp by which we recognize notebook servers."
|
|
:type 'string
|
|
:group 'ein)
|
|
|
|
|
|
(defcustom ein:process-lsof "lsof"
|
|
"Executable for lsof command."
|
|
:type 'string
|
|
:group 'ein)
|
|
|
|
(defun ein:process-divine-dir (pid args &optional error-buffer)
|
|
"Returns notebook-dir or cwd of PID. Supply ERROR-BUFFER to capture stderr"
|
|
(if (string-match "\\bnotebook-dir\\(=\\|\\s-+\\)\\(\\S-+\\)" args)
|
|
(directory-file-name (match-string 2 args))
|
|
(if (executable-find ein:process-lsof)
|
|
(ein:trim-right
|
|
(with-output-to-string
|
|
(shell-command (format "%s -p %d -a -d cwd -Fn | grep ^n | tail -c +2"
|
|
ein:process-lsof pid)
|
|
standard-output error-buffer))))))
|
|
|
|
(defun ein:process-divine-port (pid args &optional error-buffer)
|
|
"Returns port on which PID is listening or 0 if none. Supply ERROR-BUFFER to capture stderr"
|
|
(if (string-match "\\bport\\(=\\|\\s-+\\)\\(\\S-+\\)" args)
|
|
(string-to-number (match-string 2 args))
|
|
(if (executable-find ein:process-lsof)
|
|
(string-to-number
|
|
(ein:trim-right
|
|
(with-output-to-string
|
|
(shell-command (format "%s -p %d -a -iTCP -sTCP:LISTEN -Fn | grep ^n | sed \"s/[^0-9]//g\""
|
|
ein:process-lsof pid)
|
|
standard-output error-buffer)))))))
|
|
|
|
(defun ein:process-divine-ip (pid args)
|
|
"Returns notebook-ip of PID"
|
|
(if (string-match "\\bip\\(=\\|\\s-+\\)\\(\\S-+\\)" args)
|
|
(match-string 2 args)
|
|
ein:url-localhost))
|
|
|
|
(defcustom ein:process-jupyter-regexp "\\(jupyter\\|ipython\\)\\(-\\|\\s-+\\)note"
|
|
"Regexp by which we recognize notebook servers."
|
|
:type 'string
|
|
:group 'ein)
|
|
|
|
|
|
(defcustom ein:process-lsof "lsof"
|
|
"Executable for lsof command."
|
|
:type 'string
|
|
:group 'ein)
|
|
|
|
(defun ein:process-divine-dir (pid args &optional error-buffer)
|
|
(if (string-match "\\bnotebook-dir\\(=\\|\\s-+\\)\\(\\S-+\\)" args)
|
|
(directory-file-name (match-string 2 args))
|
|
(if (executable-find ein:process-lsof)
|
|
(ein:trim-right
|
|
(with-output-to-string
|
|
(shell-command (format "%s -p %d -a -d cwd -Fn | grep ^n | tail -c +2"
|
|
ein:process-lsof pid)
|
|
standard-output error-buffer))))))
|
|
|
|
(defun ein:process-divine-port (pid args &optional error-buffer)
|
|
"Returns port on which PID is listening or 0 if none. Supply ERROR-BUFFER to capture stderr"
|
|
(if (string-match "\\bport\\(=\\|\\s-+\\)\\(\\S-+\\)" args)
|
|
(string-to-number (match-string 2 args))
|
|
(if (executable-find ein:process-lsof)
|
|
(string-to-number
|
|
(ein:trim-right
|
|
(with-output-to-string
|
|
(shell-command (format "%s -p %d -a -iTCP -sTCP:LISTEN -Fn | grep ^n | sed \"s/[^0-9]//g\""
|
|
ein:process-lsof pid)
|
|
standard-output error-buffer)))))))
|
|
|
|
(defun ein:process-divine-ip (pid args)
|
|
"Returns notebook-ip of PID"
|
|
(if (string-match "\\bip\\(=\\|\\s-+\\)\\(\\S-+\\)" args)
|
|
(match-string 2 args)
|
|
"localhost"))
|
|
|
|
(defstruct ein:$process
|
|
"Hold process variables.
|
|
|
|
`ein:$process-pid' : integer
|
|
PID.
|
|
|
|
`ein:$process-url': string
|
|
URL
|
|
|
|
`ein:$process-dir' : string
|
|
Arg of --notebook-dir or 'readlink -e /proc/<pid>/cwd'."
|
|
pid
|
|
url
|
|
dir
|
|
)
|
|
|
|
(ein:deflocal ein:%processes% (make-hash-table :test #'equal)
|
|
"Process table of `ein:$process' keyed on dir.")
|
|
|
|
(defun ein:process-processes ()
|
|
(ein:hash-vals ein:%processes%))
|
|
|
|
(defun ein:process-alive-p (proc)
|
|
(not (null (process-attributes (ein:$process-pid proc)))))
|
|
|
|
(defun ein:process-suitable-notebook-dir (filename)
|
|
"Return the uppermost parent dir of DIR that contains ipynb files."
|
|
(let ((fn (expand-file-name filename)))
|
|
(loop with directory = (directory-file-name
|
|
(if (f-file? fn) (f-parent fn) fn))
|
|
with suitable = directory
|
|
until (string= (file-name-nondirectory directory) "")
|
|
do (if (directory-files directory nil "\\.ipynb$")
|
|
(setq suitable directory))
|
|
(setq directory (directory-file-name (file-name-directory directory)))
|
|
finally return suitable)))
|
|
|
|
(defun ein:process-refresh-processes ()
|
|
"Use `jupyter notebook list --json` to populate ein:%processes%"
|
|
(clrhash ein:%processes%)
|
|
(loop for line in (process-lines ein:jupyter-default-server-command
|
|
"notebook" "list" "--json")
|
|
do (destructuring-bind
|
|
(&key pid url notebook_dir &allow-other-keys)
|
|
(ein:json-read-from-string line)
|
|
(puthash (directory-file-name notebook_dir)
|
|
(make-ein:$process :pid pid
|
|
:url (ein:url url)
|
|
:dir (directory-file-name notebook_dir))
|
|
ein:%processes%))))
|
|
|
|
(defun ein:process-ps-refresh-processes ()
|
|
"Can delete this. It pokes around unix ps when it's far better to use `jupyter notebook list'"
|
|
(loop for pid in (list-system-processes)
|
|
for attrs = (process-attributes pid)
|
|
for args = (alist-get 'args attrs)
|
|
with seen = (mapcar #'ein:$process-pid (ein:hash-vals ein:%processes%))
|
|
if (and (null (member pid seen))
|
|
(string-match ein:process-jupyter-regexp (alist-get 'comm attrs)))
|
|
do (ein:and-let* ((dir (ein:process-divine-dir pid args))
|
|
(port (ein:process-divine-port pid args))
|
|
(ip (ein:process-divine-ip pid args)))
|
|
(puthash dir (make-ein:$process :pid pid
|
|
:url (ein:url (format "http://%s:%s" ip port)) :dir dir)
|
|
ein:%processes%))
|
|
end))
|
|
|
|
(defun ein:process-dir-match (filename)
|
|
"Return ein:process whose directory is prefix of FILENAME."
|
|
(loop for dir in (ein:hash-keys ein:%processes%)
|
|
when (search dir filename)
|
|
return (gethash dir ein:%processes%)))
|
|
|
|
(defsubst ein:process-url-or-port (proc)
|
|
"Naively construct url-or-port from ein:process PROC's port and ip fields"
|
|
(ein:$process-url proc))
|
|
|
|
(defsubst ein:process-path (proc filename)
|
|
"Construct path by eliding PROC's dir from filename"
|
|
(subseq filename (length (file-name-as-directory (ein:$process-dir proc)))))
|
|
|
|
(defun ein:process-open-notebook* (filename callback)
|
|
"Open FILENAME as a notebook and start a notebook server if necessary. CALLBACK with arity 2 (passed into `ein:notebook-open--callback')."
|
|
(ein:process-refresh-processes)
|
|
(let* ((proc (ein:process-dir-match filename)))
|
|
(if proc
|
|
(let* ((url-or-port (ein:process-url-or-port proc))
|
|
(path (ein:process-path proc filename))
|
|
(callback1 (apply-partially (lambda (url-or-port* path* callback* buffer)
|
|
(ein:notebook-open
|
|
url-or-port* path* nil callback*))
|
|
url-or-port path callback)))
|
|
(if (ein:notebooklist-list-get url-or-port)
|
|
(ein:notebook-open url-or-port path nil callback)
|
|
(ein:notebooklist-login url-or-port callback1)))
|
|
(let* ((nbdir (read-directory-name "Notebook directory: "
|
|
(ein:process-suitable-notebook-dir filename)))
|
|
(path (subseq filename (length (file-name-as-directory nbdir))))
|
|
(callback1 (apply-partially (lambda (path* callback* buffer)
|
|
(pop-to-buffer buffer)
|
|
(ein:notebook-open
|
|
(car (ein:jupyter-server-conn-info))
|
|
path* nil callback*))
|
|
path callback)))
|
|
(ein:jupyter-server-start (executable-find ein:jupyter-default-server-command) nbdir nil callback1)))))
|
|
|
|
(defun ein:process-open-notebook (&optional filename buffer-callback)
|
|
"When FILENAME is unspecified the variable `buffer-file-name'
|
|
is used instead. BUFFER-CALLBACK is called after opening notebook with the
|
|
current buffer as the only one argument."
|
|
(interactive)
|
|
(unless filename (setq filename buffer-file-name))
|
|
(assert filename nil "Not visiting a file")
|
|
(let ((callback2 (apply-partially (lambda (buffer buffer-callback* notebook created
|
|
&rest args)
|
|
(when (buffer-live-p buffer)
|
|
(funcall buffer-callback* buffer)))
|
|
(current-buffer) (or buffer-callback #'ignore))))
|
|
(ein:process-open-notebook* (expand-file-name filename) callback2)))
|
|
|
|
(defun ein:process-find-file-callback ()
|
|
"A callback function for `find-file-hook' to open notebook."
|
|
(interactive)
|
|
(ein:and-let* ((filename buffer-file-name)
|
|
((string-match-p "\\.ipynb$" filename)))
|
|
(ein:process-open-notebook filename #'kill-buffer-if-not-modified)))
|
|
|
|
(provide 'ein-process)
|
|
|
|
;;; ein-process.el ends here
|