-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add a comint-based backend for the Julia REPL
- Loading branch information
Showing
3 changed files
with
404 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
;;; julia-repl.el --- A minor mode for a Julia REPL -*- lexical-binding:t -*- | ||
|
||
;; Copyright (C) 2016 Tamas K. Papp | ||
;; Author: Tamas Papp <[email protected]> | ||
;; Keywords: languages | ||
;; Version: 0.0.1 | ||
;; Package-Requires: ((emacs "25")) | ||
;; URL: https://github.com/tpapp/julia-repl | ||
|
||
;;; Usage: | ||
;; Put the following code in your .emacs, site-load.el, or other relevant file | ||
;; (add-to-list 'load-path "path-to-julia-repl") | ||
;; (require 'julia-repl) | ||
|
||
;;; License: | ||
;; Permission is hereby granted, free of charge, to any person obtaining | ||
;; a copy of this software and associated documentation files (the | ||
;; "Software"), to deal in the Software without restriction, including | ||
;; without limitation the rights to use, copy, modify, merge, publish, | ||
;; distribute, sublicense, and/or sell copies of the Software, and to | ||
;; permit persons to whom the Software is furnished to do so, subject to | ||
;; the following conditions: | ||
;; | ||
;; The above copyright notice and this permission notice shall be | ||
;; included in all copies or substantial portions of the Software. | ||
;; | ||
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
||
;;; Commentary: | ||
;; Run a julia REPL inside a comint buffer in Emacs. | ||
|
||
;;; Code: | ||
|
||
(require 'subr-x) | ||
(require 'cl-lib) | ||
|
||
(defgroup julia-repl-comint nil | ||
"A Julia REPL through comint." | ||
:group 'julia-repl) | ||
|
||
(defcustom julia-repl-comint-hook nil | ||
"Hook to run after starting a Julia REPL comint buffer." | ||
:type 'hook | ||
:group 'julia-repl-comint) | ||
|
||
(defcustom julia-repl-comint-use-prompt-faces nil | ||
"When non-nil, color the julia prompts according to their respective faces." | ||
:group 'julia-repl-comint) | ||
|
||
(defgroup julia-repl-prompt-faces nil | ||
"Faces used for Julia prompts in julia-repl." | ||
:group 'julia-repl-comint | ||
:group 'faces) | ||
|
||
(defface julia-repl-julia-prompt-face | ||
'((t (:foreground "green"))) | ||
"Face for the julia> prompt in the REPL buffer." | ||
:group 'julia-repl-prompt-faces) | ||
|
||
(defface julia-repl-help-prompt-face | ||
'((t (:foreground "yellow"))) | ||
"Face for the help?> prompt in the REPL buffer." | ||
:group 'julia-repl-prompt-faces) | ||
|
||
(defface julia-repl-pkg-prompt-face | ||
'((t (:foreground "blue"))) | ||
"Face for the (env)pkg> prompt in the REPL buffer." | ||
:group 'julia-repl-prompt-faces) | ||
|
||
(defface julia-repl-shell-prompt-face | ||
'((t (:foreground "red"))) | ||
"Face for the shell> prompt in the REPL buffer." | ||
:group 'julia-repl-prompt-faces) | ||
|
||
|
||
|
||
;; sending to the REPL | ||
|
||
(defun julia-repl-comint--send-string (string &optional no-newline no-bracketed-paste) | ||
"Send STRING to the Julia REPL comint buffer. | ||
A closing newline is sent according to NO-NEWLINE: | ||
1. NIL sends the newline, | ||
2. 'PREFIX sends it according to ‘current-prefix-arg’, | ||
3. otherwise no newline. | ||
NO-BRACKETED-PASTE is ignored for sending to a comint process." | ||
(let ((inferior-buffer (julia-repl-inferior-buffer))) | ||
(display-buffer inferior-buffer) | ||
(when (eq no-newline 'prefix) | ||
(setq no-newline current-prefix-arg)) | ||
(with-current-buffer inferior-buffer | ||
(comint-send-string inferior-buffer | ||
(concat string (unless no-newline "\n")))))) | ||
|
||
(defun julia-repl-comint--result-as-string (command) | ||
(save-window-excursion | ||
(let ((inf-buf (julia-repl-inferior-buffer)) | ||
(buf (get-buffer-create "*julia-result*")) | ||
(cmd (concat command)) | ||
result) | ||
(with-current-buffer buf (erase-buffer)) | ||
(with-current-buffer inf-buf | ||
(comint-redirect-send-command-to-process cmd buf inf-buf nil) | ||
(while (not comint-redirect-completed) | ||
(sleep-for 0 1))) | ||
(with-current-buffer buf | ||
(replace-regexp-in-string "\\(^\"\\|\"$\\)" "" (string-trim (buffer-string))))))) | ||
|
||
(defun julia-repl-comint-send-line () | ||
"Send the current line to the Julia REPL comint buffer. | ||
Closed with a newline, unless used with a prefix argument. | ||
This is the only REPL interaction function that does not use | ||
bracketed paste. Unless you want this specifically, you should | ||
probably be using `julia-repl-send-region-or-line'." | ||
(interactive) | ||
(julia-repl-comint--send-string (thing-at-point 'line t) 'prefix t) | ||
(forward-line)) | ||
|
||
(defun julia-repl--comint-input-sender (proc string) | ||
(save-current-buffer | ||
(let* ((help-?-regexp "^ *\\(?:\\(?1: *?\\? *\\)\\(?2:.+\\)\\)") | ||
(help-?-match (string-match help-?-regexp string)) | ||
(pkg-bracket-regexp "^ *\\(?:\\(?1: *?\\] *\\)\\(?2:.+\\)\\)") | ||
(pkg-bracket-match (string-match pkg-bracket-regexp string)) | ||
(shell-sc-regexp "^ *\\(?:\\(?1: *?; *\\)\\(?2:.+\\)\\)") | ||
(shell-sc-match (string-match shell-sc-regexp string))) | ||
(cond (help-?-match | ||
(comint-simple-send proc (format "@doc %s" (match-string 2 string)))) | ||
(pkg-bracket-match | ||
(comint-simple-send proc (format "using Pkg; pkg\"%s\"" (match-string 2 string)))) | ||
(shell-sc-match | ||
(comint-simple-send proc (format "run(`%s`)" (match-string 2 string)))) | ||
(t | ||
(comint-simple-send proc string)))))) | ||
|
||
(defun julia-repl-comint--start-inferior (inferior-buffer-name executable-path) | ||
(let ((buf (get-buffer-create (julia-repl--add-earmuffs inferior-buffer-name)))) | ||
(with-current-buffer buf | ||
(apply #'make-comint-in-buffer inferior-buffer-name nil | ||
(executable-find executable-path) nil (julia-repl--split-switches)) | ||
(julia-repl-inferior-julia-mode) | ||
(setq comint-input-sender 'julia-repl--comint-input-sender) | ||
buf))) | ||
|
||
|
||
(defvar julia-repl-julia-prompt-regexp "^julia> " | ||
"Regexp for matching the julia prompt.") | ||
|
||
(defun julia-repl-comint-r-square-bracket () | ||
"Switch to the Pkg3 REPL interface at the Julia prompt or insert ']'." | ||
(interactive) | ||
(insert "]") | ||
(when (looking-back (concat julia-repl-julia-prompt-regexp "\\]")) | ||
(let ((prompt (julia-repl-comint--result-as-string "using Pkg; Pkg.REPLMode.promptf()")) | ||
(bol (save-excursion (search-backward-regexp "^") (point))) | ||
(pt (point))) | ||
(when julia-repl-comint-use-prompt-faces | ||
(put-text-property 0 (1- (length prompt)) 'face 'julia-repl-pkg-prompt-face prompt)) | ||
(put-text-property bol pt 'display prompt)))) | ||
|
||
(defun julia-repl-comint-question-mark () | ||
"Switch to the help interface at the Julia prompt or insert '?'." | ||
(interactive) | ||
(insert "?") | ||
(when (looking-back (concat julia-repl-julia-prompt-regexp "\\?")) | ||
(let ((prompt "help?> ") | ||
(bol (save-excursion (search-backward-regexp "^") (point)))) | ||
(when julia-repl-comint-use-prompt-faces | ||
(put-text-property 0 (1- (length prompt)) 'face 'julia-repl-help-prompt-face prompt)) | ||
(put-text-property bol (point) 'display prompt)))) | ||
|
||
(defun julia-repl-comint-semicolon () | ||
"Switch to the shell interface at the Julia prompt or insert ';'." | ||
(interactive) | ||
(insert ";") | ||
(when (looking-back (concat julia-repl-julia-prompt-regexp ";")) | ||
(let ((prompt "shell> ") | ||
(bol (save-excursion (search-backward-regexp "^") (point)))) | ||
(when julia-repl-comint-use-prompt-faces | ||
(put-text-property 0 (1- (length prompt)) 'face 'julia-repl-shell-prompt-face prompt)) | ||
(put-text-property bol (point) 'display prompt)))) | ||
|
||
(defun julia-repl-comint-backspace () | ||
(interactive) | ||
(let ((bol (save-excursion (search-backward-regexp "^") (point)))) | ||
(cond | ||
((looking-back julia-repl-julia-prompt-regexp) | ||
nil) | ||
((looking-back (concat julia-repl-julia-prompt-regexp ".")) | ||
(put-text-property bol (point) 'display nil) | ||
(delete-backward-char 1)) | ||
(t | ||
(delete-backward-char 1))))) | ||
|
||
|
||
(defvar julia-repl-inferior-julia-mode-map | ||
(let ((keymap (copy-keymap comint-mode-map))) | ||
(define-key keymap (kbd "]") #'julia-repl-comint-r-square-bracket) | ||
(define-key keymap (kbd "?") #'julia-repl-comint-question-mark) | ||
(define-key keymap (kbd ";") #'julia-repl-comint-semicolon) | ||
(define-key keymap (kbd "DEL") #'julia-repl-comint-backspace) | ||
keymap) | ||
"Key bindings for the Julia REPL running in the comint buffer.") | ||
|
||
|
||
(define-derived-mode julia-repl-inferior-julia-mode comint-mode "julia-repl" | ||
"Major mode for interacting with the Julia REPL. | ||
\\{julia-repl-inferior-julia-mode-map}" | ||
:group 'julia-repl-comint | ||
|
||
(when julia-repl-comint-use-prompt-faces | ||
(set-face-attribute 'comint-highlight-prompt nil :inherit 'julia-repl-julia-prompt-face)) | ||
|
||
(setq comint-prompt-regexp julia-repl-julia-prompt-regexp | ||
comint-prompt-read-only nil | ||
comint-use-prompt-regexp nil)) | ||
|
||
(provide 'julia-repl-comint) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
;;; julia-repl.el --- A minor mode for a Julia REPL -*- lexical-binding:t -*- | ||
|
||
;; Copyright (C) 2016 Tamas K. Papp | ||
;; Author: Tamas Papp <[email protected]> | ||
;; Keywords: languages | ||
;; Version: 0.0.1 | ||
;; Package-Requires: ((emacs "25")) | ||
;; URL: https://github.com/tpapp/julia-repl | ||
|
||
;;; Usage: | ||
;; Put the following code in your .emacs, site-load.el, or other relevant file | ||
;; (add-to-list 'load-path "path-to-julia-repl") | ||
;; (require 'julia-repl) | ||
|
||
;;; License: | ||
;; Permission is hereby granted, free of charge, to any person obtaining | ||
;; a copy of this software and associated documentation files (the | ||
;; "Software"), to deal in the Software without restriction, including | ||
;; without limitation the rights to use, copy, modify, merge, publish, | ||
;; distribute, sublicense, and/or sell copies of the Software, and to | ||
;; permit persons to whom the Software is furnished to do so, subject to | ||
;; the following conditions: | ||
;; | ||
;; The above copyright notice and this permission notice shall be | ||
;; included in all copies or substantial portions of the Software. | ||
;; | ||
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
||
;;; Commentary: | ||
;; Run a julia REPL inside a terminal in Emacs. In contrast to ESS, use | ||
;; the Julia REPL facilities for interactive features, such readline, | ||
;; help, debugging. | ||
|
||
;;; Code: | ||
|
||
(require 'term) | ||
(require 'subr-x) | ||
(require 'cl-lib) | ||
|
||
(defgroup julia-repl-term nil | ||
"A Julia REPL." | ||
:group 'julia-repl) | ||
|
||
(defcustom julia-repl-term-hook nil | ||
"Hook to run after starting a Julia REPL term buffer." | ||
:type 'hook | ||
:group 'julia-repl-term) | ||
|
||
(defcustom julia-repl-term-captures (list (kbd "M-x")) | ||
"List of key sequences that are passed through (the global binding is used). | ||
Note that this affects all buffers using the ‘ansi-term’ map." | ||
:type '(repeat key-sequence) | ||
:group 'julia-repl-term) | ||
|
||
|
||
;; sending to the REPL | ||
|
||
(defun julia-repl-term--send-string (string &optional no-newline no-bracketed-paste) | ||
"Send STRING to the Julia REPL term buffer. | ||
A closing newline is sent according to NO-NEWLINE: | ||
1. NIL sends the newline, | ||
2. 'PREFIX sends it according to ‘current-prefix-arg’, | ||
3. otherwise no newline. | ||
Unless NO-BRACKETED-PASTE, bracketed paste control sequences are used." | ||
(let ((inferior-buffer (julia-repl-inferior-buffer))) | ||
(display-buffer inferior-buffer) | ||
(with-current-buffer inferior-buffer | ||
(unless no-bracketed-paste ; bracketed paste start | ||
(term-send-raw-string "\e[200~")) | ||
(term-send-raw-string (string-trim string)) | ||
(when (eq no-newline 'prefix) | ||
(setq no-newline current-prefix-arg)) | ||
(unless no-newline | ||
(term-send-raw-string "\^M")) | ||
(unless no-bracketed-paste ; bracketed paste stop | ||
(term-send-raw-string "\e[201~"))))) | ||
|
||
(defun julia-repl-term-send-line () | ||
"Send the current line to the Julia REPL term buffer. | ||
Closed with a newline, unless used with a prefix argument. | ||
This is the only REPL interaction function that does not use | ||
bracketed paste. Unless you want this specifically, you should | ||
probably be using `julia-repl-send-region-or-line'." | ||
(interactive) | ||
(julia-repl--send-string (thing-at-point 'line t) 'prefix t) | ||
(forward-line)) | ||
|
||
|
||
(defun julia-repl-term--start-inferior (inferior-buffer-name executable-path) | ||
"Start a Julia REPL inferior process. | ||
Creates INFERIOR-BUFFER-NAME (‘make-term’ surrounds it with *s), | ||
running EXECUTABLE-PATH. | ||
Return the inferior buffer. No setup is performed." | ||
(message "name %s path %s" inferior-buffer-name executable-path) | ||
(apply #'make-term inferior-buffer-name executable-path nil | ||
(julia-repl--split-switches))) | ||
|
||
(defun julia-repl-term--setup-captures () | ||
"Set up captured keys which are captured from ‘term’. | ||
Note that this affects ‘term’ globally." | ||
(mapc (lambda (k) | ||
(define-key term-raw-map k (global-key-binding k))) | ||
julia-repl-term-captures)) | ||
|
||
(defun julia-repl-term--setup-term (inferior-buffer) | ||
"Set up customizations for term mode in INFERIOR-BUFFER. | ||
Note that not all effects are buffer local." | ||
(with-current-buffer inferior-buffer | ||
(term-char-mode) | ||
(term-set-escape-char ?\C-x) ; useful for switching windows | ||
(setq-local term-prompt-regexp "^(julia|shell|help\\?|(\\d+\\|debug ))>") | ||
(setq-local term-suppress-hard-newline t) ; reflow text | ||
(setq-local term-scroll-show-maximum-output t) | ||
;; do I need this? | ||
(setq-local term-scroll-to-bottom-on-output t) | ||
)) | ||
|
||
(defun julia-repl-comint--live-buffer () | ||
"" | ||
(if-let ((inferior-buffer | ||
(get-buffer (julia-repl--add-earmuffs | ||
(julia-repl--inferior-buffer-name)))))) | ||
(or (comint-check-proc ))) | ||
|
||
(provide 'julia-repl-term) |
Oops, something went wrong.