Run REPLs in a terminal emulator backend
termint.el
provides a flexible way to define and manage interactions with
Read-Eval-Print Loops (REPLs) running inside a terminal emulator backend within
Emacs. It allows you to easily configure how Emacs communicates with different
REPLs, leveraging the capabilities of fully-featured terminal emulators like
term
, vterm
, or eat
.
Instead of relying on Emacs's built-in "dumb" terminal (comint-mode
),
termint
runs REPLs in a full terminal emulator, enabling features like
bracketed paste mode, proper rendering, and potentially advanced interaction for
complex terminal applications.
The core of the package is the termint-define
macro, which generates a set of
functions and keybindings tailored to a specific REPL, making it easy to start
sessions, send code snippets, source files, and manage the REPL window.
- Flexible REPL Definition: Define interactions for various REPLs using the
termint-define
macro. - Multiple Backends: Supports
term
,vterm
, andeat
as terminal emulator backends. Configure your preferred backend usingtermint-backend
. - Customizable Interaction: Control aspects like bracketed paste mode, start/end patterns for sending code (with different behaviour for single vs. multi-line input), and code preprocessing.
- Code Sourcing: Provides a mechanism (
:source-syntax
) to source code, often by writing to a temporary file and sending a command to source it (helpers included for common languages). - Generated Commands: Automatically creates functions for:
- Starting/switching to REPL sessions
- Sending regions/strings
- Sourcing regions
- Hiding the REPL window
- Evil Integration: Automatically defines Evil operators if Evil mode is detected.
- Dedicated Keymap: Creates a prefix keymap for easy access to the generated commands.
termint
is available on the MELPA package repository. You can install it using
your preferred package manager.
(package-install 'termint)
;; Alternatively, using straight.el
(straight-use-package 'termint)
Choose your preferred terminal backend by customizing the termint-backend
variable:
;; Choose one: 'term, 'vterm, or 'eat
(setq termint-backend 'eat)
Make sure the corresponding backend package (vterm
or eat
) is installed if
you choose it. term
is built-in.
We recommend using eat
or vterm
due to their superior performance compared
to the built-in term
.
The primary way to use termint
is by defining REPL configurations in your
Emacs init file using the termint-define
macro.
This approach takes inspiration from the reformatter
package, which similarly
provides a macro to define distinct formatters. Each formatter is accompanied by
its own command, allowing you to apply different configurations across various
languages.
This macro defines a schema for interacting with a specific REPL.
(termint-define REPL-NAME REPL-CMD &rest ARGS)
For a (termint-define "myrepl" ...)
definition, the macro generates several
components prefixed with termint-myrepl-
:
Functions: A set of interactive functions are created for common REPL interactions:
termint-myrepl-start
: Start or switch to the REPL session.termint-myrepl-send-string
: Send a string (prompted) to the REPL.termint-myrepl-hide-window
: Hide the REPL window.
Region Operations:
termint-myrepl-send-region
: Send the selected region to the REPL.termint-myrepl-source-region
: Source the selected region in the REPL.
Paragraph Operations:
termint-myrepl-send-paragraph
: Send the current paragraph to the REPL.termint-myrepl-source-paragraph
: Source the current paragraph in the REPL.
Buffer Operations:
termint-myrepl-send-buffer
: Send the current buffer to the REPL.termint-myrepl-source-buffer
: Source the current buffer in the REPL.
Defun Operations:
termint-myrepl-send-defun
: Send the current defun to the REPL.termint-myrepl-source-defun
: Source the current defun in the REPL.
Evil Operators: If Evil mode is loaded, the following operators are defined, mirroring the region operations:
termint-myrepl-send-region-operator
: Evil operator for sending text objects, motions, or regions.termint-myrepl-source-region-operator
: Evil operator for sourcing text objects, motions, or regions.
Keymap:
termint-myrepl-map
: A keymap containing default bindings for the generated functions. You can bind this map to a prefix key for convenient access.
Customizable Variables: Several variables are generated to allow customization of the REPL's behavior:
termint-myrepl-cmd
: The command used to start the REPL.termint-myrepl-use-bracketed-paste-mode
: Whether to use bracketed paste mode.termint-myrepl-start-pattern
: Pattern marking the start of code to send (optional).termint-myrepl-end-pattern
: Pattern marking the end of code to send (optional).termint-myrepl-str-process-func
: Function to preprocess strings before sending.termint-myrepl-source-syntax
: Template for the command used to source code.termint-myrepl-show-source-command-hint
: When enabled, display the first non-empty line of the code chunk as overlay. Alongside the source command sent to the REPL, providing a useful hint about the actual command being executed. the default value is nil.
As termint-define
is a macro, it cannot automatically generate autoloads for
the derived commands. To address this, we recommend configuring termint
using
use-package
, which provides advanced lazy-loading functionality out of the
box.
We also recommend declare separate use-package
forms for each
REPL schema. This structure allows you to activate specific REPL
commands as long as their corresponding programming language mode
loads.
In the below example, we created two REPL schemas:
ipython
- loads with Python modezsh
- lazy-loaded with global keybindings
(use-package termint
:demand t
:after python
:bind
(:map python-ts-mode-map
("C-c s" . termint-ipython-start))
:config
(termint-define "ipython" "ipython" :bracketed-paste-p t
:source-syntax termint-ipython-source-syntax-template)
;; C-c m s: `termint-ipython-start'
;; C-c m e: `termint-ipython-send-string'
;; C-c m r: `termint-ipython-send-region' (or `termint-ipython-send-region-operator' if evil is installed.)
;; C-c m p: `termint-ipython-send-paragraph'
;; C-c m b: `termint-ipython-send-buffer'
;; C-c m f: `termint-ipython-send-defun'
;; C-c m R: `termint-ipython-source-region' (or `termint-ipython-source-region-operator' if evil is installed.)
;; C-c m P: `termint-ipython-source-paragraph'
;; C-c m B: `termint-ipython-source-buffer'
;; C-c m F: `termint-ipython-source-defun'
;; C-c m h: `termint-ipython-hide-window'
(define-key python-ts-mode-map (kbd "C-c m") termint-ipython-map)
;; for evil user
(evil-define-key '(normal visual)
python-ts-mode-map (kbd "SPC r") #'termint-ipython-send-region-operator)
(evil-define-key '(normal visual)
python-ts-mode-map (kbd "SPC R") #'termint-ipython-source-region-operator))
(use-package termint
:bind (("C-c Z" . termint-zsh-start))
:bind-keymap (("C-c z" . termint-zsh-map))
:config
(setq termint-backend 'eat)
(termint-define "zsh" "zsh" :bracketed-paste-p t
:source-syntax termint-bash-source-syntax-template))
When using use-package
to lazy-load termint
, keep in mind the following:
- To bind a
termint-repl-map
to a prefix key globally, utilize the:bind-keymap
block withinuse-package
. - To bind a
termint-repl-map
to a prefix key within a major mode (e.g., Python mode), usedefine-key
manually inside the:config
block withinuse-package
.
The display behavior of REPL buffers created by termint
can be customized by
defining window popup rules in display-buffer-alist
.
For example, to configure the window layout for ipython
buffers, use the
following configuration:
(add-to-list 'display-buffer-alist
'("\\*ipython\\*"
(display-buffer-in-side-window display-buffer-reuse-window)
(window-width . 0.5)
(window-height 0.5)
(side . bottom)
(slot . -1)))
termint
differentiates between "sourcing" and simply "sending" code to the
REPL.
-
Sending typically involves passing code directly to the REPL's standard input, mimicking manual typing (or a copy-pasted input via bracketed-paste). This is suitable for interactive commands or a few lines code snippets
-
Sourcing utilizes the
:source-syntax
to manage the code execution. The conventional pattern involves:- Writing the code to a temporary file.
- Sending a command to the REPL, instructing it to execute the contents of that file.
The primary advantage of source
lies in handling large code content. Instead
of sending huge code chunks directly to the REPL, it prevents cluttering your
interaction history, maintaining a cleaner session. Additionally, on Windows,
sourcing from a file mitigates potential issues with stdin processing when the
REPL must read substantial input, as the file-based approach significantly
reduces the data read from the stdin.
However, one notable drawback involves the security implications of temporary file creation. Since the REPL executes code from this file, any vulnerability in temporary file handling—such as exposure to malicious attack—could pose security risks. Thus, while beneficial in certain scenarios, this method requires careful consideration of its potential drawbacks.
you may consider enabling :show-source-command-hint
as t
, which
provides useful hint about the underlying command being executed.
Bracketed-paste mode wraps pasted text in escape sequences, letting terminals
distinguish it from typed input. In termint
, control it via
:bracketed-paste-p
in termint-define
. This prevents REPLs from
misinterpreting pasted newlines or special characters. Most modern REPLs support
it, making :bracketed-paste-p t
a generally recommended setting, but some may
not work perfectly. Test with your setup to confirm compatibility.
comint-mode
is Emacs's standard way of interacting with inferior processes,
including shells and REPLs.
comint
runs the process in a "dumb" process. This means it doesn't fully
support complex terminal escape sequences for things like advanced color
rendering, or features like bracketed paste mode (though some workarounds
exist). This can lead to display glitches or less robust interaction with modern
REPLs that expect a capable terminal.
termint
runs the REPL inside a real terminal emulator backend (term
,
vterm
, eat
). This enables the REPL to function as it would in a standalone
terminal, taking advantage of features such as accurate rendering, bracketed
paste support, and complex interaction capabilities, leading to a potentially
smoother and more reliable experience.
Packages like py-vterm-interaction
(for Python in vterm
) or vterm-julia
(for Julia in vterm
) provide tight integration between a specific language and
a specific terminal backend.
termint offers a general and flexible definition mechanism that is
backend-agnostic (works withterm
, vterm
, eat
) and language-agnostic. You
can use the same framework to define interactions for Python, R, Julia, shell
scripting, or any other tool with a command-line REPL, simply by providing the
appropriate command and interaction parameters. This provides a consistent
approach across different tools and backends. The trade-off might be less deep
language-specific integration compared to a dedicated package, but it offers
much broader applicability and user choice regarding the terminal backend.
Contributions, bug reports, and feature requests are welcome. Please open an issue or pull request on the project repository.