py-vterm-interaction.el/julia-vterm.el

351 lines
13 KiB
EmacsLisp
Raw Normal View History

;;; julia-vterm.el --- A mode for Julia REPL using vterm -*- lexical-binding: t -*-
2020-10-07 14:08:31 +09:00
2022-05-11 07:59:47 +09:00
;; Copyright (C) 2020-2022 Shigeaki Nishina
2020-10-07 14:08:31 +09:00
2020-03-11 10:30:20 +00:00
;; Author: Shigeaki Nishina
;; Maintainer: Shigeaki Nishina
2020-10-07 14:08:31 +09:00
;; Created: March 11, 2020
;; URL: https://github.com/shg/julia-vterm.el
2020-10-09 13:57:30 +09:00
;; Package-Requires: ((emacs "25.1") (vterm "0.0.1"))
;; Version: 0.17
2020-10-07 14:08:31 +09:00
;; Keywords: languages, julia
2020-03-11 10:30:20 +00:00
;; This file is not part of GNU Emacs.
2020-10-07 14:08:31 +09:00
2020-03-11 10:30:20 +00:00
;;; License:
2020-10-07 14:08:31 +09:00
2020-03-11 10:30:20 +00:00
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
2020-03-11 10:30:20 +00:00
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
2020-03-11 10:30:20 +00:00
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see https://www.gnu.org/licenses/.
2020-10-07 14:08:31 +09:00
;;; Commentary:
;; Provides a major-mode for inferior Julia process that runs in vterm, and a
;; minor-mode that extends julia-mode to support interaction with the inferior
;; Julia process.
;;; Usage:
;; You must have julia-mode and vterm installed.
;; Install julia-vterm.el manually using package.el
;;
;; (package-install-file "/path-to-download-dir/julia-vterm.el")
;;
;; Eval the following line. Add this line to your init file to enable this
;; mode in future sessions.
;;
;; (add-hook 'julia-mode-hook #'julia-vterm-mode)
;;
;; Now you can interact with an inferior Julia REPL from a Julia buffer.
;;
;; C-c C-z in a julia-mode buffer to open an inferior Julia REPL buffer.
;; C-c C-z in the REPL buffer to switch back to the script buffer.
;; C-<return> in the script buffer to send region or current line to REPL.
;;
;; See the code below for a few more key bidindings.
2020-03-11 10:30:20 +00:00
;;; Code:
(require 'vterm)
2022-07-13 11:14:44 +09:00
(require 'rx)
2020-03-11 10:30:20 +00:00
2020-10-07 14:08:31 +09:00
2020-03-11 10:30:20 +00:00
;;----------------------------------------------------------------------
(defgroup julia-vterm-repl nil
"A major mode for inferior Julia REPL."
2020-03-11 10:30:20 +00:00
:group 'julia)
(defvar julia-vterm-repl-program "julia"
"Name of the command for executing Julia code.
Maybe either a command in the path, like julia
or an absolute path name, like /usr/local/bin/julia
parameters may be used, like julia -q")
2020-03-11 10:30:20 +00:00
(defvar-local julia-vterm-repl-script-buffer nil)
2020-03-11 10:30:20 +00:00
(defvar julia-vterm-repl-mode-map
2020-03-11 10:30:20 +00:00
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-z") #'julia-vterm-repl-switch-to-script-buffer)
(define-key map (kbd "M-k") #'julia-vterm-repl-clear-buffer)
(define-key map (kbd "C-c C-t") #'julia-vterm-repl-copy-mode)
2020-03-11 10:30:20 +00:00
(define-key map (kbd "C-l") #'recenter-top-bottom)
map))
(define-derived-mode julia-vterm-repl-mode vterm-mode "Inf-Julia"
2020-03-11 10:30:20 +00:00
"A major mode for inferior Julia REPL."
:group 'julia-vterm-repl)
2020-03-11 10:30:20 +00:00
(defun julia-vterm-repl-buffer-name (&optional session-name)
"Return a Julia REPL buffer name whose session name is SESSION-NAME."
(format "*julia:%s*" (if session-name session-name "main")))
(defun julia-vterm-repl-session-name (repl-buffer)
"Return the session name of REPL-BUFFER."
(let ((bn (buffer-name repl-buffer)))
(if (string= (substring bn 1 7) "julia:")
(substring bn 7 -1)
nil)))
(defun julia-vterm-repl-buffer-with-session-name (session-name &optional restart)
"Return an inferior Julia REPL buffer of the session name SESSION-NAME.
If there exists no such buffer, one is created and returned.
With non-nil RESTART, the existing buffer will be killed and
recreated."
(if-let ((buffer (get-buffer (julia-vterm-repl-buffer-name session-name)))
(alive (vterm-check-proc buffer))
(no-restart (not restart)))
2020-03-11 10:30:20 +00:00
buffer
(if (get-buffer-process buffer) (delete-process buffer))
2020-03-11 10:30:20 +00:00
(if buffer (kill-buffer buffer))
(let ((buffer (generate-new-buffer (julia-vterm-repl-buffer-name session-name)))
(vterm-shell julia-vterm-repl-program))
2020-03-11 10:30:20 +00:00
(with-current-buffer buffer
(julia-vterm-repl-mode)
(add-function :filter-args (process-filter vterm--process)
(julia-vterm-repl-run-filter-functions-func session-name)))
2020-03-11 10:30:20 +00:00
buffer)))
(defun julia-vterm-repl-buffer (&optional session-name restart)
"Return an inferior Julia REPL buffer.
The main REPL buffer will be returned if SESSION-NAME is not
given. If non-nil RESTART is given, the REPL buffer will be
recreated even when a process is alive and running in the buffer."
(if session-name
(julia-vterm-repl-buffer-with-session-name session-name restart)
(julia-vterm-repl-buffer-with-session-name "main" restart)))
(defun julia-vterm-repl (&optional arg)
"Create an inferior Julia REPL buffer and open it.
The buffer name will be `*julia:main*` where `main` is the default session name.
With prefix ARG, prompt for a session name.
If there's already an alive REPL buffer for the session, it will be opened."
(interactive "P")
(let* ((session-name
(cond ((null arg) nil)
(t (read-from-minibuffer "Session name: "))))
(orig-buffer (current-buffer))
(repl-buffer (julia-vterm-repl-buffer session-name)))
(if (and (boundp 'julia-vterm-mode) julia-vterm-mode)
(with-current-buffer repl-buffer
(setq julia-vterm-repl-script-buffer orig-buffer)))
(pop-to-buffer-same-window repl-buffer)))
2020-03-11 10:30:20 +00:00
(defun julia-vterm-repl-switch-to-script-buffer ()
"Switch to the script buffer that is paired with this Julia REPL buffer."
2020-03-11 10:30:20 +00:00
(interactive)
(let ((repl-buffer (current-buffer))
(script-buffer (if (buffer-live-p julia-vterm-repl-script-buffer)
julia-vterm-repl-script-buffer
nil)))
(if script-buffer
(with-current-buffer script-buffer
(setq julia-vterm-fellow-repl-buffer repl-buffer)
(let ((display-buffer-alist '((".*"
(display-buffer-reuse-window)
(reusable-frames . visible)))))
(switch-to-buffer-other-window script-buffer))))))
2020-03-11 10:30:20 +00:00
(defun julia-vterm-repl-clear-buffer ()
"Clear the content of the Julia REPL buffer."
2020-03-11 10:30:20 +00:00
(interactive)
(save-excursion
2020-08-30 23:10:51 +09:00
(goto-char (point-min))
(vterm-clear 1)))
(defvar-local julia-vterm-repl-filter-functions '()
"List of filter functions that process the output to the REPL buffer.")
(defun julia-vterm-repl-run-filter-functions-func (session)
"Return a function that runs registered filter functions for SESSION with args."
(lambda (args)
(with-current-buffer (julia-vterm-repl-buffer session)
(let ((proc (car args))
(str (cadr args)))
(let ((funcs julia-vterm-repl-filter-functions))
(while funcs
(setq str (apply (pop funcs) (list str))))
(list proc str))))))
2020-03-13 02:42:28 +00:00
2022-05-03 22:17:12 +09:00
(defun julia-vterm-repl-buffer-status ()
(let* ((bs (buffer-string))
(tail (substring bs (- (min 256 (length bs))))))
2022-05-03 22:17:12 +09:00
(set-text-properties 0 (length tail) nil tail)
(let* ((lines (split-string (string-trim-right tail "[\t\n\r]+")
(char-to-string ?\n)))
(prompt (car (last lines))))
(pcase prompt
("julia> " :julia)
("help?> " :help)
((rx "(@v" (1+ (in "0-9.")) ") pkg> ") :pkg)
("shell> " :shell)))))
(defvar julia-vterm-repl-copy-mode-map
2020-03-13 02:42:28 +00:00
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-t") #'julia-vterm-repl-copy-mode)
(define-key map [return] #'julia-vterm-repl-copy-mode-done)
(define-key map (kbd "RET") #'julia-vterm-repl-copy-mode-done)
2020-03-13 02:42:28 +00:00
(define-key map (kbd "C-c C-r") #'vterm-reset-cursor-point)
map))
(define-minor-mode julia-vterm-repl-copy-mode
2020-03-13 02:42:28 +00:00
"Toggle copy mode."
:group 'julia-vterm-repl
2020-03-13 02:42:28 +00:00
:lighter " VTermCopy"
:keymap julia-vterm-repl-copy-mode-map
(if julia-vterm-repl-copy-mode
(progn
(message "Start copy mode")
(use-local-map nil)
(vterm-send-stop))
(vterm-reset-cursor-point)
(use-local-map julia-vterm-repl-mode-map)
(vterm-send-start)
(message "End copy mode")))
2020-03-13 02:42:28 +00:00
(defun julia-vterm-repl-copy-mode-done ()
"Save the active region to the kill ring and exit copy mode."
2020-03-13 02:42:28 +00:00
(interactive)
(if (region-active-p)
(kill-ring-save (region-beginning) (region-end))
(user-error "No active region"))
(julia-vterm-repl-copy-mode -1))
2020-03-13 02:42:28 +00:00
2020-10-07 14:08:31 +09:00
2020-03-11 10:30:20 +00:00
;;----------------------------------------------------------------------
(defgroup julia-vterm nil
2020-10-09 18:16:14 +09:00
"A minor mode to interact with an inferior Julia REPL."
2020-03-11 10:30:20 +00:00
:group 'julia)
(defcustom julia-vterm-hook nil
2020-10-09 18:16:14 +09:00
"Hook run after starting a Julia script buffer with an inferior Julia REPL."
2020-03-11 10:30:20 +00:00
:type 'hook
:group 'julia-vterm)
2020-03-11 10:30:20 +00:00
(defvar-local julia-vterm-fellow-repl-buffer nil)
(defun julia-vterm-fellow-repl-buffer (&optional session-name)
"Return the paired REPL buffer or the one specified with SESSION-NAME."
(if session-name
(julia-vterm-repl-buffer session-name)
(if (buffer-live-p julia-vterm-fellow-repl-buffer)
julia-vterm-fellow-repl-buffer
(julia-vterm-repl-buffer))))
(defun julia-vterm-switch-to-repl-buffer (&optional arg)
"Switch to the paired REPL buffer or to the one with a specified session name.
With prefix ARG, prompt for session name."
(interactive "P")
(let* ((session-name
(cond ((null arg) nil)
(t (read-from-minibuffer "Session name: "))))
(script-buffer (current-buffer))
(repl-buffer (julia-vterm-fellow-repl-buffer session-name)))
2022-07-02 23:09:05 +09:00
(setq julia-vterm-fellow-repl-buffer repl-buffer)
(with-current-buffer repl-buffer
(setq julia-vterm-repl-script-buffer script-buffer)
(let ((display-buffer-alist '((".*"
(display-buffer-reuse-window display-buffer-reuse-mode-window)
(reusable-frames . visible)))))
(switch-to-buffer-other-window repl-buffer)))))
2020-03-11 10:30:20 +00:00
(defun julia-vterm-send-return-key ()
"Send a return key to the Julia REPL."
(with-current-buffer (julia-vterm-fellow-repl-buffer)
2020-10-02 08:56:13 +09:00
(vterm-send-return)))
(defun julia-vterm-paste-string (string &optional session-name)
"Send STRING to the Julia REPL buffer using brackted paste mode."
(with-current-buffer (julia-vterm-fellow-repl-buffer session-name)
2020-10-02 08:56:13 +09:00
(vterm-send-string string t)))
2020-03-11 10:30:20 +00:00
(defun julia-vterm-send-current-line ()
"Send the current line to the Julia REPL, and move to the next line.
This sends a newline after the content of the current line even if there's no
newline at the end. A newline is also inserted after the current line of the
script buffer."
2020-03-11 10:30:20 +00:00
(interactive)
(save-excursion
(end-of-line)
(let ((clmn (current-column))
(char (char-after))
(line (string-trim (thing-at-point 'line t))))
(unless (and (zerop clmn) char)
(when (/= 0 clmn)
(julia-vterm-paste-string line)
(julia-vterm-send-return-key)
(if (not char)
(newline))))))
2020-03-11 10:30:20 +00:00
(forward-line))
(defun julia-vterm-send-region-or-current-line ()
"Send the content of the region if the region is active, or send the current line."
(interactive)
(if (use-region-p)
(progn
(julia-vterm-paste-string
2020-10-07 14:08:31 +09:00
(buffer-substring-no-properties (region-beginning) (region-end)))
(deactivate-mark))
(julia-vterm-send-current-line)))
(defun julia-vterm-send-buffer ()
"Send the whole content of the script buffer to the Julia REPL line by line."
2020-03-13 02:42:28 +00:00
(interactive)
(save-excursion
(julia-vterm-paste-string (buffer-string))))
2020-03-13 02:42:28 +00:00
(defun julia-vterm-send-include-buffer-file (&optional arg)
"Send a line to evaluate the buffer's file using include() to the Julia REPL.
With prefix ARG, use Revise.includet() instead."
(interactive "P")
(let ((fmt (if arg "Revise.includet(\"%s\")\n" "include(\"%s\")\n")))
(if (and buffer-file-name
(file-exists-p buffer-file-name)
(not (buffer-modified-p)))
(julia-vterm-paste-string (format fmt buffer-file-name))
(message "The buffer must be saved in a file to include."))))
2020-10-27 09:24:35 +09:00
(defun julia-vterm-send-cd-to-buffer-directory ()
"Send cd() function call to the Julia REPL to change the current working directory of REPL to the buffer's directory."
(interactive)
(if buffer-file-name
(let ((buffer-directory (file-name-directory buffer-file-name)))
(julia-vterm-paste-string (format "cd(\"%s\")\n" buffer-directory))
(with-current-buffer (julia-vterm-fellow-repl-buffer)
2020-10-27 09:24:35 +09:00
(setq default-directory buffer-directory)))
(message "The buffer is not associated with a directory.")))
(defalias 'julia-vterm-sync-wd 'julia-vterm-send-cd-to-buffer-directory)
(defun julia-vterm-fellow-repl-buffer-status ()
"Return REPL mode or nil if REPL is not ready for input."
(with-current-buffer (julia-vterm-fellow-repl-buffer)
2022-05-03 22:17:12 +09:00
(julia-vterm-repl-buffer-status)))
(unless (fboundp 'julia)
(defalias 'julia 'julia-vterm-repl))
2020-10-09 10:02:50 +09:00
;;;###autoload
(define-minor-mode julia-vterm-mode
2020-03-11 10:30:20 +00:00
"A minor mode for a Julia script buffer that interacts with an inferior Julia REPL."
:init-value nil
:lighter ""
:keymap
`((,(kbd "C-c C-z") . julia-vterm-switch-to-repl-buffer)
(,(kbd "C-<return>") . julia-vterm-send-region-or-current-line)
(,(kbd "C-c C-b") . julia-vterm-send-buffer)
2020-10-27 09:24:35 +09:00
(,(kbd "C-c C-i") . julia-vterm-send-include-buffer-file)
(,(kbd "C-c C-d") . julia-vterm-send-cd-to-buffer-directory)))
2020-03-11 10:30:20 +00:00
(provide 'julia-vterm)
2020-10-07 14:08:31 +09:00
;;; julia-vterm.el ends here