Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
8.3 KiB

  1. ;;; ein-process.el --- Notebook list buffer
  2. ;; Copyright (C) 2018- John M. Miller
  3. ;; Authors: Takafumi Arakaki <aka.tkf at gmail.com>
  4. ;; John M. Miller <millejoh at mac.com>
  5. ;; This file is NOT part of GNU Emacs.
  6. ;; ein-process.el is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; ein-process.el is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with ein-process.el. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;;; Code:
  18. (require 'ein-core)
  19. (require 'ein-jupyter)
  20. (require 'ein-file)
  21. (require 'ein-notebooklist)
  22. (defcustom ein:process-jupyter-regexp "\\(jupyter\\|ipython\\)\\(-\\|\\s-+\\)note"
  23. "Regexp by which we recognize notebook servers."
  24. :type 'string
  25. :group 'ein)
  26. (defcustom ein:process-lsof "lsof"
  27. "Executable for lsof command."
  28. :type 'string
  29. :group 'ein)
  30. (defun ein:process-divine-dir (pid args &optional error-buffer)
  31. "Returns notebook-dir or cwd of PID. Supply ERROR-BUFFER to capture stderr"
  32. (if (string-match "\\bnotebook-dir\\(=\\|\\s-+\\)\\(\\S-+\\)" args)
  33. (directory-file-name (match-string 2 args))
  34. (if (executable-find ein:process-lsof)
  35. (ein:trim-right
  36. (with-output-to-string
  37. (shell-command (format "%s -p %d -a -d cwd -Fn | grep ^n | tail -c +2"
  38. ein:process-lsof pid)
  39. standard-output error-buffer))))))
  40. (defun ein:process-divine-port (pid args &optional error-buffer)
  41. "Returns port on which PID is listening or 0 if none. Supply ERROR-BUFFER to capture stderr"
  42. (if (string-match "\\bport\\(=\\|\\s-+\\)\\(\\S-+\\)" args)
  43. (string-to-number (match-string 2 args))
  44. (if (executable-find ein:process-lsof)
  45. (string-to-number
  46. (ein:trim-right
  47. (with-output-to-string
  48. (shell-command (format "%s -p %d -a -iTCP -sTCP:LISTEN -Fn | grep ^n | sed \"s/[^0-9]//g\""
  49. ein:process-lsof pid)
  50. standard-output error-buffer)))))))
  51. (defun ein:process-divine-ip (pid args)
  52. "Returns notebook-ip of PID"
  53. (if (string-match "\\bip\\(=\\|\\s-+\\)\\(\\S-+\\)" args)
  54. (match-string 2 args)
  55. ein:url-localhost))
  56. (defcustom ein:process-jupyter-regexp "\\(jupyter\\|ipython\\)\\(-\\|\\s-+\\)note"
  57. "Regexp by which we recognize notebook servers."
  58. :type 'string
  59. :group 'ein)
  60. (defcustom ein:process-lsof "lsof"
  61. "Executable for lsof command."
  62. :type 'string
  63. :group 'ein)
  64. (cl-defstruct ein:$process
  65. "Hold process variables.
  66. `ein:$process-pid' : integer
  67. PID.
  68. `ein:$process-url': string
  69. URL
  70. `ein:$process-dir' : string
  71. Arg of --notebook-dir or 'readlink -e /proc/<pid>/cwd'."
  72. pid
  73. url
  74. dir
  75. )
  76. (ein:deflocal ein:%processes% (make-hash-table :test #'equal)
  77. "Process table of `ein:$process' keyed on dir.")
  78. (defun ein:process-processes ()
  79. (hash-table-values ein:%processes%))
  80. (defun ein:process-alive-p (proc)
  81. (not (null (process-attributes (ein:$process-pid proc)))))
  82. (defun ein:process-suitable-notebook-dir (filename)
  83. "Return the uppermost parent dir of DIR that contains ipynb files."
  84. (let ((fn (expand-file-name filename)))
  85. (cl-loop with directory = (directory-file-name
  86. (if (file-regular-p fn)
  87. (file-name-directory (directory-file-name fn))
  88. fn))
  89. with suitable = directory
  90. until (string= (file-name-nondirectory directory) "")
  91. do (if (directory-files directory nil "\\.ipynb$")
  92. (setq suitable directory))
  93. (setq directory (directory-file-name (file-name-directory directory)))
  94. finally return suitable)))
  95. (defun ein:process-refresh-processes ()
  96. "Use `jupyter notebook list --json` to populate ein:%processes%"
  97. (clrhash ein:%processes%)
  98. (cl-loop for json in (ein:jupyter-notebook-list 'ein:process-refresh-processes)
  99. do (cl-destructuring-bind (&key pid url notebook_dir &allow-other-keys) json
  100. (puthash (directory-file-name notebook_dir)
  101. (make-ein:$process :pid pid
  102. :url (ein:url url)
  103. :dir (directory-file-name notebook_dir))
  104. ein:%processes%))))
  105. (defun ein:process-dir-match (filename)
  106. "Return ein:process whose directory is prefix of FILENAME."
  107. (cl-loop for dir in (hash-table-keys ein:%processes%)
  108. when (cl-search dir filename)
  109. return (gethash dir ein:%processes%)))
  110. (defun ein:process-url-match (url-or-port)
  111. "Return ein:process whose url matches URL-OR-PORT."
  112. (cl-loop with parsed-url-or-port = (url-generic-parse-url url-or-port)
  113. for proc in (ein:process-processes)
  114. for parsed-url-proc = (url-generic-parse-url (ein:process-url-or-port proc))
  115. when (and (string= (url-host parsed-url-or-port) (url-host parsed-url-proc))
  116. (= (url-port parsed-url-or-port) (url-port parsed-url-proc)))
  117. return proc))
  118. (defsubst ein:process-url-or-port (proc)
  119. "Naively construct url-or-port from ein:process PROC's port and ip fields"
  120. (ein:$process-url proc))
  121. (defsubst ein:process-path (proc filename)
  122. "Construct path by eliding PROC's dir from filename"
  123. (cl-subseq filename (length (file-name-as-directory (ein:$process-dir proc)))))
  124. (defun ein:process-open-notebook* (filename callback)
  125. "Open FILENAME as a notebook and start a notebook server if necessary. CALLBACK with arity 2 (passed into `ein:notebook-open--callback')."
  126. (ein:process-refresh-processes)
  127. (let* ((proc (ein:process-dir-match filename)))
  128. (if proc
  129. (let* ((url-or-port (ein:process-url-or-port proc))
  130. (path (ein:process-path proc filename))
  131. (callback2 (apply-partially (lambda (path* callback* buffer url-or-port)
  132. (ein:notebook-open
  133. url-or-port path* nil callback*))
  134. path callback)))
  135. (if (ein:notebooklist-list-get url-or-port)
  136. (ein:notebook-open url-or-port path nil callback)
  137. (ein:notebooklist-login url-or-port callback2)))
  138. (let* ((nbdir (read-directory-name "Notebook directory: "
  139. (ein:process-suitable-notebook-dir filename)))
  140. (path (cl-subseq filename (length (file-name-as-directory nbdir))))
  141. (callback2 (apply-partially (lambda (path* callback* buffer url-or-port)
  142. (pop-to-buffer buffer)
  143. (ein:notebook-open url-or-port
  144. path* nil callback*))
  145. path callback)))
  146. (ein:jupyter-server-start (executable-find ein:jupyter-default-server-command) nbdir nil callback2)))))
  147. (defun ein:process-open-notebook (&optional filename buffer-callback)
  148. "When FILENAME is unspecified the variable `buffer-file-name'
  149. is used instead. BUFFER-CALLBACK is called after opening notebook with the
  150. current buffer as the only one argument."
  151. (interactive)
  152. (unless filename (setq filename buffer-file-name))
  153. (cl-assert filename nil "Not visiting a file")
  154. (let ((callback2 (apply-partially (lambda (buffer buffer-callback* notebook created
  155. &rest args)
  156. (when (buffer-live-p buffer)
  157. (funcall buffer-callback* buffer)))
  158. (current-buffer) (or buffer-callback #'ignore))))
  159. (ein:process-open-notebook* (expand-file-name filename) callback2)))
  160. (defun ein:process-find-file-callback ()
  161. "A callback function for `find-file-hook' to open notebook."
  162. (interactive)
  163. (ein:and-let* ((filename buffer-file-name)
  164. ((string-match-p "\\.ipynb$" filename)))
  165. (ein:process-open-notebook filename #'kill-buffer-if-not-modified)))
  166. (provide 'ein-process)
  167. ;;; ein-process.el ends here