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
: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-call-next-method)
(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))))))))))
(setf (jupyter-org-request-id-cleared-p req) t)))
;; TODO: Externalize this, for example by adding a slot for transormation
;; functions to a `jupyter-org-client'. Or for a `jupyter-org-request' object
;; 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.
(cl-defgeneric jupyter-org-transform-result (render-result)
"Do some final transformations of RENDER-RESULT.
RENDER-RESULT is the cons cell returned by
`jupyter-org-prepare-result' and KERNEL-LANG is the kernel
language."
(let ((render-param (or (car render-result) "scalar"))
(result (cdr render-result)))
(cond
((and (equal render-param "scalar") (equal kernel-lang "python"))
(cons "scalar" (when result (org-babel-python-table-or-string result))))
(t
(if (equal render-param "scalar")
(cons "scalar" (when result (org-babel-script-escape result)))
render-result)))))
`jupyter-org-prepare-result'. Return the transformed
RENDER-RESULT cons cell.
The default method calls `org-babel-script-escape' on the RESULT
if it is a scalar, otherwise it just returns RENDER-RESULT."
(cond
((equal (car render-result) "scalar")
(cons "scalar" (org-babel-script-escape (cdr render-result))))
(t 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)
"For a request made with CLIENT, add RESULT.
@ -438,14 +437,14 @@ block for the request."
(message "%s" (cdr result)))
(if (jupyter-org-request-async 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)
(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))
(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.
RESULTS is either a single cons cell or a list of such cells,
each cell having the form
@ -454,8 +453,7 @@ each cell having the form
They should have been collected by previous calls to
`jupyter-org-prepare-result'. PARAMS are the parameters
passed to `org-babel-execute:jupyter'. KERNEL-LANG is the
language of the kernel that produced RESULTS.
passed to `org-babel-execute:jupyter'.
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
@ -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.
with info = (list nil nil params)
with result-params = (alist-get :result-params params)
for (render-param . result) in
(mapcar (lambda (r) (jupyter-org--transform-result r kernel-lang))
results)
for (render-param . result) in (mapcar #'jupyter-org-transform-result results)
do (jupyter-org--inject-render-param render-param params)
(cl-letf (((symbol-function 'message) #'ignore))
(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
(lambda ()
(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--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))))))))))
(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
running BODY."
(declare (indent 0) (debug (&rest form)))
`(with-current-buffer jupyter-repl-lang-buffer
(let ((inhibit-read-only t))
(erase-buffer)
,@body)))
(let ((client (make-symbol "clientvar")))
`(let ((,client jupyter-current-client))
(with-current-buffer jupyter-repl-lang-buffer
(let ((inhibit-read-only t)
(jupyter-current-client ,client))
(erase-buffer)
,@body)))))
(defmacro with-jupyter-repl-cell (&rest body)
"Narrow to the current cell, run BODY, then widen.
@ -282,10 +285,6 @@ erased."
(with-jupyter-repl-buffer client
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
(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)
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 ()
"Handle markdown links specially."
(interactive)
(let ((link (markdown-link-at-pos (point))))
;; TODO: How to generalize this to kernels which do not utilize markdown in
;; their docstrings? Maybe if you do M-RET on a symbol it will call the
;; 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))))
(when (car link)
(apply #'jupyter-markdown-follow-link (cddr link)))))
(defun jupyter-repl-insert-markdown (text)
"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
(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 ()
"Indent the line according to the language of the REPL."
(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
(insert code)
(goto-char pos)
(indent-according-to-mode)
(jupyter-indent-line)
(setq pos (point))
(buffer-string))))
;; Don't modify the buffer when unnecessary, this allows
@ -1628,41 +1637,49 @@ Works for Julia and Python."
(list (jupyter-repl-cell-code)
(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.
This default function checks to see if the
`jupyter-kernel-language' of the `jupyter-current-client'
has a `:completion-prefix' support function set by
`jupyter-kernel-support-put' and calls that function to obtain
the completion prefix. If the language does not have a completion
prefix function, `jupyter-completion-grab-symbol-cons' is used.
The default method calls `jupyter-completion-grab-symbol-cons'
with RE and MAX-LEN as arguments, RE defaulting to \"\\\\.\". It
also handles argument lists surrounded by parentheses specially
by considering an open parentheses and the symbol before it as a
completion prefix since some kernels will complete argument lists
if given such a prefix.
Note that the prefix returned is not the content sent to the
kernel, but the symbol at `point'. See
`jupyter-code-context-at-point' for what is actually sent."
(when jupyter-repl-current-client
(let ((lang (jupyter-repl-language jupyter-repl-current-client))
(lang-mode (jupyter-repl-language-mode jupyter-repl-current-client)))
(and (memq major-mode `(,lang-mode jupyter-repl-mode))
;; No completion in finalized cells
(not (get-text-property (point) 'read-only))
(cond
;; Completing argument lists
((and (eq (char-syntax (char-before)) ?\()
(or (not (char-after))
(not (memq (char-syntax (char-after)) '(?w ?_)))))
(buffer-substring
(jupyter-completion-symbol-beginning (1- (point)))
(point)))
(t
(let* ((s (jupyter-completion-grab-symbol-cons "\\." 1)))
(unless (and (consp s) (string= (car s) "")
(jupyter-completion-floating-point-p))
s))))))))
kernel, but the prefix used by `jupyter-completion-at-point'. See
`jupyter-code-context' for what is actually sent to the kernel."
(or re (setq re "\\."))
(cond
;; Completing argument lists
((and (char-before)
(eq (char-syntax (char-before)) ?\()
(or (not (char-after))
(looking-at-p "\\_>")
(not (memq (char-syntax (char-after)) '(?w ?_)))))
(buffer-substring-no-properties
(jupyter-completion-symbol-beginning (1- (point)))
(point)))
;; FIXME: Needed for cases where all completions are retrieved
;; from Base.| and the prefix turns empty again after
;; Base.REPLCompletions)|
;;
;; Actually the problem stems from stting the prefix length to 0
;; in company in the case Base.| and we have not selected a
;; completion and just pass over it.
((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))
(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)
"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)
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
(put 'jupyter-repl-mode 'mode-class 'special)
(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-isearch-setup)
(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 ()
"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."
(cl-destructuring-bind (&key file_extension &allow-other-keys)
language-info
(let (mode syntax)
(with-temp-buffer
(let ((buffer-file-name
(concat "jupyter-repl-lang" file_extension)))
(delay-mode-hooks (set-auto-mode))
(setq mode major-mode)
(setq syntax (syntax-table))))
(list mode syntax))))
(with-temp-buffer
(let ((buffer-file-name
(concat "jupyter-repl-lang" file_extension)))
(delay-mode-hooks (set-auto-mode)))
(list major-mode (syntax-table)))))
(defun jupyter-repl--new-repl (client)
"Initialize a new REPL buffer based on CLIENT.