mirror of
https://github.com/vale981/py-vterm-interaction.el
synced 2025-03-04 17:41:40 -05:00
port to python
This commit is contained in:
parent
6d67b618a6
commit
a7a603c24d
3 changed files with 410 additions and 396 deletions
63
README.org
63
README.org
|
@ -1,95 +1,96 @@
|
|||
# -*- eval: (visual-line-mode 1) -*-
|
||||
#+STARTUP: showall
|
||||
|
||||
[[https://melpa.org/#/julia-vterm][file:https://melpa.org/packages/julia-vterm-badge.svg]] [[https://stable.melpa.org/#/julia-vterm][file:https://stable.melpa.org/packages/julia-vterm-badge.svg]]
|
||||
*This* is a straight port of [[https://github.com/shg/julia-vterm.el][julia-vterm.el]]. I only modified the code slightly to deal correctly with python environments
|
||||
and play nicely with =ipython=.
|
||||
|
||||
* julia-vterm
|
||||
* python-vterm
|
||||
|
||||
Julia-vterm provides a major-mode for an inferior Julia process (or REPL) that runs in vterm, and a minor-mode that extends julia-mode with the ability to interact with the inferior Julia process.
|
||||
Python-vterm provides a major-mode for an inferior Python process (or REPL) that runs in vterm, and a minor-mode that extends python-mode with the ability to interact with the inferior Python process. This is particularly useful for *fancy repls like =ipython= or =ptpython=*.
|
||||
|
||||
The functionalities required for typical REPL interaction have been implemented. While I would like to keep this package simple, suggestions are always welcome.
|
||||
|
||||
If you want to use Julia with [[https://orgmode.org/worg/org-contrib/babel/][Org Babel]], please consider trying [[https://github.com/shg/ob-julia-vterm.el][ob-julia-vterm]]. It uses julia-vterm and provides a solid babel development experience with session and asynchronous execution support.
|
||||
If you want to use Python with [[https://orgmode.org/worg/org-contrib/babel/][Org Babel]], please consider trying [[https://github.com/shg/ob-python-vterm.el][ob-python-vterm]]. It uses python-vterm and provides a solid babel development experience with session and asynchronous execution support.
|
||||
|
||||
** Installation
|
||||
|
||||
You can install this package from MELPA. The package name is “julia-vterm”.
|
||||
You can install this package from MELPA. The package name is “python-vterm”.
|
||||
|
||||
For manual installation, download =julia-vterm.el= into somewhere in your local directory and use =package-install-file= command. Please make sure [[https://github.com/JuliaEditorSupport/julia-emacs][julia-mode]] and [[https://github.com/akermu/emacs-libvterm][emacs-libvterm]] are installed and configured correctly.
|
||||
For manual installation, download =python-vterm.el= into somewhere in your local directory and use =package-install-file= command. Please make sure [[https://github.com/PythonEditorSupport/python-emacs][python-mode]] and [[https://github.com/akermu/emacs-libvterm][emacs-libvterm]] are installed and configured correctly.
|
||||
|
||||
Turn on =julia-vterm-mode= in a =julia-mode= buffer to use this package. A symbol “⁂” in the mode line indicates that the julia-mode buffer is ready to interact with the julia-vterm REPL. Add the following line to your init file to enable =julia-vterm-mode= in =julia-mode= buffers automatically.
|
||||
Turn on =python-vterm-mode= in a =python-mode= buffer to use this package. A symbol “⁂” in the mode line indicates that the python-mode buffer is ready to interact with the python-vterm REPL. Add the following line to your init file to enable =python-vterm-mode= in =python-mode= buffers automatically.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(add-hook 'julia-mode-hook #'julia-vterm-mode)
|
||||
(add-hook 'python-mode-hook #'python-vterm-mode)
|
||||
#+END_SRC
|
||||
|
||||
By default, the command named =julia= in your =PATH= is used. You can use a Julia executable in any path by setting the =julia-vterm-repl-program= variable to its absolute path. The variable can contain switches for the =julia= command. For example, you can use a =julia= executable at a certain path, with 4 threads enabled, by the line like the following.
|
||||
By default, the command named =python= in your =PATH= is used. You can use a Python executable in any path by setting the =python-vterm-repl-program= variable to its absolute path. The variable can contain switches for the =python= command. For example, you can use a =python= executable at a certain path, with 4 threads enabled, by the line like the following.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(setq julia-vterm-repl-program "/path/to/julia -t 4")
|
||||
(setq python-vterm-repl-program "ipython -i")
|
||||
#+END_SRC
|
||||
|
||||
** How to use
|
||||
|
||||
=M-x julia-vterm-repl= (or =M-x julia= if no other packages define it before julia-vterm is loaded) opens an inferior Julia REPL buffer.
|
||||
=M-x python-vterm-repl= (or =M-x python= if no other packages define it before python-vterm is loaded) opens an inferior Python REPL buffer.
|
||||
|
||||
In a julia script buffer with =julia-vterm-mode= on, you can open a Julia REPL with =M-x julia-vterm-switch-to-repl-buffer= (or =C-c C-z=). See below for other commands.
|
||||
In a python script buffer with =python-vterm-mode= on, you can open a Python REPL with =M-x python-vterm-switch-to-repl-buffer= (or =C-c C-z=). See below for other commands.
|
||||
|
||||
Both of the above operations open a REPL with the default session name =main=. You can specify a different session name by using the prefix argument =C-u=. A new session will be created and opened if there is no REPL with that session name.
|
||||
|
||||
You can also specify a session name by defining a file local variable =julia-vterm-session= (or =julia-session= if no other packages pre-define it). If the variable is defined, =C-c C-z= will open a REPL with that session name.
|
||||
You can also specify a session name by defining a file local variable =python-vterm-session= (or =python-session= if no other packages pre-define it). If the variable is defined, =C-c C-z= will open a REPL with that session name.
|
||||
|
||||
** Key bindings
|
||||
|
||||
*** julia-vterm-mode
|
||||
*** python-vterm-mode
|
||||
|
||||
#+begin_example
|
||||
Key Command / Description
|
||||
------------------------------------------------------------------------------------------
|
||||
C-c C-z julia-vterm-switch-to-repl-buffer
|
||||
C-c C-z python-vterm-switch-to-repl-buffer
|
||||
Switch to the paired REPL buffer or to the one with a specified session name.
|
||||
With prefix ARG, prompt for session name.
|
||||
|
||||
C-<return> julia-vterm-send-region-or-current-line
|
||||
C-c C-c python-vterm-send-region-or-current-line
|
||||
Send the content of the region if the region is active, or send the current
|
||||
line.
|
||||
|
||||
C-c C-b julia-vterm-send-buffer
|
||||
Send the whole content of the script buffer to the Julia REPL line by line.
|
||||
C-c C-b python-vterm-send-buffer
|
||||
Send the whole content of the script buffer to the Python REPL line by line.
|
||||
|
||||
C-c C-i julia-vterm-send-include-buffer-file
|
||||
Send a line to evaluate the buffer's file using include() to the Julia REPL.
|
||||
C-c C-i python-vterm-send-include-buffer-file
|
||||
Send a line to evaluate the buffer's file using include() to the Python REPL.
|
||||
With prefix ARG, Revise.includet() is used instead.
|
||||
|
||||
C-c C-d julia-vterm-send-cd-to-buffer-directory
|
||||
Send cd() function call to the Julia REPL to change the current working
|
||||
C-c C-d python-vterm-send-cd-to-buffer-directory
|
||||
Send cd() function call to the Python REPL to change the current working
|
||||
directory of REPL to the buffer's directory.
|
||||
#+end_example
|
||||
|
||||
*** julia-vterm-repl-mode
|
||||
*** python-vterm-repl-mode
|
||||
|
||||
#+begin_example
|
||||
Key Command / Description
|
||||
------------------------------------------------------------------------------------------
|
||||
C-c C-z julia-vterm-repl-switch-to-script-buffer
|
||||
Switch to the script buffer that is paired with the current Julia REPL buffer.
|
||||
C-c C-z python-vterm-repl-switch-to-script-buffer
|
||||
Switch to the script buffer that is paired with the current Python REPL buffer.
|
||||
|
||||
M-k julia-vterm-repl-clear-buffer
|
||||
Clear the content of the Julia REPL buffer.
|
||||
M-k python-vterm-repl-clear-buffer
|
||||
Clear the content of the Python REPL buffer.
|
||||
|
||||
C-c C-t julia-vterm-repl-copy-mode
|
||||
C-c C-t python-vterm-repl-copy-mode
|
||||
Enter copy mode.
|
||||
#+end_example
|
||||
|
||||
*** julia-vterm-repl-mode (copy mode)
|
||||
*** python-vterm-repl-mode (copy mode)
|
||||
|
||||
#+begin_example
|
||||
Key Command / Description
|
||||
------------------------------------------------------------------------------------------
|
||||
C-c C-t julia-vterm-repl-copy-mode
|
||||
C-c C-t python-vterm-repl-copy-mode
|
||||
Exit copy mode.
|
||||
|
||||
<return> julia-vterm-repl-copy-mode-done
|
||||
<return> python-vterm-repl-copy-mode-done
|
||||
Copy the region to the kill ring and exit copy mode.
|
||||
|
||||
C-c C-r vterm-reset-cursor-point
|
||||
|
|
365
julia-vterm.el
365
julia-vterm.el
|
@ -1,365 +0,0 @@
|
|||
;;; julia-vterm.el --- A mode for Julia REPL using vterm -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2020-2023 Shigeaki Nishina
|
||||
|
||||
;; Author: Shigeaki Nishina
|
||||
;; Maintainer: Shigeaki Nishina
|
||||
;; Created: March 11, 2020
|
||||
;; URL: https://github.com/shg/julia-vterm.el
|
||||
;; Package-Requires: ((emacs "25.1") (vterm "0.0.1"))
|
||||
;; Version: 0.24
|
||||
;; Keywords: languages, julia
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;;; License:
|
||||
|
||||
;; 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.
|
||||
;;
|
||||
;; 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.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
;;; 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.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'vterm)
|
||||
(require 'rx)
|
||||
|
||||
|
||||
;;----------------------------------------------------------------------
|
||||
(defgroup julia-vterm-repl nil
|
||||
"A major mode for inferior Julia REPL."
|
||||
:group 'julia)
|
||||
|
||||
(defvar-local 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")
|
||||
|
||||
(defvar-local julia-vterm-repl-script-buffer nil)
|
||||
|
||||
(defvar julia-vterm-repl-mode-map
|
||||
(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)
|
||||
(define-key map (kbd "C-l") #'recenter-top-bottom)
|
||||
map))
|
||||
|
||||
(define-derived-mode julia-vterm-repl-mode vterm-mode "Inf-Julia"
|
||||
"A major mode for inferior Julia REPL."
|
||||
:group 'julia-vterm-repl)
|
||||
|
||||
(defun julia-vterm-repl-buffer-name (&optional session-name)
|
||||
"Return a Julia REPL buffer name whose session name is SESSION-NAME.
|
||||
If SESSION-NAME is not given, the default session name `main' is assumed."
|
||||
(format "*julia:%s*" (or 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-list-sessions ()
|
||||
"Return a list of existing Julia REPL sessions."
|
||||
(mapcan (lambda (bn)
|
||||
(if (string-match "\\*julia:\\(.*\\)\\*" bn)
|
||||
(list (match-string 1 bn))
|
||||
nil))
|
||||
(mapcar #'buffer-name (buffer-list))))
|
||||
|
||||
(defun julia-vterm-repl-buffer (&optional session-name 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."
|
||||
(let ((ses-name (or session-name "main")))
|
||||
(if-let ((buffer (get-buffer (julia-vterm-repl-buffer-name ses-name)))
|
||||
(alive (vterm-check-proc buffer))
|
||||
(no-restart (not restart)))
|
||||
buffer
|
||||
(if (get-buffer-process buffer) (delete-process buffer))
|
||||
(if buffer (kill-buffer buffer))
|
||||
(let ((buffer (generate-new-buffer (julia-vterm-repl-buffer-name ses-name)))
|
||||
(vterm-shell julia-vterm-repl-program))
|
||||
(with-current-buffer buffer
|
||||
(julia-vterm-repl-mode)
|
||||
(add-function :filter-args (process-filter vterm--process)
|
||||
(julia-vterm-repl-run-filter-functions-func ses-name)))
|
||||
buffer))))
|
||||
|
||||
(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 (completing-read "Session name: " (julia-vterm-repl-list-sessions) nil nil nil nil
|
||||
(julia-vterm-repl-session-name (julia-vterm-fellow-repl-buffer))))))
|
||||
(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)))
|
||||
|
||||
(defun julia-vterm-repl-switch-to-script-buffer ()
|
||||
"Switch to the script buffer that is paired with this Julia REPL buffer."
|
||||
(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)
|
||||
(switch-to-buffer-other-window script-buffer)))))
|
||||
|
||||
(defun julia-vterm-repl-clear-buffer ()
|
||||
"Clear the content of the Julia REPL buffer."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(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))))))
|
||||
|
||||
(defun julia-vterm-repl-buffer-status ()
|
||||
"Check and return the prompt status of the REPL.
|
||||
Return a corresponding symbol or nil if not ready for input."
|
||||
(let* ((bs (buffer-string))
|
||||
(tail (substring bs (- (min 256 (length bs))))))
|
||||
(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
|
||||
((rx bol "julia> " eol) :julia)
|
||||
((rx bol "In [" (one-or-more (any "0-9")) "]: " eol) :julia)
|
||||
((rx bol "help?> " eol) :help)
|
||||
((rx bol "(" (+? any) ") pkg> " eol) :pkg)
|
||||
((rx bol "shell> " eol) :shell)))))
|
||||
|
||||
(defvar julia-vterm-repl-copy-mode-map
|
||||
(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)
|
||||
(define-key map (kbd "C-c C-r") #'vterm-reset-cursor-point)
|
||||
map))
|
||||
|
||||
(define-minor-mode julia-vterm-repl-copy-mode
|
||||
"Toggle copy mode."
|
||||
:group 'julia-vterm-repl
|
||||
: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")))
|
||||
|
||||
(defun julia-vterm-repl-copy-mode-done ()
|
||||
"Save the active region to the kill ring and exit copy mode."
|
||||
(interactive)
|
||||
(if (region-active-p)
|
||||
(kill-ring-save (region-beginning) (region-end))
|
||||
(user-error "No active region"))
|
||||
(julia-vterm-repl-copy-mode -1))
|
||||
|
||||
|
||||
;;----------------------------------------------------------------------
|
||||
(defgroup julia-vterm nil
|
||||
"A minor mode to interact with an inferior Julia REPL."
|
||||
:group 'julia)
|
||||
|
||||
(defcustom julia-vterm-hook nil
|
||||
"Hook run after starting a Julia script buffer with an inferior Julia REPL."
|
||||
:type 'hook
|
||||
:group 'julia-vterm)
|
||||
|
||||
(defvar-local julia-vterm-fellow-repl-buffer nil)
|
||||
(defvar-local julia-vterm-session 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
|
||||
(if julia-vterm-session
|
||||
(julia-vterm-repl-buffer julia-vterm-session)
|
||||
(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 (completing-read "Session name: " (julia-vterm-repl-list-sessions) nil nil nil nil
|
||||
(julia-vterm-repl-session-name (julia-vterm-fellow-repl-buffer))))))
|
||||
(script-buffer (current-buffer))
|
||||
(repl-buffer (julia-vterm-fellow-repl-buffer session-name)))
|
||||
(setq julia-vterm-fellow-repl-buffer repl-buffer)
|
||||
(with-current-buffer repl-buffer
|
||||
(setq julia-vterm-repl-script-buffer script-buffer)
|
||||
(switch-to-buffer-other-window repl-buffer))))
|
||||
|
||||
(defun julia-vterm-send-return-key ()
|
||||
"Send a return key to the Julia REPL."
|
||||
(with-current-buffer (julia-vterm-fellow-repl-buffer)
|
||||
(vterm-send-return)))
|
||||
|
||||
(defun julia-vterm-paste-string (string &optional session-name)
|
||||
"Send STRING to the Julia REPL buffer using brackted paste mode.
|
||||
If SESSION-NAME is given, the REPL with the session name, otherwise
|
||||
the main REPL, is used."
|
||||
(with-current-buffer (julia-vterm-fellow-repl-buffer session-name)
|
||||
(vterm-send-string string t)))
|
||||
|
||||
(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."
|
||||
(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))))))
|
||||
(forward-line))
|
||||
|
||||
(defun julia-vterm-ensure-newline (str)
|
||||
"Add a newline at the end of STR if the last character is not a newline."
|
||||
(concat str (if (string= (substring str -1 nil) "\n") "" "\n")))
|
||||
|
||||
(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)
|
||||
(let ((str (julia-vterm-ensure-newline (buffer-substring-no-properties (region-beginning) (region-end)))))
|
||||
(julia-vterm-paste-string str)
|
||||
(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."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(julia-vterm-paste-string (julia-vterm-ensure-newline (buffer-string)))))
|
||||
|
||||
(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."))))
|
||||
|
||||
(defun julia-vterm-send-cd-to-buffer-directory ()
|
||||
"Change the REPL's working directory to the directory of the buffer file."
|
||||
(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)
|
||||
(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)
|
||||
(julia-vterm-repl-buffer-status)))
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode julia-vterm-mode
|
||||
"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)
|
||||
(,(kbd "C-c C-i") . julia-vterm-send-include-buffer-file)
|
||||
(,(kbd "C-c C-d") . julia-vterm-send-cd-to-buffer-directory)))
|
||||
|
||||
|
||||
;;----------------------------------------------------------------------
|
||||
;; Define some utility aliases but not override if the names are already used.
|
||||
(unless (fboundp 'julia)
|
||||
(defalias 'julia 'julia-vterm-repl))
|
||||
|
||||
(unless (boundp 'julia-session)
|
||||
(defvaralias 'julia-session 'julia-vterm-session))
|
||||
|
||||
|
||||
(provide 'julia-vterm)
|
||||
|
||||
;;; julia-vterm.el ends here
|
378
python-vterm.el
Normal file
378
python-vterm.el
Normal file
|
@ -0,0 +1,378 @@
|
|||
;;; python-vterm.el --- A mode for Python REPL using vterm -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2020-2023 Shigeaki Nishina
|
||||
|
||||
;; Author: Shigeaki Nishina, Valentin Boettcher
|
||||
;; Maintainer: Valentin Boettcher
|
||||
;; Created: March 11, 2020
|
||||
;; URL: https://github.com/vale981/python-vterm.el
|
||||
;; Package-Requires: ((emacs "25.1") (vterm "0.0.1"))
|
||||
;; Version: 0.24
|
||||
;; Keywords: languages, python
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;;; License:
|
||||
|
||||
;; 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.
|
||||
;;
|
||||
;; 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.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Provides a major-mode for inferior Python process that runs in vterm, and a
|
||||
;; minor-mode that extends python-mode to support interaction with the inferior
|
||||
;; Python process. This is primarily useful for running fancy python shells like
|
||||
;; ipython or ptpython.
|
||||
;; This is a straight port of julia-vterm.el by Shigeaki Nishina
|
||||
;; (https://github.com/shg/julia-vterm.el) with only minor modifications.
|
||||
|
||||
;;; Usage:
|
||||
|
||||
;; You must have python-mode and vterm installed.
|
||||
;; Install python-vterm.el manually using package.el
|
||||
;;
|
||||
;; (package-install-file "/path-to-download-dir/python-vterm.el")
|
||||
;;
|
||||
;; Eval the following line. Add this line to your init file to enable this
|
||||
;; mode in future sessions.
|
||||
;;
|
||||
;; (add-hook 'python-mode-hook #'python-vterm-mode)
|
||||
;;
|
||||
;; Now you can interact with an inferior Python REPL from a Python buffer.
|
||||
;;
|
||||
;; C-c C-z in a python-mode buffer to open an inferior Python REPL buffer.
|
||||
;; C-c C-z in the REPL buffer to switch back to the script buffer.
|
||||
;; C-c C-c in the script buffer to send region or current line to REPL.
|
||||
;;
|
||||
;; See the code below for a few more key bidindings.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'vterm)
|
||||
(require 'rx)
|
||||
|
||||
|
||||
;;----------------------------------------------------------------------
|
||||
(defgroup python-vterm-repl nil
|
||||
"A major mode for inferior Python REPL."
|
||||
:group 'python)
|
||||
|
||||
(defvar-local python-vterm-repl-program "ipython"
|
||||
"Name of the command for executing Python code.
|
||||
Maybe either a command in the path, like python
|
||||
or an absolute path name, like /usr/local/bin/python
|
||||
parameters may be used, like python -q")
|
||||
|
||||
(defvar-local python-vterm-repl-script-buffer nil)
|
||||
|
||||
(defvar python-vterm-repl-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "C-c C-z") #'python-vterm-repl-switch-to-script-buffer)
|
||||
(define-key map (kbd "M-k") #'python-vterm-repl-clear-buffer)
|
||||
(define-key map (kbd "C-c C-t") #'python-vterm-repl-copy-mode)
|
||||
(define-key map (kbd "C-l") #'recenter-top-bottom)
|
||||
map))
|
||||
|
||||
(define-derived-mode python-vterm-repl-mode vterm-mode "Inf-Python"
|
||||
"A major mode for inferior Python REPL."
|
||||
:group 'python-vterm-repl)
|
||||
|
||||
(defcustom python-vterm-repl-mode-hook nil
|
||||
"Hook run after starting a Python script buffer with an inferior Python REPL."
|
||||
:type 'hook
|
||||
:group 'python-vterm-repl)
|
||||
|
||||
(defun python-vterm-repl-buffer-name (&optional session-name)
|
||||
"Return a Python REPL buffer name whose session name is SESSION-NAME.
|
||||
If SESSION-NAME is not given, the default session name `main' is assumed."
|
||||
(format "*python:%s*" (or session-name "main")))
|
||||
|
||||
(defun python-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) "python:")
|
||||
(substring bn 7 -1)
|
||||
nil)))
|
||||
|
||||
(defun python-vterm-repl-buffer (&optional session-name restart)
|
||||
"Return an inferior Python 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."
|
||||
(let ((ses-name (or session-name "main"))
|
||||
(env (cons "TERM=xterm-256color" process-environment)))
|
||||
(if-let ((buffer (get-buffer (python-vterm-repl-buffer-name ses-name)))
|
||||
(alive (vterm-check-proc buffer))
|
||||
(no-restart (not restart)))
|
||||
buffer
|
||||
(if (get-buffer-process buffer) (delete-process buffer))
|
||||
(if buffer (kill-buffer buffer))
|
||||
(let ((buffer (generate-new-buffer (python-vterm-repl-buffer-name ses-name))))
|
||||
(with-current-buffer buffer
|
||||
(let ((vterm-environment env)
|
||||
(vterm-shell python-vterm-repl-program))
|
||||
(python-vterm-repl-mode)
|
||||
(run-hooks python-vterm-repl-mode-hook)
|
||||
(add-function :filter-args (process-filter vterm--process)
|
||||
(python-vterm-repl-run-filter-functions-func ses-name))))
|
||||
buffer))))
|
||||
|
||||
|
||||
(defun python-vterm-repl-list-sessions ()
|
||||
"Return a list of existing Python REPL sessions."
|
||||
(mapcan (lambda (bn)
|
||||
(if (string-match "\\*python:\\(.*\\)\\*" bn)
|
||||
(list (match-string 1 bn))
|
||||
nil))
|
||||
(mapcar #'buffer-name (buffer-list))))
|
||||
|
||||
(defun python-vterm-repl (&optional arg)
|
||||
"Create an inferior Python REPL buffer and open it.
|
||||
The buffer name will be `*python: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 (completing-read "Session name: " (python-vterm-repl-list-sessions) nil nil nil nil
|
||||
(python-vterm-repl-session-name (python-vterm-fellow-repl-buffer))))))
|
||||
(orig-buffer (current-buffer))
|
||||
(repl-buffer (python-vterm-repl-buffer session-name)))
|
||||
(if (and (boundp 'python-vterm-mode) python-vterm-mode)
|
||||
(with-current-buffer repl-buffer
|
||||
(setq python-vterm-repl-script-buffer orig-buffer)))
|
||||
(pop-to-buffer-same-window repl-buffer)))
|
||||
|
||||
(defun python-vterm-repl-switch-to-script-buffer ()
|
||||
"Switch to the script buffer that is paired with this Python REPL buffer."
|
||||
(interactive)
|
||||
(let ((repl-buffer (current-buffer))
|
||||
(script-buffer (if (buffer-live-p python-vterm-repl-script-buffer)
|
||||
python-vterm-repl-script-buffer
|
||||
nil)))
|
||||
(if script-buffer
|
||||
(with-current-buffer script-buffer
|
||||
(setq python-vterm-fellow-repl-buffer repl-buffer)
|
||||
(switch-to-buffer-other-window script-buffer)))))
|
||||
|
||||
(defun python-vterm-repl-clear-buffer ()
|
||||
"Clear the content of the Python REPL buffer."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(vterm-clear 1)))
|
||||
|
||||
(defvar-local python-vterm-repl-filter-functions '()
|
||||
"List of filter functions that process the output to the REPL buffer.")
|
||||
|
||||
(defun python-vterm-repl-run-filter-functions-func (session)
|
||||
"Return a function that runs registered filter functions for SESSION with args."
|
||||
(lambda (args)
|
||||
(with-current-buffer (python-vterm-repl-buffer session)
|
||||
(let ((proc (car args))
|
||||
(str (cadr args)))
|
||||
(let ((funcs python-vterm-repl-filter-functions))
|
||||
(while funcs
|
||||
(setq str (apply (pop funcs) (list str))))
|
||||
(list proc str))))))
|
||||
|
||||
(defun python-vterm-repl-buffer-status ()
|
||||
"Check and return the prompt status of the REPL.
|
||||
Return a corresponding symbol or nil if not ready for input."
|
||||
(let* ((bs (buffer-string))
|
||||
(tail (substring bs (- (min 256 (length bs))))))
|
||||
(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
|
||||
((rx bol "python> " eol) :python)
|
||||
((rx bol "In [" (one-or-more (any "0-9")) "]: " eol) :python)))))
|
||||
|
||||
(defvar python-vterm-repl-copy-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "C-c C-t") #'python-vterm-repl-copy-mode)
|
||||
(define-key map [return] #'python-vterm-repl-copy-mode-done)
|
||||
(define-key map (kbd "RET") #'python-vterm-repl-copy-mode-done)
|
||||
(define-key map (kbd "C-c C-r") #'vterm-reset-cursor-point)
|
||||
map))
|
||||
|
||||
(define-minor-mode python-vterm-repl-copy-mode
|
||||
"Toggle copy mode."
|
||||
:group 'python-vterm-repl
|
||||
:lighter " VTermCopy"
|
||||
:keymap python-vterm-repl-copy-mode-map
|
||||
(if python-vterm-repl-copy-mode
|
||||
(progn
|
||||
(message "Start copy mode")
|
||||
(use-local-map nil)
|
||||
(vterm-send-stop))
|
||||
(vterm-reset-cursor-point)
|
||||
(use-local-map python-vterm-repl-mode-map)
|
||||
(vterm-send-start)
|
||||
(message "End copy mode")))
|
||||
|
||||
(defun python-vterm-repl-copy-mode-done ()
|
||||
"Save the active region to the kill ring and exit copy mode."
|
||||
(interactive)
|
||||
(if (region-active-p)
|
||||
(kill-ring-save (region-beginning) (region-end))
|
||||
(user-error "No active region"))
|
||||
(python-vterm-repl-copy-mode -1))
|
||||
|
||||
|
||||
;;----------------------------------------------------------------------
|
||||
(defgroup python-vterm nil
|
||||
"A minor mode to interact with an inferior Python REPL."
|
||||
:group 'python)
|
||||
|
||||
(defcustom python-vterm-hook nil
|
||||
"Hook run after starting a Python script buffer with an inferior Python REPL."
|
||||
:type 'hook
|
||||
:group 'python-vterm)
|
||||
|
||||
(defvar-local python-vterm-paste-with-return t
|
||||
"Whether to send a return key after pasting a string to the Python REPL.")
|
||||
|
||||
(defvar-local python-vterm-fellow-repl-buffer nil)
|
||||
(defvar-local python-vterm-session nil)
|
||||
|
||||
(defun python-vterm-fellow-repl-buffer (&optional session-name)
|
||||
"Return the paired REPL buffer or the one specified with SESSION-NAME."
|
||||
(if session-name
|
||||
(python-vterm-repl-buffer session-name)
|
||||
(if (buffer-live-p python-vterm-fellow-repl-buffer)
|
||||
python-vterm-fellow-repl-buffer
|
||||
(if python-vterm-session
|
||||
(python-vterm-repl-buffer python-vterm-session)
|
||||
(python-vterm-repl-buffer)))))
|
||||
|
||||
(defun python-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 (completing-read "Session name: " (python-vterm-repl-list-sessions) nil nil nil nil
|
||||
(python-vterm-repl-session-name (python-vterm-fellow-repl-buffer))))))
|
||||
(script-buffer (current-buffer))
|
||||
(repl-buffer (python-vterm-fellow-repl-buffer session-name)))
|
||||
(setq python-vterm-fellow-repl-buffer repl-buffer)
|
||||
(with-current-buffer repl-buffer
|
||||
(setq python-vterm-repl-script-buffer script-buffer)
|
||||
(switch-to-buffer-other-window repl-buffer))))
|
||||
|
||||
(defun python-vterm-send-return-key ()
|
||||
"Send a return key to the Python REPL."
|
||||
(with-current-buffer (python-vterm-fellow-repl-buffer)
|
||||
(vterm-send-return)))
|
||||
|
||||
(defun python-vterm-send-current-line ()
|
||||
"Send the current line to the Python 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."
|
||||
(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)
|
||||
(python-vterm-paste-string line)
|
||||
(if (not python-vterm-paste-with-return)
|
||||
(python-vterm-send-return-key))
|
||||
(if (not char)
|
||||
(newline))))))
|
||||
(forward-line))
|
||||
|
||||
(defun python-vterm-paste-string (string &optional session-name)
|
||||
"Send STRING to the Python REPL buffer using brackted paste mode.
|
||||
If SESSION-NAME is given, the REPL with the session name, otherwise
|
||||
the main REPL, is used."
|
||||
(with-current-buffer (python-vterm-fellow-repl-buffer session-name)
|
||||
(vterm-send-key (kbd "C-a"))
|
||||
(vterm-send-key (kbd "C-k"))
|
||||
(vterm-send-string string t)
|
||||
(if python-vterm-paste-with-return
|
||||
(python-vterm-send-return-key))))
|
||||
|
||||
(defun python-vterm-ensure-newline (str)
|
||||
"Add a newline at the end of STR if the last character is not a newline."
|
||||
(concat str (if (string= (substring str -1 nil) "\n") "" "\n")))
|
||||
|
||||
(defun python-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)
|
||||
(let ((str (buffer-substring-no-properties (region-beginning) (region-end))))
|
||||
(python-vterm-paste-string str)
|
||||
(deactivate-mark))
|
||||
(python-vterm-send-current-line)))
|
||||
|
||||
(defun python-vterm-send-buffer ()
|
||||
"Send the whole content of the script buffer to the Python REPL line by line."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(python-vterm-paste-string (python-vterm-ensure-newline (buffer-string)))))
|
||||
|
||||
(defun python-vterm-send-run-buffer-file ()
|
||||
"Run the current buffer file in the python vterm buffer.
|
||||
|
||||
This is equivalent to running `%run <buffer-file-name>` in the python vterm buffer."
|
||||
(interactive)
|
||||
(python-vterm-paste-string (concat "%run " (buffer-file-name))))
|
||||
|
||||
(defun python-vterm-send-cd-to-buffer-directory ()
|
||||
"Change the REPL's working directory to the directory of the buffer file."
|
||||
(interactive)
|
||||
(if buffer-file-name
|
||||
(let ((buffer-directory (file-name-directory buffer-file-name)))
|
||||
(python-vterm-paste-string (format "%%cd \"%s\"\n" buffer-directory))
|
||||
(with-current-buffer (python-vterm-fellow-repl-buffer)
|
||||
(setq default-directory buffer-directory)))
|
||||
(message "The buffer is not associated with a directory.")))
|
||||
|
||||
(defalias 'python-vterm-sync-wd 'python-vterm-send-cd-to-buffer-directory)
|
||||
|
||||
(defun python-vterm-fellow-repl-buffer-status ()
|
||||
"Return REPL mode or nil if REPL is not ready for input."
|
||||
(with-current-buffer (python-vterm-fellow-repl-buffer)
|
||||
(python-vterm-repl-buffer-status)))
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode python-vterm-mode
|
||||
"A minor mode for a Python script buffer that interacts with an inferior Python REPL."
|
||||
:init-value nil
|
||||
:lighter " PY"
|
||||
:keymap
|
||||
`((,(kbd "C-c C-z") . python-vterm-switch-to-repl-buffer)
|
||||
(,(kbd "C-c C-c") . python-vterm-send-region-or-current-line)
|
||||
(,(kbd "C-c C-b") . python-vterm-send-buffer)
|
||||
(,(kbd "C-c C-r") . python-vterm-run-buffer-file)
|
||||
(,(kbd "C-c C-d") . python-vterm-send-cd-to-buffer-directory)))
|
||||
|
||||
|
||||
;;----------------------------------------------------------------------
|
||||
;; Define some utility aliases but not override if the names are already used.
|
||||
(unless (fboundp 'python)
|
||||
(defalias 'python 'python-vterm-repl))
|
||||
|
||||
(unless (boundp 'python-session)
|
||||
(defvaralias 'python-session 'python-vterm-session))
|
||||
|
||||
|
||||
(provide 'python-vterm)
|
||||
|
||||
;;; python-vterm.el ends here
|
Loading…
Add table
Reference in a new issue