mirror of
https://github.com/vale981/emacs-jupyter
synced 2025-03-05 07:41:37 -05:00
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:
parent
c0c9d8dd70
commit
19abd9a5c6
3 changed files with 140 additions and 90 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
164
jupyter-repl.el
164
jupyter-repl.el
|
@ -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.
|
||||
|
|
Loading…
Add table
Reference in a new issue