Define and use the jupyter-lang method context specializer

* Define the `jupyter-lang` method context specializer that
  specializes against the kernel language of
  `jupyter-current-client`

* Remove the kernel support API

* Replace the kernel support API with methods that can be
  specialized using the `jupyter-lang` context specializer.
This commit is contained in:
Nathaniel Nicandro 2018-09-17 19:15:28 -05:00
parent c0c9d8dd70
commit 19abd9a5c6
3 changed files with 140 additions and 90 deletions

View file

@ -156,6 +156,17 @@ buffer.")
:initarg :hb-channel :initarg :hb-channel
:documentation "The heartbeat channel."))) :documentation "The heartbeat channel.")))
;; Since the `eql' generalizer has the highest precedence,
;; `jupyter-lang' will be the first method called, then
;; the `major-mode' generalizer.
;;
;; TODO: Make `jupyter-kernel-language' a symbol to avoid
;; interning a constant string.
(cl-generic-define-context-rewriter jupyter-lang (lang)
`((when jupyter-current-client
(intern (jupyter-kernel-language jupyter-current-client)))
(eql ,lang)))
(cl-defmethod initialize-instance ((client jupyter-kernel-client) &rest _slots) (cl-defmethod initialize-instance ((client jupyter-kernel-client) &rest _slots)
(cl-call-next-method) (cl-call-next-method)
(push client jupyter--clients) (push client jupyter--clients)

View file

@ -395,26 +395,25 @@ the PARAMS list. If RENDER-PARAM is a string, remove it from the
(1+ (line-end-position)))))))))) (1+ (line-end-position))))))))))
(setf (jupyter-org-request-id-cleared-p req) t))) (setf (jupyter-org-request-id-cleared-p req) t)))
;; TODO: Externalize this, for example by adding a slot for transormation (cl-defgeneric jupyter-org-transform-result (render-result)
;; functions to a `jupyter-org-client'. Or for a `jupyter-org-request' object "Do some final transformations of RENDER-RESULT.
;; since transformations will be based on the block language.
(defun jupyter-org--transform-result (render-result kernel-lang)
"Do some final transformations of RENDER-RESULT based on KERNEL-LANG.
For example, call `org-babel-python-table-or-string' on the
results when rendering scalar data for a python code block.
RENDER-RESULT is the cons cell returned by RENDER-RESULT is the cons cell returned by
`jupyter-org-prepare-result' and KERNEL-LANG is the kernel `jupyter-org-prepare-result'. Return the transformed
language." RENDER-RESULT cons cell.
(let ((render-param (or (car render-result) "scalar"))
(result (cdr render-result))) The default method calls `org-babel-script-escape' on the RESULT
(cond if it is a scalar, otherwise it just returns RENDER-RESULT."
((and (equal render-param "scalar") (equal kernel-lang "python")) (cond
(cons "scalar" (when result (org-babel-python-table-or-string result)))) ((equal (car render-result) "scalar")
(t (cons "scalar" (org-babel-script-escape (cdr render-result))))
(if (equal render-param "scalar") (t render-result)))
(cons "scalar" (when result (org-babel-script-escape result)))
render-result))))) (cl-defmethod jupyter-org-transform-result (render-result
&context (jupyter-lang python))
(cond
((equal (car render-result) "scalar")
(cons "scalar" (org-babel-python-table-or-string (cdr render-result))))
(t render-result)))
(defun jupyter-org-add-result (client req result) (defun jupyter-org-add-result (client req result)
"For a request made with CLIENT, add RESULT. "For a request made with CLIENT, add RESULT.
@ -438,14 +437,14 @@ block for the request."
(message "%s" (cdr result))) (message "%s" (cdr result)))
(if (jupyter-org-request-async req) (if (jupyter-org-request-async req)
(let ((params (jupyter-org-request-block-params req)) (let ((params (jupyter-org-request-block-params req))
(kernel-lang (jupyter-kernel-language client))) (jupyter-current-client client))
(org-with-point-at (jupyter-org-request-marker req) (org-with-point-at (jupyter-org-request-marker req)
(jupyter-org-clear-request-id req) (jupyter-org-clear-request-id req)
(jupyter-org-insert-results result params kernel-lang)) (jupyter-org-insert-results result params))
(jupyter-org--inject-render-param "append" params)) (jupyter-org--inject-render-param "append" params))
(push result (jupyter-org-request-results req))))) (push result (jupyter-org-request-results req)))))
(defun jupyter-org-insert-results (results params kernel-lang) (defun jupyter-org-insert-results (results params)
"Insert RESULTS at the current source block location. "Insert RESULTS at the current source block location.
RESULTS is either a single cons cell or a list of such cells, RESULTS is either a single cons cell or a list of such cells,
each cell having the form each cell having the form
@ -454,8 +453,7 @@ each cell having the form
They should have been collected by previous calls to They should have been collected by previous calls to
`jupyter-org-prepare-result'. PARAMS are the parameters `jupyter-org-prepare-result'. PARAMS are the parameters
passed to `org-babel-execute:jupyter'. KERNEL-LANG is the passed to `org-babel-execute:jupyter'.
language of the kernel that produced RESULTS.
Note that if RESULTS is a list, the last result in the list will Note that if RESULTS is a list, the last result in the list will
be the one that eventually is shown in the org document. This is be the one that eventually is shown in the org document. This is
@ -475,9 +473,7 @@ it does not need to be added by the user."
;; caring about the parameters of the info and not anything else. ;; caring about the parameters of the info and not anything else.
with info = (list nil nil params) with info = (list nil nil params)
with result-params = (alist-get :result-params params) with result-params = (alist-get :result-params params)
for (render-param . result) in for (render-param . result) in (mapcar #'jupyter-org-transform-result results)
(mapcar (lambda (r) (jupyter-org--transform-result r kernel-lang))
results)
do (jupyter-org--inject-render-param render-param params) do (jupyter-org--inject-render-param render-param params)
(cl-letf (((symbol-function 'message) #'ignore)) (cl-letf (((symbol-function 'message) #'ignore))
(org-babel-insert-result result result-params info)) (org-babel-insert-result result result-params info))
@ -503,10 +499,11 @@ Meant to be used as the return value of `org-babel-execute:jupyter'."
0.01 nil 0.01 nil
(lambda () (lambda ()
(org-with-point-at (jupyter-org-request-marker req) (org-with-point-at (jupyter-org-request-marker req)
(let ((params (jupyter-org-request-block-params req))) (let ((params (jupyter-org-request-block-params req))
(jupyter-current-client client))
(jupyter-org--clear-render-param render-param params) (jupyter-org--clear-render-param render-param params)
(jupyter-org--inject-render-param "append" params) (jupyter-org--inject-render-param "append" params)
(jupyter-org-insert-results (cdr results) params kernel-lang) (jupyter-org-insert-results (cdr results) params)
(set-marker (jupyter-org-request-marker req) nil)))))))))) (set-marker (jupyter-org-request-marker req) nil))))))))))
(provide 'jupyter-org-client) (provide 'jupyter-org-client)

View file

@ -216,10 +216,13 @@ output of REQ should be inserted."
The contents of `jupyter-repl-lang-buffer' is erased before The contents of `jupyter-repl-lang-buffer' is erased before
running BODY." running BODY."
(declare (indent 0) (debug (&rest form))) (declare (indent 0) (debug (&rest form)))
`(with-current-buffer jupyter-repl-lang-buffer (let ((client (make-symbol "clientvar")))
(let ((inhibit-read-only t)) `(let ((,client jupyter-current-client))
(erase-buffer) (with-current-buffer jupyter-repl-lang-buffer
,@body))) (let ((inhibit-read-only t)
(jupyter-current-client ,client))
(erase-buffer)
,@body)))))
(defmacro with-jupyter-repl-cell (&rest body) (defmacro with-jupyter-repl-cell (&rest body)
"Narrow to the current cell, run BODY, then widen. "Narrow to the current cell, run BODY, then widen.
@ -282,10 +285,6 @@ erased."
(with-jupyter-repl-buffer client (with-jupyter-repl-buffer client
jupyter-repl-lang-mode)) jupyter-repl-lang-mode))
(cl-defmethod jupyter-repl-language ((client jupyter-repl-client))
"Return the name of CLIENT's kernel language."
(plist-get (plist-get (jupyter-kernel-info client) :language_info) :name))
;;; Text insertion ;;; Text insertion
(defun jupyter-repl-add-font-lock-properties (start end &optional object) (defun jupyter-repl-add-font-lock-properties (start end &optional object)
@ -440,24 +439,28 @@ can contain the following keywords along with their values:
(define-key map [mouse-2] 'jupyter-repl-markdown-follow-link-at-point) (define-key map [mouse-2] 'jupyter-repl-markdown-follow-link-at-point)
map)) map))
(cl-defgeneric jupyter-markdown-follow-link (_link-text _url _ref-label _title-text _bang)
"Follow the markdown link at `point'."
(markdown-follow-link-at-point))
(cl-defmethod jupyter-markdown-follow-link (link-text url _ref-label _title-text _bang
&context (jupyter-lang julia))
"Send a help query to the Julia REPL for LINK-TEXT if URL is \"@ref\".
Otherwise follow the link normally."
(if (string= url "@ref")
;; Links have the form `fun`
(let ((fun (substring link-text 1 -1)))
(goto-char (point-max))
(jupyter-repl-replace-cell-code (concat "?" fun))
(jupyter-repl-ret))
(cl-call-next-method)))
(defun jupyter-repl-markdown-follow-link-at-point () (defun jupyter-repl-markdown-follow-link-at-point ()
"Handle markdown links specially." "Handle markdown links specially."
(interactive) (interactive)
(let ((link (markdown-link-at-pos (point)))) (let ((link (markdown-link-at-pos (point))))
;; TODO: How to generalize this to kernels which do not utilize markdown in (when (car link)
;; their docstrings? Maybe if you do M-RET on a symbol it will call the (apply #'jupyter-markdown-follow-link (cddr link)))))
;; help function on it. For example in the python kernel, you can pull up
;; help on a symbol by calling the help function on it or appending a
;; question mark at the end of the symbol.
(if (and (string= (nth 3 link) "@ref")
(string= (jupyter-repl-language jupyter-repl-current-client)
"julia"))
;; Links have the form `fun`
(let ((fun (substring (nth 2 link) 1 -1)))
(goto-char (point-max))
(jupyter-repl-replace-cell-code (concat "?" fun))
(jupyter-repl-ret))
(markdown-follow-link-at-point))))
(defun jupyter-repl-insert-markdown (text) (defun jupyter-repl-insert-markdown (text)
"Insert TEXT, fontifying it using `markdown-mode' first." "Insert TEXT, fontifying it using `markdown-mode' first."
@ -1406,6 +1409,12 @@ Reset `jupyter-repl-use-builtin-is-complete' to nil if this is only temporary.")
;; No cells in the current buffer, just insert one ;; No cells in the current buffer, just insert one
(jupyter-repl-insert-prompt 'in)))) (jupyter-repl-insert-prompt 'in))))
(cl-defgeneric jupyter-indent-line ()
(call-interactively #'indent-for-tab-command))
(cl-defmethod jupyter-indent-line (&context (major-mode julia-mode))
(call-interactively #'julia-latexsub-or-indent))
(defun jupyter-repl-indent-line () (defun jupyter-repl-indent-line ()
"Indent the line according to the language of the REPL." "Indent the line according to the language of the REPL."
(let* ((spos (jupyter-repl-cell-code-beginning-position)) (let* ((spos (jupyter-repl-cell-code-beginning-position))
@ -1414,7 +1423,7 @@ Reset `jupyter-repl-use-builtin-is-complete' to nil if this is only temporary.")
(replacement (with-jupyter-repl-lang-buffer (replacement (with-jupyter-repl-lang-buffer
(insert code) (insert code)
(goto-char pos) (goto-char pos)
(indent-according-to-mode) (jupyter-indent-line)
(setq pos (point)) (setq pos (point))
(buffer-string)))) (buffer-string))))
;; Don't modify the buffer when unnecessary, this allows ;; Don't modify the buffer when unnecessary, this allows
@ -1628,41 +1637,49 @@ Works for Julia and Python."
(list (jupyter-repl-cell-code) (list (jupyter-repl-cell-code)
(1- (jupyter-repl-cell-code-position)))) (1- (jupyter-repl-cell-code-position))))
(cl-defgeneric jupyter-completion-prefix () (cl-defgeneric jupyter-completion-prefix (&optional (re string) max-len)
"Return the prefix for the current completion context. "Return the prefix for the current completion context.
This default function checks to see if the The default method calls `jupyter-completion-grab-symbol-cons'
`jupyter-kernel-language' of the `jupyter-current-client' with RE and MAX-LEN as arguments, RE defaulting to \"\\\\.\". It
has a `:completion-prefix' support function set by also handles argument lists surrounded by parentheses specially
`jupyter-kernel-support-put' and calls that function to obtain by considering an open parentheses and the symbol before it as a
the completion prefix. If the language does not have a completion completion prefix since some kernels will complete argument lists
prefix function, `jupyter-completion-grab-symbol-cons' is used. if given such a prefix.
Note that the prefix returned is not the content sent to the Note that the prefix returned is not the content sent to the
kernel, but the symbol at `point'. See kernel, but the prefix used by `jupyter-completion-at-point'. See
`jupyter-code-context-at-point' for what is actually sent." `jupyter-code-context' for what is actually sent to the kernel."
(when jupyter-repl-current-client (or re (setq re "\\."))
(let ((lang (jupyter-repl-language jupyter-repl-current-client)) (cond
(lang-mode (jupyter-repl-language-mode jupyter-repl-current-client))) ;; Completing argument lists
(and (memq major-mode `(,lang-mode jupyter-repl-mode)) ((and (char-before)
;; No completion in finalized cells (eq (char-syntax (char-before)) ?\()
(not (get-text-property (point) 'read-only)) (or (not (char-after))
(cond (looking-at-p "\\_>")
;; Completing argument lists (not (memq (char-syntax (char-after)) '(?w ?_)))))
((and (eq (char-syntax (char-before)) ?\() (buffer-substring-no-properties
(or (not (char-after)) (jupyter-completion-symbol-beginning (1- (point)))
(not (memq (char-syntax (char-after)) '(?w ?_))))) (point)))
(buffer-substring ;; FIXME: Needed for cases where all completions are retrieved
(jupyter-completion-symbol-beginning (1- (point))) ;; from Base.| and the prefix turns empty again after
(point))) ;; Base.REPLCompletions)|
(t ;;
(let* ((s (jupyter-completion-grab-symbol-cons "\\." 1))) ;; Actually the problem stems from stting the prefix length to 0
(unless (and (consp s) (string= (car s) "") ;; in company in the case Base.| and we have not selected a
(jupyter-completion-floating-point-p)) ;; completion and just pass over it.
s)))))))) ((and (looking-at-p "\\_>")
(eq (char-syntax (char-before)) ?\)))
nil)
(t
(unless (jupyter-completion-number-p)
(jupyter-completion-grab-symbol-cons re max-len)))))
(cl-defmethod jupyter-completion-prefix (&context (major-mode jupyter-repl-mode)) (cl-defmethod jupyter-completion-prefix (&context (major-mode jupyter-repl-mode))
(and (not (get-text-property (point) 'read-only)) (and (not (get-text-property (point) 'read-only))
(cl-call-next-method))) (cl-call-next-method "\\." 1)))
(cl-defmethod jupyter-completion-prefix (&context (jupyter-lang julia))
(cl-call-next-method "\\\\\\|\\.\\|::\\|->" 2))
(defun jupyter-completion-construct-candidates (matches metadata) (defun jupyter-completion-construct-candidates (matches metadata)
"Construct candidates for completion. "Construct candidates for completion.
@ -2172,6 +2189,32 @@ in the appropriate direction, to the saved element."
(define-key map (kbd "M-p") #'jupyter-repl-history-previous) (define-key map (kbd "M-p") #'jupyter-repl-history-previous)
map)) map))
(cl-defgeneric jupyter-repl-after-init ()
"Hook function called whenever `jupyter-repl-mode' is enabled/disabled.
You may override this function for a particular language using a
jupyter-lang &context specializer. For example, to do something
when the language if the REPL is python the method signature
would be
(cl-defmethod jupyter-repl-after-init (&context (jupyter-lang python)))"
nil)
(cl-defmethod jupyter-repl-after-init (&context (jupyter-lang javascript))
;; Special case since `js2-mode' does not use `font-lock-defaults' for
;; highlighting.
(when (and (eq jupyter-repl-lang-mode 'js2-mode)
(null (nth 0 font-lock-defaults)))
(add-hook 'after-change-functions
(lambda (_beg _end _len)
(unless (jupyter-repl-cell-finalized-p)
(save-restriction
(narrow-to-region
(jupyter-repl-cell-code-beginning-position)
(jupyter-repl-cell-code-end-position))
(js2-parse)
(js2-mode-apply-deferred-properties))))
t t)))
;; TODO: Gaurd against a major mode change ;; TODO: Gaurd against a major mode change
(put 'jupyter-repl-mode 'mode-class 'special) (put 'jupyter-repl-mode 'mode-class 'special)
(define-derived-mode jupyter-repl-mode fundamental-mode (define-derived-mode jupyter-repl-mode fundamental-mode
@ -2220,7 +2263,9 @@ in the appropriate direction, to the saved element."
(jupyter-repl-initialize-fontification) (jupyter-repl-initialize-fontification)
(jupyter-repl-isearch-setup) (jupyter-repl-isearch-setup)
(jupyter-repl-sync-execution-state) (jupyter-repl-sync-execution-state)
(jupyter-repl-interaction-mode))) (jupyter-repl-interaction-mode)
(when jupyter-repl-mode
(jupyter-repl-after-init))))
(defun jupyter-repl-initialize-hooks () (defun jupyter-repl-initialize-hooks ()
"Initialize startup hooks. "Initialize startup hooks.
@ -2400,14 +2445,11 @@ Where MODE is the `major-mode' to use for syntax highlighting
purposes and SYNTAX-TABLE is the syntax table of MODE." purposes and SYNTAX-TABLE is the syntax table of MODE."
(cl-destructuring-bind (&key file_extension &allow-other-keys) (cl-destructuring-bind (&key file_extension &allow-other-keys)
language-info language-info
(let (mode syntax) (with-temp-buffer
(with-temp-buffer (let ((buffer-file-name
(let ((buffer-file-name (concat "jupyter-repl-lang" file_extension)))
(concat "jupyter-repl-lang" file_extension))) (delay-mode-hooks (set-auto-mode)))
(delay-mode-hooks (set-auto-mode)) (list major-mode (syntax-table)))))
(setq mode major-mode)
(setq syntax (syntax-table))))
(list mode syntax))))
(defun jupyter-repl--new-repl (client) (defun jupyter-repl--new-repl (client)
"Initialize a new REPL buffer based on CLIENT. "Initialize a new REPL buffer based on CLIENT.