mirror of
https://github.com/vale981/emacs-jupyter
synced 2025-03-05 07:41:37 -05:00
Move evaluation functions from jupyter-repl.el to jupyter-client.el
These functions are general and not only useful for REPLs.
This commit is contained in:
parent
2f7268c8dc
commit
6a1da44904
2 changed files with 563 additions and 541 deletions
|
@ -34,8 +34,18 @@
|
|||
(require 'jupyter-base)
|
||||
(require 'jupyter-channels)
|
||||
(require 'jupyter-channel-ioloop)
|
||||
(require 'jupyter-mime)
|
||||
(require 'jupyter-messages)
|
||||
|
||||
(declare-function company-begin-backend "ext:company" (backend &optional callback))
|
||||
(declare-function company-doc-buffer "ext:company" (&optional string))
|
||||
(declare-function company-post-command "ext:company")
|
||||
(declare-function company-input-noop "ext:company")
|
||||
(declare-function company-auto-begin "ext:company")
|
||||
|
||||
(declare-function yas-minor-mode "ext:yasnippet" (&optional arg))
|
||||
(declare-function yas-expand-snippet "ext:yasnippet" (content &optional start end expand-env))
|
||||
|
||||
(declare-function hash-table-values "subr-x" (hash-table))
|
||||
(declare-function jupyter-insert "jupyter-mime")
|
||||
|
||||
|
@ -878,13 +888,73 @@ the user. Otherwise `read-from-minibuffer' is used."
|
|||
|
||||
;;;; Evaluation
|
||||
|
||||
(cl-defgeneric jupyter-load-file-code (_file)
|
||||
"Return a string suitable to send as code to a kernel for loading FILE.
|
||||
Use the jupyter-lang method specializer to add a method for a
|
||||
particular language."
|
||||
(error "Kernel language (%s) not supported yet"
|
||||
(jupyter-kernel-language jupyter-current-client)))
|
||||
|
||||
;;;;; Evaluation routines
|
||||
|
||||
(defvar-local jupyter-eval-expression-history nil
|
||||
"A client local variable to store the evaluation history.
|
||||
The evaluation history is used when reading code to evaluate from
|
||||
the minibuffer.")
|
||||
|
||||
(cl-defgeneric jupyter-read-expression ()
|
||||
"Read an expression using the `jupyter-current-client' for completion.
|
||||
The expression is read from the minibuffer and the expression
|
||||
history is obtained from the `jupyter-eval-expression-history'
|
||||
client local variable.
|
||||
|
||||
Methods that extend this generic function should
|
||||
`cl-call-next-method' as a last step."
|
||||
(cl-check-type jupyter-current-client jupyter-kernel-client
|
||||
"Need a client to read an expression")
|
||||
(let ((client jupyter-current-client))
|
||||
(jupyter-with-client-buffer jupyter-current-client
|
||||
(minibuffer-with-setup-hook
|
||||
(lambda ()
|
||||
(setq jupyter-current-client client)
|
||||
;; TODO: Enable the kernel languages mode using
|
||||
;; `jupyter-repl-language-mode', but there are
|
||||
;; issues with enabling a major mode.
|
||||
(add-hook 'completion-at-point-functions
|
||||
'jupyter-completion-at-point nil t))
|
||||
(read-from-minibuffer
|
||||
"Jupyter Ex: " nil
|
||||
read-expression-map
|
||||
nil 'jupyter-eval-expression-history)))))
|
||||
|
||||
(defun jupyter--display-eval-result (msg)
|
||||
(jupyter-with-message-data msg ((res text/plain))
|
||||
(if (null res)
|
||||
(jupyter-with-output-buffer "result" 'reset
|
||||
(jupyter-with-message-content msg (data metadata)
|
||||
(jupyter-insert data metadata))
|
||||
(goto-char (point-min))
|
||||
(display-buffer (current-buffer)))
|
||||
(setq res (ansi-color-apply res))
|
||||
(if (cl-loop
|
||||
with nlines = 0
|
||||
for c across res when (eq c ?\n) do (cl-incf nlines)
|
||||
thereis (> nlines 10))
|
||||
(jupyter-with-output-buffer "result" 'reset
|
||||
(insert res)
|
||||
(goto-char (point-min))
|
||||
(display-buffer (current-buffer)))
|
||||
(if (equal res "") (message "jupyter: eval done")
|
||||
(message "%s" res))))))
|
||||
|
||||
(defun jupyter-eval (code &optional mime)
|
||||
"Send an execute request for CODE, wait for the execute result.
|
||||
The `jupyter-current-client' is used to send the execute request.
|
||||
All client handlers except the status handler are inhibited for
|
||||
the request. In addition, the history of the request is not
|
||||
stored. Return the MIME representation of the result. If MIME is
|
||||
nil, return the text/plain representation."
|
||||
All client handlers are inhibited for the request. In addition,
|
||||
the history of the request is not stored. Return the MIME
|
||||
representation of the result. If MIME is nil, return the
|
||||
text/plain representation."
|
||||
(interactive (list (jupyter-read-expression) nil))
|
||||
(cl-check-type jupyter-current-client jupyter-kernel-client
|
||||
"Need a client to evaluate code")
|
||||
(let ((msg (jupyter-wait-until-received :execute-result
|
||||
|
@ -894,6 +964,118 @@ nil, return the text/plain representation."
|
|||
(when msg
|
||||
(jupyter-message-data msg (or mime :text/plain)))))
|
||||
|
||||
(defun jupyter-eval-string (str &optional cb)
|
||||
"Evaluate STR using the `jupyter-current-client'.
|
||||
Replaces the contents of the last cell in the REPL buffer with
|
||||
STR before evaluating.
|
||||
|
||||
If the result of evaluation is more than 10 lines long, a buffer
|
||||
displaying the results is shown. For results less than 10 lines
|
||||
long, the result is displayed in the minibuffer.
|
||||
|
||||
CB is a function to call with the `:execute-result' message when
|
||||
the evalution is succesful. When CB is nil, its behavior defaults
|
||||
to the above explanation."
|
||||
(interactive (list (jupyter-read-expression) current-prefix-arg nil))
|
||||
(unless jupyter-current-client
|
||||
(user-error "No `jupyter-current-client' set"))
|
||||
(let* ((jupyter-inhibit-handlers '(not :status))
|
||||
(req (jupyter-send-execute-request jupyter-current-client
|
||||
:code str :store-history nil)))
|
||||
(jupyter-add-callback req
|
||||
:execute-reply
|
||||
(lambda (msg)
|
||||
(jupyter-with-message-content msg (status evalue)
|
||||
(unless (equal status "ok")
|
||||
(message "%s" (ansi-color-apply evalue)))))
|
||||
:execute-result
|
||||
(or (and (functionp cb) cb) #'jupyter--display-eval-result)
|
||||
:error
|
||||
(lambda (msg)
|
||||
(jupyter-with-message-content msg (traceback)
|
||||
;; FIXME: Assumes the error in the
|
||||
;; execute-reply is good enough
|
||||
(when (> (apply '+ (mapcar 'length traceback)) 250)
|
||||
(jupyter-display-traceback traceback))))
|
||||
:stream
|
||||
(lambda (msg)
|
||||
(jupyter-with-message-content msg (name text)
|
||||
(when (equal name "stdout")
|
||||
(jupyter-with-output-buffer "output" req
|
||||
(jupyter-insert-ansi-coded-text text)
|
||||
(display-buffer (current-buffer)
|
||||
'(display-buffer-below-selected)))))))
|
||||
req))
|
||||
|
||||
(defun jupyter-eval-region (beg end &optional cb)
|
||||
"Evaluate a region with the `jupyter-current-client'.
|
||||
BEG and END are the beginning and end of the region to evaluate.
|
||||
CB has the same meaning as in `jupyter-eval-string'. CB is
|
||||
ignored when called interactively."
|
||||
(interactive "r")
|
||||
(jupyter-eval-string (buffer-substring-no-properties beg end) cb))
|
||||
|
||||
(defun jupyter-eval--insert-result (pos region msg)
|
||||
(jupyter-with-message-data msg ((res text/plain))
|
||||
(when res
|
||||
(setq res (ansi-color-apply res))
|
||||
(with-current-buffer (marker-buffer pos)
|
||||
(save-excursion
|
||||
(cond
|
||||
(region
|
||||
(goto-char (car region))
|
||||
(delete-region (car region) (cdr region)))
|
||||
(t
|
||||
(goto-char pos)
|
||||
(end-of-line)
|
||||
(insert "\n")))
|
||||
(set-marker pos nil)
|
||||
(insert res)
|
||||
(when region (push-mark)))))))
|
||||
|
||||
(defun jupyter-eval-line-or-region (insert)
|
||||
"Evaluate the current line or region with the `jupyter-current-client'.
|
||||
If the current region is active send the current region using
|
||||
`jupyter-eval-region', otherwise send the current line.
|
||||
|
||||
With a prefix argument, evaluate and INSERT the results in the
|
||||
current buffer."
|
||||
(interactive "P")
|
||||
(let ((cb (when insert
|
||||
(apply-partially
|
||||
#'jupyter-eval--insert-result
|
||||
(point-marker) (when (use-region-p)
|
||||
(car (region-bounds)))))))
|
||||
(if (use-region-p)
|
||||
(jupyter-eval-region (region-beginning) (region-end) cb)
|
||||
(jupyter-eval-region (line-beginning-position) (line-end-position) cb))))
|
||||
|
||||
(defun jupyter-load-file (file)
|
||||
"Send the contents of FILE using `jupyter-current-client'."
|
||||
(interactive
|
||||
(list (read-file-name "File name: " nil nil nil
|
||||
(file-name-nondirectory
|
||||
(or (buffer-file-name) "")))))
|
||||
(message "Evaluating %s..." file)
|
||||
(setq file (expand-file-name file))
|
||||
(if (file-exists-p file)
|
||||
(jupyter-eval-string (jupyter-load-file-code file))
|
||||
(error "Not a file (%s)" file)))
|
||||
|
||||
(defun jupyter-eval-buffer (buffer)
|
||||
"Send the contents of BUFFER using `jupyter-current-client'."
|
||||
(interactive (list (current-buffer)))
|
||||
(jupyter-eval-string (with-current-buffer buffer (buffer-string))))
|
||||
|
||||
(defun jupyter-eval-defun ()
|
||||
"Evaluate the function at `point'."
|
||||
(interactive)
|
||||
(cl-destructuring-bind (beg . end)
|
||||
(bounds-of-thing-at-point 'defun)
|
||||
(jupyter-eval-region beg end)))
|
||||
|
||||
;;;;; Handlers
|
||||
|
||||
(cl-defgeneric jupyter-send-execute-request ((client jupyter-kernel-client)
|
||||
&key code
|
||||
(silent nil)
|
||||
|
@ -1005,6 +1187,8 @@ DETAIL is the detail level to use for the request and defaults to
|
|||
|
||||
;;;; Completion
|
||||
|
||||
;;;;; Code context
|
||||
|
||||
(cl-defgeneric jupyter-code-context (type)
|
||||
"Return a list, (CODE POS), for the context around `point'.
|
||||
CODE is the required context for TYPE (either `inspect' or
|
||||
|
@ -1043,6 +1227,361 @@ in `jupyter-line-context' and is ignored if the region is active."
|
|||
(cl-defmethod jupyter-code-context ((_type (eql completion)))
|
||||
(jupyter-line-or-region-context))
|
||||
|
||||
;;;;; Helpers for completion interface
|
||||
|
||||
(defun jupyter-completion-symbol-beginning (&optional pos)
|
||||
"Return the starting position of a completion symbol.
|
||||
If POS is non-nil return the position of the symbol before POS
|
||||
otherwise return the position of the symbol before point."
|
||||
(save-excursion
|
||||
(and pos (goto-char pos))
|
||||
(if (and (eq (char-syntax (char-before)) ?.)
|
||||
(not (eq (char-before) ?.)))
|
||||
;; Complete operators, but not the field/attribute
|
||||
;; accessor .
|
||||
(skip-syntax-backward ".")
|
||||
(skip-syntax-backward "w_"))
|
||||
(point)))
|
||||
|
||||
;; Adapted from `company-grab-symbol-cons'
|
||||
(defun jupyter-completion-grab-symbol-cons (re &optional max-len)
|
||||
"Return the current completion prefix before point.
|
||||
Return either a STRING or a (STRING . t) pair. If RE matches the
|
||||
beginning of the current symbol before point, return the latter.
|
||||
Otherwise return the symbol before point. If no completion can be
|
||||
done at point, return nil.
|
||||
|
||||
MAX-LEN is the maximum number of characters to search behind the
|
||||
begiining of the symbol at point to look for a match of RE."
|
||||
(let ((symbol (if (or (looking-at "\\>\\|\\_>")
|
||||
;; Complete operators
|
||||
(and (char-before)
|
||||
(eq (char-syntax (char-before)) ?.)))
|
||||
(buffer-substring-no-properties
|
||||
(jupyter-completion-symbol-beginning) (point))
|
||||
(unless (and (char-after)
|
||||
(memq (char-syntax (char-after)) '(?w ?_)))
|
||||
""))))
|
||||
(when symbol
|
||||
(save-excursion
|
||||
(forward-char (- (length symbol)))
|
||||
(if (looking-back re (if max-len
|
||||
(- (point) max-len)
|
||||
(line-beginning-position)))
|
||||
(cons symbol t)
|
||||
symbol)))))
|
||||
|
||||
(defun jupyter-completion-number-p ()
|
||||
"Return non-nil if the text before `point' may be a floating point number."
|
||||
(and (char-before)
|
||||
(or (<= ?0 (char-before) ?9)
|
||||
(eq (char-before) ?.))
|
||||
(save-excursion
|
||||
(skip-syntax-backward "w.")
|
||||
(looking-at-p "[0-9]+\\.?[0-9]*"))))
|
||||
|
||||
;;;;; Extracting arguments from argument strings
|
||||
;; This is mainly used for the Julia kernel which will return the type
|
||||
;; information of method arguments and the methods file locations.
|
||||
|
||||
(defconst jupyter-completion-argument-regexp
|
||||
(rx
|
||||
(group "(" (zero-or-more anything) ")")
|
||||
(one-or-more anything) " "
|
||||
(group (one-or-more anything)) ?: (group (one-or-more digit)))
|
||||
"Regular expression to match arguments and file locations.")
|
||||
|
||||
(defun jupyter-completion--arg-extract-1 (pos)
|
||||
"Helper function for `jupyter-completion--arg-extract'.
|
||||
Extract the arguments starting at POS, narrowing to the first
|
||||
SEXP before extraction."
|
||||
(save-restriction
|
||||
(goto-char pos)
|
||||
(narrow-to-region
|
||||
pos (save-excursion (forward-sexp) (point)))
|
||||
(jupyter-completion--arg-extract)))
|
||||
|
||||
(defun jupyter-completion--arg-extract ()
|
||||
"Extract arguments from an argument string.
|
||||
Works for Julia and Python."
|
||||
(let (arg-info
|
||||
inner-args ppss depth inner
|
||||
(start (1+ (point-min)))
|
||||
(get-sexp
|
||||
(lambda ()
|
||||
(buffer-substring-no-properties
|
||||
(point) (progn (forward-sexp) (point)))))
|
||||
(get-string
|
||||
(lambda (start)
|
||||
(string-trim
|
||||
(buffer-substring-no-properties
|
||||
start (1- (point)))))))
|
||||
(while (re-search-forward ",\\|::" nil t)
|
||||
(setq ppss (syntax-ppss)
|
||||
depth (nth 0 ppss)
|
||||
inner (nth 1 ppss))
|
||||
(cl-case (char-before)
|
||||
(?:
|
||||
(if (eq (char-after) ?{)
|
||||
(push (jupyter-completion--arg-extract-1 (point)) inner-args)
|
||||
(push (list (list (funcall get-sexp))) inner-args)))
|
||||
(?,
|
||||
(if (/= depth 1)
|
||||
(push (jupyter-completion--arg-extract-1 inner) inner-args)
|
||||
(push (cons (funcall get-string start) (pop inner-args))
|
||||
arg-info)
|
||||
(setq start (1+ (point)))))))
|
||||
(goto-char (point-max))
|
||||
(push (cons (funcall get-string start) (pop inner-args)) arg-info)
|
||||
(nreverse arg-info)))
|
||||
|
||||
(defun jupyter-completion--make-arg-snippet (args)
|
||||
"Construct a snippet from ARGS."
|
||||
(cl-loop
|
||||
with i = 1
|
||||
for top-args in args
|
||||
;; TODO: Handle nested arguments
|
||||
for (arg . inner-args) = top-args
|
||||
collect (format "${%d:%s}" i arg) into constructs
|
||||
and do (setq i (1+ i))
|
||||
finally return
|
||||
(concat "(" (mapconcat #'identity constructs ", ") ")")))
|
||||
|
||||
;;;;; Completion prefix
|
||||
|
||||
(cl-defgeneric jupyter-completion-prefix (&optional re max-len)
|
||||
"Return the prefix for the current completion context.
|
||||
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 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)))))
|
||||
|
||||
(defun jupyter-completion-construct-candidates (matches metadata)
|
||||
"Construct candidates for completion.
|
||||
MATCHES are the completion matches returned by the kernel,
|
||||
METADATA is any extra data associated with MATCHES that was
|
||||
supplied by the kernel."
|
||||
(let* ((matches (append matches nil))
|
||||
(tail matches)
|
||||
(types (append (plist-get metadata :_jupyter_types_experimental) nil))
|
||||
(buf))
|
||||
(save-current-buffer
|
||||
(unwind-protect
|
||||
(while tail
|
||||
(cond
|
||||
((string-match jupyter-completion-argument-regexp (car tail))
|
||||
(let* ((str (car tail))
|
||||
(args-str (match-string 1 str))
|
||||
(end (match-end 1))
|
||||
(path (match-string 2 str))
|
||||
(line (string-to-number (match-string 3 str)))
|
||||
(snippet (progn
|
||||
(unless buf
|
||||
(setq buf (generate-new-buffer " *temp*"))
|
||||
(set-buffer buf))
|
||||
(insert args-str)
|
||||
(goto-char (point-min))
|
||||
(prog1 (jupyter-completion--make-arg-snippet
|
||||
(jupyter-completion--arg-extract))
|
||||
(erase-buffer)))))
|
||||
(setcar tail (substring (car tail) 0 end))
|
||||
(put-text-property 0 1 'snippet snippet (car tail))
|
||||
(put-text-property 0 1 'location (cons path line) (car tail))
|
||||
(put-text-property 0 1 'docsig (car tail) (car tail))))
|
||||
;; TODO: This is specific to the results that
|
||||
;; the python kernel returns, make a support
|
||||
;; function?
|
||||
((string-match-p "\\." (car tail))
|
||||
(setcar tail (car (last (split-string (car tail) "\\."))))))
|
||||
(setq tail (cdr tail)))
|
||||
(when buf (kill-buffer buf))))
|
||||
;; When a type is supplied add it as an annotation
|
||||
(when types
|
||||
(let ((max-len (apply #'max (mapcar #'length matches))))
|
||||
(cl-mapc
|
||||
(lambda (match meta)
|
||||
(let* ((prefix (make-string (1+ (- max-len (length match))) ? ))
|
||||
(annot (concat prefix (plist-get meta :type))))
|
||||
(put-text-property 0 1 'annot annot match)))
|
||||
matches types)))
|
||||
matches))
|
||||
|
||||
;;;;; Completion at point interface
|
||||
|
||||
(defvar jupyter-completion-cache nil
|
||||
"The cache for completion candidates.
|
||||
A list that can take the following forms
|
||||
|
||||
(PREFIX . CANDIDATES)
|
||||
(fetched PREFIX MESSAGE)
|
||||
|
||||
The first form states that the list of CANDIDATES is for the
|
||||
prefix, PREFIX.
|
||||
|
||||
The second form signifies that the CANDIDATES for PREFIX must be
|
||||
extracted from MESSAGE and converted to the first form.")
|
||||
|
||||
(defun jupyter-completion-prefetch-p (prefix)
|
||||
"Return non-nil if a prefetch for PREFIX should be performed.
|
||||
Looks at `jupyter-completion-cache' to determine if its
|
||||
candidates can be used for PREFIX."
|
||||
(not (and jupyter-completion-cache
|
||||
(if (eq (car jupyter-completion-cache) 'fetched)
|
||||
(equal (nth 1 jupyter-completion-cache) prefix)
|
||||
(or (equal (car jupyter-completion-cache) prefix)
|
||||
(and (not (string= (car jupyter-completion-cache) ""))
|
||||
(string-prefix-p (car jupyter-completion-cache) prefix))))
|
||||
;; Invalidate the cache when completing argument lists
|
||||
(or (string= prefix "")
|
||||
(not (eq (aref prefix (1- (length prefix))) ?\())))))
|
||||
|
||||
;; TODO: Move functionality into a default `jupyter-handle-complete-reply'
|
||||
;; method rather than callbacks?
|
||||
(defun jupyter-completion-prefetch (fun)
|
||||
"Get completions for the current completion context.
|
||||
Run FUN when the completions are available."
|
||||
(cl-destructuring-bind (code pos)
|
||||
(jupyter-code-context 'completion)
|
||||
(let ((req (let ((jupyter-inhibit-handlers t))
|
||||
(jupyter-send-complete-request
|
||||
jupyter-current-client
|
||||
:code code :pos pos))))
|
||||
(prog1 req
|
||||
(jupyter-add-callback req :complete-reply fun)))))
|
||||
|
||||
(defvar jupyter-completion--company-timer nil)
|
||||
(defvar company-minimum-prefix-length)
|
||||
|
||||
(defun jupyter-completion--company-idle-begin ()
|
||||
"Trigger an idle completion."
|
||||
(when jupyter-completion--company-timer
|
||||
(cancel-timer jupyter-completion--company-timer))
|
||||
(setq jupyter-completion--company-timer
|
||||
;; NOTE: When we reach here `company-idle-delay' is `now' since
|
||||
;; we are already inside a company completion so we can't use
|
||||
;; it, just use a sensible time value instead.
|
||||
(run-with-idle-timer
|
||||
0.1 nil
|
||||
(lambda ()
|
||||
(let ((company-minimum-prefix-length 0))
|
||||
(when (company-auto-begin)
|
||||
(company-input-noop)
|
||||
(let ((this-command 'company-idle-begin))
|
||||
(company-post-command))))))))
|
||||
|
||||
(defun jupyter-completion-at-point ()
|
||||
"Function to add to `completion-at-point-functions'."
|
||||
(let ((prefix (jupyter-completion-prefix)) req)
|
||||
(when (and prefix jupyter-current-client)
|
||||
(when (consp prefix)
|
||||
(setq prefix (car prefix))
|
||||
(when (and (bound-and-true-p company-mode)
|
||||
(< (length prefix) company-minimum-prefix-length))
|
||||
(jupyter-completion--company-idle-begin)))
|
||||
(when (jupyter-completion-prefetch-p prefix)
|
||||
(setq jupyter-completion-cache nil
|
||||
req (jupyter-completion-prefetch
|
||||
(lambda (msg) (setq jupyter-completion-cache
|
||||
(list 'fetched prefix msg))))))
|
||||
(list
|
||||
(- (point) (length prefix)) (point)
|
||||
(completion-table-dynamic
|
||||
(lambda (_)
|
||||
(when (and req (not (jupyter-request-idle-received-p req))
|
||||
(not (eq (jupyter-message-type
|
||||
(jupyter-request-last-message req))
|
||||
:complete-reply)))
|
||||
(jupyter-wait-until-received :complete-reply req))
|
||||
(when (eq (car jupyter-completion-cache) 'fetched)
|
||||
(jupyter-with-message-content (nth 2 jupyter-completion-cache)
|
||||
(status matches metadata)
|
||||
(setq jupyter-completion-cache
|
||||
(cons (nth 1 jupyter-completion-cache)
|
||||
(when (equal status "ok")
|
||||
(jupyter-completion-construct-candidates
|
||||
matches metadata))))))
|
||||
(cdr jupyter-completion-cache)))
|
||||
:exit-function
|
||||
#'jupyter-completion--post-completion
|
||||
:company-location
|
||||
(lambda (arg) (get-text-property 0 'location arg))
|
||||
:annotation-function
|
||||
(lambda (arg) (get-text-property 0 'annot arg))
|
||||
:company-docsig
|
||||
(lambda (arg) (get-text-property 0 'docsig arg))
|
||||
:company-doc-buffer
|
||||
#'jupyter-completion--company-doc-buffer))))
|
||||
|
||||
(defun jupyter-completion--company-doc-buffer (arg)
|
||||
"Send an inspect request for ARG to the kernel.
|
||||
Use the `company-doc-buffer' to insert the results."
|
||||
(let ((buf (company-doc-buffer)))
|
||||
(jupyter-inspect arg nil buf)
|
||||
(with-current-buffer buf
|
||||
(when (> (point-max) (point-min))
|
||||
(let ((inhibit-read-only t))
|
||||
(remove-text-properties
|
||||
(point-min) (point-max) '(read-only))
|
||||
(font-lock-mode 1)
|
||||
(goto-char (point-min))
|
||||
(current-buffer))))))
|
||||
|
||||
(defun jupyter-completion--post-completion (arg status)
|
||||
"If ARG is a completion with a snippet, expand the snippet.
|
||||
Do this only if STATUS is sole or finished."
|
||||
(when (memq status '(sole finished))
|
||||
(jupyter-completion-post-completion arg)))
|
||||
|
||||
(defvar yas-minor-mode)
|
||||
|
||||
(cl-defgeneric jupyter-completion-post-completion (candidate)
|
||||
"Called when CANDIDATE was selected as the completion candidate.
|
||||
The default implementation expands the snippet in CANDIDATE's
|
||||
snippet text property, if any, and if `yasnippet' is available."
|
||||
(when (and (get-text-property 0 'snippet candidate)
|
||||
(require 'yasnippet nil t))
|
||||
(unless yas-minor-mode
|
||||
(yas-minor-mode 1))
|
||||
;; Due to packages like smartparens
|
||||
(when (eq (char-after) ?\))
|
||||
(delete-char 1))
|
||||
(yas-expand-snippet
|
||||
(get-text-property 0 'snippet candidate)
|
||||
(save-excursion
|
||||
(forward-sexp -1)
|
||||
(point))
|
||||
(point))))
|
||||
|
||||
(cl-defgeneric jupyter-send-complete-request ((client jupyter-kernel-client)
|
||||
&key code
|
||||
(pos 0))
|
||||
|
|
557
jupyter-repl.el
557
jupyter-repl.el
|
@ -43,8 +43,8 @@
|
|||
;; `jupyter-repl-interaction-mode' adds the following keybindings for
|
||||
;; interacing a REPL client:
|
||||
;;
|
||||
;; C-c C-c `jupyter-repl-eval-line-or-region'
|
||||
;; C-c C-l `jupyter-repl-eval-file'
|
||||
;; C-c C-c `jupyter-eval-line-or-region'
|
||||
;; C-c C-l `jupyter-eval-file'
|
||||
;; C-c C-f `jupyter-inspect-at-point'
|
||||
;; C-c C-r `jupyter-repl-restart-kernel'
|
||||
;; C-c C-i `jupyter-repl-interrupt-kernel'
|
||||
|
@ -63,16 +63,6 @@
|
|||
(require 'jupyter-kernel-manager)
|
||||
(require 'ring)
|
||||
|
||||
(declare-function company-begin-backend "ext:company" (backend &optional callback))
|
||||
(declare-function company-doc-buffer "ext:company" (&optional string))
|
||||
(declare-function company-post-command "ext:company")
|
||||
(declare-function company-input-noop "ext:company")
|
||||
(declare-function company-auto-begin "ext:company")
|
||||
|
||||
|
||||
(declare-function yas-minor-mode "ext:yasnippet" (&optional arg))
|
||||
(declare-function yas-expand-snippet "ext:yasnippet" (content &optional start end expand-env))
|
||||
|
||||
(declare-function string-trim "subr-x")
|
||||
|
||||
;; TODO: Fallbacks for when the language doesn't have a major mode installed.
|
||||
|
@ -1207,535 +1197,28 @@ value."
|
|||
|
||||
;;; Completion
|
||||
|
||||
(defconst jupyter-completion-argument-regexp
|
||||
(rx
|
||||
(group "(" (zero-or-more anything) ")")
|
||||
(one-or-more anything) " "
|
||||
(group (one-or-more anything)) ?: (group (one-or-more digit)))
|
||||
"Regular expression to match arguments and file locations.")
|
||||
|
||||
;;; Helpers for completion interface
|
||||
|
||||
(defun jupyter-completion-symbol-beginning (&optional pos)
|
||||
"Return the starting position of a completion symbol.
|
||||
If POS is non-nil return the position of the symbol before POS
|
||||
otherwise return the position of the symbol before point."
|
||||
(save-excursion
|
||||
(and pos (goto-char pos))
|
||||
(if (and (eq (char-syntax (char-before)) ?.)
|
||||
(not (eq (char-before) ?.)))
|
||||
;; Complete operators, but not the field/attribute
|
||||
;; accessor .
|
||||
(skip-syntax-backward ".")
|
||||
(skip-syntax-backward "w_"))
|
||||
(point)))
|
||||
|
||||
;; Adapted from `company-grab-symbol-cons'
|
||||
(defun jupyter-completion-grab-symbol-cons (re &optional max-len)
|
||||
"Return the current completion prefix before point.
|
||||
Return either a STRING or a (STRING . t) pair. If RE matches the
|
||||
beginning of the current symbol before point, return the latter.
|
||||
Otherwise return the symbol before point. If no completion can be
|
||||
done at point, return nil.
|
||||
|
||||
MAX-LEN is the maximum number of characters to search behind the
|
||||
begiining of the symbol at point to look for a match of RE."
|
||||
(let ((symbol (if (or (looking-at "\\>\\|\\_>")
|
||||
;; Complete operators
|
||||
(and (char-before)
|
||||
(eq (char-syntax (char-before)) ?.)))
|
||||
(buffer-substring-no-properties
|
||||
(jupyter-completion-symbol-beginning) (point))
|
||||
(unless (and (char-after)
|
||||
(memq (char-syntax (char-after)) '(?w ?_)))
|
||||
""))))
|
||||
(when symbol
|
||||
(save-excursion
|
||||
(forward-char (- (length symbol)))
|
||||
(if (looking-back re (if max-len
|
||||
(- (point) max-len)
|
||||
(line-beginning-position)))
|
||||
(cons symbol t)
|
||||
symbol)))))
|
||||
|
||||
(defun jupyter-completion-number-p ()
|
||||
"Return non-nil if the text before `point' may be a floating point number."
|
||||
(and (char-before)
|
||||
(or (<= ?0 (char-before) ?9)
|
||||
(eq (char-before) ?.))
|
||||
(save-excursion
|
||||
(skip-syntax-backward "w.")
|
||||
(looking-at-p "[0-9]+\\.?[0-9]*"))))
|
||||
|
||||
;;; Extracting arguments from argument strings
|
||||
|
||||
(defun jupyter-completion--arg-extract-1 (pos)
|
||||
"Helper function for `jupyter-completion--arg-extract'.
|
||||
Extract the arguments starting at POS, narrowing to the first
|
||||
SEXP before extraction."
|
||||
(save-restriction
|
||||
(goto-char pos)
|
||||
(narrow-to-region
|
||||
pos (save-excursion (forward-sexp) (point)))
|
||||
(jupyter-completion--arg-extract)))
|
||||
|
||||
(defun jupyter-completion--arg-extract ()
|
||||
"Extract arguments from an argument string.
|
||||
Works for Julia and Python."
|
||||
(let (arg-info
|
||||
inner-args ppss depth inner
|
||||
(start (1+ (point-min)))
|
||||
(get-sexp
|
||||
(lambda ()
|
||||
(buffer-substring-no-properties
|
||||
(point) (progn (forward-sexp) (point)))))
|
||||
(get-string
|
||||
(lambda (start)
|
||||
(string-trim
|
||||
(buffer-substring-no-properties
|
||||
start (1- (point)))))))
|
||||
(while (re-search-forward ",\\|::" nil t)
|
||||
(setq ppss (syntax-ppss)
|
||||
depth (nth 0 ppss)
|
||||
inner (nth 1 ppss))
|
||||
(cl-case (char-before)
|
||||
(?:
|
||||
(if (eq (char-after) ?{)
|
||||
(push (jupyter-completion--arg-extract-1 (point)) inner-args)
|
||||
(push (list (list (funcall get-sexp))) inner-args)))
|
||||
(?,
|
||||
(if (/= depth 1)
|
||||
(push (jupyter-completion--arg-extract-1 inner) inner-args)
|
||||
(push (cons (funcall get-string start) (pop inner-args))
|
||||
arg-info)
|
||||
(setq start (1+ (point)))))))
|
||||
(goto-char (point-max))
|
||||
(push (cons (funcall get-string start) (pop inner-args)) arg-info)
|
||||
(nreverse arg-info)))
|
||||
|
||||
(defun jupyter-completion--make-arg-snippet (args)
|
||||
"Construct a snippet from ARGS."
|
||||
(cl-loop
|
||||
with i = 1
|
||||
for top-args in args
|
||||
;; TODO: Handle nested arguments
|
||||
for (arg . inner-args) = top-args
|
||||
collect (format "${%d:%s}" i arg) into constructs
|
||||
and do (setq i (1+ i))
|
||||
finally return
|
||||
(concat "(" (mapconcat #'identity constructs ", ") ")")))
|
||||
|
||||
;;; Getting the completion context
|
||||
|
||||
(cl-defmethod jupyter-code-context ((_type (eql completion))
|
||||
&context (major-mode jupyter-repl-mode))
|
||||
(list (jupyter-repl-cell-code)
|
||||
(1- (jupyter-repl-cell-code-position))))
|
||||
|
||||
(cl-defgeneric jupyter-completion-prefix (&optional re max-len)
|
||||
"Return the prefix for the current completion context.
|
||||
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 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)))
|
||||
|
||||
(defun jupyter-completion-construct-candidates (matches metadata)
|
||||
"Construct candidates for completion.
|
||||
MATCHES are the completion matches returned by the kernel,
|
||||
METADATA is any extra data associated with MATCHES that was
|
||||
supplied by the kernel."
|
||||
(let* ((matches (append matches nil))
|
||||
(tail matches)
|
||||
(types (append (plist-get metadata :_jupyter_types_experimental) nil))
|
||||
(buf))
|
||||
(save-current-buffer
|
||||
(unwind-protect
|
||||
(while tail
|
||||
(cond
|
||||
((string-match jupyter-completion-argument-regexp (car tail))
|
||||
(let* ((str (car tail))
|
||||
(args-str (match-string 1 str))
|
||||
(end (match-end 1))
|
||||
(path (match-string 2 str))
|
||||
(line (string-to-number (match-string 3 str)))
|
||||
(snippet (progn
|
||||
(unless buf
|
||||
(setq buf (generate-new-buffer " *temp*"))
|
||||
(set-buffer buf))
|
||||
(insert args-str)
|
||||
(goto-char (point-min))
|
||||
(prog1 (jupyter-completion--make-arg-snippet
|
||||
(jupyter-completion--arg-extract))
|
||||
(erase-buffer)))))
|
||||
(setcar tail (substring (car tail) 0 end))
|
||||
(put-text-property 0 1 'snippet snippet (car tail))
|
||||
(put-text-property 0 1 'location (cons path line) (car tail))
|
||||
(put-text-property 0 1 'docsig (car tail) (car tail))))
|
||||
;; TODO: This is specific to the results that
|
||||
;; the python kernel returns, make a support
|
||||
;; function?
|
||||
((string-match-p "\\." (car tail))
|
||||
(setcar tail (car (last (split-string (car tail) "\\."))))))
|
||||
(setq tail (cdr tail)))
|
||||
(when buf (kill-buffer buf))))
|
||||
;; When a type is supplied add it as an annotation
|
||||
(when types
|
||||
(let ((max-len (apply #'max (mapcar #'length matches))))
|
||||
(cl-mapc
|
||||
(lambda (match meta)
|
||||
(let* ((prefix (make-string (1+ (- max-len (length match))) ? ))
|
||||
(annot (concat prefix (plist-get meta :type))))
|
||||
(put-text-property 0 1 'annot annot match)))
|
||||
matches types)))
|
||||
matches))
|
||||
|
||||
;;; Completion at point interface
|
||||
|
||||
(defvar jupyter-completion-cache nil
|
||||
"The cache for completion candidates.
|
||||
A list that can take the following forms
|
||||
|
||||
(PREFIX . CANDIDATES)
|
||||
(fetched PREFIX MESSAGE)
|
||||
|
||||
The first form states that the list of CANDIDATES is for the
|
||||
prefix, PREFIX.
|
||||
|
||||
The second form signifies that the CANDIDATES for PREFIX must be
|
||||
extracted from MESSAGE and converted to the first form.")
|
||||
|
||||
(defun jupyter-completion-prefetch-p (prefix)
|
||||
"Return non-nil if a prefetch for PREFIX should be performed.
|
||||
Looks at `jupyter-completion-cache' to determine if its
|
||||
candidates can be used for PREFIX."
|
||||
(not (and jupyter-completion-cache
|
||||
(if (eq (car jupyter-completion-cache) 'fetched)
|
||||
(equal (nth 1 jupyter-completion-cache) prefix)
|
||||
(or (equal (car jupyter-completion-cache) prefix)
|
||||
(and (not (string= (car jupyter-completion-cache) ""))
|
||||
(string-prefix-p (car jupyter-completion-cache) prefix))))
|
||||
;; Invalidate the cache when completing argument lists
|
||||
(or (string= prefix "")
|
||||
(not (eq (aref prefix (1- (length prefix))) ?\())))))
|
||||
|
||||
(defun jupyter-completion-prefetch (fun)
|
||||
"Get completions for the current completion context.
|
||||
Run FUN when the completions are available."
|
||||
(cl-destructuring-bind (code pos)
|
||||
(jupyter-code-context 'completion)
|
||||
(let ((req (let ((jupyter-inhibit-handlers t))
|
||||
(jupyter-send-complete-request
|
||||
jupyter-current-client
|
||||
:code code :pos pos))))
|
||||
(prog1 req
|
||||
(jupyter-add-callback req :complete-reply fun)))))
|
||||
|
||||
(defvar jupyter-completion--company-timer nil)
|
||||
(defvar company-minimum-prefix-length)
|
||||
|
||||
(defun jupyter-completion--company-idle-begin ()
|
||||
"Trigger an idle completion."
|
||||
(when jupyter-completion--company-timer
|
||||
(cancel-timer jupyter-completion--company-timer))
|
||||
(setq jupyter-completion--company-timer
|
||||
;; NOTE: When we reach here `company-idle-delay' is `now' since
|
||||
;; we are already inside a company completion so we can't use
|
||||
;; it, just use a sensible time value instead.
|
||||
(run-with-idle-timer
|
||||
0.1 nil
|
||||
(lambda ()
|
||||
(let ((company-minimum-prefix-length 0))
|
||||
(when (company-auto-begin)
|
||||
(company-input-noop)
|
||||
(let ((this-command 'company-idle-begin))
|
||||
(company-post-command))))))))
|
||||
|
||||
(defun jupyter-completion-at-point ()
|
||||
"Function to add to `completion-at-point-functions'."
|
||||
(let ((prefix (jupyter-completion-prefix)) req)
|
||||
(when (and prefix jupyter-current-client)
|
||||
(when (consp prefix)
|
||||
(setq prefix (car prefix))
|
||||
(when (and (bound-and-true-p company-mode)
|
||||
(< (length prefix) company-minimum-prefix-length))
|
||||
(jupyter-completion--company-idle-begin)))
|
||||
(when (jupyter-completion-prefetch-p prefix)
|
||||
(setq jupyter-completion-cache nil
|
||||
req (jupyter-completion-prefetch
|
||||
(lambda (msg) (setq jupyter-completion-cache
|
||||
(list 'fetched prefix msg))))))
|
||||
(list
|
||||
(- (point) (length prefix)) (point)
|
||||
(completion-table-dynamic
|
||||
(lambda (_)
|
||||
(when (and req (not (jupyter-request-idle-received-p req))
|
||||
(not (eq (jupyter-message-type
|
||||
(jupyter-request-last-message req))
|
||||
:complete-reply)))
|
||||
(jupyter-wait-until-received :complete-reply req))
|
||||
(when (eq (car jupyter-completion-cache) 'fetched)
|
||||
(jupyter-with-message-content (nth 2 jupyter-completion-cache)
|
||||
(status matches metadata)
|
||||
(setq jupyter-completion-cache
|
||||
(cons (nth 1 jupyter-completion-cache)
|
||||
(when (equal status "ok")
|
||||
(jupyter-completion-construct-candidates
|
||||
matches metadata))))))
|
||||
(cdr jupyter-completion-cache)))
|
||||
:exit-function
|
||||
#'jupyter-completion--post-completion
|
||||
:company-location
|
||||
(lambda (arg) (get-text-property 0 'location arg))
|
||||
:annotation-function
|
||||
(lambda (arg) (get-text-property 0 'annot arg))
|
||||
:company-docsig
|
||||
(lambda (arg) (get-text-property 0 'docsig arg))
|
||||
:company-doc-buffer
|
||||
#'jupyter-completion--company-doc-buffer))))
|
||||
|
||||
(defun jupyter-completion--company-doc-buffer (arg)
|
||||
"Send an inspect request for ARG to the kernel.
|
||||
Use the `company-doc-buffer' to insert the results."
|
||||
(let ((buf (company-doc-buffer)))
|
||||
(jupyter-inspect arg nil buf)
|
||||
(with-current-buffer buf
|
||||
(when (> (point-max) (point-min))
|
||||
(let ((inhibit-read-only t))
|
||||
(remove-text-properties
|
||||
(point-min) (point-max) '(read-only))
|
||||
(font-lock-mode 1)
|
||||
(goto-char (point-min))
|
||||
(current-buffer))))))
|
||||
|
||||
(defun jupyter-completion--post-completion (arg status)
|
||||
"If ARG is a completion with a snippet, expand the snippet.
|
||||
Do this only if STATUS is sole or finished."
|
||||
(when (memq status '(sole finished))
|
||||
(jupyter-completion-post-completion arg)))
|
||||
|
||||
(cl-defgeneric jupyter-completion-post-completion (candidate)
|
||||
"Called when CANDIDATE was selected as the completion candidate.
|
||||
The default implementation expands the snippet in CANDIDATE's
|
||||
snippet text property, if any, and if `yasnippet' is available."
|
||||
(when (and (get-text-property 0 'snippet candidate)
|
||||
(require 'yasnippet nil t))
|
||||
(unless yas-minor-mode
|
||||
(yas-minor-mode 1))
|
||||
;; Due to packages like smartparens
|
||||
(when (eq (char-after) ?\))
|
||||
(delete-char 1))
|
||||
(yas-expand-snippet
|
||||
(get-text-property 0 'snippet candidate)
|
||||
(save-excursion
|
||||
(forward-sexp -1)
|
||||
(point))
|
||||
(point))))
|
||||
|
||||
;;; Evaluation
|
||||
|
||||
(defvar jupyter-repl-eval-expression-history nil)
|
||||
|
||||
(defun jupyter-repl--read-expression ()
|
||||
(cl-defmethod jupyter-read-expression (&context ((and
|
||||
jupyter-current-client
|
||||
(object-of-class-p
|
||||
jupyter-current-client
|
||||
'jupyter-repl-client)
|
||||
t)
|
||||
(eql t)))
|
||||
(jupyter-with-repl-buffer jupyter-current-client
|
||||
(let ((client jupyter-current-client)
|
||||
(jupyter-repl-eval-expression-history
|
||||
(ring-elements jupyter-repl-history)))
|
||||
(minibuffer-with-setup-hook
|
||||
(lambda ()
|
||||
(setq jupyter-current-client client)
|
||||
;; TODO: Enable the kernel languages mode using
|
||||
;; `jupyter-repl-language-mode', but there are
|
||||
;; issues with enabling a major mode.
|
||||
(add-hook 'completion-at-point-functions
|
||||
'jupyter-completion-at-point nil t))
|
||||
(read-from-minibuffer
|
||||
"Jupyter Eval: " nil
|
||||
read-expression-map
|
||||
nil 'jupyter-repl-eval-expression-history)))))
|
||||
|
||||
(defun jupyter-repl-eval-string (str &optional silently cb)
|
||||
"Evaluate STR with the `jupyter-current-client's REPL.
|
||||
Replaces the contents of the last cell in the REPL buffer with
|
||||
STR before evaluating.
|
||||
|
||||
If the result of evaluation is more than 10 lines long, a buffer
|
||||
displaying the results is shown. For results less than 10 lines
|
||||
long, the result is displayed in the minibuffer.
|
||||
|
||||
If a prefix argument is given, SILENTLY evaluate STR without any
|
||||
modification to the REPL buffer. Only the results of evaluation
|
||||
are displayed.
|
||||
|
||||
CB is a function to call with the `:execute-result' message when
|
||||
the evalution is succesful. When CB is nil, its behavior defaults
|
||||
to the above explanation."
|
||||
(interactive (list (jupyter-repl--read-expression) current-prefix-arg nil))
|
||||
(unless jupyter-current-client
|
||||
(user-error "No `jupyter-current-client' set, see `jupyter-repl-associate-buffer'"))
|
||||
(jupyter-with-repl-buffer jupyter-current-client
|
||||
(goto-char (point-max))
|
||||
(unless (= (save-excursion (jupyter-repl-previous-cell)) 0)
|
||||
(jupyter-repl-insert-prompt 'in))
|
||||
(unless silently
|
||||
(jupyter-repl-replace-cell-code str)
|
||||
;; Allow the REPL to evaluate the current cell
|
||||
(setq str nil))
|
||||
(let* ((jupyter-inhibit-handlers '(not :status))
|
||||
(req (jupyter-send-execute-request jupyter-current-client
|
||||
:code str :store-history (unless silently t))))
|
||||
(jupyter-add-callback req
|
||||
:execute-reply (lambda (msg)
|
||||
(jupyter-with-message-content msg (status evalue)
|
||||
(unless (equal status "ok")
|
||||
(message "%s" (ansi-color-apply evalue)))))
|
||||
:execute-result
|
||||
(or (and (functionp cb) cb)
|
||||
(lambda (msg)
|
||||
(jupyter-with-message-data msg ((res text/plain))
|
||||
(if (null res)
|
||||
(jupyter-with-output-buffer "result" 'reset
|
||||
(jupyter-with-message-content msg (data metadata)
|
||||
(jupyter-insert data metadata))
|
||||
(goto-char (point-min))
|
||||
(display-buffer (current-buffer)))
|
||||
(setq res (ansi-color-apply res))
|
||||
(if (cl-loop
|
||||
with nlines = 0
|
||||
for c across res when (eq c ?\n) do (cl-incf nlines)
|
||||
thereis (> nlines 10))
|
||||
(jupyter-with-output-buffer "result" 'reset
|
||||
(insert res)
|
||||
(goto-char (point-min))
|
||||
(display-buffer (current-buffer)))
|
||||
(if (equal res "") (message "jupyter: eval done")
|
||||
(message "%s" res)))))))
|
||||
:error
|
||||
(lambda (msg)
|
||||
(jupyter-with-message-content msg (traceback)
|
||||
;; FIXME: Assumes the error in the
|
||||
;; execute-reply is good enough
|
||||
(when (> (apply '+ (mapcar 'length traceback)) 250)
|
||||
(jupyter-repl-display-traceback traceback))))
|
||||
:stream
|
||||
(lambda (msg)
|
||||
(jupyter-with-message-content msg (name text)
|
||||
(when (equal name "stdout")
|
||||
(jupyter-with-output-buffer "output" req
|
||||
(jupyter-insert-ansi-coded-text text)
|
||||
(display-buffer (current-buffer)
|
||||
'(display-buffer-below-selected)))))))
|
||||
req)))
|
||||
|
||||
(cl-defgeneric jupyter-load-file-code (_file)
|
||||
"Return a string suitable to send as code to a kernel for loading FILE.
|
||||
Use the jupyter-lang method specializer to add a method for a
|
||||
particular language."
|
||||
(error "Kernel language (%s) not supported yet"
|
||||
(jupyter-kernel-language jupyter-current-client)))
|
||||
|
||||
(defun jupyter-repl-eval-file (file)
|
||||
"Send the contents of FILE using `jupyter-current-client'."
|
||||
(interactive
|
||||
(list (read-file-name "File name: " nil nil nil
|
||||
(file-name-nondirectory
|
||||
(or (buffer-file-name) "")))))
|
||||
(message "Evaluating %s..." file)
|
||||
(setq file (expand-file-name file))
|
||||
(if (file-exists-p file)
|
||||
(jupyter-repl-eval-string
|
||||
(jupyter-load-file-code file) 'silently)
|
||||
(error "Not a file (%s)" file)))
|
||||
|
||||
(defun jupyter-repl-eval-buffer (buffer)
|
||||
"Send the contents of BUFFER using `jupyter-current-client'."
|
||||
(interactive (list (current-buffer)))
|
||||
(jupyter-repl-eval-string
|
||||
(with-current-buffer buffer (buffer-string)) 'silently))
|
||||
|
||||
(defun jupyter-repl-eval-region (beg end &optional silently cb)
|
||||
"Evaluate a region with the `jupyter-current-client'.
|
||||
BEG and END are the beginning and end of the region to evaluate.
|
||||
SILENTLY and CB has the same meaning as in `jupyter-repl-eval-string'.
|
||||
CB is ignored when called interactively."
|
||||
(interactive "rP")
|
||||
(jupyter-repl-eval-string
|
||||
(buffer-substring-no-properties beg end) silently cb))
|
||||
|
||||
(defun jupyter-repl-eval-line-or-region (insert)
|
||||
"Evaluate the current line or region with the `jupyter-current-client'.
|
||||
If the current region is active send the current region using
|
||||
`jupyter-repl-eval-region', otherwise send the current line.
|
||||
|
||||
With a prefix argument, evaluate and INSERT the results in the
|
||||
current buffer."
|
||||
(interactive "P")
|
||||
(let ((cb (when insert
|
||||
(let ((pos (point-marker))
|
||||
(region (when (use-region-p)
|
||||
(car (region-bounds)))))
|
||||
(lambda (msg)
|
||||
(jupyter-with-message-data msg ((res text/plain))
|
||||
(when res
|
||||
(setq res (ansi-color-apply res))
|
||||
(with-current-buffer (marker-buffer pos)
|
||||
(save-excursion
|
||||
(cond
|
||||
(region
|
||||
(goto-char (car region))
|
||||
(delete-region (car region) (cdr region)))
|
||||
(t
|
||||
(goto-char pos)
|
||||
(end-of-line)
|
||||
(insert "\n")))
|
||||
(set-marker pos nil)
|
||||
(insert res)
|
||||
(when region (push-mark)))))))))))
|
||||
(if (use-region-p)
|
||||
(jupyter-repl-eval-region
|
||||
(region-beginning) (region-end) 'silently cb)
|
||||
(jupyter-repl-eval-region
|
||||
(line-beginning-position) (line-end-position) 'silently cb))))
|
||||
|
||||
(defun jupyter-repl-eval-defun ()
|
||||
"Evaluate the function at `point'."
|
||||
(interactive)
|
||||
(cl-destructuring-bind (beg . end)
|
||||
(bounds-of-thing-at-point 'defun)
|
||||
(jupyter-repl-eval-region beg end 'silently)))
|
||||
(jupyter-set jupyter-current-client 'jupyter-eval-expression-history
|
||||
(ring-elements jupyter-repl-history))
|
||||
(cl-call-next-method)))
|
||||
|
||||
;;; Kernel management
|
||||
|
||||
|
@ -1919,8 +1402,8 @@ in the appropriate direction, to the saved element."
|
|||
(insert
|
||||
(substitute-command-keys
|
||||
"Jupyter scratch buffer for evaluation.
|
||||
\\[jupyter-repl-eval-line-or-region] to evaluate the line or region.
|
||||
\\[jupyter-repl-eval-buffer] to evaluate the whole buffer.
|
||||
\\[jupyter-eval-line-or-region] to evaluate the line or region.
|
||||
\\[jupyter-eval-buffer] to evaluate the whole buffer.
|
||||
\\[jupyter-repl-pop-to-buffer] to show the REPL buffer."))
|
||||
(comment-region (point-min) (point-max))
|
||||
(insert "\n\n")))
|
||||
|
@ -2149,13 +1632,13 @@ If CLIENT is a buffer or the name of a buffer, use the
|
|||
|
||||
(defvar jupyter-repl-interaction-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "C-x C-e") #'jupyter-repl-eval-line-or-region)
|
||||
(define-key map (kbd "C-c C-c") #'jupyter-repl-eval-line-or-region)
|
||||
(define-key map (kbd "C-M-x") #'jupyter-repl-eval-defun)
|
||||
(define-key map (kbd "C-x C-e") #'jupyter-eval-line-or-region)
|
||||
(define-key map (kbd "C-c C-c") #'jupyter-eval-line-or-region)
|
||||
(define-key map (kbd "C-M-x") #'jupyter-eval-defun)
|
||||
(define-key map (kbd "C-c C-s") #'jupyter-repl-scratch-buffer)
|
||||
(define-key map (kbd "C-c C-b") #'jupyter-repl-eval-buffer)
|
||||
(define-key map (kbd "C-c C-l") #'jupyter-repl-eval-file)
|
||||
(define-key map (kbd "C-c M-:") #'jupyter-repl-eval-string)
|
||||
(define-key map (kbd "C-c C-b") #'jupyter-eval-buffer)
|
||||
(define-key map (kbd "C-c C-l") #'jupyter-load-file)
|
||||
(define-key map (kbd "C-c M-:") #'jupyter-eval-string)
|
||||
(define-key map (kbd "M-i") #'jupyter-inspect-at-point)
|
||||
(define-key map (kbd "C-c C-r") #'jupyter-repl-restart-kernel)
|
||||
(define-key map (kbd "C-c C-i") #'jupyter-repl-interrupt-kernel)
|
||||
|
|
Loading…
Add table
Reference in a new issue