+++ /dev/null
-;;; eshell-vterm-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-
-;; Generated by the `loaddefs-generate' function.
-
-;; This file is part of GNU Emacs.
-
-;;; Code:
-
-(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
-
-
-\f
-;;; Generated autoloads from eshell-vterm.el
-
-(defvar eshell-vterm-mode nil "\
-Non-nil if Eshell-Vterm mode is enabled.
-See the `eshell-vterm-mode' command
-for a description of this minor mode.
-Setting this variable directly does not take effect;
-either customize it (see the info node `Easy Customization')
-or call the function `eshell-vterm-mode'.")
-(custom-autoload 'eshell-vterm-mode "eshell-vterm" nil)
-(autoload 'eshell-vterm-mode "eshell-vterm" "\
-Use Vterm for eshell visual commands.
-
-This is a global minor mode. If called interactively, toggle the
-`Eshell-Vterm mode' mode. If the prefix argument is positive,
-enable the mode, and if it is zero or negative, disable the mode.
-
-If called from Lisp, toggle the mode if ARG is `toggle'. Enable
-the mode if ARG is nil, omitted, or is a positive number.
-Disable the mode if ARG is a negative number.
-
-To check whether the minor mode is enabled in the current buffer,
-evaluate `(default-value \\='eshell-vterm-mode)'.
-
-The mode's hook is called both when the mode is enabled and when
-it is disabled.
-
-(fn &optional ARG)" t)
-(register-definition-prefixes "eshell-vterm" '("eshell-vterm-"))
-\f
-;;; End of scraped data
-
-(provide 'eshell-vterm-autoloads)
-
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; no-native-compile: t
-;; coding: utf-8-emacs-unix
-;; End:
-
-;;; eshell-vterm-autoloads.el ends here
+++ /dev/null
-(define-package "eshell-vterm" "20221229.1328" "Vterm for visual commands in eshell"
- '((emacs "27.1")
- (vterm "0.0.1"))
- :commit "e96c30ad5e7d63b778fc3232e81b090b63b13277" :authors
- '(("Illia Ostapyshyn" . "ilya.ostapyshynNOSPAMgmail.com"))
- :maintainers
- '(("Illia Ostapyshyn" . "ilya.ostapyshynNOSPAMgmail.com"))
- :maintainer
- '("Illia Ostapyshyn" . "ilya.ostapyshynNOSPAMgmail.com")
- :keywords
- '("eshell" "vterm" "terminals" "shell" "visual" "tools" "processes")
- :url "https://github.com/iostapyshyn/eshell-vterm")
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
+++ /dev/null
-;;; eshell-vterm.el --- Vterm for visual commands in eshell -*- lexical-binding: t; -*-
-
-;; Author: Illia Ostapyshyn <ilya.ostapyshynNOSPAMgmail.com>
-;; Maintainer: Illia Ostapyshyn <ilya.ostapyshynNOSPAMgmail.com>
-;; Created: 2021-06-29
-;; Version: 0.1
-;; Package-Requires: ((emacs "27.1") (vterm "0.0.1"))
-;; Keywords: eshell, vterm, terminals, shell, visual, tools, processes
-;; URL: https://github.com/iostapyshyn/eshell-vterm
-
-;; This file is not part of GNU Emacs.
-
-;; This file 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 2, or (at your option)
-;; any later version.
-
-;; This file 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; This package provides a global minor mode allowing eshell to use
-;; vterm for visual commands.
-
-;;; Code:
-
-(require 'vterm)
-(require 'em-term)
-(require 'esh-ext)
-
-(defvar eshell-parent-buffer)
-
-(defun eshell-vterm-exec-visual (&rest args)
- "Run the specified PROGRAM in a terminal emulation buffer.
-ARGS are passed to the program. At the moment, no piping of input is
-allowed. In case ARGS is nil, a new VTerm session is created."
- (if args
- (let* (eshell-interpreter-alist
- (interp (eshell-find-interpreter (car args) (cdr args)))
- (program (car interp))
- (args (flatten-tree
- (eshell-stringify-list (append (cdr interp)
- (cdr args)))))
- (args (mapconcat #'shell-quote-argument args " "))
- (term-buf (generate-new-buffer
- (concat "*" (file-name-nondirectory program) "*")))
- (eshell-buf (current-buffer))
- (vterm-shell (concat (shell-quote-argument
- (file-local-name program))
- " " args)))
- (save-current-buffer
- (switch-to-buffer term-buf)
- (vterm-mode)
- (setq-local eshell-parent-buffer eshell-buf)
- (let ((proc (get-buffer-process term-buf)))
- (if (and proc (eq 'run (process-status proc)))
- (set-process-sentinel proc #'eshell-vterm-sentinel)
- (error "Failed to invoke visual command")))))
- (vterm '(4))) ; Start a new session
- nil)
-
-(defun eshell-vterm-sentinel (proc msg)
- "Clean up the buffer visiting PROC with message MSG.
-If `eshell-destroy-buffer-when-process-dies' is non-nil, destroy
-the buffer."
- (let ((vterm-kill-buffer-on-exit nil))
- (vterm--sentinel proc msg)) ;; First call the normal term sentinel.
- (when eshell-destroy-buffer-when-process-dies
- (let ((proc-buf (process-buffer proc)))
- (when (and proc-buf (buffer-live-p proc-buf)
- (not (eq 'run (process-status proc)))
- (= (process-exit-status proc) 0))
- (if (eq (current-buffer) proc-buf)
- (let ((buf (and (boundp 'eshell-parent-buffer)
- eshell-parent-buffer
- (buffer-live-p eshell-parent-buffer)
- eshell-parent-buffer)))
- (if buf
- (switch-to-buffer buf))))
- (kill-buffer proc-buf)))))
-
-;;;###autoload
-(define-minor-mode eshell-vterm-mode
- "Use Vterm for eshell visual commands."
- :global t
- :group 'eshell-vterm
- (if eshell-vterm-mode
- (advice-add #'eshell-exec-visual :override #'eshell-vterm-exec-visual)
- (advice-remove #'eshell-exec-visual #'eshell-vterm-exec-visual)))
-
-(provide 'eshell-vterm)
-;;; eshell-vterm.el ends here
--- /dev/null
+;;; eshell-vterm-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from eshell-vterm.el
+
+(defvar eshell-vterm-mode nil "\
+Non-nil if Eshell-Vterm mode is enabled.
+See the `eshell-vterm-mode' command
+for a description of this minor mode.
+Setting this variable directly does not take effect;
+either customize it (see the info node `Easy Customization')
+or call the function `eshell-vterm-mode'.")
+(custom-autoload 'eshell-vterm-mode "eshell-vterm" nil)
+(autoload 'eshell-vterm-mode "eshell-vterm" "\
+Use Vterm for eshell visual commands.
+
+This is a global minor mode. If called interactively, toggle the
+`Eshell-Vterm mode' mode. If the prefix argument is positive,
+enable the mode, and if it is zero or negative, disable the mode.
+
+If called from Lisp, toggle the mode if ARG is `toggle'. Enable
+the mode if ARG is nil, omitted, or is a positive number.
+Disable the mode if ARG is a negative number.
+
+To check whether the minor mode is enabled in the current buffer,
+evaluate `(default-value \\='eshell-vterm-mode)'.
+
+The mode's hook is called both when the mode is enabled and when
+it is disabled.
+
+(fn &optional ARG)" t)
+(register-definition-prefixes "eshell-vterm" '("eshell-vterm-"))
+\f
+;;; End of scraped data
+
+(provide 'eshell-vterm-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; eshell-vterm-autoloads.el ends here
--- /dev/null
+(define-package "eshell-vterm" "20240305.1149" "Vterm for visual commands in eshell"
+ '((emacs "27.1")
+ (vterm "0.0.1"))
+ :commit "20f4b246fa605a1533cdfbe3cb7faf31a24e3d2e" :authors
+ '(("Illia Ostapyshyn" . "ilya.ostapyshynNOSPAMgmail.com"))
+ :maintainers
+ '(("Illia Ostapyshyn" . "ilya.ostapyshynNOSPAMgmail.com"))
+ :maintainer
+ '("Illia Ostapyshyn" . "ilya.ostapyshynNOSPAMgmail.com")
+ :keywords
+ '("eshell" "vterm" "terminals" "shell" "visual" "tools" "processes")
+ :url "https://github.com/iostapyshyn/eshell-vterm")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; eshell-vterm.el --- Vterm for visual commands in eshell -*- lexical-binding: t; -*-
+
+;; Author: Illia Ostapyshyn <ilya.ostapyshynNOSPAMgmail.com>
+;; Maintainer: Illia Ostapyshyn <ilya.ostapyshynNOSPAMgmail.com>
+;; Created: 2021-06-29
+;; Version: 0.1
+;; Package-Requires: ((emacs "27.1") (vterm "0.0.1"))
+;; Keywords: eshell, vterm, terminals, shell, visual, tools, processes
+;; URL: https://github.com/iostapyshyn/eshell-vterm
+
+;; This file is not part of GNU Emacs.
+
+;; This file 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 2, or (at your option)
+;; any later version.
+
+;; This file 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package provides a global minor mode allowing eshell to use
+;; vterm for visual commands.
+
+;;; Code:
+
+(require 'vterm)
+(require 'em-term)
+(require 'esh-ext)
+
+(defvar eshell-parent-buffer)
+
+(defun eshell-vterm-exec-visual (&rest args)
+ "Run the specified PROGRAM in a terminal emulation buffer.
+ARGS are passed to the program. At the moment, no piping of input is
+allowed. In case ARGS is nil, a new VTerm session is created."
+ (if args
+ (let* (eshell-interpreter-alist
+ (interp (eshell-find-interpreter (car args) (cdr args)))
+ (program (car interp))
+ (args (flatten-tree
+ (eshell-stringify-list (append (cdr interp)
+ (cdr args)))))
+ (args (mapconcat #'shell-quote-argument args " "))
+ (term-buf (generate-new-buffer
+ (concat "*" (file-name-nondirectory program) "*")))
+ (eshell-buf (current-buffer))
+ (vterm-shell (concat (shell-quote-argument
+ (file-local-name program))
+ " " args)))
+ (save-current-buffer
+ (switch-to-buffer term-buf)
+ (cl-letf (((symbol-function 'vterm--get-shell)
+ (lambda () vterm-shell)))
+ (vterm-mode))
+ (setq-local eshell-parent-buffer eshell-buf)
+ (let ((proc (get-buffer-process term-buf)))
+ (if (and proc (eq 'run (process-status proc)))
+ (set-process-sentinel proc #'eshell-vterm-sentinel)
+ (error "Failed to invoke visual command")))))
+ (vterm '(4))) ; Start a new session
+ nil)
+
+(defun eshell-vterm-sentinel (proc msg)
+ "Clean up the buffer visiting PROC with message MSG.
+If `eshell-destroy-buffer-when-process-dies' is non-nil, destroy
+the buffer."
+ (let ((vterm-kill-buffer-on-exit nil))
+ (vterm--sentinel proc msg)) ;; First call the normal term sentinel.
+ (when eshell-destroy-buffer-when-process-dies
+ (let ((proc-buf (process-buffer proc)))
+ (when (and proc-buf (buffer-live-p proc-buf)
+ (not (eq 'run (process-status proc)))
+ (= (process-exit-status proc) 0))
+ (if (eq (current-buffer) proc-buf)
+ (let ((buf (and (boundp 'eshell-parent-buffer)
+ eshell-parent-buffer
+ (buffer-live-p eshell-parent-buffer)
+ eshell-parent-buffer)))
+ (if buf
+ (switch-to-buffer buf))))
+ (kill-buffer proc-buf)))))
+
+;;;###autoload
+(define-minor-mode eshell-vterm-mode
+ "Use Vterm for eshell visual commands."
+ :global t
+ :group 'eshell-vterm
+ (if eshell-vterm-mode
+ (advice-add #'eshell-exec-visual :override #'eshell-vterm-exec-visual)
+ (advice-remove #'eshell-exec-visual #'eshell-vterm-exec-visual)))
+
+(provide 'eshell-vterm)
+;;; eshell-vterm.el ends here
+++ /dev/null
-cmake_minimum_required(VERSION 3.11)
-include(ExternalProject)
-
-project(emacs-libvterm C)
-
-if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
- set(LIBVTERM_BUILD_COMMAND "gmake")
-else()
- set(LIBVTERM_BUILD_COMMAND "make")
-endif()
-
-add_library(vterm-module MODULE vterm-module.c utf8.c elisp.c)
-set_target_properties(vterm-module PROPERTIES
- C_STANDARD 99
- C_VISIBILITY_PRESET "hidden"
- POSITION_INDEPENDENT_CODE ON
- PREFIX ""
- LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}
- )
-
-# Set RelWithDebInfo as default build type
-if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
- message(STATUS "No build type selected, defaulting to RelWithDebInfo")
- set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Build type (default RelWithDebInfo)" FORCE)
-endif()
-
-# Look for the header file.
-option(USE_SYSTEM_LIBVTERM "Use system libvterm instead of the vendored version." ON)
-
-# Try to find the libvterm in system.
-if (USE_SYSTEM_LIBVTERM)
- # try to find the vterm.h header file.
- find_path(LIBVTERM_INCLUDE_DIR
- NAMES vterm.h
- )
-
- # vterm.h is found.
- if (LIBVTERM_INCLUDE_DIR)
- message(STATUS "System libvterm detected")
- execute_process(COMMAND grep -c "VTermStringFragment" "${LIBVTERM_INCLUDE_DIR}/vterm.h" OUTPUT_VARIABLE VTermStringFragmentExists)
- if (${VTermStringFragmentExists} EQUAL "0")
-# add_compile_definitions(VTermStringFragmentNotExists)
- add_definitions(-DVTermStringFragmentNotExists)
- endif()
- execute_process(COMMAND grep -c "VTermSelectionMask" "${LIBVTERM_INCLUDE_DIR}/vterm.h" OUTPUT_VARIABLE VTermSelectionMaskExists)
- if (${VTermSelectionMaskExists} EQUAL "0")
-# add_compile_definitions(VTermStringFragmentNotExists)
- add_definitions(-DVTermSelectionMaskNotExists)
- endif()
- execute_process(COMMAND grep -c "sb_clear" "${LIBVTERM_INCLUDE_DIR}/vterm.h" OUTPUT_VARIABLE VTermSBClearExists)
- if (${VTermSBClearExists} EQUAL "0")
- add_definitions(-DVTermSBClearNotExists)
- endif()
- else()
- message(STATUS "System libvterm not found: libvterm will be downloaded and compiled as part of the build process")
- endif()
-endif()
-
-if (LIBVTERM_INCLUDE_DIR)
- find_library(LIBVTERM_LIBRARY NAMES
- vterm
- libvterm
- )
-
- if(NOT LIBVTERM_LIBRARY)
- message(FATAL_ERROR "libvterm not found")
- endif()
-else()
- find_program(LIBTOOL NAMES libtool glibtool)
- if(NOT LIBTOOL)
- message(FATAL_ERROR "libtool not found. Please install libtool")
- endif()
-
- ExternalProject_add(libvterm
- GIT_REPOSITORY https://github.com/Sbozzolo/libvterm-mirror.git
- GIT_TAG 64f1775952dbe001e989f2ab679563b54f2fca55
- CONFIGURE_COMMAND ""
- BUILD_COMMAND ${LIBVTERM_BUILD_COMMAND} "CFLAGS='-fPIC'" "LDFLAGS='-static'"
- BUILD_IN_SOURCE ON
- INSTALL_COMMAND "")
-
- ExternalProject_Get_property(libvterm SOURCE_DIR)
-
- set(LIBVTERM_INCLUDE_DIR ${SOURCE_DIR}/include)
- set(LIBVTERM_LIBRARY ${SOURCE_DIR}/.libs/libvterm.a)
-
- add_dependencies(vterm-module libvterm)
-
- # Workaround for https://gitlab.kitware.com/cmake/cmake/issues/15052
- file(MAKE_DIRECTORY ${LIBVTERM_INCLUDE_DIR})
-endif()
-
-add_library(vterm STATIC IMPORTED)
-set_target_properties(vterm PROPERTIES IMPORTED_LOCATION ${LIBVTERM_LIBRARY})
-target_include_directories(vterm INTERFACE ${LIBVTERM_INCLUDE_DIR})
-
-# Link with libvterm
-target_link_libraries(vterm-module PUBLIC vterm)
-
-# Custom run command for testing
-add_custom_target(run
- COMMAND emacs -Q -L ${CMAKE_SOURCE_DIR} -L ${CMAKE_BINARY_DIR} --eval "\\(require \\'vterm\\)" --eval "\\(vterm\\)"
- DEPENDS vterm-module
- )
+++ /dev/null
-#include "elisp.h"
-#include <stdio.h>
-
-// Emacs symbols
-emacs_value Qt;
-emacs_value Qnil;
-emacs_value Qnormal;
-emacs_value Qbold;
-emacs_value Qitalic;
-emacs_value Qforeground;
-emacs_value Qbackground;
-emacs_value Qweight;
-emacs_value Qunderline;
-emacs_value Qslant;
-emacs_value Qreverse;
-emacs_value Qstrike;
-emacs_value Qextend;
-emacs_value Qface;
-emacs_value Qbox;
-emacs_value Qbar;
-emacs_value Qhbar;
-emacs_value Qcursor_type;
-emacs_value Qemacs_major_version;
-emacs_value Qvterm_line_wrap;
-emacs_value Qrear_nonsticky;
-emacs_value Qvterm_prompt;
-
-// Emacs functions
-emacs_value Fblink_cursor_mode;
-emacs_value Fsymbol_value;
-emacs_value Flength;
-emacs_value Flist;
-emacs_value Fnth;
-emacs_value Ferase_buffer;
-emacs_value Finsert;
-emacs_value Fgoto_char;
-emacs_value Fforward_char;
-emacs_value Fforward_line;
-emacs_value Fgoto_line;
-emacs_value Fdelete_lines;
-emacs_value Frecenter;
-emacs_value Fset_window_point;
-emacs_value Fwindow_body_height;
-emacs_value Fpoint;
-emacs_value Fapply;
-
-emacs_value Fput_text_property;
-emacs_value Fadd_text_properties;
-emacs_value Fset;
-emacs_value Fvterm_flush_output;
-emacs_value Fget_buffer_window_list;
-emacs_value Fselected_window;
-emacs_value Fvterm_set_title;
-emacs_value Fvterm_set_directory;
-emacs_value Fvterm_invalidate;
-emacs_value Feq;
-emacs_value Fvterm_get_color;
-emacs_value Fvterm_eval;
-emacs_value Fvterm_set_selection;
-
-/* Set the function cell of the symbol named NAME to SFUN using
- the 'fset' function. */
-void bind_function(emacs_env *env, const char *name, emacs_value Sfun) {
- emacs_value Qfset = env->intern(env, "fset");
- emacs_value Qsym = env->intern(env, name);
-
- env->funcall(env, Qfset, 2, (emacs_value[]){Qsym, Sfun});
-}
-
-/* Provide FEATURE to Emacs. */
-void provide(emacs_env *env, const char *feature) {
- emacs_value Qfeat = env->intern(env, feature);
- emacs_value Qprovide = env->intern(env, "provide");
-
- env->funcall(env, Qprovide, 1, (emacs_value[]){Qfeat});
-}
-
-emacs_value symbol_value(emacs_env *env, emacs_value symbol) {
- return env->funcall(env, Fsymbol_value, 1, (emacs_value[]){symbol});
-}
-
-int string_bytes(emacs_env *env, emacs_value string) {
- ptrdiff_t size = 0;
- env->copy_string_contents(env, string, NULL, &size);
- return size;
-}
-
-emacs_value length(emacs_env *env, emacs_value string) {
- return env->funcall(env, Flength, 1, (emacs_value[]){string});
-}
-
-emacs_value list(emacs_env *env, emacs_value elements[], ptrdiff_t len) {
- return env->funcall(env, Flist, len, elements);
-}
-emacs_value nth(emacs_env *env, int idx, emacs_value list) {
- emacs_value eidx = env->make_integer(env, idx);
- return env->funcall(env, Fnth, 2, (emacs_value[]){eidx, list});
-}
-
-void put_text_property(emacs_env *env, emacs_value string, emacs_value property,
- emacs_value value) {
- emacs_value start = env->make_integer(env, 0);
- emacs_value end = length(env, string);
-
- env->funcall(env, Fput_text_property, 5,
- (emacs_value[]){start, end, property, value, string});
-}
-
-void add_text_properties(emacs_env *env, emacs_value string,
- emacs_value property) {
- emacs_value start = env->make_integer(env, 0);
- emacs_value end = length(env, string);
-
- env->funcall(env, Fadd_text_properties, 4,
- (emacs_value[]){start, end, property, string});
-}
-
-void erase_buffer(emacs_env *env) { env->funcall(env, Ferase_buffer, 0, NULL); }
-
-void insert(emacs_env *env, emacs_value string) {
- env->funcall(env, Finsert, 1, (emacs_value[]){string});
-}
-
-void goto_char(emacs_env *env, int pos) {
- emacs_value point = env->make_integer(env, pos);
- env->funcall(env, Fgoto_char, 1, (emacs_value[]){point});
-}
-
-void forward_line(emacs_env *env, int n) {
- emacs_value nline = env->make_integer(env, n);
- env->funcall(env, Fforward_line, 1, (emacs_value[]){nline});
-}
-void goto_line(emacs_env *env, int n) {
- emacs_value nline = env->make_integer(env, n);
- env->funcall(env, Fgoto_line, 1, (emacs_value[]){nline});
-}
-void delete_lines(emacs_env *env, int linenum, int count, bool del_whole_line) {
- emacs_value Qlinenum = env->make_integer(env, linenum);
- emacs_value Qcount = env->make_integer(env, count);
- if (del_whole_line) {
- env->funcall(env, Fdelete_lines, 3, (emacs_value[]){Qlinenum, Qcount, Qt});
- } else {
- env->funcall(env, Fdelete_lines, 3,
- (emacs_value[]){Qlinenum, Qcount, Qnil});
- }
-}
-void recenter(emacs_env *env, emacs_value pos) {
- env->funcall(env, Frecenter, 1, (emacs_value[]){pos});
-}
-emacs_value point(emacs_env *env) { return env->funcall(env, Fpoint, 0, NULL); }
-
-void set_window_point(emacs_env *env, emacs_value win, emacs_value point) {
- env->funcall(env, Fset_window_point, 2, (emacs_value[]){win, point});
-}
-emacs_value window_body_height(emacs_env *env, emacs_value win) {
- return env->funcall(env, Fwindow_body_height, 1, (emacs_value[]){win});
-}
-
-bool eq(emacs_env *env, emacs_value e1, emacs_value e2) {
- emacs_value Qeq = env->funcall(env, Feq, 2, (emacs_value[]){e1, e2});
- return env->is_not_nil(env, Qeq);
-}
-
-void forward_char(emacs_env *env, emacs_value n) {
- env->funcall(env, Fforward_char, 1, (emacs_value[]){n});
-}
-
-emacs_value get_buffer_window_list(emacs_env *env) {
- return env->funcall(env, Fget_buffer_window_list, 3,
- (emacs_value[]){Qnil, Qnil, Qt});
-}
-
-emacs_value selected_window(emacs_env *env) {
- return env->funcall(env, Fselected_window, 0, (emacs_value[]){});
-}
-
-void set_cursor_type(emacs_env *env, emacs_value cursor_type) {
- env->funcall(env, Fset, 2, (emacs_value[]){Qcursor_type, cursor_type});
-}
-
-void set_cursor_blink(emacs_env *env, bool blink) {
- env->funcall(env, Fblink_cursor_mode, 1,
- (emacs_value[]){env->make_integer(env, blink)});
-}
-
-emacs_value vterm_get_color(emacs_env *env, int index, emacs_value args) {
- emacs_value idx = env->make_integer(env, index);
- return env->funcall(env, Fapply, 3, (emacs_value[]){ Fvterm_get_color, idx, args });
-}
-
-void set_title(emacs_env *env, emacs_value string) {
- env->funcall(env, Fvterm_set_title, 1, (emacs_value[]){string});
-}
-
-void set_directory(emacs_env *env, emacs_value string) {
- env->funcall(env, Fvterm_set_directory, 1, (emacs_value[]){string});
-}
-
-void vterm_invalidate(emacs_env *env) {
- env->funcall(env, Fvterm_invalidate, 0, NULL);
-}
-emacs_value vterm_eval(emacs_env *env, emacs_value string) {
- return env->funcall(env, Fvterm_eval, 1, (emacs_value[]){string});
-}
-
-emacs_value vterm_set_selection(emacs_env *env, emacs_value selection_target,
- emacs_value selection_data) {
- return env->funcall(env, Fvterm_set_selection, 2,
- (emacs_value[]){selection_target, selection_data});
-}
+++ /dev/null
-#ifndef ELISP_H
-#define ELISP_H
-
-#include "emacs-module.h"
-#include "vterm.h"
-
-// Emacs symbols
-extern emacs_value Qt;
-extern emacs_value Qnil;
-extern emacs_value Qnormal;
-extern emacs_value Qbold;
-extern emacs_value Qitalic;
-extern emacs_value Qforeground;
-extern emacs_value Qbackground;
-extern emacs_value Qweight;
-extern emacs_value Qunderline;
-extern emacs_value Qslant;
-extern emacs_value Qreverse;
-extern emacs_value Qstrike;
-extern emacs_value Qextend;
-extern emacs_value Qface;
-extern emacs_value Qbox;
-extern emacs_value Qbar;
-extern emacs_value Qhbar;
-extern emacs_value Qcursor_type;
-extern emacs_value Qemacs_major_version;
-extern emacs_value Qvterm_line_wrap;
-extern emacs_value Qrear_nonsticky;
-extern emacs_value Qvterm_prompt;
-
-// Emacs functions
-extern emacs_value Fapply;
-extern emacs_value Fblink_cursor_mode;
-extern emacs_value Fsymbol_value;
-extern emacs_value Flength;
-extern emacs_value Flist;
-extern emacs_value Fnth;
-extern emacs_value Ferase_buffer;
-extern emacs_value Finsert;
-extern emacs_value Fgoto_char;
-extern emacs_value Fforward_char;
-extern emacs_value Fforward_line;
-extern emacs_value Fgoto_line;
-extern emacs_value Fdelete_lines;
-extern emacs_value Frecenter;
-extern emacs_value Fset_window_point;
-extern emacs_value Fwindow_body_height;
-extern emacs_value Fpoint;
-
-extern emacs_value Fput_text_property;
-extern emacs_value Fadd_text_properties;
-extern emacs_value Fset;
-extern emacs_value Fvterm_flush_output;
-extern emacs_value Fget_buffer_window_list;
-extern emacs_value Fselected_window;
-extern emacs_value Fvterm_set_title;
-extern emacs_value Fvterm_set_directory;
-extern emacs_value Fvterm_invalidate;
-extern emacs_value Feq;
-extern emacs_value Fvterm_get_color;
-extern emacs_value Fvterm_eval;
-extern emacs_value Fvterm_set_selection;
-
-// Utils
-void bind_function(emacs_env *env, const char *name, emacs_value Sfun);
-void provide(emacs_env *env, const char *feature);
-emacs_value symbol_value(emacs_env *env, emacs_value symbol);
-int string_bytes(emacs_env *env, emacs_value string);
-emacs_value length(emacs_env *env, emacs_value string);
-emacs_value list(emacs_env *env, emacs_value elements[], ptrdiff_t len);
-emacs_value nth(emacs_env *env, int idx, emacs_value list);
-void put_text_property(emacs_env *env, emacs_value string, emacs_value property,
- emacs_value value);
-void add_text_properties(emacs_env *env, emacs_value string,
- emacs_value property);
-void erase_buffer(emacs_env *env);
-void insert(emacs_env *env, emacs_value string);
-void goto_char(emacs_env *env, int pos);
-void forward_line(emacs_env *env, int n);
-void goto_line(emacs_env *env, int n);
-void set_cursor_type(emacs_env *env, emacs_value cursor_type);
-void set_cursor_blink(emacs_env *env, bool blink);
-void delete_lines(emacs_env *env, int linenum, int count, bool del_whole_line);
-void recenter(emacs_env *env, emacs_value pos);
-void set_window_point(emacs_env *env, emacs_value win, emacs_value point);
-emacs_value window_body_height(emacs_env *env, emacs_value win);
-emacs_value point(emacs_env *env);
-bool eq(emacs_env *env, emacs_value e1, emacs_value e2);
-void forward_char(emacs_env *env, emacs_value n);
-emacs_value get_buffer_window_list(emacs_env *env);
-emacs_value selected_window(emacs_env *env);
-void set_title(emacs_env *env, emacs_value string);
-void set_directory(emacs_env *env, emacs_value string);
-void vterm_invalidate(emacs_env *env);
-emacs_value vterm_get_color(emacs_env *env, int index, emacs_value args);
-emacs_value vterm_eval(emacs_env *env, emacs_value string);
-emacs_value vterm_set_selection(emacs_env *env, emacs_value selection_target,
- emacs_value selection_data);
-
-#endif /* ELISP_H */
+++ /dev/null
-/* emacs-module.h - GNU Emacs module API.
-
-Copyright (C) 2015-2018 Free Software Foundation, Inc.
-
-This file is part of GNU Emacs.
-
-GNU Emacs 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.
-
-GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
-
-#ifndef EMACS_MODULE_H
-#define EMACS_MODULE_H
-
-#include <stddef.h>
-#include <stdint.h>
-
-#ifndef __cplusplus
-#include <stdbool.h>
-#endif
-
-#if defined __cplusplus && __cplusplus >= 201103L
-#define EMACS_NOEXCEPT noexcept
-#else
-#define EMACS_NOEXCEPT
-#endif
-
-#ifdef __has_attribute
-#if __has_attribute(__nonnull__)
-#define EMACS_ATTRIBUTE_NONNULL(...) __attribute__((__nonnull__(__VA_ARGS__)))
-#endif
-#endif
-#ifndef EMACS_ATTRIBUTE_NONNULL
-#define EMACS_ATTRIBUTE_NONNULL(...)
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* Current environment. */
-typedef struct emacs_env_25 emacs_env;
-
-/* Opaque pointer representing an Emacs Lisp value.
- BEWARE: Do not assume NULL is a valid value! */
-typedef struct emacs_value_tag *emacs_value;
-
-enum { emacs_variadic_function = -2 };
-
-/* Struct passed to a module init function (emacs_module_init). */
-struct emacs_runtime {
- /* Structure size (for version checking). */
- ptrdiff_t size;
-
- /* Private data; users should not touch this. */
- struct emacs_runtime_private *private_members;
-
- /* Return an environment pointer. */
- emacs_env *(*get_environment)(struct emacs_runtime *ert)
- EMACS_ATTRIBUTE_NONNULL(1);
-};
-
-/* Possible Emacs function call outcomes. */
-enum emacs_funcall_exit {
- /* Function has returned normally. */
- emacs_funcall_exit_return = 0,
-
- /* Function has signaled an error using `signal'. */
- emacs_funcall_exit_signal = 1,
-
- /* Function has exit using `throw'. */
- emacs_funcall_exit_throw = 2
-};
-
-struct emacs_env_25 {
- /* Structure size (for version checking). */
- ptrdiff_t size;
-
- /* Private data; users should not touch this. */
- struct emacs_env_private *private_members;
-
- /* Memory management. */
-
- emacs_value (*make_global_ref)(emacs_env *env, emacs_value any_reference)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void (*free_global_ref)(emacs_env *env, emacs_value global_reference)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- /* Non-local exit handling. */
-
- enum emacs_funcall_exit (*non_local_exit_check)(emacs_env *env)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void (*non_local_exit_clear)(emacs_env *env) EMACS_ATTRIBUTE_NONNULL(1);
-
- enum emacs_funcall_exit (*non_local_exit_get)(
- emacs_env *env, emacs_value *non_local_exit_symbol_out,
- emacs_value *non_local_exit_data_out) EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
-
- void (*non_local_exit_signal)(emacs_env *env,
- emacs_value non_local_exit_symbol,
- emacs_value non_local_exit_data)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void (*non_local_exit_throw)(emacs_env *env, emacs_value tag,
- emacs_value value) EMACS_ATTRIBUTE_NONNULL(1);
-
- /* Function registration. */
-
- emacs_value (*make_function)(
- emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity,
- emacs_value (*function)(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *)
- EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1),
- const char *documentation, void *data) EMACS_ATTRIBUTE_NONNULL(1, 4);
-
- emacs_value (*funcall)(emacs_env *env, emacs_value function, ptrdiff_t nargs,
- emacs_value args[]) EMACS_ATTRIBUTE_NONNULL(1);
-
- emacs_value (*intern)(emacs_env *env, const char *symbol_name)
- EMACS_ATTRIBUTE_NONNULL(1, 2);
-
- /* Type conversion. */
-
- emacs_value (*type_of)(emacs_env *env, emacs_value value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- bool (*is_not_nil)(emacs_env *env, emacs_value value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- bool (*eq)(emacs_env *env, emacs_value a, emacs_value b)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- intmax_t (*extract_integer)(emacs_env *env, emacs_value value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- emacs_value (*make_integer)(emacs_env *env, intmax_t value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- double (*extract_float)(emacs_env *env, emacs_value value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- emacs_value (*make_float)(emacs_env *env, double value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
- null-terminated string.
-
- SIZE must point to the total size of the buffer. If BUFFER is
- NULL or if SIZE is not big enough, write the required buffer size
- to SIZE and return true.
-
- Note that SIZE must include the last null byte (e.g. "abc" needs
- a buffer of size 4).
-
- Return true if the string was successfully copied. */
-
- bool (*copy_string_contents)(emacs_env *env, emacs_value value, char *buffer,
- ptrdiff_t *size_inout)
- EMACS_ATTRIBUTE_NONNULL(1, 4);
-
- /* Create a Lisp string from a utf8 encoded string. */
- emacs_value (*make_string)(emacs_env *env, const char *contents,
- ptrdiff_t length) EMACS_ATTRIBUTE_NONNULL(1, 2);
-
- /* Embedded pointer type. */
- emacs_value (*make_user_ptr)(emacs_env *env,
- void (*fin)(void *) EMACS_NOEXCEPT, void *ptr)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void *(*get_user_ptr)(emacs_env *env,
- emacs_value uptr)EMACS_ATTRIBUTE_NONNULL(1);
- void (*set_user_ptr)(emacs_env *env, emacs_value uptr, void *ptr)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void (*(*get_user_finalizer)(emacs_env *env,
- emacs_value uptr))(void *) EMACS_NOEXCEPT
- EMACS_ATTRIBUTE_NONNULL(1);
- void (*set_user_finalizer)(emacs_env *env, emacs_value uptr,
- void (*fin)(void *) EMACS_NOEXCEPT)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- /* Vector functions. */
- emacs_value (*vec_get)(emacs_env *env, emacs_value vec, ptrdiff_t i)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void (*vec_set)(emacs_env *env, emacs_value vec, ptrdiff_t i, emacs_value val)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- ptrdiff_t (*vec_size)(emacs_env *env, emacs_value vec)
- EMACS_ATTRIBUTE_NONNULL(1);
-};
-
-struct emacs_env_26 {
- /* Structure size (for version checking). */
- ptrdiff_t size;
-
- /* Private data; users should not touch this. */
- struct emacs_env_private *private_members;
-
- /* Memory management. */
-
- emacs_value (*make_global_ref)(emacs_env *env, emacs_value any_reference)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void (*free_global_ref)(emacs_env *env, emacs_value global_reference)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- /* Non-local exit handling. */
-
- enum emacs_funcall_exit (*non_local_exit_check)(emacs_env *env)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void (*non_local_exit_clear)(emacs_env *env) EMACS_ATTRIBUTE_NONNULL(1);
-
- enum emacs_funcall_exit (*non_local_exit_get)(
- emacs_env *env, emacs_value *non_local_exit_symbol_out,
- emacs_value *non_local_exit_data_out) EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
-
- void (*non_local_exit_signal)(emacs_env *env,
- emacs_value non_local_exit_symbol,
- emacs_value non_local_exit_data)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void (*non_local_exit_throw)(emacs_env *env, emacs_value tag,
- emacs_value value) EMACS_ATTRIBUTE_NONNULL(1);
-
- /* Function registration. */
-
- emacs_value (*make_function)(
- emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity,
- emacs_value (*function)(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *)
- EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1),
- const char *documentation, void *data) EMACS_ATTRIBUTE_NONNULL(1, 4);
-
- emacs_value (*funcall)(emacs_env *env, emacs_value function, ptrdiff_t nargs,
- emacs_value args[]) EMACS_ATTRIBUTE_NONNULL(1);
-
- emacs_value (*intern)(emacs_env *env, const char *symbol_name)
- EMACS_ATTRIBUTE_NONNULL(1, 2);
-
- /* Type conversion. */
-
- emacs_value (*type_of)(emacs_env *env, emacs_value value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- bool (*is_not_nil)(emacs_env *env, emacs_value value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- bool (*eq)(emacs_env *env, emacs_value a, emacs_value b)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- intmax_t (*extract_integer)(emacs_env *env, emacs_value value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- emacs_value (*make_integer)(emacs_env *env, intmax_t value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- double (*extract_float)(emacs_env *env, emacs_value value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- emacs_value (*make_float)(emacs_env *env, double value)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
- null-terminated string.
-
- SIZE must point to the total size of the buffer. If BUFFER is
- NULL or if SIZE is not big enough, write the required buffer size
- to SIZE and return true.
-
- Note that SIZE must include the last null byte (e.g. "abc" needs
- a buffer of size 4).
-
- Return true if the string was successfully copied. */
-
- bool (*copy_string_contents)(emacs_env *env, emacs_value value, char *buffer,
- ptrdiff_t *size_inout)
- EMACS_ATTRIBUTE_NONNULL(1, 4);
-
- /* Create a Lisp string from a utf8 encoded string. */
- emacs_value (*make_string)(emacs_env *env, const char *contents,
- ptrdiff_t length) EMACS_ATTRIBUTE_NONNULL(1, 2);
-
- /* Embedded pointer type. */
- emacs_value (*make_user_ptr)(emacs_env *env,
- void (*fin)(void *) EMACS_NOEXCEPT, void *ptr)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void *(*get_user_ptr)(emacs_env *env,
- emacs_value uptr)EMACS_ATTRIBUTE_NONNULL(1);
- void (*set_user_ptr)(emacs_env *env, emacs_value uptr, void *ptr)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void (*(*get_user_finalizer)(emacs_env *env,
- emacs_value uptr))(void *) EMACS_NOEXCEPT
- EMACS_ATTRIBUTE_NONNULL(1);
- void (*set_user_finalizer)(emacs_env *env, emacs_value uptr,
- void (*fin)(void *) EMACS_NOEXCEPT)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- /* Vector functions. */
- emacs_value (*vec_get)(emacs_env *env, emacs_value vec, ptrdiff_t i)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- void (*vec_set)(emacs_env *env, emacs_value vec, ptrdiff_t i, emacs_value val)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- ptrdiff_t (*vec_size)(emacs_env *env, emacs_value vec)
- EMACS_ATTRIBUTE_NONNULL(1);
-
- /* Returns whether a quit is pending. */
- bool (*should_quit)(emacs_env *env) EMACS_ATTRIBUTE_NONNULL(1);
-};
-
-/* Every module should define a function as follows. */
-extern int emacs_module_init(struct emacs_runtime *ert) EMACS_NOEXCEPT
- EMACS_ATTRIBUTE_NONNULL(1);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* EMACS_MODULE_H */
+++ /dev/null
-# Some of the most useful features in emacs-libvterm require shell-side
-# configurations. The main goal of these additional functions is to enable the
-# shell to send information to `vterm` via properly escaped sequences. A
-# function that helps in this task, `vterm_printf`, is defined below.
-
-function vterm_printf(){
- if [ -n "$TMUX" ] && ([ "${TERM%%-*}" = "tmux" ] || [ "${TERM%%-*}" = "screen" ] ); then
- # Tell tmux to pass the escape sequences through
- printf "\ePtmux;\e\e]%s\007\e\\" "$1"
- elif [ "${TERM%%-*}" = "screen" ]; then
- # GNU screen (screen, screen-256color, screen-256color-bce)
- printf "\eP\e]%s\007\e\\" "$1"
- else
- printf "\e]%s\e\\" "$1"
- fi
-}
-
-# Completely clear the buffer. With this, everything that is not on screen
-# is erased.
-if [[ "$INSIDE_EMACS" = 'vterm' ]]; then
- function clear(){
- vterm_printf "51;Evterm-clear-scrollback";
- tput clear;
- }
-fi
-
-# With vterm_cmd you can execute Emacs commands directly from the shell.
-# For example, vterm_cmd message "HI" will print "HI".
-# To enable new commands, you have to customize Emacs's variable
-# vterm-eval-cmds.
-vterm_cmd() {
- local vterm_elisp
- vterm_elisp=""
- while [ $# -gt 0 ]; do
- vterm_elisp="$vterm_elisp""$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")"
- shift
- done
- vterm_printf "51;E$vterm_elisp"
-}
-
-# This is to change the title of the buffer based on information provided by the
-# shell. See, http://tldp.org/HOWTO/Xterm-Title-4.html, for the meaning of the
-# various symbols.
-PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }"'echo -ne "\033]0;${HOSTNAME}:${PWD}\007"'
-
-# Sync directory and host in the shell with Emacs's current directory.
-# You may need to manually specify the hostname instead of $(hostname) in case
-# $(hostname) does not return the correct string to connect to the server.
-#
-# The escape sequence "51;A" has also the role of identifying the end of the
-# prompt
-vterm_prompt_end(){
- vterm_printf "51;A$(whoami)@$(hostname):$(pwd)"
-}
-PS1=$PS1'\[$(vterm_prompt_end)\]'
+++ /dev/null
-# Some of the most useful features in emacs-libvterm require shell-side
-# configurations. The main goal of these additional functions is to enable the
-# shell to send information to `vterm` via properly escaped sequences. A
-# function that helps in this task, `vterm_printf`, is defined below.
-
-function vterm_printf(){
- if [ -n "$TMUX" ] && ([ "${TERM%%-*}" = "tmux" ] || [ "${TERM%%-*}" = "screen" ] ); then
- # Tell tmux to pass the escape sequences through
- printf "\ePtmux;\e\e]%s\007\e\\" "$1"
- elif [ "${TERM%%-*}" = "screen" ]; then
- # GNU screen (screen, screen-256color, screen-256color-bce)
- printf "\eP\e]%s\007\e\\" "$1"
- else
- printf "\e]%s\e\\" "$1"
- fi
-}
-
-# Completely clear the buffer. With this, everything that is not on screen
-# is erased.
-if [[ "$INSIDE_EMACS" = 'vterm' ]]; then
- alias clear='vterm_printf "51;Evterm-clear-scrollback";tput clear'
-fi
-
-# With vterm_cmd you can execute Emacs commands directly from the shell.
-# For example, vterm_cmd message "HI" will print "HI".
-# To enable new commands, you have to customize Emacs's variable
-# vterm-eval-cmds.
-vterm_cmd() {
- local vterm_elisp
- vterm_elisp=""
- while [ $# -gt 0 ]; do
- vterm_elisp="$vterm_elisp""$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")"
- shift
- done
- vterm_printf "51;E$vterm_elisp"
-}
-
-# This is to change the title of the buffer based on information provided by the
-# shell. See, http://tldp.org/HOWTO/Xterm-Title-4.html, for the meaning of the
-# various symbols.
-autoload -U add-zsh-hook
-add-zsh-hook -Uz chpwd (){ print -Pn "\e]2;%m:%2~\a" }
-
-# Sync directory and host in the shell with Emacs's current directory.
-# You may need to manually specify the hostname instead of $(hostname) in case
-# $(hostname) does not return the correct string to connect to the server.
-#
-# The escape sequence "51;A" has also the role of identifying the end of the
-# prompt
-vterm_prompt_end() {
- vterm_printf "51;A$(whoami)@$(hostname):$(pwd)"
-}
-setopt PROMPT_SUBST
-PROMPT=$PROMPT'%{$(vterm_prompt_end)%}'
+++ /dev/null
-# Some of the most useful features in emacs-libvterm require shell-side
-# configurations. The main goal of these additional functions is to enable the
-# shell to send information to `vterm` via properly escaped sequences. A
-# function that helps in this task, `vterm_printf`, is defined below.
-
-function vterm_printf;
- if begin; [ -n "$TMUX" ] ; and string match -q -r "screen|tmux" "$TERM"; end
- # tell tmux to pass the escape sequences through
- printf "\ePtmux;\e\e]%s\007\e\\" "$argv"
- else if string match -q -- "screen*" "$TERM"
- # GNU screen (screen, screen-256color, screen-256color-bce)
- printf "\eP\e]%s\007\e\\" "$argv"
- else
- printf "\e]%s\e\\" "$argv"
- end
-end
-
-# Completely clear the buffer. With this, everything that is not on screen
-# is erased.
-if [ "$INSIDE_EMACS" = 'vterm' ]
- function clear
- vterm_printf "51;Evterm-clear-scrollback";
- tput clear;
- end
-end
-
-# This is to change the title of the buffer based on information provided by the
-# shell. See, http://tldp.org/HOWTO/Xterm-Title-4.html, for the meaning of the
-# various symbols.
-function fish_title
- hostname
- echo ":"
- prompt_pwd
-end
-
-# With vterm_cmd you can execute Emacs commands directly from the shell.
-# For example, vterm_cmd message "HI" will print "HI".
-# To enable new commands, you have to customize Emacs's variable
-# vterm-eval-cmds.
-function vterm_cmd --description 'Run an Emacs command among the ones defined in vterm-eval-cmds.'
- set -l vterm_elisp ()
- for arg in $argv
- set -a vterm_elisp (printf '"%s" ' (string replace -a -r '([\\\\"])' '\\\\\\\\$1' $arg))
- end
- vterm_printf '51;E'(string join '' $vterm_elisp)
-end
-
-# Sync directory and host in the shell with Emacs's current directory.
-# You may need to manually specify the hostname instead of $(hostname) in case
-# $(hostname) does not return the correct string to connect to the server.
-#
-# The escape sequence "51;A" has also the role of identifying the end of the
-# prompt
-function vterm_prompt_end;
- vterm_printf '51;A'(whoami)'@'(hostname)':'(pwd)
-end
-
-# We are going to add a portion to the prompt, so we copy the old one
-functions --copy fish_prompt vterm_old_fish_prompt
-
-function fish_prompt --description 'Write out the prompt; do not replace this. Instead, put this at end of your file.'
- # Remove the trailing newline from the original prompt. This is done
- # using the string builtin from fish, but to make sure any escape codes
- # are correctly interpreted, use %b for printf.
- printf "%b" (string join "\n" (vterm_old_fish_prompt))
- vterm_prompt_end
-end
+++ /dev/null
-#include "utf8.h"
-
-size_t codepoint_to_utf8(const uint32_t codepoint, unsigned char buffer[4]) {
- if (codepoint <= 0x7F) {
- buffer[0] = codepoint;
- return 1;
- }
- if (codepoint >= 0x80 && codepoint <= 0x07FF) {
- buffer[0] = 0xC0 | (codepoint >> 6);
- buffer[1] = 0x80 | (codepoint & 0x3F);
- return 2;
- }
- if (codepoint >= 0x0800 && codepoint <= 0xFFFF) {
- buffer[0] = 0xE0 | (codepoint >> 12);
- buffer[1] = 0x80 | ((codepoint >> 6) & 0x3F);
- buffer[2] = 0x80 | (codepoint & 0x3F);
- return 3;
- }
-
- if (codepoint >= 0x10000 && codepoint <= 0x10FFFF) {
- buffer[0] = 0xF0 | (codepoint >> 18);
- buffer[1] = 0x80 | ((codepoint >> 12) & 0x3F);
- buffer[2] = 0x80 | ((codepoint >> 6) & 0x3F);
- buffer[3] = 0x80 | (codepoint & 0x3F);
- return 4;
- }
- return 0;
-}
-
-bool utf8_to_codepoint(const unsigned char buffer[4], const size_t len,
- uint32_t *codepoint) {
- *codepoint = 0;
- if (len == 1 && buffer[0] <= 0x7F) {
- *codepoint = buffer[0];
- return true;
- }
- if (len == 2 && (buffer[0] >= 0xC0 && buffer[0] <= 0xDF) &&
- (buffer[1] >= 0x80 && buffer[1] <= 0xBF)) {
- *codepoint = buffer[0] & 0x1F;
- *codepoint = *codepoint << 6;
- *codepoint = *codepoint | (buffer[1] & 0x3F);
- return true;
- }
- if (len == 3 && (buffer[0] >= 0xE0 && buffer[0] <= 0xEF) &&
- (buffer[1] >= 0x80 && buffer[1] <= 0xBF) &&
- (buffer[2] >= 0x80 && buffer[2] <= 0xBF)) {
- *codepoint = buffer[0] & 0xF;
- *codepoint = *codepoint << 6;
- *codepoint = *codepoint | (buffer[1] & 0x3F);
- *codepoint = *codepoint << 6;
- *codepoint = *codepoint | (buffer[2] & 0x3F);
- return true;
- }
- if (len == 4 && (buffer[0] >= 0xF0 && buffer[0] <= 0xF7) &&
- (buffer[1] >= 0x80 && buffer[1] <= 0xBF) &&
- (buffer[2] >= 0x80 && buffer[2] <= 0xBF) &&
- (buffer[3] >= 0x80 && buffer[3] <= 0xBF)) {
- *codepoint = buffer[0] & 7;
- *codepoint = *codepoint << 6;
- *codepoint = *codepoint | (buffer[1] & 0x3F);
- *codepoint = *codepoint << 6;
- *codepoint = *codepoint | (buffer[2] & 0x3F);
- *codepoint = *codepoint << 6;
- *codepoint = *codepoint | (buffer[3] & 0x3F);
- return true;
- }
-
- return false;
-}
+++ /dev/null
-#ifndef UTF8_H
-#define UTF8_H
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-size_t codepoint_to_utf8(const uint32_t codepoint, unsigned char buffer[4]);
-bool utf8_to_codepoint(const unsigned char buffer[4], const size_t len,
- uint32_t *codepoint);
-
-#endif /* UTF8_H */
+++ /dev/null
-;;; vterm-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*-
-;;
-;;; Code:
-
-(add-to-list 'load-path (directory-file-name
- (or (file-name-directory #$) (car load-path))))
-
-\f
-;;;### (autoloads nil "vterm" "vterm.el" (0 0 0 0))
-;;; Generated autoloads from vterm.el
-
-(autoload 'vterm-module-compile "vterm" "\
-Compile vterm-module." t nil)
-
-(autoload 'vterm--bookmark-handler "vterm" "\
-Handler to restore a vterm bookmark BMK.
-
-If a vterm buffer of the same name does not exist, the function will create a
-new vterm buffer of the name. It also checks the current directory and sets
-it to the bookmarked directory if needed.
-
-\(fn BMK)" nil nil)
-
-(autoload 'vterm-next-error-function "vterm" "\
-Advance to the next error message and visit the file where the error was.
-This is the value of `next-error-function' in Compilation
-buffers. Prefix arg N says how many error messages to move
-forwards (or backwards, if negative).
-
-Optional argument RESET clears all the errors.
-
-\(fn N &optional RESET)" t nil)
-
-(autoload 'vterm "vterm" "\
-Create an interactive Vterm buffer.
-Start a new Vterm session, or switch to an already active
-session. Return the buffer selected (or created).
-
-With a nonnumeric prefix arg, create a new session.
-
-With a string prefix arg, create a new session with arg as buffer name.
-
-With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch
-to the session with that number, or create it if it doesn't
-already exist.
-
-The buffer name used for Vterm sessions is determined by the
-value of `vterm-buffer-name'.
-
-\(fn &optional ARG)" t nil)
-
-(autoload 'vterm-other-window "vterm" "\
-Create an interactive Vterm buffer in another window.
-Start a new Vterm session, or switch to an already active
-session. Return the buffer selected (or created).
-
-With a nonnumeric prefix arg, create a new session.
-
-With a string prefix arg, create a new session with arg as buffer name.
-
-With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch
-to the session with that number, or create it if it doesn't
-already exist.
-
-The buffer name used for Vterm sessions is determined by the
-value of `vterm-buffer-name'.
-
-\(fn &optional ARG)" t nil)
-
-(register-definition-prefixes "vterm" '("vterm-"))
-
-;;;***
-\f
-;;;### (autoloads nil nil ("vterm-pkg.el") (0 0 0 0))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; vterm-autoloads.el ends here
+++ /dev/null
-#include "vterm-module.h"
-#include "elisp.h"
-#include "utf8.h"
-#include <assert.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-#include <termios.h>
-#include <unistd.h>
-#include <vterm.h>
-
-static LineInfo *alloc_lineinfo() {
- LineInfo *info = malloc(sizeof(LineInfo));
- info->directory = NULL;
- info->prompt_col = -1;
- return info;
-}
-void free_lineinfo(LineInfo *line) {
- if (line == NULL) {
- return;
- }
- if (line->directory != NULL) {
- free(line->directory);
- line->directory = NULL;
- }
- free(line);
-}
-static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) {
- Term *term = (Term *)data;
-
- if (!term->sb_size) {
- return 0;
- }
-
- // copy vterm cells into sb_buffer
- size_t c = (size_t)cols;
- ScrollbackLine *sbrow = NULL;
- if (term->sb_current == term->sb_size) {
- if (term->sb_buffer[term->sb_current - 1]->cols == c) {
- // Recycle old row if it's the right size
- sbrow = term->sb_buffer[term->sb_current - 1];
- } else {
- if (term->sb_buffer[term->sb_current - 1]->info != NULL) {
- free_lineinfo(term->sb_buffer[term->sb_current - 1]->info);
- term->sb_buffer[term->sb_current - 1]->info = NULL;
- }
- free(term->sb_buffer[term->sb_current - 1]);
- }
-
- // Make room at the start by shifting to the right.
- memmove(term->sb_buffer + 1, term->sb_buffer,
- sizeof(term->sb_buffer[0]) * (term->sb_current - 1));
-
- } else if (term->sb_current > 0) {
- // Make room at the start by shifting to the right.
- memmove(term->sb_buffer + 1, term->sb_buffer,
- sizeof(term->sb_buffer[0]) * term->sb_current);
- }
-
- if (!sbrow) {
- sbrow = malloc(sizeof(ScrollbackLine) + c * sizeof(sbrow->cells[0]));
- sbrow->cols = c;
- sbrow->info = NULL;
- }
- if (sbrow->info != NULL) {
- free_lineinfo(sbrow->info);
- }
- sbrow->info = term->lines[0];
- memmove(term->lines, term->lines + 1,
- sizeof(term->lines[0]) * (term->lines_len - 1));
- if (term->resizing) {
- /* pushed by window height decr */
- if (term->lines[term->lines_len - 1] != NULL) {
- /* do not need free here ,it is reused ,we just need set null */
- term->lines[term->lines_len - 1] = NULL;
- }
- term->lines_len--;
- } else {
- LineInfo *lastline = term->lines[term->lines_len - 1];
- if (lastline != NULL) {
- LineInfo *line = alloc_lineinfo();
- if (lastline->directory != NULL) {
- line->directory = malloc(1 + strlen(lastline->directory));
- strcpy(line->directory, lastline->directory);
- }
- term->lines[term->lines_len - 1] = line;
- }
- }
-
- // New row is added at the start of the storage buffer.
- term->sb_buffer[0] = sbrow;
- if (term->sb_current < term->sb_size) {
- term->sb_current++;
- }
-
- if (term->sb_pending < term->sb_size) {
- term->sb_pending++;
- /* when window height decreased */
- if (term->height_resize < 0 &&
- term->sb_pending_by_height_decr < -term->height_resize) {
- term->sb_pending_by_height_decr++;
- }
- }
-
- memcpy(sbrow->cells, cells, c * sizeof(cells[0]));
-
- return 1;
-}
-/// Scrollback pop handler (from pangoterm).
-///
-/// @param cols
-/// @param cells VTerm state to update.
-/// @param data Term
-static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) {
- Term *term = (Term *)data;
-
- if (!term->sb_current) {
- return 0;
- }
-
- if (term->sb_pending) {
- term->sb_pending--;
- }
-
- ScrollbackLine *sbrow = term->sb_buffer[0];
- term->sb_current--;
- // Forget the "popped" row by shifting the rest onto it.
- memmove(term->sb_buffer, term->sb_buffer + 1,
- sizeof(term->sb_buffer[0]) * (term->sb_current));
-
- size_t cols_to_copy = (size_t)cols;
- if (cols_to_copy > sbrow->cols) {
- cols_to_copy = sbrow->cols;
- }
-
- // copy to vterm state
- memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy);
- size_t col;
- for (col = cols_to_copy; col < (size_t)cols; col++) {
- cells[col].chars[0] = 0;
- cells[col].width = 1;
- }
-
- LineInfo **lines = malloc(sizeof(LineInfo *) * (term->lines_len + 1));
-
- memmove(lines + 1, term->lines, sizeof(term->lines[0]) * term->lines_len);
- lines[0] = sbrow->info;
- free(sbrow);
- term->lines_len += 1;
- free(term->lines);
- term->lines = lines;
-
- return 1;
-}
-
-static int term_sb_clear(void *data) {
- Term *term = (Term *)data;
-
- if (term->sb_clear_pending) {
- // Another scrollback clear is already pending, so skip this one.
- return 0;
- }
-
- for (int i = 0; i < term->sb_current; i++) {
- if (term->sb_buffer[i]->info != NULL) {
- free_lineinfo(term->sb_buffer[i]->info);
- term->sb_buffer[i]->info = NULL;
- }
- free(term->sb_buffer[i]);
- }
- free(term->sb_buffer);
- term->sb_buffer = malloc(sizeof(ScrollbackLine *) * term->sb_size);
- term->sb_clear_pending = true;
- term->sb_current = 0;
- term->sb_pending = 0;
- term->sb_pending_by_height_decr = 0;
- invalidate_terminal(term, -1, -1);
-
- return 0;
-}
-
-static int row_to_linenr(Term *term, int row) {
- return row != INT_MAX ? row + (int)term->sb_current + 1 : INT_MAX;
-}
-
-static int linenr_to_row(Term *term, int linenr) {
- return linenr - (int)term->sb_current - 1;
-}
-
-static void fetch_cell(Term *term, int row, int col, VTermScreenCell *cell) {
- if (row < 0) {
- ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
- if ((size_t)col < sbrow->cols) {
- *cell = sbrow->cells[col];
- } else {
- // fill the pointer with an empty cell
- VTermColor fg, bg;
- VTermState *state = vterm_obtain_state(term->vt);
- vterm_state_get_default_colors(state, &fg, &bg);
-
- *cell = (VTermScreenCell){.chars = {0}, .width = 1, .bg = bg};
- }
- } else {
- vterm_screen_get_cell(term->vts, (VTermPos){.row = row, .col = col}, cell);
- }
-}
-
-static char *get_row_directory(Term *term, int row) {
- if (row < 0) {
- ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
- return sbrow->info->directory;
- /* return term->dirs[0]; */
- } else {
- LineInfo *line = term->lines[row];
- return line ? line->directory : NULL;
- }
-}
-static LineInfo *get_lineinfo(Term *term, int row) {
- if (row < 0) {
- ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
- return sbrow->info;
- /* return term->dirs[0]; */
- } else {
- return term->lines[row];
- }
-}
-static bool is_eol(Term *term, int end_col, int row, int col) {
- /* This cell is EOL if this and every cell to the right is black */
- if (row >= 0) {
- VTermPos pos = {.row = row, .col = col};
- return vterm_screen_is_eol(term->vts, pos);
- }
-
- ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
- int c;
- for (c = col; c < end_col && c < sbrow->cols;) {
- if (sbrow->cells[c].chars[0]) {
- return 0;
- }
- c += sbrow->cells[c].width;
- }
- return 1;
-}
-static int is_end_of_prompt(Term *term, int end_col, int row, int col) {
- LineInfo *info = get_lineinfo(term, row);
- if (info == NULL) {
- return 0;
- }
- if (info->prompt_col < 0) {
- return 0;
- }
- if (info->prompt_col == col) {
- return 1;
- }
- if (is_eol(term, end_col, row, col) && info->prompt_col >= col) {
- return 1;
- }
- return 0;
-}
-
-static void goto_col(Term *term, emacs_env *env, int row, int end_col) {
- int col = 0;
- size_t offset = 0;
- size_t beyond_eol = 0;
-
- int height;
- int width;
- vterm_get_size(term->vt, &height, &width);
-
- while (col < end_col) {
- VTermScreenCell cell;
- fetch_cell(term, row, col, &cell);
- if (cell.chars[0]) {
- if (cell.width > 1) {
- offset += cell.width - 1;
- }
- } else {
- if (is_eol(term, term->width, row, col)) {
- offset += cell.width;
- beyond_eol += cell.width;
- }
- }
- col += cell.width;
- }
-
- forward_char(env, env->make_integer(env, end_col - offset));
- emacs_value space = env->make_string(env, " ", 1);
- for (int i = 0; i < beyond_eol; i += 1)
- insert(env, space);
-}
-
-static void refresh_lines(Term *term, emacs_env *env, int start_row,
- int end_row, int end_col) {
- if (end_row < start_row) {
- return;
- }
- int i, j;
-
-#define PUSH_BUFFER(c) \
- do { \
- if (length == capacity) { \
- capacity += end_col * 4; \
- buffer = realloc(buffer, capacity * sizeof(char)); \
- } \
- buffer[length] = (c); \
- length++; \
- } while (0)
-
- int capacity = ((end_row - start_row + 1) * end_col) * 4;
- int length = 0;
- char *buffer = malloc(capacity * sizeof(char));
- VTermScreenCell cell;
- VTermScreenCell lastCell;
- fetch_cell(term, start_row, 0, &lastCell);
-
- for (i = start_row; i < end_row; i++) {
-
- int newline = 0;
- int isprompt = 0;
- for (j = 0; j < end_col; j++) {
- fetch_cell(term, i, j, &cell);
- if (isprompt && length > 0) {
- emacs_value text = render_text(env, term, buffer, length, &lastCell);
- insert(env, render_prompt(env, text));
- length = 0;
- }
-
- isprompt = is_end_of_prompt(term, end_col, i, j);
- if (isprompt && length > 0) {
- insert(env, render_text(env, term, buffer, length, &lastCell));
- length = 0;
- }
-
- if (!compare_cells(&cell, &lastCell)) {
- emacs_value text = render_text(env, term, buffer, length, &lastCell);
- insert(env, text);
- length = 0;
- }
-
- lastCell = cell;
- if (cell.chars[0] == 0) {
- if (is_eol(term, end_col, i, j)) {
- /* This cell is EOL if this and every cell to the right is black */
- PUSH_BUFFER('\n');
- newline = 1;
- break;
- }
- PUSH_BUFFER(' ');
- } else {
- for (int k = 0; k < VTERM_MAX_CHARS_PER_CELL && cell.chars[k]; ++k) {
- unsigned char bytes[4];
- size_t count = codepoint_to_utf8(cell.chars[k], bytes);
- for (int l = 0; l < count; l++) {
- PUSH_BUFFER(bytes[l]);
- }
- }
- }
-
- if (cell.width > 1) {
- int w = cell.width - 1;
- j = j + w;
- }
- }
- if (isprompt && length > 0) {
- emacs_value text = render_text(env, term, buffer, length, &lastCell);
- insert(env, render_prompt(env, text));
- length = 0;
- isprompt = 0;
- }
-
- if (!newline) {
- emacs_value text = render_text(env, term, buffer, length, &lastCell);
- insert(env, text);
- length = 0;
- text = render_fake_newline(env, term);
- insert(env, text);
- }
- }
- emacs_value text = render_text(env, term, buffer, length, &lastCell);
- insert(env, text);
-
-#undef PUSH_BUFFER
- free(buffer);
-
- return;
-}
-// Refresh the screen (visible part of the buffer when the terminal is
-// focused) of a invalidated terminal
-static void refresh_screen(Term *term, emacs_env *env) {
- // Term height may have decreased before `invalid_end` reflects it.
- term->invalid_end = MIN(term->invalid_end, term->height);
-
- if (term->invalid_end >= term->invalid_start) {
- int startrow = -(term->height - term->invalid_start - term->linenum_added);
- /* startrow is negative,so we backward -startrow lines from end of buffer
- then delete lines there.
- */
- goto_line(env, startrow);
- delete_lines(env, startrow, term->invalid_end - term->invalid_start, true);
- refresh_lines(term, env, term->invalid_start, term->invalid_end,
- term->width);
-
- /* term->linenum_added is lines added by window height increased */
- term->linenum += term->linenum_added;
- term->linenum_added = 0;
- }
-
- term->invalid_start = INT_MAX;
- term->invalid_end = -1;
-}
-
-static int term_resize(int rows, int cols, void *user_data) {
- /* can not use invalidate_terminal here */
- /* when the window height decreased, */
- /* the value of term->invalid_end can't bigger than window height */
- Term *term = (Term *)user_data;
- term->invalid_start = 0;
- term->invalid_end = rows;
-
- /* if rows=term->lines_len, that means term_sb_pop already resize term->lines
- */
- /* if rows<term->lines_len, term_sb_push would resize term->lines there */
- /* we noly need to take care of rows>term->height */
-
- if (rows > term->height) {
- if (rows > term->lines_len) {
- LineInfo **infos = term->lines;
- term->lines = malloc(sizeof(LineInfo *) * rows);
- memmove(term->lines, infos, sizeof(infos[0]) * term->lines_len);
-
- LineInfo *lastline = term->lines[term->lines_len - 1];
- for (int i = term->lines_len; i < rows; i++) {
- if (lastline != NULL) {
- LineInfo *line = alloc_lineinfo();
- if (lastline->directory != NULL) {
- line->directory =
- malloc(1 + strlen(term->lines[term->lines_len - 1]->directory));
- strcpy(line->directory,
- term->lines[term->lines_len - 1]->directory);
- }
- term->lines[i] = line;
- } else {
- term->lines[i] = NULL;
- }
- }
- term->lines_len = rows;
- free(infos);
- }
- }
-
- term->width = cols;
- term->height = rows;
-
- invalidate_terminal(term, -1, -1);
- term->resizing = false;
-
- return 1;
-}
-
-// Refresh the scrollback of an invalidated terminal.
-static void refresh_scrollback(Term *term, emacs_env *env) {
- int max_line_count = (int)term->sb_current + term->height;
- int del_cnt = 0;
- if (term->sb_clear_pending) {
- del_cnt = term->linenum - term->height;
- if (del_cnt > 0) {
- delete_lines(env, 1, del_cnt, true);
- term->linenum -= del_cnt;
- }
- term->sb_clear_pending = false;
- }
- if (term->sb_pending > 0) {
- // This means that either the window height has decreased or the screen
- // became full and libvterm had to push all rows up. Convert the first
- // pending scrollback row into a string and append it just above the visible
- // section of the buffer
-
- del_cnt = term->linenum - term->height - (int)term->sb_size +
- term->sb_pending - term->sb_pending_by_height_decr;
- if (del_cnt > 0) {
- delete_lines(env, 1, del_cnt, true);
- term->linenum -= del_cnt;
- }
-
- term->linenum += term->sb_pending;
- del_cnt = term->linenum - max_line_count; /* extra lines at the bottom */
- /* buf_index is negative,so we move to end of buffer,then backward
- -buf_index lines. goto lines backward is effectively when
- vterm-max-scrollback is a large number.
- */
- int buf_index = -(term->height + del_cnt);
- goto_line(env, buf_index);
- refresh_lines(term, env, -term->sb_pending, 0, term->width);
-
- term->sb_pending = 0;
- }
-
- // Remove extra lines at the bottom
- del_cnt = term->linenum - max_line_count;
- if (del_cnt > 0) {
- term->linenum -= del_cnt;
- /* -del_cnt is negative,so we delete_lines from end of buffer.
- this line means: delete del_cnt count of lines at end of buffer.
- */
- delete_lines(env, -del_cnt, del_cnt, true);
- }
-
- term->sb_pending_by_height_decr = 0;
- term->height_resize = 0;
-}
-
-static void adjust_topline(Term *term, emacs_env *env) {
- VTermState *state = vterm_obtain_state(term->vt);
- VTermPos pos;
- vterm_state_get_cursorpos(state, &pos);
-
- /* pos.row-term->height is negative,so we backward term->height-pos.row
- * lines from end of buffer
- */
-
- goto_line(env, pos.row - term->height);
- goto_col(term, env, pos.row, pos.col);
-
- emacs_value windows = get_buffer_window_list(env);
- emacs_value swindow = selected_window(env);
- int winnum = env->extract_integer(env, length(env, windows));
- for (int i = 0; i < winnum; i++) {
- emacs_value window = nth(env, i, windows);
- if (eq(env, window, swindow)) {
- int win_body_height =
- env->extract_integer(env, window_body_height(env, window));
-
- /* recenter:If ARG is negative, it counts up from the bottom of the
- * window. (ARG should be less than the height of the window ) */
- if (term->height - pos.row <= win_body_height) {
- recenter(env, env->make_integer(env, pos.row - term->height));
- } else {
- recenter(env, env->make_integer(env, pos.row));
- }
- } else {
- if (env->is_not_nil(env, window)) {
- set_window_point(env, window, point(env));
- }
- }
- }
-}
-
-static void invalidate_terminal(Term *term, int start_row, int end_row) {
- if (start_row != -1 && end_row != -1) {
- term->invalid_start = MIN(term->invalid_start, start_row);
- term->invalid_end = MAX(term->invalid_end, end_row);
- }
- term->is_invalidated = true;
-}
-
-static int term_damage(VTermRect rect, void *data) {
- invalidate_terminal(data, rect.start_row, rect.end_row);
- return 1;
-}
-
-static int term_moverect(VTermRect dest, VTermRect src, void *data) {
- invalidate_terminal(data, MIN(dest.start_row, src.start_row),
- MAX(dest.end_row, src.end_row));
- return 1;
-}
-
-static int term_movecursor(VTermPos new, VTermPos old, int visible,
- void *data) {
- Term *term = data;
- term->cursor.row = new.row;
- term->cursor.col = new.col;
- invalidate_terminal(term, old.row, old.row + 1);
- invalidate_terminal(term, new.row, new.row + 1);
-
- return 1;
-}
-
-static void term_redraw_cursor(Term *term, emacs_env *env) {
- if (term->cursor.cursor_blink_changed) {
- term->cursor.cursor_blink_changed = false;
- set_cursor_blink(env, term->cursor.cursor_blink);
- }
-
- if (term->cursor.cursor_type_changed) {
- term->cursor.cursor_type_changed = false;
-
- if (!term->cursor.cursor_visible) {
- set_cursor_type(env, Qnil);
- return;
- }
-
- switch (term->cursor.cursor_type) {
- case VTERM_PROP_CURSORSHAPE_BLOCK:
- set_cursor_type(env, Qbox);
- break;
- case VTERM_PROP_CURSORSHAPE_UNDERLINE:
- set_cursor_type(env, Qhbar);
- break;
- case VTERM_PROP_CURSORSHAPE_BAR_LEFT:
- set_cursor_type(env, Qbar);
- break;
- default:
- set_cursor_type(env, Qt);
- break;
- }
- }
-}
-
-static void term_redraw(Term *term, emacs_env *env) {
- term_redraw_cursor(term, env);
-
- if (term->is_invalidated) {
- int oldlinenum = term->linenum;
- refresh_scrollback(term, env);
- refresh_screen(term, env);
- term->linenum_added = term->linenum - oldlinenum;
- adjust_topline(term, env);
- term->linenum_added = 0;
- }
-
- if (term->title_changed) {
- set_title(env, env->make_string(env, term->title, strlen(term->title)));
- term->title_changed = false;
- }
-
- if (term->directory_changed) {
- set_directory(
- env, env->make_string(env, term->directory, strlen(term->directory)));
- term->directory_changed = false;
- }
-
- while (term->elisp_code_first) {
- ElispCodeListNode *node = term->elisp_code_first;
- term->elisp_code_first = node->next;
- emacs_value elisp_code = env->make_string(env, node->code, node->code_len);
- vterm_eval(env, elisp_code);
-
- free(node->code);
- free(node);
- }
- term->elisp_code_p_insert = &term->elisp_code_first;
-
- if (term->selection_data) {
- emacs_value selection_mask = env->make_integer(env, term->selection_mask);
- emacs_value selection_data = env->make_string(env, term->selection_data,
- strlen(term->selection_data));
- vterm_set_selection(env, selection_mask, selection_data);
- free(term->selection_data);
- term->selection_data = NULL;
- term->selection_mask = 0;
- }
-
- term->is_invalidated = false;
-}
-
-static VTermScreenCallbacks vterm_screen_callbacks = {
- .damage = term_damage,
- .moverect = term_moverect,
- .movecursor = term_movecursor,
- .settermprop = term_settermprop,
- .resize = term_resize,
- .sb_pushline = term_sb_push,
- .sb_popline = term_sb_pop,
-#if !defined(VTermSBClearNotExists)
- .sb_clear = term_sb_clear,
-#endif
-};
-
-static bool compare_cells(VTermScreenCell *a, VTermScreenCell *b) {
- bool equal = true;
- equal = equal && vterm_color_is_equal(&a->fg, &b->fg);
- equal = equal && vterm_color_is_equal(&a->bg, &b->bg);
- equal = equal && (a->attrs.bold == b->attrs.bold);
- equal = equal && (a->attrs.underline == b->attrs.underline);
- equal = equal && (a->attrs.italic == b->attrs.italic);
- equal = equal && (a->attrs.reverse == b->attrs.reverse);
- equal = equal && (a->attrs.strike == b->attrs.strike);
- return equal;
-}
-
-static bool is_key(unsigned char *key, size_t len, char *key_description) {
- return (len == strlen(key_description) &&
- memcmp(key, key_description, len) == 0);
-}
-
-/* str1=concat(str1,str2,str2_len,true); */
-/* str1 can be NULL */
-static char *concat(char *str1, const char *str2, size_t str2_len,
- bool free_str1) {
- if (str1 == NULL) {
- str1 = malloc(str2_len + 1);
- memcpy(str1, str2, str2_len);
- str1[str2_len] = '\0';
- return str1;
- }
- size_t str1_len = strlen(str1);
- char *buf = malloc(str1_len + str2_len + 1);
- memcpy(buf, str1, str1_len);
- memcpy(&buf[str1_len], str2, str2_len);
- buf[str1_len + str2_len] = '\0';
- if (free_str1) {
- free(str1);
- }
- return buf;
-}
-static void term_set_title(Term *term, const char *title, size_t len,
- bool initial, bool final) {
- if (term->title && initial) {
- free(term->title);
- term->title = NULL;
- term->title_changed = false;
- }
- term->title = concat(term->title, title, len, true);
- if (final) {
- term->title_changed = true;
- }
- return;
-}
-
-static int term_settermprop(VTermProp prop, VTermValue *val, void *user_data) {
- Term *term = (Term *)user_data;
- switch (prop) {
- case VTERM_PROP_CURSORVISIBLE:
- invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
- term->cursor.cursor_visible = val->boolean;
- term->cursor.cursor_type_changed = true;
- break;
- case VTERM_PROP_CURSORBLINK:
- if (term->ignore_blink_cursor)
- break;
- invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
- term->cursor.cursor_blink = val->boolean;
- term->cursor.cursor_blink_changed = true;
- break;
- case VTERM_PROP_CURSORSHAPE:
- invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
- term->cursor.cursor_type = val->number;
- term->cursor.cursor_type_changed = true;
- break;
- case VTERM_PROP_TITLE:
-#ifdef VTermStringFragmentNotExists
- term_set_title(term, val->string, strlen(val->string), true, true);
-#else
- term_set_title(term, val->string.str, val->string.len, val->string.initial,
- val->string.final);
-#endif
- break;
- case VTERM_PROP_ALTSCREEN:
- invalidate_terminal(term, 0, term->height);
- break;
- default:
- return 0;
- }
-
- return 1;
-}
-
-static emacs_value render_text(emacs_env *env, Term *term, char *buffer,
- int len, VTermScreenCell *cell) {
- emacs_value text;
- if (len == 0) {
- text = env->make_string(env, "", 0);
- return text;
- } else {
- text = env->make_string(env, buffer, len);
- }
-
- emacs_value fg = cell_rgb_color(env, term, cell, true);
- emacs_value bg = cell_rgb_color(env, term, cell, false);
- /* With vterm-disable-bold-font, vterm-disable-underline,
- * vterm-disable-inverse-video, users can disable some text properties.
- * Here, we check whether the text would require adding such properties.
- * In case it does, and the user does not disable the attribute, we later
- * append the property to the list props. If the text does not require
- * such property, or the user disable it, we set the variable to nil.
- * Properties that are marked as nil are not added to the text. */
- emacs_value bold =
- cell->attrs.bold && !term->disable_bold_font ? Qbold : Qnil;
- emacs_value underline =
- cell->attrs.underline && !term->disable_underline ? Qt : Qnil;
- emacs_value italic = cell->attrs.italic ? Qitalic : Qnil;
- emacs_value reverse =
- cell->attrs.reverse && !term->disable_inverse_video ? Qt : Qnil;
- emacs_value strike = cell->attrs.strike ? Qt : Qnil;
-
- // TODO: Blink, font, dwl, dhl is missing
- int emacs_major_version =
- env->extract_integer(env, symbol_value(env, Qemacs_major_version));
- emacs_value properties;
- emacs_value props[64];
- int props_len = 0;
- if (env->is_not_nil(env, fg))
- props[props_len++] = Qforeground, props[props_len++] = fg;
- if (env->is_not_nil(env, bg))
- props[props_len++] = Qbackground, props[props_len++] = bg;
- if (bold != Qnil)
- props[props_len++] = Qweight, props[props_len++] = bold;
- if (underline != Qnil)
- props[props_len++] = Qunderline, props[props_len++] = underline;
- if (italic != Qnil)
- props[props_len++] = Qslant, props[props_len++] = italic;
- if (reverse != Qnil)
- props[props_len++] = Qreverse, props[props_len++] = reverse;
- if (strike != Qnil)
- props[props_len++] = Qstrike, props[props_len++] = strike;
- if (emacs_major_version >= 27)
- props[props_len++] = Qextend, props[props_len++] = Qt;
-
- properties = list(env, props, props_len);
-
- if (props_len)
- put_text_property(env, text, Qface, properties);
-
- return text;
-}
-static emacs_value render_prompt(emacs_env *env, emacs_value text) {
-
- emacs_value properties;
-
- properties =
- list(env, (emacs_value[]){Qvterm_prompt, Qt, Qrear_nonsticky, Qt}, 4);
-
- add_text_properties(env, text, properties);
-
- return text;
-}
-
-static emacs_value render_fake_newline(emacs_env *env, Term *term) {
-
- emacs_value text;
- text = env->make_string(env, "\n", 1);
-
- emacs_value properties;
-
- properties =
- list(env, (emacs_value[]){Qvterm_line_wrap, Qt, Qrear_nonsticky, Qt}, 4);
-
- add_text_properties(env, text, properties);
-
- return text;
-}
-
-static emacs_value cell_rgb_color(emacs_env *env, Term *term,
- VTermScreenCell *cell, bool is_foreground) {
- VTermColor *color = is_foreground ? &cell->fg : &cell->bg;
-
- int props_len = 0;
- emacs_value props[3];
- if (is_foreground)
- props[props_len++] = Qforeground;
- if (cell->attrs.underline)
- props[props_len++] = Qunderline;
- if (cell->attrs.reverse)
- props[props_len++] = Qreverse;
-
- emacs_value args = list(env, props, props_len);
-
- /** NOTE: -10 is used as index offset for special indexes,
- * see C-h f vterm--get-color RET
- */
- if (VTERM_COLOR_IS_DEFAULT_FG(color) || VTERM_COLOR_IS_DEFAULT_BG(color)) {
- return vterm_get_color(env, -1, args);
- }
- if (VTERM_COLOR_IS_INDEXED(color)) {
- if (color->indexed.idx < 16) {
- return vterm_get_color(env, color->indexed.idx, args);
- } else {
- VTermState *state = vterm_obtain_state(term->vt);
- vterm_state_get_palette_color(state, color->indexed.idx, color);
- }
- } else if (VTERM_COLOR_IS_RGB(color)) {
- /* do nothing just use the argument color directly */
- }
-
- char buffer[8];
- snprintf(buffer, 8, "#%02X%02X%02X", color->rgb.red, color->rgb.green,
- color->rgb.blue);
- return env->make_string(env, buffer, 7);
-}
-
-static void term_flush_output(Term *term, emacs_env *env) {
- size_t len = vterm_output_get_buffer_current(term->vt);
- if (len) {
- char buffer[len];
- len = vterm_output_read(term->vt, buffer, len);
-
- emacs_value output = env->make_string(env, buffer, len);
- env->funcall(env, Fvterm_flush_output, 1, (emacs_value[]){output});
- }
-}
-
-static void term_clear_scrollback(Term *term, emacs_env *env) {
- term_sb_clear(term);
- vterm_screen_flush_damage(term->vts);
- term_redraw(term, env);
-}
-
-static void term_process_key(Term *term, emacs_env *env, unsigned char *key,
- size_t len, VTermModifier modifier) {
- if (is_key(key, len, "<clear_scrollback>")) {
- term_clear_scrollback(term, env);
- } else if (is_key(key, len, "<start>")) {
- tcflow(term->pty_fd, TCOON);
- } else if (is_key(key, len, "<stop>")) {
- tcflow(term->pty_fd, TCOOFF);
- } else if (is_key(key, len, "<start_paste>")) {
- vterm_keyboard_start_paste(term->vt);
- } else if (is_key(key, len, "<end_paste>")) {
- vterm_keyboard_end_paste(term->vt);
- } else if (is_key(key, len, "<tab>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_TAB, modifier);
- } else if (is_key(key, len, "<backtab>") ||
- is_key(key, len, "<iso-lefttab>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_TAB, VTERM_MOD_SHIFT);
- } else if (is_key(key, len, "<backspace>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_BACKSPACE, modifier);
- } else if (is_key(key, len, "<escape>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_ESCAPE, modifier);
- } else if (is_key(key, len, "<up>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_UP, modifier);
- } else if (is_key(key, len, "<down>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_DOWN, modifier);
- } else if (is_key(key, len, "<left>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_LEFT, modifier);
- } else if (is_key(key, len, "<right>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_RIGHT, modifier);
- } else if (is_key(key, len, "<insert>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_INS, modifier);
- } else if (is_key(key, len, "<delete>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_DEL, modifier);
- } else if (is_key(key, len, "<home>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_HOME, modifier);
- } else if (is_key(key, len, "<end>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_END, modifier);
- } else if (is_key(key, len, "<prior>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_PAGEUP, modifier);
- } else if (is_key(key, len, "<next>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_PAGEDOWN, modifier);
- } else if (is_key(key, len, "<f0>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(0), modifier);
- } else if (is_key(key, len, "<f1>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(1), modifier);
- } else if (is_key(key, len, "<f2>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(2), modifier);
- } else if (is_key(key, len, "<f3>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(3), modifier);
- } else if (is_key(key, len, "<f4>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(4), modifier);
- } else if (is_key(key, len, "<f5>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(5), modifier);
- } else if (is_key(key, len, "<f6>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(6), modifier);
- } else if (is_key(key, len, "<f7>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(7), modifier);
- } else if (is_key(key, len, "<f8>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(8), modifier);
- } else if (is_key(key, len, "<f9>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(9), modifier);
- } else if (is_key(key, len, "<f10>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(10), modifier);
- } else if (is_key(key, len, "<f11>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(11), modifier);
- } else if (is_key(key, len, "<f12>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(12), modifier);
- } else if (is_key(key, len, "<kp-0>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_0, modifier);
- } else if (is_key(key, len, "<kp-1>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_1, modifier);
- } else if (is_key(key, len, "<kp-2>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_2, modifier);
- } else if (is_key(key, len, "<kp-3>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_3, modifier);
- } else if (is_key(key, len, "<kp-4>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_4, modifier);
- } else if (is_key(key, len, "<kp-5>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_5, modifier);
- } else if (is_key(key, len, "<kp-6>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_6, modifier);
- } else if (is_key(key, len, "<kp-7>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_7, modifier);
- } else if (is_key(key, len, "<kp-8>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_8, modifier);
- } else if (is_key(key, len, "<kp-9>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_9, modifier);
- } else if (is_key(key, len, "<kp-add>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_PLUS, modifier);
- } else if (is_key(key, len, "<kp-subtract>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_MINUS, modifier);
- } else if (is_key(key, len, "<kp-multiply>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_MULT, modifier);
- } else if (is_key(key, len, "<kp-divide>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_DIVIDE, modifier);
- } else if (is_key(key, len, "<kp-equal>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_EQUAL, modifier);
- } else if (is_key(key, len, "<kp-decimal>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_PERIOD, modifier);
- } else if (is_key(key, len, "<kp-separator>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_COMMA, modifier);
- } else if (is_key(key, len, "<kp-enter>")) {
- vterm_keyboard_key(term->vt, VTERM_KEY_KP_ENTER, modifier);
- } else if (is_key(key, len, "j") && (modifier == VTERM_MOD_CTRL)) {
- vterm_keyboard_unichar(term->vt, '\n', 0);
- } else if (is_key(key, len, "SPC")) {
- vterm_keyboard_unichar(term->vt, ' ', modifier);
- } else if (len <= 4) {
- uint32_t codepoint;
- if (utf8_to_codepoint(key, len, &codepoint)) {
- vterm_keyboard_unichar(term->vt, codepoint, modifier);
- }
- }
-}
-
-void term_finalize(void *object) {
- Term *term = (Term *)object;
- for (int i = 0; i < term->sb_current; i++) {
- if (term->sb_buffer[i]->info != NULL) {
- free_lineinfo(term->sb_buffer[i]->info);
- term->sb_buffer[i]->info = NULL;
- }
- free(term->sb_buffer[i]);
- }
- if (term->title) {
- free(term->title);
- term->title = NULL;
- }
-
- if (term->directory) {
- free(term->directory);
- term->directory = NULL;
- }
-
- while (term->elisp_code_first) {
- ElispCodeListNode *node = term->elisp_code_first;
- term->elisp_code_first = node->next;
- free(node->code);
- free(node);
- }
- term->elisp_code_p_insert = &term->elisp_code_first;
-
- if (term->cmd_buffer) {
- free(term->cmd_buffer);
- term->cmd_buffer = NULL;
- }
- if (term->selection_data) {
- free(term->selection_data);
- term->selection_data = NULL;
- }
-
- for (int i = 0; i < term->lines_len; i++) {
- if (term->lines[i] != NULL) {
- free_lineinfo(term->lines[i]);
- term->lines[i] = NULL;
- }
- }
-
- if (term->pty_fd > 0) {
- close(term->pty_fd);
- }
-
- free(term->sb_buffer);
- free(term->lines);
- vterm_free(term->vt);
- free(term);
-}
-
-static int handle_osc_cmd_51(Term *term, char subCmd, char *buffer) {
- if (subCmd == 'A') {
- /* "51;A" sets the current directory */
- /* "51;A" has also the role of identifying the end of the prompt */
- if (term->directory != NULL) {
- free(term->directory);
- term->directory = NULL;
- }
- term->directory = malloc(strlen(buffer) + 1);
- strcpy(term->directory, buffer);
- term->directory_changed = true;
-
- for (int i = term->cursor.row; i < term->lines_len; i++) {
- if (term->lines[i] == NULL) {
- term->lines[i] = alloc_lineinfo();
- }
-
- if (term->lines[i]->directory != NULL) {
- free(term->lines[i]->directory);
- }
- term->lines[i]->directory = malloc(strlen(buffer) + 1);
- strcpy(term->lines[i]->directory, buffer);
- if (i == term->cursor.row) {
- term->lines[i]->prompt_col = term->cursor.col;
- } else {
- term->lines[i]->prompt_col = -1;
- }
- }
- return 1;
- } else if (subCmd == 'E') {
- /* "51;E" executes elisp code */
- /* The elisp code is executed in term_redraw */
- ElispCodeListNode *node = malloc(sizeof(ElispCodeListNode));
- node->code_len = strlen(buffer);
- node->code = malloc(node->code_len + 1);
- strcpy(node->code, buffer);
- node->next = NULL;
-
- *(term->elisp_code_p_insert) = node;
- term->elisp_code_p_insert = &(node->next);
- return 1;
- }
- return 0;
-}
-
-static int handle_osc_cmd(Term *term, int cmd, char *buffer) {
- if (cmd == 51) {
- char subCmd = '0';
- if (strlen(buffer) == 0) {
- return 0;
- }
- subCmd = buffer[0];
- /* ++ skip the subcmd char */
- return handle_osc_cmd_51(term, subCmd, ++buffer);
- }
- return 0;
-}
-/* maybe we should drop support of libvterm < v0.2 */
-/* VTermStringFragmentNotExists was introduced when libvterm is not released */
-#ifdef VTermStringFragmentNotExists
-static int osc_callback(const char *command, size_t cmdlen, void *user) {
- Term *term = (Term *)user;
- char buffer[cmdlen + 1];
- buffer[cmdlen] = '\0';
- memcpy(buffer, command, cmdlen);
-
- if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' && buffer[2] == ';' &&
- buffer[3] == 'A') {
- return handle_osc_cmd_51(term, 'A', &buffer[4]);
- } else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' &&
- buffer[2] == ';' && buffer[3] == 'E') {
- return handle_osc_cmd_51(term, 'E', &buffer[4]);
- }
- return 0;
-}
-static VTermParserCallbacks parser_callbacks = {
- .text = NULL,
- .control = NULL,
- .escape = NULL,
- .csi = NULL,
- .osc = &osc_callback,
- .dcs = NULL,
-};
-#else
-
-static int osc_callback(int cmd, VTermStringFragment frag, void *user) {
- /* osc_callback (OSC = Operating System Command) */
-
- /* We interpret escape codes that start with "51;" */
- /* "51;A" sets the current directory */
- /* "51;A" has also the role of identifying the end of the prompt */
- /* "51;E" executes elisp code */
- /* The elisp code is executed in term_redraw */
- Term *term = (Term *)user;
-
- if (frag.initial) {
- /* drop old fragment,because this is a initial fragment */
- if (term->cmd_buffer) {
- free(term->cmd_buffer);
- term->cmd_buffer = NULL;
- }
- }
-
- if (!frag.initial && !frag.final && frag.len == 0) {
- return 0;
- }
-
- term->cmd_buffer = concat(term->cmd_buffer, frag.str, frag.len, true);
- if (frag.final) {
- handle_osc_cmd(term, cmd, term->cmd_buffer);
- free(term->cmd_buffer);
- term->cmd_buffer = NULL;
- }
- return 0;
-}
-static VTermStateFallbacks parser_callbacks = {
- .control = NULL,
- .csi = NULL,
- .osc = &osc_callback,
- .dcs = NULL,
-};
-#ifndef VTermSelectionMaskNotExists
-static int set_selection(VTermSelectionMask mask, VTermStringFragment frag,
- void *user) {
- Term *term = (Term *)user;
-
- if (frag.initial) {
- term->selection_mask = mask;
- if (term->selection_data) {
- free(term->selection_data);
- }
- term->selection_data = NULL;
- }
-
- if (frag.len) {
- term->selection_data =
- concat(term->selection_data, frag.str, frag.len, true);
- }
- return 1;
-}
-/* OSC 52 ; Pc ; Pd BEL */
-/* Manipulate Selection Data */
-/* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html */
-/* test by printf "\033]52;c;$(printf "%s" "blabla" | base64)\a" */
-/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
-/* for clipboard, primary, secondary, select, or cut buffers 0 through 7 */
-/* respectively */
-static VTermSelectionCallbacks selection_callbacks = {
- .set = &set_selection,
- .query = NULL,
-};
-#endif /* VTermSelectionMaskNotExists */
-
-#endif
-
-emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
- void *data) {
- Term *term = malloc(sizeof(Term));
-
- int rows = env->extract_integer(env, args[0]);
- int cols = env->extract_integer(env, args[1]);
- int sb_size = env->extract_integer(env, args[2]);
- int disable_bold_font = env->is_not_nil(env, args[3]);
- int disable_underline = env->is_not_nil(env, args[4]);
- int disable_inverse_video = env->is_not_nil(env, args[5]);
- int ignore_blink_cursor = env->is_not_nil(env, args[6]);
- int set_bold_hightbright = env->is_not_nil(env, args[7]);
-
- term->vt = vterm_new(rows, cols);
- vterm_set_utf8(term->vt, 1);
-
- term->vts = vterm_obtain_screen(term->vt);
-
- VTermState *state = vterm_obtain_state(term->vt);
- vterm_state_set_unrecognised_fallbacks(state, &parser_callbacks, term);
-
-#ifndef VTermSelectionMaskNotExists
- vterm_state_set_selection_callbacks(state, &selection_callbacks, term,
- term->selection_buf, SELECTION_BUF_LEN);
-#endif
- vterm_state_set_bold_highbright(state, set_bold_hightbright);
-
- vterm_screen_reset(term->vts, 1);
- vterm_screen_set_callbacks(term->vts, &vterm_screen_callbacks, term);
- vterm_screen_set_damage_merge(term->vts, VTERM_DAMAGE_SCROLL);
- vterm_screen_enable_altscreen(term->vts, true);
- term->sb_size = MIN(SB_MAX, sb_size);
- term->sb_current = 0;
- term->sb_pending = 0;
- term->sb_clear_pending = false;
- term->sb_pending_by_height_decr = 0;
- term->sb_buffer = malloc(sizeof(ScrollbackLine *) * term->sb_size);
- term->invalid_start = 0;
- term->invalid_end = rows;
- term->is_invalidated = false;
- term->width = cols;
- term->height = rows;
- term->height_resize = 0;
- term->disable_bold_font = disable_bold_font;
- term->disable_underline = disable_underline;
- term->disable_inverse_video = disable_inverse_video;
- term->ignore_blink_cursor = ignore_blink_cursor;
- emacs_value newline = env->make_string(env, "\n", 1);
- for (int i = 0; i < term->height; i++) {
- insert(env, newline);
- }
- term->linenum = term->height;
- term->linenum_added = 0;
- term->resizing = false;
-
- term->pty_fd = -1;
-
- term->title = NULL;
- term->title_changed = false;
-
- term->cursor.row = 0;
- term->cursor.col = 0;
- term->cursor.cursor_type = -1;
- term->cursor.cursor_visible = true;
- term->cursor.cursor_type_changed = false;
- term->cursor.cursor_blink = false;
- term->cursor.cursor_blink_changed = false;
- term->directory = NULL;
- term->directory_changed = false;
- term->elisp_code_first = NULL;
- term->elisp_code_p_insert = &term->elisp_code_first;
- term->selection_data = NULL;
- term->selection_mask = 0;
-
- term->cmd_buffer = NULL;
-
- term->lines = malloc(sizeof(LineInfo *) * rows);
- term->lines_len = rows;
- for (int i = 0; i < rows; i++) {
- term->lines[i] = NULL;
- }
-
- return env->make_user_ptr(env, term_finalize, term);
-}
-
-emacs_value Fvterm_update(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
- void *data) {
- Term *term = env->get_user_ptr(env, args[0]);
-
- // Process keys
- if (nargs > 1) {
- ptrdiff_t len = string_bytes(env, args[1]);
- unsigned char key[len];
- env->copy_string_contents(env, args[1], (char *)key, &len);
- VTermModifier modifier = VTERM_MOD_NONE;
- if (nargs > 2 && env->is_not_nil(env, args[2]))
- modifier = modifier | VTERM_MOD_SHIFT;
- if (nargs > 3 && env->is_not_nil(env, args[3]))
- modifier = modifier | VTERM_MOD_ALT;
- if (nargs > 4 && env->is_not_nil(env, args[4]))
- modifier = modifier | VTERM_MOD_CTRL;
-
- // Ignore the final zero byte
- term_process_key(term, env, key, len - 1, modifier);
- }
-
- // Flush output
- term_flush_output(term, env);
- if (term->is_invalidated) {
- vterm_invalidate(env);
- }
-
- return env->make_integer(env, 0);
-}
-
-emacs_value Fvterm_redraw(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
- void *data) {
- Term *term = env->get_user_ptr(env, args[0]);
- term_redraw(term, env);
- return env->make_integer(env, 0);
-}
-
-emacs_value Fvterm_write_input(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *data) {
- Term *term = env->get_user_ptr(env, args[0]);
- ptrdiff_t len = string_bytes(env, args[1]);
- char bytes[len];
-
- env->copy_string_contents(env, args[1], bytes, &len);
-
- vterm_input_write(term->vt, bytes, len);
- vterm_screen_flush_damage(term->vts);
-
- return env->make_integer(env, 0);
-}
-
-emacs_value Fvterm_set_size(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
- void *data) {
- Term *term = env->get_user_ptr(env, args[0]);
- int rows = env->extract_integer(env, args[1]);
- int cols = env->extract_integer(env, args[2]);
-
- if (cols != term->width || rows != term->height) {
- term->height_resize = rows - term->height;
- if (rows > term->height) {
- if (rows - term->height > term->sb_current) {
- term->linenum_added = rows - term->height - term->sb_current;
- }
- }
- term->resizing = true;
- vterm_set_size(term->vt, rows, cols);
- vterm_screen_flush_damage(term->vts);
-
- term_redraw(term, env);
- }
-
- return Qnil;
-}
-
-emacs_value Fvterm_set_pty_name(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *data) {
- Term *term = env->get_user_ptr(env, args[0]);
-
- if (nargs > 1) {
- ptrdiff_t len = string_bytes(env, args[1]);
- char filename[len];
-
- env->copy_string_contents(env, args[1], filename, &len);
-
- term->pty_fd = open(filename, O_RDONLY);
- }
- return Qnil;
-}
-emacs_value Fvterm_get_pwd(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
- void *data) {
- Term *term = env->get_user_ptr(env, args[0]);
- int linenum = env->extract_integer(env, args[1]);
- int row = linenr_to_row(term, linenum);
- char *dir = get_row_directory(term, row);
-
- return dir ? env->make_string(env, dir, strlen(dir)) : Qnil;
-}
-
-emacs_value Fvterm_get_icrnl(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *data) {
- Term *term = env->get_user_ptr(env, args[0]);
-
- if (term->pty_fd > 0) {
- struct termios keys;
- tcgetattr(term->pty_fd, &keys);
-
- if (keys.c_iflag & ICRNL)
- return Qt;
- else
- return Qnil;
- }
- return Qnil;
-}
-
-emacs_value Fvterm_reset_cursor_point(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *data) {
- Term *term = env->get_user_ptr(env, args[0]);
- int line = row_to_linenr(term, term->cursor.row);
- goto_line(env, line);
- goto_col(term, env, term->cursor.row, term->cursor.col);
- return point(env);
-}
-
-int emacs_module_init(struct emacs_runtime *ert) {
- emacs_env *env = ert->get_environment(ert);
-
- // Symbols;
- Qt = env->make_global_ref(env, env->intern(env, "t"));
- Qnil = env->make_global_ref(env, env->intern(env, "nil"));
- Qnormal = env->make_global_ref(env, env->intern(env, "normal"));
- Qbold = env->make_global_ref(env, env->intern(env, "bold"));
- Qitalic = env->make_global_ref(env, env->intern(env, "italic"));
- Qforeground = env->make_global_ref(env, env->intern(env, ":foreground"));
- Qbackground = env->make_global_ref(env, env->intern(env, ":background"));
- Qweight = env->make_global_ref(env, env->intern(env, ":weight"));
- Qunderline = env->make_global_ref(env, env->intern(env, ":underline"));
- Qslant = env->make_global_ref(env, env->intern(env, ":slant"));
- Qreverse = env->make_global_ref(env, env->intern(env, ":inverse-video"));
- Qstrike = env->make_global_ref(env, env->intern(env, ":strike-through"));
- Qextend = env->make_global_ref(env, env->intern(env, ":extend"));
- Qemacs_major_version =
- env->make_global_ref(env, env->intern(env, "emacs-major-version"));
- Qvterm_line_wrap =
- env->make_global_ref(env, env->intern(env, "vterm-line-wrap"));
- Qrear_nonsticky =
- env->make_global_ref(env, env->intern(env, "rear-nonsticky"));
- Qvterm_prompt = env->make_global_ref(env, env->intern(env, "vterm-prompt"));
-
- Qface = env->make_global_ref(env, env->intern(env, "font-lock-face"));
- Qbox = env->make_global_ref(env, env->intern(env, "box"));
- Qbar = env->make_global_ref(env, env->intern(env, "bar"));
- Qhbar = env->make_global_ref(env, env->intern(env, "hbar"));
- Qcursor_type = env->make_global_ref(env, env->intern(env, "cursor-type"));
-
- // Functions
- Fapply = env->make_global_ref(env, env->intern(env, "apply"));
- Fblink_cursor_mode =
- env->make_global_ref(env, env->intern(env, "blink-cursor-mode"));
- Fsymbol_value = env->make_global_ref(env, env->intern(env, "symbol-value"));
- Flength = env->make_global_ref(env, env->intern(env, "length"));
- Flist = env->make_global_ref(env, env->intern(env, "list"));
- Fnth = env->make_global_ref(env, env->intern(env, "nth"));
- Ferase_buffer = env->make_global_ref(env, env->intern(env, "erase-buffer"));
- Finsert = env->make_global_ref(env, env->intern(env, "vterm--insert"));
- Fgoto_char = env->make_global_ref(env, env->intern(env, "goto-char"));
- Fput_text_property =
- env->make_global_ref(env, env->intern(env, "put-text-property"));
- Fadd_text_properties =
- env->make_global_ref(env, env->intern(env, "add-text-properties"));
- Fset = env->make_global_ref(env, env->intern(env, "set"));
- Fvterm_flush_output =
- env->make_global_ref(env, env->intern(env, "vterm--flush-output"));
- Fforward_line = env->make_global_ref(env, env->intern(env, "forward-line"));
- Fgoto_line = env->make_global_ref(env, env->intern(env, "vterm--goto-line"));
- Fdelete_lines =
- env->make_global_ref(env, env->intern(env, "vterm--delete-lines"));
- Frecenter = env->make_global_ref(env, env->intern(env, "recenter"));
- Fset_window_point =
- env->make_global_ref(env, env->intern(env, "set-window-point"));
- Fwindow_body_height =
- env->make_global_ref(env, env->intern(env, "window-body-height"));
-
- Fpoint = env->make_global_ref(env, env->intern(env, "point"));
- Fforward_char = env->make_global_ref(env, env->intern(env, "forward-char"));
- Fget_buffer_window_list =
- env->make_global_ref(env, env->intern(env, "get-buffer-window-list"));
- Fselected_window =
- env->make_global_ref(env, env->intern(env, "selected-window"));
-
- Fvterm_set_title =
- env->make_global_ref(env, env->intern(env, "vterm--set-title"));
- Fvterm_set_directory =
- env->make_global_ref(env, env->intern(env, "vterm--set-directory"));
- Fvterm_invalidate =
- env->make_global_ref(env, env->intern(env, "vterm--invalidate"));
- Feq = env->make_global_ref(env, env->intern(env, "eq"));
- Fvterm_get_color =
- env->make_global_ref(env, env->intern(env, "vterm--get-color"));
- Fvterm_eval = env->make_global_ref(env, env->intern(env, "vterm--eval"));
- Fvterm_set_selection =
- env->make_global_ref(env, env->intern(env, "vterm--set-selection"));
-
- // Exported functions
- emacs_value fun;
- fun =
- env->make_function(env, 4, 8, Fvterm_new, "Allocate a new vterm.", NULL);
- bind_function(env, "vterm--new", fun);
-
- fun = env->make_function(env, 1, 5, Fvterm_update,
- "Process io and update the screen.", NULL);
- bind_function(env, "vterm--update", fun);
-
- fun =
- env->make_function(env, 1, 1, Fvterm_redraw, "Redraw the screen.", NULL);
- bind_function(env, "vterm--redraw", fun);
-
- fun = env->make_function(env, 2, 2, Fvterm_write_input,
- "Write input to vterm.", NULL);
- bind_function(env, "vterm--write-input", fun);
-
- fun = env->make_function(env, 3, 3, Fvterm_set_size,
- "Set the size of the terminal.", NULL);
- bind_function(env, "vterm--set-size", fun);
-
- fun = env->make_function(env, 2, 2, Fvterm_set_pty_name,
- "Set the name of the pty.", NULL);
- bind_function(env, "vterm--set-pty-name", fun);
- fun = env->make_function(env, 2, 2, Fvterm_get_pwd,
- "Get the working directory of at line n.", NULL);
- bind_function(env, "vterm--get-pwd-raw", fun);
- fun = env->make_function(env, 1, 1, Fvterm_reset_cursor_point,
- "Reset cursor postion.", NULL);
- bind_function(env, "vterm--reset-point", fun);
-
- fun = env->make_function(env, 1, 1, Fvterm_get_icrnl,
- "Get the icrnl state of the pty", NULL);
- bind_function(env, "vterm--get-icrnl", fun);
-
- provide(env, "vterm-module");
-
- return 0;
-}
+++ /dev/null
-#ifndef VTERM_MODULE_H
-#define VTERM_MODULE_H
-
-#include "emacs-module.h"
-#include <inttypes.h>
-#include <stdbool.h>
-#include <vterm.h>
-
-// https://gcc.gnu.org/wiki/Visibility
-#if defined _WIN32 || defined __CYGWIN__
-#ifdef __GNUC__
-#define VTERM_EXPORT __attribute__((dllexport))
-#else
-#define VTERM_EXPORT __declspec(dllexport)
-#endif
-#else
-#if __GNUC__ >= 4
-#define VTERM_EXPORT __attribute__((visibility("default")))
-#else
-#define VTERM_EXPORT
-#endif
-#endif
-
-VTERM_EXPORT int plugin_is_GPL_compatible;
-
-#define SB_MAX 100000 // Maximum 'scrollback' value.
-
-#ifndef MIN
-#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
-#endif
-#ifndef MAX
-#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
-#endif
-
-typedef struct LineInfo {
- char *directory; /* working directory */
-
- int prompt_col; /* end column of the prompt, if the current line contains the
- * prompt */
-} LineInfo;
-
-typedef struct ScrollbackLine {
- size_t cols;
- LineInfo *info;
- VTermScreenCell cells[];
-} ScrollbackLine;
-
-typedef struct ElispCodeListNode {
- char *code;
- size_t code_len;
- struct ElispCodeListNode *next;
-} ElispCodeListNode;
-
-/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
-/* clipboard, primary, secondary, select, or cut buffers 0 through 7 */
-#define SELECTION_BUF_LEN 4096
-
-typedef struct Cursor {
- int row, col;
- int cursor_type;
- bool cursor_visible;
- bool cursor_blink;
- bool cursor_type_changed;
- bool cursor_blink_changed;
-} Cursor;
-
-typedef struct Term {
- VTerm *vt;
- VTermScreen *vts;
- // buffer used to:
- // - convert VTermScreen cell arrays into utf8 strings
- // - receive data from libvterm as a result of key presses.
- ScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm
- size_t sb_current; // number of rows pushed to sb_buffer
- size_t sb_size; // sb_buffer size
- // "virtual index" that points to the first sb_buffer row that we need to
- // push to the terminal buffer when refreshing the scrollback. When negative,
- // it actually points to entries that are no longer in sb_buffer (because the
- // window height has increased) and must be deleted from the terminal buffer
- int sb_pending;
- int sb_pending_by_height_decr;
- bool sb_clear_pending;
- long linenum;
- long linenum_added;
-
- int invalid_start, invalid_end; // invalid rows in libvterm screen
- bool is_invalidated;
-
- Cursor cursor;
- char *title;
- bool title_changed;
-
- char *directory;
- bool directory_changed;
-
- // Single-linked list of elisp_code.
- // Newer commands are added at the tail.
- ElispCodeListNode *elisp_code_first;
- ElispCodeListNode **elisp_code_p_insert; // pointer to the position where new
- // node should be inserted
-
- /* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
- /* clipboard, primary, secondary, select, or cut buffers 0 through 7 */
- int selection_mask; /* see VTermSelectionMask in vterm.h */
- char *selection_data;
- char selection_buf[SELECTION_BUF_LEN];
-
- /* the size of dirs almost = window height, value = directory of that line */
- LineInfo **lines;
- int lines_len;
-
- int width, height;
- int height_resize;
- bool resizing;
- bool disable_bold_font;
- bool disable_underline;
- bool disable_inverse_video;
- bool ignore_blink_cursor;
-
- char *cmd_buffer;
-
- int pty_fd;
-} Term;
-
-static bool compare_cells(VTermScreenCell *a, VTermScreenCell *b);
-static bool is_key(unsigned char *key, size_t len, char *key_description);
-static emacs_value render_text(emacs_env *env, Term *term, char *string,
- int len, VTermScreenCell *cell);
-static emacs_value render_fake_newline(emacs_env *env, Term *term);
-static emacs_value render_prompt(emacs_env *env, emacs_value text);
-static emacs_value cell_rgb_color(emacs_env *env, Term *term,
- VTermScreenCell *cell, bool is_foreground);
-
-static int term_settermprop(VTermProp prop, VTermValue *val, void *user_data);
-
-static void term_redraw(Term *term, emacs_env *env);
-static void term_flush_output(Term *term, emacs_env *env);
-static void term_process_key(Term *term, emacs_env *env, unsigned char *key,
- size_t len, VTermModifier modifier);
-static void invalidate_terminal(Term *term, int start_row, int end_row);
-
-void term_finalize(void *object);
-
-emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
- void *data);
-emacs_value Fvterm_update(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
- void *data);
-emacs_value Fvterm_redraw(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
- void *data);
-emacs_value Fvterm_write_input(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *data);
-emacs_value Fvterm_set_size(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
- void *data);
-emacs_value Fvterm_set_pty_name(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *data);
-emacs_value Fvterm_get_icrnl(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *data);
-
-emacs_value Fvterm_get_pwd(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
- void *data);
-
-emacs_value Fvterm_get_prompt_point(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *data);
-emacs_value Fvterm_reset_cursor_point(emacs_env *env, ptrdiff_t nargs,
- emacs_value args[], void *data);
-
-VTERM_EXPORT int emacs_module_init(struct emacs_runtime *ert);
-
-#endif /* VTERM_MODULE_H */
+++ /dev/null
-(define-package "vterm" "20240102.1640" "Fully-featured terminal emulator"
- '((emacs "25.1"))
- :commit "e96c53f5035c841b20937b65142498bd8e161a40" :authors
- '(("Lukas Fürmetz" . "fuermetzNOSPAMmailbox.org"))
- :maintainers
- '(("Lukas Fürmetz" . "fuermetzNOSPAMmailbox.org"))
- :maintainer
- '("Lukas Fürmetz" . "fuermetzNOSPAMmailbox.org")
- :keywords
- '("terminals")
- :url "https://github.com/akermu/emacs-libvterm")
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
+++ /dev/null
-;;; vterm.el --- Fully-featured terminal emulator -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2017-2020 by Lukas Fürmetz & Contributors
-;;
-;; Author: Lukas Fürmetz <fuermetzNOSPAMmailbox.org>
-;; Version: 0.0.2
-;; URL: https://github.com/akermu/emacs-libvterm
-;; Keywords: terminals
-;; Package-Requires: ((emacs "25.1"))
-
-
-;; This file is not part of GNU Emacs.
-
-;; This file 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 2, or (at your option)
-;; any later version.
-
-;; This file 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
-
-
-;;; Commentary:
-;;
-;; Emacs-libvterm (vterm) is fully-fledged terminal emulator based on an
-;; external library (libvterm) loaded as a dynamic module. As a result of using
-;; compiled code (instead of elisp), emacs-libvterm is fully capable, fast, and
-;; it can seamlessly handle large outputs.
-
-;;; Installation
-
-;; Emacs-libvterm requires support for loading modules. You can check if your
-;; Emacs supports modules by inspecting the variable module-file-suffix. If it
-;; nil, than, you need to recompile Emacs or obtain a copy of Emacs with this
-;; option enabled.
-
-;; Emacs-libvterm requires CMake and libvterm. If libvterm is not available,
-;; emacs-libvterm will downloaded and compiled. In this case, libtool is
-;; needed.
-
-;; The reccomended way to install emacs-libvterm is from MELPA.
-
-;;; Usage
-
-;; To open a terminal, simply use the command M-x vterm.
-
-;;; Tips and tricks
-
-;; Adding some shell-side configuration enables a large set of additional
-;; features, including, directory tracking, prompt recognition, message passing.
-
-;;; Code:
-
-(require 'term/xterm)
-
-(unless module-file-suffix
- (error "VTerm needs module support. Please compile Emacs with
- the --with-modules option!"))
-
-(defvar vterm-copy-mode)
-
-;;; Compilation of the module
-
-(defcustom vterm-module-cmake-args ""
- "Arguments given to CMake to compile vterm-module.
-
-Currently, vterm defines the following flags (in addition to the
-ones already available in CMake):
-
-`USE_SYSTEM_LIBVTERM'. Set it to `Off' to use the vendored version of
-libvterm instead of the one installed on your system.
-
-This string is given verbatim to CMake, so it has to have the
-correct syntax. An example of meaningful value for this variable
-is `-DUSE_SYSTEM_LIBVTERM=Off'."
- :type 'string
- :group 'vterm)
-
-(defcustom vterm-always-compile-module nil
- "If not nil, if `vterm-module' is not found, compile it without asking.
-
-When `vterm-always-compile-module' is nil, vterm will ask for
-confirmation before compiling."
- :type 'boolean
- :group 'vterm)
-
-(defvar vterm-install-buffer-name " *Install vterm* "
- "Name of the buffer used for compiling vterm-module.")
-
-(defun vterm-module--cmake-is-available ()
- "Return t if cmake is available.
-CMake is needed to build vterm, here we check that we can find
-the executable."
-
- (unless (executable-find "cmake")
- (error "Vterm needs CMake to be compiled. Please, install CMake"))
- t)
-
-;;;###autoload
-(defun vterm-module-compile ()
- "Compile vterm-module."
- (interactive)
- (when (vterm-module--cmake-is-available)
- (let* ((vterm-directory
- (shell-quote-argument
- ;; NOTE: This is a workaround to fix an issue with how the Emacs
- ;; feature/native-comp branch changes the result of
- ;; `(locate-library "vterm")'. See emacs-devel thread
- ;; https://lists.gnu.org/archive/html/emacs-devel/2020-07/msg00306.html
- ;; for a discussion.
- (file-name-directory (locate-library "vterm.el" t))))
- (make-commands
- (concat
- "cd " vterm-directory "; \
- mkdir -p build; \
- cd build; \
- cmake -G 'Unix Makefiles' "
- vterm-module-cmake-args
- " ..; \
- make; \
- cd -"))
- (buffer (get-buffer-create vterm-install-buffer-name)))
- (pop-to-buffer buffer)
- (compilation-mode)
- (if (zerop (let ((inhibit-read-only t))
- (call-process "sh" nil buffer t "-c" make-commands)))
- (message "Compilation of `emacs-libvterm' module succeeded")
- (error "Compilation of `emacs-libvterm' module failed!")))))
-
-;; If the vterm-module is not compiled yet, compile it
-(unless (require 'vterm-module nil t)
- (if (or vterm-always-compile-module
- (y-or-n-p "Vterm needs `vterm-module' to work. Compile it now? "))
- (progn
- (vterm-module-compile)
- (require 'vterm-module))
- (error "Vterm will not work until `vterm-module' is compiled!")))
-
-;;; Dependencies
-
-;; Generate this list with:
-;; awk -F\" '/bind_function*/ {print "(declare-function", $2, "\"vterm-module\")"}' vterm-module.c
-(declare-function vterm--new "vterm-module")
-(declare-function vterm--update "vterm-module")
-(declare-function vterm--redraw "vterm-module")
-(declare-function vterm--write-input "vterm-module")
-(declare-function vterm--set-size "vterm-module")
-(declare-function vterm--set-pty-name "vterm-module")
-(declare-function vterm--get-pwd-raw "vterm-module")
-(declare-function vterm--reset-point "vterm-module")
-(declare-function vterm--get-icrnl "vterm-module")
-
-(require 'subr-x)
-(require 'find-func)
-(require 'cl-lib)
-(require 'term)
-(require 'color)
-(require 'compile)
-(require 'face-remap)
-(require 'tramp)
-(require 'bookmark)
-
-;;; Options
-
-(defcustom vterm-shell shell-file-name
- "The shell that gets run in the vterm."
- :type 'string
- :group 'vterm)
-
-(defcustom vterm-tramp-shells '(("docker" "/bin/sh"))
- "The shell that gets run in the vterm for tramp.
-
-`vterm-tramp-shells' has to be a list of pairs of the format:
-\(TRAMP-METHOD SHELL)"
- :type '(alist :key-type string :value-type string)
- :group 'vterm)
-
-(defcustom vterm-buffer-name "*vterm*"
- "The basename used for vterm buffers.
-This is the default name used when running `vterm' or
-`vterm-other-window'.
-
-With a numeric prefix argument to `vterm', the buffer name will
-be the value of this variable followed by the number. For
-example, with the numeric prefix argument 2, the buffer would be
-named \"*vterm*<2>\"."
- :type 'string
- :group 'vterm)
-
-(defcustom vterm-max-scrollback 1000
- "Maximum \\='scrollback\\=' value.
-
-The maximum allowed is 100000. This value can modified by
-changing the SB_MAX variable in vterm-module.h and recompiling
-the module."
- :type 'number
- :group 'vterm)
-
-(defcustom vterm-min-window-width 80
- "Minimum window width."
- :type 'number
- :group 'vterm)
-
-(defcustom vterm-kill-buffer-on-exit t
- "If not nil vterm buffers are killed when the attached process is terminated.
-
-If `vterm-kill-buffer-on-exit' is set to t, when the process
-associated to a vterm buffer quits, the buffer is killed. When
-nil, the buffer will still be available as if it were in
-`fundamental-mode'."
- :type 'boolean
- :group 'vterm)
-
-(define-obsolete-variable-alias 'vterm-clear-scrollback
- 'vterm-clear-scrollback-when-clearing "0.0.1")
-
-(define-obsolete-variable-alias 'vterm-use-vterm-prompt
- 'vterm-use-vterm-prompt-detection-method "0.0.1")
-
-(defcustom vterm-clear-scrollback-when-clearing nil
- "If not nil `vterm-clear' clears both screen and scrollback.
-
-The scrollback is everything that is not current visible on
-screen in vterm buffers.
-
-If `vterm-clear-scrollback-when-clearing' is nil, `vterm-clear'
-clears only the screen, so the scrollback is accessible moving
-the point up."
- :type 'boolean
- :group 'vterm)
-
-(defcustom vterm-keymap-exceptions
- '("C-c" "C-x" "C-u" "C-g" "C-h" "C-l" "M-x" "M-o" "C-y" "M-y")
- "Exceptions for `vterm-keymap'.
-
-If you use a keybinding with a prefix-key, add that prefix-key to
-this list. Note that after doing so that prefix-key cannot be sent
-to the terminal anymore.
-
-The mapping is done by the macro `vterm-define-key', and the
-function `vterm--exclude-keys' removes the keybindings defined in
-`vterm-keymap-exceptions'."
- :type '(repeat string)
- :set (lambda (sym val)
- (set sym val)
- (when (and (fboundp 'vterm--exclude-keys)
- (boundp 'vterm-mode-map))
- (vterm--exclude-keys vterm-mode-map val)))
- :group 'vterm)
-
-(defcustom vterm-exit-functions nil
- "List of functions called when a vterm process exits.
-
-Each function is called with two arguments: the vterm buffer of
-the process if any, and a string describing the event passed from
-the sentinel.
-
-This hook applies only to new vterms, created after setting this
-value with `add-hook'.
-
-Note that this hook will not work if another package like
-`shell-pop' sets its own sentinel to the `vterm' process."
- :type 'hook
- :group 'vterm)
-
-(make-obsolete-variable 'vterm-set-title-functions
- "This variable was substituted by `vterm-buffer-name-string'."
- "0.0.1")
-
-(defcustom vterm-buffer-name-string nil
- "Format string for the title of vterm buffers.
-
-If `vterm-buffer-name-string' is nil, vterm will not set the
-title of its buffers. If not nil, `vterm-buffer-name-string' has
-to be a format control string (see `format') containing one
-instance of %s which will be substituted with the string TITLE.
-The argument TITLE is provided by the shell. This requires shell
-side configuration.
-
-For example, if `vterm-buffer-name-string' is set to \"vterm %s\",
-and the shell properly configured to set TITLE=$(pwd), than vterm
-buffers will be named \"vterm\" followed by the current path.
-
-See URL http://tldp.org/HOWTO/Xterm-Title-4.html for additional
-information on the how to configure the shell."
- :type 'string
- :group 'vterm)
-
-(defcustom vterm-term-environment-variable "xterm-256color"
- "TERM value for terminal."
- :type 'string
- :group 'vterm)
-
-(defcustom vterm-environment nil
- "List of extra environment variables to the vterm shell processes only.
-
-demo: \\='(\"env1=v1\" \"env2=v2\")"
- :type '(repeat string)
- :group 'vterm)
-
-
-(defcustom vterm-enable-manipulate-selection-data-by-osc52 nil
- "Support OSC 52 MANIPULATE SELECTION DATA(libvterm 0.2 is needed).
-
-Support copy text to Emacs kill ring and system clipboard by using OSC 52.
-For example: send base64 encoded \\='foo\\=' to kill ring: echo -en \\='\\e]52;c;Zm9v\\a\\=',
-tmux can share its copy buffer to terminals by supporting osc52(like iterm2
- xterm) you can enable this feature for tmux by :
-set -g set-clipboard on #osc 52 copy paste share with iterm
-set -ga terminal-overrides \\=',xterm*:XT:Ms=\\E]52;%p1%s;%p2%s\\007\\='
-set -ga terminal-overrides \\=',screen*:XT:Ms=\\E]52;%p1%s;%p2%s\\007\\='
-
-The clipboard querying/clearing functionality offered by OSC 52 is not
-implemented here,And for security reason, this feature is disabled
-by default."
- :type 'boolean
- :group 'vterm)
-
-;; TODO: Improve doc string, it should not point to the readme but it should
-;; be self-contained.
-(defcustom vterm-eval-cmds '(("find-file" find-file)
- ("message" message)
- ("vterm-clear-scrollback" vterm-clear-scrollback))
- "Whitelisted Emacs functions that can be executed from vterm.
-
-You can execute Emacs functions directly from vterm buffers. To do this,
-you have to escape the name of the function and its arguments with \e]51;E.
-
-See Message passing in README.
-
-The function you want to execute has to be in `vterm-eval-cmds'.
-
-`vterm-eval-cmds' has to be a list of pairs of the format:
-\(NAME-OF-COMMAND-IN-SHELL EMACS-FUNCTION)
-
-The need for an explicit map is to avoid arbitrary code execution."
- :type '(alist :key-type string)
- :group 'vterm)
-
-(defcustom vterm-disable-underline nil
- "When not-nil, underline text properties are ignored.
-
-This means that vterm will render underlined text as if it was not
-underlined."
- :type 'boolean
- :group 'vterm)
-
-(defcustom vterm-disable-inverse-video nil
- "When not-nil, inverse video text properties are ignored.
-
-This means that vterm will render reversed video text as if it was not
-such."
- :type 'boolean
- :group 'vterm)
-
-(define-obsolete-variable-alias 'vterm-disable-bold-font
- 'vterm-disable-bold "0.0.1")
-
-(defcustom vterm-disable-bold-font nil
- "When not-nil, bold text properties are ignored.
-
-This means that vterm will render bold with the default face weight."
- :type 'boolean
- :group 'vterm)
-
-(defcustom vterm-set-bold-hightbright nil
- "When not-nil, using hightbright colors for bolded text, see #549."
- :type 'boolean
- :group 'vterm)
-
-(defcustom vterm-ignore-blink-cursor t
- "When t, vterm will ignore request from application to turn on/off cursor blink.
-
-If nil, cursor in any window may begin to blink or not blink because
-`blink-cursor-mode`is a global minor mode in Emacs,
-you can use `M-x blink-cursor-mode` to toggle."
- :type 'boolean
- :group 'vterm)
-
-(defcustom vterm-copy-exclude-prompt t
- "When not-nil, the prompt is not included by `vterm-copy-mode-done'."
- :type 'boolean
- :group 'vterm)
-
-(defcustom vterm-use-vterm-prompt-detection-method t
- "When not-nil, the prompt is detected through the shell.
-
-Vterm needs to know where the shell prompt is to enable all the
-available features. There are two supported ways to do this.
-First, the shell can inform vterm on the location of the prompt.
-This requires shell-side configuration: the escape code 51;A is
-used to set the current directory and prompt location. This
-detection method is the most-reliable. To use it, you have
-to change your shell prompt to print 51;A.
-
-The second method is using a regular expression. This method does
-not require any shell-side configuration. See
-`term-prompt-regexp', for more information."
- :type 'boolean
- :group 'vterm)
-
-(defcustom vterm-bookmark-check-dir t
- "When set to non-nil, also restore directory when restoring a vterm bookmark."
- :type 'boolean
- :group 'vterm)
-
-(defcustom vterm-copy-mode-remove-fake-newlines nil
- "When not-nil fake newlines are removed on entering copy mode.
-
-vterm inserts \\='fake\\=' newlines purely for rendering. When using
-vterm-copy-mode these are in conflict with many emacs functions
-like isearch-forward. if this varialbe is not-nil the
-fake-newlines are removed on entering copy-mode and re-inserted
-on leaving copy mode. Also truncate-lines is set to t on entering
-copy-mode and set to nil on leaving."
- :type 'boolean
- :group 'vterm)
-
-;;; Faces
-
-(defface vterm-color-black
- `((t :inherit term-color-black))
- "Face used to render black color code."
- :group 'vterm)
-
-(defface vterm-color-red
- `((t :inherit term-color-red))
- "Face used to render red color code."
- :group 'vterm)
-
-(defface vterm-color-green
- `((t :inherit term-color-green))
- "Face used to render green color code."
- :group 'vterm)
-
-(defface vterm-color-yellow
- `((t :inherit term-color-yellow))
- "Face used to render yellow color code."
- :group 'vterm)
-
-(defface vterm-color-blue
- `((t :inherit term-color-blue))
- "Face used to render blue color code."
- :group 'vterm)
-
-(defface vterm-color-magenta
- `((t :inherit term-color-magenta))
- "Face used to render magenta color code."
- :group 'vterm)
-
-(defface vterm-color-cyan
- `((t :inherit term-color-cyan))
- "Face used to render cyan color code."
- :group 'vterm)
-
-(defface vterm-color-white
- `((t :inherit term-color-white))
- "Face used to render white color code."
- :group 'vterm)
-
-(defface vterm-color-bright-black
- `((t :inherit ,(if (facep 'term-color-bright-black)
- 'term-color-bright-black
- 'term-color-black)))
- "Face used to render bright black color code."
- :group 'vterm)
-
-(defface vterm-color-bright-red
- `((t :inherit ,(if (facep 'term-color-bright-red)
- 'term-color-bright-red
- 'term-color-red)))
- "Face used to render bright red color code."
- :group 'vterm)
-
-(defface vterm-color-bright-green
- `((t :inherit ,(if (facep 'term-color-bright-green)
- 'term-color-bright-green
- 'term-color-green)))
- "Face used to render bright green color code."
- :group 'vterm)
-
-(defface vterm-color-bright-yellow
- `((t :inherit ,(if (facep 'term-color-bright-yellow)
- 'term-color-bright-yellow
- 'term-color-yellow)))
- "Face used to render bright yellow color code."
- :group 'vterm)
-
-(defface vterm-color-bright-blue
- `((t :inherit ,(if (facep 'term-color-bright-blue)
- 'term-color-bright-blue
- 'term-color-blue)))
- "Face used to render bright blue color code."
- :group 'vterm)
-
-(defface vterm-color-bright-magenta
- `((t :inherit ,(if (facep 'term-color-bright-magenta)
- 'term-color-bright-magenta
- 'term-color-magenta)))
- "Face used to render bright magenta color code."
- :group 'vterm)
-
-(defface vterm-color-bright-cyan
- `((t :inherit ,(if (facep 'term-color-bright-cyan)
- 'term-color-bright-cyan
- 'term-color-cyan)))
- "Face used to render bright cyan color code."
- :group 'vterm)
-
-(defface vterm-color-bright-white
- `((t :inherit ,(if (facep 'term-color-bright-white)
- 'term-color-bright-white
- 'term-color-white)))
- "Face used to render bright white color code."
- :group 'vterm)
-
-(defface vterm-color-underline
- `((t :inherit default))
- "Face used to render cells with underline attribute.
-Only foreground is used."
- :group 'vterm)
-
-(defface vterm-color-inverse-video
- `((t :inherit default))
- "Face used to render cells with inverse video attribute.
-Only background is used."
- :group 'vterm)
-
-;;; Variables
-
-(defvar vterm-color-palette
- [vterm-color-black
- vterm-color-red
- vterm-color-green
- vterm-color-yellow
- vterm-color-blue
- vterm-color-magenta
- vterm-color-cyan
- vterm-color-white
- vterm-color-bright-black
- vterm-color-bright-red
- vterm-color-bright-green
- vterm-color-bright-yellow
- vterm-color-bright-blue
- vterm-color-bright-magenta
- vterm-color-bright-cyan
- vterm-color-bright-white]
- "Color palette for the foreground and background.")
-
-(defvar-local vterm--term nil
- "Pointer to Term.")
-
-(defvar-local vterm--process nil
- "Shell process of current term.")
-
-(defvar-local vterm--redraw-timer nil)
-(defvar-local vterm--redraw-immididately nil)
-(defvar-local vterm--linenum-remapping nil)
-(defvar-local vterm--prompt-tracking-enabled-p nil)
-(defvar-local vterm--insert-function (symbol-function #'insert))
-(defvar-local vterm--delete-char-function (symbol-function #'delete-char))
-(defvar-local vterm--delete-region-function (symbol-function #'delete-region))
-(defvar-local vterm--undecoded-bytes nil)
-(defvar-local vterm--copy-mode-fake-newlines nil)
-
-
-(defvar vterm-timer-delay 0.1
- "Delay for refreshing the buffer after receiving updates from libvterm.
-
-A larger delary improves performance when receiving large bursts
-of data. If nil, never delay. The units are seconds.")
-
-;;; Keybindings
-
-;; We have many functions defined by vterm-define-key. Later, we will bind some
-;; of the functions. If the following is not evaluated during compilation, the compiler
-;; will complain that some functions are not defined (eg, vterm-send-C-c)
-(eval-and-compile
- (defmacro vterm-define-key (key)
- "Define a command that sends KEY with modifiers C and M to vterm."
- (declare (indent defun)
- (doc-string 3))
- `(progn (defun ,(intern (format "vterm-send-%s" key))()
- ,(format "Sends %s to the libvterm." key)
- (interactive)
- (vterm-send-key ,(char-to-string (get-byte (1- (length key)) key))
- ,(let ((case-fold-search nil))
- (or (string-match-p "[A-Z]$" key)
- (string-match-p "S-" key)))
- ,(string-match-p "M-" key)
- ,(string-match-p "C-" key)))
- (make-obsolete ',(intern (format "vterm-send-%s" key))
- "use `vterm--self-insert' or `vterm-send' or `vterm-send-key'."
- "v0.1")))
- (make-obsolete 'vterm-define-key "" "v0.1")
- (mapc (lambda (key)
- (eval `(vterm-define-key ,key)))
- (cl-loop for prefix in '("M-")
- append (cl-loop for char from ?A to ?Z
- for key = (format "%s%c" prefix char)
- collect key)))
- (mapc (lambda (key)
- (eval `(vterm-define-key ,key)))
- (cl-loop for prefix in '("C-" "M-" "C-S-")
- append (cl-loop for char from ?a to ?z
- for key = (format "%s%c" prefix char)
- collect key))))
-
-;; Function keys and most of C- and M- bindings
-(defun vterm--exclude-keys (map exceptions)
- "Remove EXCEPTIONS from the keys bound by `vterm-define-keys'.
-
-Exceptions are defined by `vterm-keymap-exceptions'."
- (mapc (lambda (key)
- (define-key map (kbd key) nil))
- exceptions)
- (mapc (lambda (key)
- (define-key map (kbd key) #'vterm--self-insert))
- (cl-loop for number from 1 to 12
- for key = (format "<f%i>" number)
- unless (member key exceptions)
- collect key))
- (let ((esc-map (lookup-key map "\e"))
- (i 0)
- key)
- (unless esc-map (setq esc-map (make-keymap)))
- (while (< i 128)
- (setq key (make-string 1 i))
- (unless (member (key-description key) exceptions)
- (define-key map key 'vterm--self-insert))
- ;; Avoid O and [. They are used in escape sequences for various keys.
- (unless (or (eq i ?O) (eq i 91))
- (unless (member (key-description key "\e") exceptions)
- (define-key esc-map key 'vterm--self-insert-meta)))
- (setq i (1+ i)))
- (define-key map "\e" esc-map)))
-
-(defun vterm-xterm-paste (event)
- "Handle xterm paste EVENT in vterm."
- (interactive "e")
- (with-temp-buffer
- (xterm-paste event)
- (kill-new (buffer-string)))
- (vterm-yank))
-
-(defvar vterm-mode-map
- (let ((map (make-sparse-keymap)))
- (vterm--exclude-keys map vterm-keymap-exceptions)
- (define-key map (kbd "C-]") #'vterm--self-insert)
- (define-key map (kbd "M-<") #'vterm--self-insert)
- (define-key map (kbd "M->") #'vterm--self-insert)
- (define-key map [tab] #'vterm-send-tab)
- (define-key map (kbd "TAB") #'vterm-send-tab)
- (define-key map [backtab] #'vterm--self-insert)
- (define-key map [backspace] #'vterm-send-backspace)
- (define-key map (kbd "DEL") #'vterm-send-backspace)
- (define-key map [delete] #'vterm-send-delete)
- (define-key map [M-backspace] #'vterm-send-meta-backspace)
- (define-key map (kbd "M-DEL") #'vterm-send-meta-backspace)
- (define-key map [C-backspace] #'vterm-send-meta-backspace)
- (define-key map [return] #'vterm-send-return)
- (define-key map (kbd "RET") #'vterm-send-return)
- (define-key map [C-left] #'vterm--self-insert)
- (define-key map [M-left] #'vterm--self-insert)
- (define-key map [C-right] #'vterm--self-insert)
- (define-key map [M-right] #'vterm--self-insert)
- (define-key map [C-up] #'vterm--self-insert)
- (define-key map [C-down] #'vterm--self-insert)
- (define-key map [M-up] #'vterm--self-insert)
- (define-key map [M-down] #'vterm--self-insert)
- (define-key map [left] #'vterm--self-insert)
- (define-key map [right] #'vterm--self-insert)
- (define-key map [up] #'vterm--self-insert)
- (define-key map [down] #'vterm--self-insert)
- (define-key map [prior] #'vterm--self-insert)
- (define-key map [S-prior] #'scroll-down-command)
- (define-key map [next] #'vterm--self-insert)
- (define-key map [S-next] #'scroll-up-command)
- (define-key map [home] #'vterm--self-insert)
- (define-key map [end] #'vterm--self-insert)
- (define-key map [C-home] #'vterm--self-insert)
- (define-key map [C-end] #'vterm--self-insert)
- (define-key map [escape] #'vterm--self-insert)
- (define-key map [remap yank] #'vterm-yank)
- (define-key map [remap xterm-paste] #'vterm-xterm-paste)
- (define-key map [remap yank-pop] #'vterm-yank-pop)
- (define-key map [remap mouse-yank-primary] #'vterm-yank-primary)
- (define-key map [mouse-1] #'vterm-mouse-set-point)
- (define-key map (kbd "C-SPC") #'vterm--self-insert)
- (define-key map (kbd "S-SPC") #'vterm-send-space)
- (define-key map (kbd "C-_") #'vterm--self-insert)
- (define-key map [remap undo] #'vterm-undo)
- (define-key map (kbd "M-.") #'vterm--self-insert)
- (define-key map (kbd "M-,") #'vterm--self-insert)
- (define-key map (kbd "C-c C-y") #'vterm--self-insert)
- (define-key map (kbd "C-c C-c") #'vterm--self-insert)
- (define-key map (kbd "C-c C-l") #'vterm-clear-scrollback)
- (define-key map (kbd "C-l") #'vterm-clear)
- (define-key map (kbd "C-\\") #'vterm--self-insert)
- (define-key map (kbd "C-c C-g") #'vterm--self-insert)
- (define-key map (kbd "C-c C-u") #'vterm--self-insert)
- (define-key map [remap self-insert-command] #'vterm--self-insert)
- (define-key map (kbd "C-c C-r") #'vterm-reset-cursor-point)
- (define-key map (kbd "C-c C-n") #'vterm-next-prompt)
- (define-key map (kbd "C-c C-p") #'vterm-previous-prompt)
- (define-key map (kbd "C-c C-t") #'vterm-copy-mode)
- map))
-
-(defvar vterm-copy-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c C-t") #'vterm-copy-mode)
- (define-key map [return] #'vterm-copy-mode-done)
- (define-key map (kbd "RET") #'vterm-copy-mode-done)
- (define-key map (kbd "C-c C-r") #'vterm-reset-cursor-point)
- (define-key map (kbd "C-a") #'vterm-beginning-of-line)
- (define-key map (kbd "C-e") #'vterm-end-of-line)
- (define-key map (kbd "C-c C-n") #'vterm-next-prompt)
- (define-key map (kbd "C-c C-p") #'vterm-previous-prompt)
- map))
-
-
-;;; Mode
-
-(define-derived-mode vterm-mode fundamental-mode "VTerm"
- "Major mode for vterm buffer."
- (buffer-disable-undo)
- (and (boundp 'display-line-numbers)
- (let ((font-height (expt text-scale-mode-step text-scale-mode-amount)))
- (setq vterm--linenum-remapping
- (face-remap-add-relative 'line-number :height font-height))))
- (hack-dir-local-variables)
- (let ((vterm-env (assq 'vterm-environment dir-local-variables-alist)))
- (when vterm-env
- (make-local-variable 'vterm-environment)
- (setq vterm-environment (cdr vterm-env))))
- (let ((process-environment (append vterm-environment
- `(,(concat "TERM="
- vterm-term-environment-variable)
- ,(concat "EMACS_VTERM_PATH="
- (file-name-directory (find-library-name "vterm")))
- "INSIDE_EMACS=vterm"
- "LINES"
- "COLUMNS")
- process-environment))
- ;; TODO: Figure out why inhibit is needed for curses to render correctly.
- (inhibit-eol-conversion nil)
- (coding-system-for-read 'binary)
- (process-adaptive-read-buffering nil)
- (width (max (- (window-body-width) (vterm--get-margin-width))
- vterm-min-window-width)))
- (setq vterm--term (vterm--new (window-body-height)
- width vterm-max-scrollback
- vterm-disable-bold-font
- vterm-disable-underline
- vterm-disable-inverse-video
- vterm-ignore-blink-cursor
- vterm-set-bold-hightbright))
- (setq buffer-read-only t)
- (setq-local scroll-conservatively 101)
- (setq-local scroll-margin 0)
- (setq-local hscroll-margin 0)
- (setq-local hscroll-step 1)
- (setq-local truncate-lines t)
-
-
- ;; Disable all automatic fontification
- (setq-local font-lock-defaults '(nil t))
-
- (add-function :filter-return
- (local 'filter-buffer-substring-function)
- #'vterm--filter-buffer-substring)
- (setq vterm--process
- (make-process
- :name "vterm"
- :buffer (current-buffer)
- :command
- `("/bin/sh" "-c"
- ,(format
- "stty -nl sane %s erase ^? rows %d columns %d >/dev/null && exec %s"
- ;; Some stty implementations (i.e. that of *BSD) do not
- ;; support the iutf8 option. to handle that, we run some
- ;; heuristics to work out if the system supports that
- ;; option and set the arg string accordingly. This is a
- ;; gross hack but FreeBSD doesn't seem to want to fix it.
- ;;
- ;; See: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=220009
- (if (eq system-type 'berkeley-unix) "" "iutf8")
- (window-body-height)
- width (vterm--get-shell)))
- ;; :coding 'no-conversion
- :connection-type 'pty
- :file-handler t
- :filter #'vterm--filter
- ;; The sentinel is needed if there are exit functions or if
- ;; vterm-kill-buffer-on-exit is set to t. In this latter case,
- ;; vterm--sentinel will kill the buffer
- :sentinel (when (or vterm-exit-functions
- vterm-kill-buffer-on-exit)
- #'vterm--sentinel))))
-
- ;; Change major-mode is not allowed
- ;; Vterm interfaces with an underlying process. Changing the major
- ;; mode can break this, leading to segmentation faults.
- (add-hook 'change-major-mode-hook
- (lambda () (interactive)
- (user-error "You cannot change major mode in vterm buffers")) nil t)
-
- (vterm--set-pty-name vterm--term (process-tty-name vterm--process))
- (process-put vterm--process 'adjust-window-size-function
- #'vterm--window-adjust-process-window-size)
- ;; Support to compilation-shell-minor-mode
- ;; Is this necessary? See vterm--compilation-setup
- (setq next-error-function 'vterm-next-error-function)
- (setq-local bookmark-make-record-function 'vterm--bookmark-make-record))
-
-(defun vterm--get-shell ()
- "Get the shell that gets run in the vterm."
- (if (ignore-errors (file-remote-p default-directory))
- (with-parsed-tramp-file-name default-directory nil
- (or (cadr (assoc method vterm-tramp-shells))
- (with-connection-local-variables shell-file-name)
- vterm-shell))
- vterm-shell))
-
-(defun vterm--bookmark-make-record ()
- "Create a vterm bookmark.
-
-Notes down the current directory and buffer name."
- `(nil
- (handler . vterm--bookmark-handler)
- (thisdir . ,default-directory)
- (buf-name . ,(buffer-name))
- (defaults . nil)))
-
-
-;;;###autoload
-(defun vterm--bookmark-handler (bmk)
- "Handler to restore a vterm bookmark BMK.
-
-If a vterm buffer of the same name does not exist, the function will create a
-new vterm buffer of the name. It also checks the current directory and sets
-it to the bookmarked directory if needed."
- (let* ((thisdir (bookmark-prop-get bmk 'thisdir))
- (buf-name (bookmark-prop-get bmk 'buf-name))
- (buf (get-buffer buf-name))
- (thismode (and buf (with-current-buffer buf major-mode))))
- ;; create if no such vterm buffer exists
- (when (or (not buf) (not (eq thismode 'vterm-mode)))
- (setq buf (generate-new-buffer buf-name))
- (with-current-buffer buf
- (when vterm-bookmark-check-dir
- (setq default-directory thisdir))
- (vterm-mode)))
- ;; check the current directory
- (with-current-buffer buf
- (when (and vterm-bookmark-check-dir
- (not (string-equal default-directory thisdir)))
- (when vterm-copy-mode
- (vterm-copy-mode-done nil))
- (vterm-insert (concat "cd " thisdir))
- (vterm-send-return)))
- ;; set to this vterm buf
- (set-buffer buf)))
-
-(defun vterm--compilation-setup ()
- "Function to enable the option `compilation-shell-minor-mode' for vterm.
-`'compilation-shell-minor-mode' would change the value of local
-variable `next-error-function', so we should call this function in
-`compilation-shell-minor-mode-hook'."
- (when (eq major-mode 'vterm-mode)
- (setq next-error-function 'vterm-next-error-function)))
-
-(add-hook 'compilation-shell-minor-mode-hook #'vterm--compilation-setup)
-
-;;;###autoload
-(defun vterm-next-error-function (n &optional reset)
- "Advance to the next error message and visit the file where the error was.
-This is the value of `next-error-function' in Compilation
-buffers. Prefix arg N says how many error messages to move
-forwards (or backwards, if negative).
-
-Optional argument RESET clears all the errors."
- (interactive "p")
- (let* ((pt (point))
- (default-directory default-directory)
- (pwd (vterm--get-pwd)))
- (when pwd
- (setq default-directory pwd))
- (goto-char pt)
- (compilation-next-error-function n reset)))
-
-;;; Copy Mode
-
-(defun vterm--enter-copy-mode ()
- (use-local-map nil)
- (vterm-send-stop)
- (when vterm-copy-mode-remove-fake-newlines
- (save-excursion
- (setq truncate-lines nil)
- (vterm--remove-fake-newlines t))))
-
-
-(defun vterm--exit-copy-mode ()
- (when vterm-copy-mode-remove-fake-newlines
- (save-excursion
- (setq truncate-lines t)
- (vterm--reinsert-fake-newlines)))
- (vterm-reset-cursor-point)
- (use-local-map vterm-mode-map)
- (vterm-send-start))
-
-(define-minor-mode vterm-copy-mode
- "Toggle `vterm-copy-mode'.
-
-When `vterm-copy-mode' is enabled, the terminal will not display
-additional output received from the underlying process and will
-behave similarly to buffer in `fundamental-mode'. This mode is
-typically used to copy text from vterm buffers.
-
-A conventient way to exit `vterm-copy-mode' is with
-`vterm-copy-mode-done', which copies the selected text and exit
-`vterm-copy-mode'."
- :group 'vterm
- :lighter " VTermCopy"
- :keymap vterm-copy-mode-map
- (if (equal major-mode 'vterm-mode)
- (if vterm-copy-mode
- (vterm--enter-copy-mode)
- (vterm--exit-copy-mode))
- (user-error "You cannot enable vterm-copy-mode outside vterm buffers")))
-
-(defun vterm-copy-mode-done (arg)
- "Save the active region or line to the kill ring and exit `vterm-copy-mode'.
-
-If a region is defined then that region is killed, with no region then
-current line is killed from start to end.
-
-The option `vterm-copy-exclude-prompt' controls if the prompt
-should be included in a line copy. Using the universal prefix ARG
-will invert `vterm-copy-exclude-prompt' for that call."
- (interactive "P")
- (unless vterm-copy-mode
- (user-error "This command is effective only in vterm-copy-mode"))
- (unless (use-region-p)
- (goto-char (vterm--get-beginning-of-line))
- ;; Are we excluding the prompt?
- (if (or (and vterm-copy-exclude-prompt (not arg))
- (and (not vterm-copy-exclude-prompt) arg))
- (goto-char (max (or (vterm--get-prompt-point) 0)
- (vterm--get-beginning-of-line))))
- (set-mark (point))
- (goto-char (vterm--get-end-of-line)))
- (kill-ring-save (region-beginning) (region-end))
- (vterm-copy-mode -1))
-
-;;; Commands
-
-(defun vterm--self-insert-meta ()
- (interactive)
- (when vterm--term
- (dolist (key (vterm--translate-event-to-args
- last-command-event :meta))
- (apply #'vterm-send-key key))))
-
-(defun vterm--self-insert ()
- "Send invoking key to libvterm."
- (interactive)
- (when vterm--term
- (dolist (key (vterm--translate-event-to-args
- last-command-event))
- (apply #'vterm-send-key key))))
-
-(defun vterm-send-key (key &optional shift meta ctrl accept-proc-output)
- "Send KEY to libvterm with optional modifiers SHIFT, META and CTRL."
- (deactivate-mark)
- (when vterm--term
- (let ((inhibit-redisplay t)
- (inhibit-read-only t))
- (vterm--update vterm--term key shift meta ctrl)
- (setq vterm--redraw-immididately t)
- (when accept-proc-output
- (accept-process-output vterm--process vterm-timer-delay nil t)))))
-
-(defun vterm-send (key)
- "Send KEY to libvterm. KEY can be anything `kbd' understands."
- (dolist (key (vterm--translate-event-to-args
- (listify-key-sequence (kbd key))))
- (apply #'vterm-send-key key)))
-
-(defun vterm-send-next-key ()
- "Read next input event and send it to the libvterm.
-
-With this you can directly send modified keys to applications
-running in the terminal (like Emacs or Nano)."
- (interactive)
- (dolist (key (vterm--translate-event-to-args
- (read-event)))
- (apply #'vterm-send-key key)))
-
-(defun vterm-send-start ()
- "Output from the system is started when the system receives START."
- (interactive)
- (vterm-send-key "<start>"))
-
-(defun vterm-send-stop ()
- "Output from the system is stopped when the system receives STOP."
- (interactive)
- (vterm-send-key "<stop>"))
-
-(defun vterm-send-return ()
- "Send `C-m' to the libvterm."
- (interactive)
- (deactivate-mark)
- (when vterm--term
- (if (vterm--get-icrnl vterm--term)
- (process-send-string vterm--process "\C-j")
- (process-send-string vterm--process "\C-m"))))
-
-(defun vterm-send-tab ()
- "Send `<tab>' to the libvterm."
- (interactive)
- (vterm-send-key "<tab>"))
-
-(defun vterm-send-space ()
- "Send `<space>' to the libvterm."
- (interactive)
- (vterm-send-key " "))
-
-(defun vterm-send-backspace ()
- "Send `<backspace>' to the libvterm."
- (interactive)
- (vterm-send-key "<backspace>"))
-
-(defun vterm-send-delete ()
- "Send `<delete>' to the libvterm."
- (interactive)
- (vterm-send-key "<delete>"))
-
-(defun vterm-send-meta-backspace ()
- "Send `M-<backspace>' to the libvterm."
- (interactive)
- (vterm-send-key "<backspace>" nil t))
-
-(defun vterm-send-up ()
- "Send `<up>' to the libvterm."
- (interactive)
- (vterm-send-key "<up>"))
-(make-obsolete 'vterm-send-up 'vterm--self-insert "v0.1")
-
-(defun vterm-send-down ()
- "Send `<down>' to the libvterm."
- (interactive)
- (vterm-send-key "<down>"))
-(make-obsolete 'vterm-send-down 'vterm--self-insert "v0.1")
-
-(defun vterm-send-left ()
- "Send `<left>' to the libvterm."
- (interactive)
- (vterm-send-key "<left>"))
-(make-obsolete 'vterm-send-left 'vterm--self-insert "v0.1")
-
-(defun vterm-send-right ()
- "Send `<right>' to the libvterm."
- (interactive)
- (vterm-send-key "<right>"))
-(make-obsolete 'vterm-send-right 'vterm--self-insert "v0.1")
-
-(defun vterm-send-prior ()
- "Send `<prior>' to the libvterm."
- (interactive)
- (vterm-send-key "<prior>"))
-(make-obsolete 'vterm-send-prior 'vterm--self-insert "v0.1")
-
-(defun vterm-send-next ()
- "Send `<next>' to the libvterm."
- (interactive)
- (vterm-send-key "<next>"))
-(make-obsolete 'vterm-send-next 'vterm--self-insert "v0.1")
-
-(defun vterm-send-meta-dot ()
- "Send `M-.' to the libvterm."
- (interactive)
- (vterm-send-key "." nil t))
-(make-obsolete 'vterm-send-meta-dot 'vterm--self-insert "v0.1")
-
-(defun vterm-send-meta-comma ()
- "Send `M-,' to the libvterm."
- (interactive)
- (vterm-send-key "," nil t))
-(make-obsolete 'vterm-send-meta-comma 'vterm--self-insert "v0.1")
-
-(defun vterm-send-ctrl-slash ()
- "Send `C-\' to the libvterm."
- (interactive)
- (vterm-send-key "\\" nil nil t))
-(make-obsolete 'vterm-send-ctrl-slash 'vterm--self-insert "v0.1")
-
-(defun vterm-send-escape ()
- "Send `<escape>' to the libvterm."
- (interactive)
- (vterm-send-key "<escape>"))
-
-(defun vterm-clear-scrollback ()
- "Send `<clear-scrollback>' to the libvterm."
- (interactive)
- (vterm-send-key "<clear_scrollback>"))
-
-(defun vterm-clear (&optional arg)
- "Send `<clear>' to the libvterm.
-
-`vterm-clear-scrollback' determines whether
-`vterm-clear' should also clear the scrollback or not.
-
-This behavior can be altered by calling `vterm-clear' with a
-prefix argument ARG or with \\[universal-argument]."
- (interactive "P")
- (if (or
- (and vterm-clear-scrollback-when-clearing (not arg))
- (and arg (not vterm-clear-scrollback-when-clearing)))
- (vterm-clear-scrollback))
- (vterm-send-key "l" nil nil :ctrl))
-
-(defun vterm-undo ()
- "Send `C-_' to the libvterm."
- (interactive)
- (vterm-send-key "_" nil nil t))
-
-(defun vterm-yank (&optional arg)
- "Yank (paste) text in vterm.
-
-Argument ARG is passed to `yank'."
- (interactive "P")
- (deactivate-mark)
- (vterm-goto-char (point))
- (let ((inhibit-read-only t))
- (cl-letf (((symbol-function 'insert-for-yank) #'vterm-insert))
- (yank arg))))
-
-(defun vterm-yank-primary ()
- "Yank text from the primary selection in vterm."
- (interactive)
- (vterm-goto-char (point))
- (let ((inhibit-read-only t)
- (primary (gui-get-primary-selection)))
- (cl-letf (((symbol-function 'insert-for-yank) #'vterm-insert))
- (insert-for-yank primary))))
-
-(defun vterm-yank-pop (&optional arg)
- "Replaced text just yanked with the next entry in the kill ring.
-
-Argument ARG is passed to `yank'"
- (interactive "p")
- (vterm-goto-char (point))
- (let ((inhibit-read-only t)
- (yank-undo-function #'(lambda (_start _end) (vterm-undo))))
- (cl-letf (((symbol-function 'insert-for-yank) #'vterm-insert))
- (yank-pop arg))))
-
-(defun vterm-mouse-set-point (event &optional promote-to-region)
- "Move point to the position clicked on with the mouse.
-But when clicking to the unused area below the last prompt,
-move the cursor to the prompt area."
- (interactive "e\np")
- (let ((pt (mouse-set-point event promote-to-region)))
- (if (= (count-words pt (point-max)) 0)
- (vterm-reset-cursor-point)
- pt))
- ;; Otherwise it selects text for every other click
- (keyboard-quit))
-
-(defun vterm-send-string (string &optional paste-p)
- "Send the string STRING to vterm.
-Optional argument PASTE-P paste-p."
- (when vterm--term
- (when paste-p
- (vterm--update vterm--term "<start_paste>" ))
- (dolist (char (string-to-list string))
- (vterm--update vterm--term (char-to-string char)))
- (when paste-p
- (vterm--update vterm--term "<end_paste>")))
- (setq vterm--redraw-immididately t)
- (accept-process-output vterm--process vterm-timer-delay nil t))
-
-(defun vterm-insert (&rest contents)
- "Insert the arguments, either strings or characters, at point.
-
-Provide similar behavior as `insert' for vterm."
- (when vterm--term
- (vterm--update vterm--term "<start_paste>")
- (dolist (c contents)
- (if (characterp c)
- (vterm--update vterm--term (char-to-string c))
- (dolist (char (string-to-list c))
- (vterm--update vterm--term (char-to-string char)))))
- (vterm--update vterm--term "<end_paste>")
- (setq vterm--redraw-immididately t)
- (accept-process-output vterm--process vterm-timer-delay nil t)))
-
-(defun vterm-delete-region (start end)
- "Delete the text between START and END for vterm. "
- (when vterm--term
- (save-excursion
- (when (get-text-property start 'vterm-line-wrap)
- ;; skip over the fake newline when start there.
- (setq start (1+ start))))
- ;; count of chars after fake newline removed
- (let ((count (length (filter-buffer-substring start end))))
- (if (vterm-goto-char start)
- (cl-loop repeat count do
- (vterm-send-key "<delete>" nil nil nil t))
- (let ((inhibit-read-only nil))
- (vterm--delete-region start end))))))
-
-(defun vterm-goto-char (pos)
- "Set point to POSITION for vterm.
-
-The return value is `t' when point moved successfully."
- (when (and vterm--term
- (vterm-cursor-in-command-buffer-p)
- (vterm-cursor-in-command-buffer-p pos))
- (vterm-reset-cursor-point)
- (let ((diff (- pos (point))))
- (cond
- ((zerop diff) t) ;do not need move
- ((< diff 0) ;backward
- (while (and
- (vterm--backward-char)
- (> (point) pos)))
- (<= (point) pos))
- (t
- (while (and (vterm--forward-char)
- (< (point) pos)))
- (>= (point) pos))))))
-
-;;; Internal
-
-(defun vterm--forward-char ()
- "Move point 1 character forward ().
-
-the return value is `t' when cursor moved."
- (vterm-reset-cursor-point)
- (let ((pt (point)))
- (vterm-send-key "<right>" nil nil nil t)
- (cond
- ((= (point) (1+ pt)) t)
- ((and (> (point) pt)
- ;; move over the fake newline
- (get-text-property (1- (point)) 'vterm-line-wrap))
- t)
- ((and (= (point) (+ 4 pt))
- (looking-back (regexp-quote "^[[C") nil)) ;escape code for <right>
- (dotimes (_ 3) (vterm-send-key "<backspace>" nil nil nil t)) ;;delete "^[[C"
- nil)
- ((> (point) (1+ pt)) ;auto suggest
- (vterm-send-key "_" nil nil t t) ;undo C-_
- nil)
- (t nil))))
-
-
-
-(defun vterm--backward-char ()
- "Move point N characters backward.
-
-Return count of moved characeters."
- (vterm-reset-cursor-point)
- (let ((pt (point)))
- (vterm-send-key "<left>" nil nil nil t)
- (cond
- ((= (point) (1- pt)) t)
- ((and (= (point) (- pt 2))
- ;; backward cross fake newline
- (string-equal (buffer-substring-no-properties
- (1+ (point)) (+ 2 (point)))
- "\n"))
- t)
- ((and (= (point) (+ 4 pt))
- (looking-back (regexp-quote "^[[D") nil)) ;escape code for <left>
- (dotimes (_ 3) (vterm-send-key "<backspace>" nil nil nil t)) ;;delete "^[[D"
- nil)
- (t nil))))
-
-(defun vterm--delete-region(start end)
- "A wrapper for `delete-region'."
- (funcall vterm--delete-region-function start end))
-
-(defun vterm--insert(&rest content)
- "A wrapper for `insert'."
- (apply vterm--insert-function content))
-
-(defun vterm--delete-char(n &optional killflag)
- "A wrapper for `delete-char'."
- (funcall vterm--delete-char-function n killflag))
-
-(defun vterm--translate-event-to-args (event &optional meta)
- "Translate EVENT as list of args for `vterm-send-key'.
-
-When some input method is enabled, one key may generate
-several characters, so the result of this function is a list,
-looks like: ((\"m\" :shift ))"
- (let* ((modifiers (event-modifiers event))
- (shift (memq 'shift modifiers))
- (meta (or meta (memq 'meta modifiers)))
- (ctrl (memq 'control modifiers))
- (raw-key (event-basic-type event))
- (ev-keys) keys)
- (if input-method-function
- (let ((inhibit-read-only t))
- (setq ev-keys (funcall input-method-function raw-key))
- (when (listp ev-keys)
- (dolist (k ev-keys)
- (when-let ((key (key-description (vector k))))
- (when (and (not (symbolp event)) shift (not meta) (not ctrl))
- (setq key (upcase key)))
- (setq keys (append keys (list (list key shift meta ctrl))))))))
- (when-let ((key (key-description (vector raw-key))))
- (when (and (not (symbolp event)) shift (not meta) (not ctrl))
- (setq key (upcase key)))
- (setq keys (list (list key shift meta ctrl)))))
- keys))
-
-(defun vterm--invalidate ()
- "The terminal buffer is invalidated, the buffer needs redrawing."
- (if (and (not vterm--redraw-immididately)
- vterm-timer-delay)
- (unless vterm--redraw-timer
- (setq vterm--redraw-timer
- (run-with-timer vterm-timer-delay nil
- #'vterm--delayed-redraw (current-buffer))))
- (vterm--delayed-redraw (current-buffer))
- (setq vterm--redraw-immididately nil)))
-
-(defun vterm-check-proc (&optional buffer)
- "Check if there is a running process associated to the vterm buffer BUFFER.
-
-BUFFER can be either a buffer or the name of one."
- (let* ((buffer (get-buffer (or buffer (current-buffer))))
- (proc (get-buffer-process buffer)))
- (and proc
- (memq (process-status proc) '(run stop open listen connect))
- (buffer-local-value 'vterm--term buffer))))
-
-(defun vterm--delayed-redraw (buffer)
- "Redraw the terminal buffer.
-Argument BUFFER the terminal buffer."
- (when (buffer-live-p buffer)
- (with-current-buffer buffer
- (let ((inhibit-redisplay t)
- (inhibit-read-only t)
- (windows (get-buffer-window-list)))
- (setq vterm--redraw-timer nil)
- (when vterm--term
- (vterm--redraw vterm--term)
- (unless (zerop (window-hscroll))
- (when (cl-member (selected-window) windows :test #'eq)
- (set-window-hscroll (selected-window) 0))))))))
-
-;; see VTermSelectionMask in vterm.el
-;; VTERM_SELECTION_CLIPBOARD = (1<<0),
-;; VTERM_SELECTION_PRIMARY = (1<<1),
-(defconst vterm--selection-clipboard 1) ;(1<<0)
-(defconst vterm--selection-primary 2) ;(1<<1)
-(defun vterm--set-selection (mask data)
- "OSC 52 Manipulate Selection Data.
-Search Manipulate Selection Data in
- https://invisible-island.net/xterm/ctlseqs/ctlseqs.html ."
- (when vterm-enable-manipulate-selection-data-by-osc52
- (let ((select-enable-clipboard select-enable-clipboard)
- (select-enable-primary select-enable-primary))
- (setq select-enable-clipboard
- (logand mask vterm--selection-clipboard))
- (setq select-enable-primary
- (logand mask vterm--selection-primary))
- (kill-new data)
- (message "kill-ring is updated by vterm OSC 52(Manipulate Selection Data)"))
- ))
-
-;;; Entry Points
-
-;;;###autoload
-(defun vterm (&optional arg)
- "Create an interactive Vterm buffer.
-Start a new Vterm session, or switch to an already active
-session. Return the buffer selected (or created).
-
-With a nonnumeric prefix arg, create a new session.
-
-With a string prefix arg, create a new session with arg as buffer name.
-
-With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch
-to the session with that number, or create it if it doesn't
-already exist.
-
-The buffer name used for Vterm sessions is determined by the
-value of `vterm-buffer-name'."
- (interactive "P")
- (vterm--internal #'pop-to-buffer-same-window arg))
-
-;;;###autoload
-(defun vterm-other-window (&optional arg)
- "Create an interactive Vterm buffer in another window.
-Start a new Vterm session, or switch to an already active
-session. Return the buffer selected (or created).
-
-With a nonnumeric prefix arg, create a new session.
-
-With a string prefix arg, create a new session with arg as buffer name.
-
-With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch
-to the session with that number, or create it if it doesn't
-already exist.
-
-The buffer name used for Vterm sessions is determined by the
-value of `vterm-buffer-name'."
- (interactive "P")
- (vterm--internal #'pop-to-buffer arg))
-
-(defun vterm--internal (pop-to-buf-fun &optional arg)
- (cl-assert vterm-buffer-name)
- (let ((buf (cond ((numberp arg)
- (get-buffer-create (format "%s<%d>"
- vterm-buffer-name
- arg)))
- ((stringp arg) (generate-new-buffer arg))
- (arg (generate-new-buffer vterm-buffer-name))
- (t
- (get-buffer-create vterm-buffer-name)))))
- (cl-assert (and buf (buffer-live-p buf)))
- (funcall pop-to-buf-fun buf)
- (with-current-buffer buf
- (unless (derived-mode-p 'vterm-mode)
- (vterm-mode)))
- buf))
-
-;;; Internal
-
-(defun vterm--flush-output (output)
- "Send the virtual terminal's OUTPUT to the shell."
- (process-send-string vterm--process output))
-;; Terminal emulation
-;; This is the standard process filter for term buffers.
-;; It emulates (most of the features of) a VT100/ANSI-style terminal.
-
-;; References:
-;; [ctlseqs]: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
-;; [ECMA-48]: https://www.ecma-international.org/publications/standards/Ecma-048.htm
-;; [vt100]: https://vt100.net/docs/vt100-ug/chapter3.html
-
-(defconst vterm-control-seq-regexp
- (concat
- ;; A control character,
- "\\(?:[\r\n\000\007\t\b\016\017]\\|"
- ;; a C1 escape coded character (see [ECMA-48] section 5.3 "Elements
- ;; of the C1 set"),
- "\e\\(?:[DM78c=]\\|"
- ;; another Emacs specific control sequence for term.el,
- "AnSiT[^\n]+\n\\|"
- ;; another Emacs specific control sequence for vterm.el
- ;; printf "\e]%s\e\\"
- "\\][^\e]+\e\\\\\\|"
- ;; or an escape sequence (section 5.4 "Control Sequences"),
- "\\[\\([\x30-\x3F]*\\)[\x20-\x2F]*[\x40-\x7E]\\)\\)")
- "Regexp matching control sequences handled by term.el.")
-
-(defconst vterm-control-seq-prefix-regexp
- "[\032\e]")
-
-(defun vterm--filter (process input)
- "I/O Event. Feeds PROCESS's INPUT to the virtual terminal.
-
-Then triggers a redraw from the module."
- (let ((inhibit-redisplay t)
- (inhibit-eol-conversion t)
- (inhibit-read-only t)
- (buf (process-buffer process))
- (i 0)
- (str-length (length input))
- decoded-substring
- funny)
- (when (buffer-live-p buf)
- (with-current-buffer buf
- ;; borrowed from term.el
- ;; Handle non-control data. Decode the string before
- ;; counting characters, to avoid garbling of certain
- ;; multibyte characters (https://github.com/akermu/emacs-libvterm/issues/394).
- ;; same bug of term.el https://debbugs.gnu.org/cgi/bugreport.cgi?bug=1006
- (when vterm--undecoded-bytes
- (setq input (concat vterm--undecoded-bytes input))
- (setq vterm--undecoded-bytes nil)
- (setq str-length (length input)))
- (while (< i str-length)
- (setq funny (string-match vterm-control-seq-regexp input i))
- (let ((ctl-end (if funny (match-end 0)
- (setq funny (string-match vterm-control-seq-prefix-regexp input i))
- (if funny
- (setq vterm--undecoded-bytes
- (substring input funny))
- (setq funny str-length))
- ;; The control sequence ends somewhere
- ;; past the end of this string.
- (1+ str-length))))
- (when (> funny i)
- ;; Handle non-control data. Decode the string before
- ;; counting characters, to avoid garbling of certain
- ;; multibyte characters (emacs bug#1006).
- (setq decoded-substring
- (decode-coding-string
- (substring input i funny)
- locale-coding-system t))
- ;; Check for multibyte characters that ends
- ;; before end of string, and save it for
- ;; next time.
- (when (= funny str-length)
- (let ((partial 0)
- (count (length decoded-substring)))
- (while (and (< partial count)
- (eq (char-charset (aref decoded-substring
- (- count 1 partial)))
- 'eight-bit))
- (cl-incf partial))
- (when (> count partial 0)
- (setq vterm--undecoded-bytes
- (substring decoded-substring (- partial)))
- (setq decoded-substring
- (substring decoded-substring 0 (- partial)))
- (cl-decf str-length partial)
- (cl-decf funny partial))))
- (ignore-errors (vterm--write-input vterm--term decoded-substring))
- (setq i funny))
- (when (<= ctl-end str-length)
- (ignore-errors (vterm--write-input vterm--term (substring input i ctl-end))))
- (setq i ctl-end)))
- (vterm--update vterm--term)))))
-
-(defun vterm--sentinel (process event)
- "Sentinel of vterm PROCESS.
-Argument EVENT process event."
- (let ((buf (process-buffer process)))
- (run-hook-with-args 'vterm-exit-functions
- (if (buffer-live-p buf) buf nil)
- event)
- (if (and vterm-kill-buffer-on-exit (buffer-live-p buf))
- (kill-buffer buf))))
-
-(defun vterm--text-scale-mode (&optional _argv)
- "Fix `line-number' height for scaled text."
- (and text-scale-mode
- (equal major-mode 'vterm-mode)
- (boundp 'display-line-numbers)
- (let ((height (expt text-scale-mode-step
- text-scale-mode-amount)))
- (when vterm--linenum-remapping
- (face-remap-remove-relative vterm--linenum-remapping))
- (setq vterm--linenum-remapping
- (face-remap-add-relative 'line-number :height height))))
- (window--adjust-process-windows))
-
-(advice-add #'text-scale-mode :after #'vterm--text-scale-mode)
-
-(defun vterm--window-adjust-process-window-size (process windows)
- "Adjust width of window WINDOWS associated to process PROCESS.
-
-`vterm-min-window-width' determines the minimum width allowed."
- ;; We want `vterm-copy-mode' to resemble a fundamental buffer as much as
- ;; possible. Hence, we must not call this function when the minor mode is
- ;; enabled, otherwise the buffer would be redrawn, messing around with the
- ;; position of the point.
- (unless vterm-copy-mode
- (let* ((size (funcall window-adjust-process-window-size-function
- process windows))
- (width (car size))
- (height (cdr size))
- (inhibit-read-only t))
- (setq width (- width (vterm--get-margin-width)))
- (setq width (max width vterm-min-window-width))
- (when (and (processp process)
- (process-live-p process)
- (> width 0)
- (> height 0))
- (vterm--set-size vterm--term height width)
- (cons width height)))))
-
-(defun vterm--get-margin-width ()
- "Get margin width of vterm buffer when `display-line-numbers-mode' is enabled."
- (let ((width 0)
- (max-line-num (+ (frame-height) vterm-max-scrollback)))
- (when (bound-and-true-p display-line-numbers)
- (setq width (+ width 4
- (string-width (number-to-string max-line-num)))))
- width))
-
-(defun vterm--delete-lines (line-num count &optional delete-whole-line)
- "Delete COUNT lines from LINE-NUM.
-If LINE-NUM is negative backward-line from end of buffer.
-If option DELETE-WHOLE-LINE is non-nil, then this command kills
-the whole line including its terminating newline"
- (save-excursion
- (when (vterm--goto-line line-num)
- (vterm--delete-region (point) (line-end-position count))
- (when (and delete-whole-line
- (looking-at "\n"))
- (vterm--delete-char 1)))))
-
-(defun vterm--goto-line (n)
- "Go to line N and return true on success.
-If N is negative backward-line from end of buffer."
- (cond
- ((> n 0)
- (goto-char (point-min))
- (eq 0 (forward-line (1- n))))
- (t
- (goto-char (point-max))
- (eq 0 (forward-line n)))))
-
-(defun vterm--set-title (title)
- "Use TITLE to set the buffer name according to `vterm-buffer-name-string'."
- (when vterm-buffer-name-string
- (rename-buffer (format vterm-buffer-name-string title) t)))
-
-(defun vterm--set-directory (path)
- "Set `default-directory' to PATH."
- (let ((dir (vterm--get-directory path)))
- (when dir (setq default-directory dir))))
-
-(defun vterm--get-directory (path)
- "Get normalized directory to PATH."
- (when path
- (let (directory)
- (if (string-match "^\\(.*?\\)@\\(.*?\\):\\(.*?\\)$" path)
- (progn
- (let ((user (match-string 1 path))
- (host (match-string 2 path))
- (dir (match-string 3 path)))
- (if (and (string-equal user user-login-name)
- (string-equal host (system-name)))
- (progn
- (when (file-directory-p dir)
- (setq directory (file-name-as-directory dir))))
- (setq directory (file-name-as-directory (concat "/-:" path))))))
- (when (file-directory-p path)
- (setq directory (file-name-as-directory path))))
- directory)))
-
-(defun vterm--get-pwd (&optional linenum)
- "Get working directory at LINENUM."
- (when vterm--term
- (let ((raw-pwd (vterm--get-pwd-raw
- vterm--term
- (or linenum (line-number-at-pos)))))
- (when raw-pwd
- (vterm--get-directory raw-pwd)))))
-
-(defun vterm--get-color (index &rest args)
- "Get color by INDEX from `vterm-color-palette'.
-
-Special INDEX of -1 is used to represent default colors. ARGS
-may optionally contain `:underline' or `:inverse-video' for cells
-with underline or inverse video attribute. If ARGS contains
-`:foreground', use foreground color of the respective face
-instead of background."
- (let ((foreground (member :foreground args))
- (underline (member :underline args))
- (inverse-video (member :inverse-video args)))
- (funcall (if foreground #'face-foreground #'face-background)
- (cond
- ((and (>= index 0) (< index 16))
- (elt vterm-color-palette index))
- ((and (= index -1) foreground underline)
- 'vterm-color-underline)
- ((and (= index -1) (not foreground) inverse-video)
- 'vterm-color-inverse-video)
- (t 'default))
- nil 'default)))
-
-(defun vterm--eval (str)
- "Check if string STR is `vterm-eval-cmds' and execute command.
-
-All passed in arguments are strings and forwarded as string to
-the called functions."
- (let* ((parts (split-string-and-unquote str))
- (command (car parts))
- (args (cdr parts))
- (f (assoc command vterm-eval-cmds)))
- (if f
- (apply (cadr f) args)
- (message "Failed to find command: %s. To execute a command,
- add it to the `vterm-eval-cmd' list" command))))
-
-;; TODO: Improve doc string, it should not point to the readme but it should
-;; be self-contained.
-(defun vterm--prompt-tracking-enabled-p ()
- "Return t if tracking the prompt is enabled.
-
-Prompt tracking need shell side configurations.
-
-For zsh user, this is done by PROMPT=$PROMPT'%{$(vterm_prompt_end)%}'.
-
-The shell send semantic information about where the prompt ends via properly
-escaped sequences to Emacs.
-
-More information see `Shell-side configuration' and `Directory tracking'
-in README."
- (or vterm--prompt-tracking-enabled-p
- (save-excursion
- (setq vterm--prompt-tracking-enabled-p
- (next-single-property-change (point-min) 'vterm-prompt)))))
-
-(defun vterm-next-prompt (n)
- "Move to end of Nth next prompt in the buffer."
- (interactive "p")
- (if (and vterm-use-vterm-prompt-detection-method
- (vterm--prompt-tracking-enabled-p))
- (let ((pt (point))
- (promp-pt (vterm--get-prompt-point)))
- (when promp-pt (goto-char promp-pt))
- (cl-loop repeat (or n 1) do
- (setq pt (next-single-property-change (line-beginning-position 2) 'vterm-prompt))
- (when pt (goto-char pt))))
- (term-next-prompt n)))
-
-(defun vterm-previous-prompt (n)
- "Move to end of Nth previous prompt in the buffer."
- (interactive "p")
- (if (and vterm-use-vterm-prompt-detection-method
- (vterm--prompt-tracking-enabled-p))
- (let ((pt (point))
- (prompt-pt (vterm--get-prompt-point)))
- (when prompt-pt
- (goto-char prompt-pt)
- (when (> pt (point))
- (setq n (1- (or n 1))))
- (cl-loop repeat n do
- (setq pt (previous-single-property-change (1- (point)) 'vterm-prompt))
- (when pt (goto-char (1- pt))))))
- (term-previous-prompt n)))
-
-(defun vterm--get-beginning-of-line (&optional pt)
- "Find the start of the line, bypassing line wraps.
-If PT is specified, find it's beginning of the line instead of the beginning
-of the line at cursor."
- (save-excursion
- (when pt (goto-char pt))
- (beginning-of-line)
- (while (and (not (bobp))
- (get-text-property (1- (point)) 'vterm-line-wrap))
- (forward-char -1)
- (beginning-of-line))
- (point)))
-
-(defun vterm--get-end-of-line (&optional pt)
- "Find the start of the line, bypassing line wraps.
-If PT is specified, find it's end of the line instead of the end
-of the line at cursor."
- (save-excursion
- (when pt (goto-char pt))
- (end-of-line)
- (while (get-text-property (point) 'vterm-line-wrap)
- (forward-char)
- (end-of-line))
- (point)))
-
-;; TODO: Improve doc string, it should not point to the readme but it should
-;; be self-contained.
-(defun vterm--get-prompt-point ()
- "Get the position of the end of current prompt.
-More information see `vterm--prompt-tracking-enabled-p' and
-`Directory tracking and Prompt tracking'in README."
- (let ((end-point (vterm--get-end-of-line))
- prompt-point)
- (save-excursion
- (if (and vterm-use-vterm-prompt-detection-method
- (vterm--prompt-tracking-enabled-p))
- (if (get-text-property end-point 'vterm-prompt)
- end-point
- (setq prompt-point (previous-single-property-change end-point 'vterm-prompt))
- (when prompt-point (setq prompt-point (1- prompt-point))))
- (goto-char end-point)
- (if (search-backward-regexp term-prompt-regexp nil t)
- (goto-char (match-end 0))
- (vterm--get-beginning-of-line))))))
-
-(defun vterm--at-prompt-p ()
- "Return t if the cursor position is at shell prompt."
- (= (point) (or (vterm--get-prompt-point) 0)))
-
-(defun vterm-cursor-in-command-buffer-p (&optional pt)
- "Check whether cursor in command buffer area."
- (save-excursion
- (vterm-reset-cursor-point)
- (let ((promp-pt (vterm--get-prompt-point)))
- (when promp-pt
- (<= promp-pt (or pt (vterm--get-cursor-point)))))))
-
-(defun vterm-beginning-of-line ()
- "Move point to the beginning of the line.
-
-Move the point to the first character after the shell prompt on this line.
-If the point is already there, move to the beginning of the line.
-Effectively toggle between the two positions."
- (interactive "^")
- (if (vterm--at-prompt-p)
- (goto-char (vterm--get-beginning-of-line))
- (goto-char (max (or (vterm--get-prompt-point) 0)
- (vterm--get-beginning-of-line)))))
-
-(defun vterm-end-of-line ()
- "Move point to the end of the line, bypassing line wraps."
- (interactive "^")
- (goto-char (vterm--get-end-of-line)))
-
-(defun vterm-reset-cursor-point ()
- "Make sure the cursor at the right position."
- (interactive)
- (when vterm--term
- (let ((inhibit-read-only t))
- (vterm--reset-point vterm--term))))
-
-(defun vterm--get-cursor-point ()
- "Get term cursor position."
- (when vterm--term
- (save-excursion
- (vterm-reset-cursor-point))))
-
-(defun vterm--reinsert-fake-newlines ()
- "Reinsert fake newline from `vterm--copy-mode-fake-newlines'."
- (let ((inhibit-read-only t)
- (inhibit-redisplay t)
- (fake-newline-text "\n")
- fake-newline-pos)
- (add-text-properties 0 1 '(vterm-line-wrap t rear-nonsticky t)
- fake-newline-text)
- (while vterm--copy-mode-fake-newlines
- (setq fake-newline-pos (car vterm--copy-mode-fake-newlines))
- (setq vterm--copy-mode-fake-newlines (cdr vterm--copy-mode-fake-newlines))
- (goto-char fake-newline-pos)
- (insert fake-newline-text))))
-
-(defun vterm--remove-fake-newlines (&optional remembering-pos-p)
- "Filter out injected newlines were injected when rendering the terminal.
-
-These newlines were tagged with \\='vterm-line-wrap property so we
-can find them and remove them.
-If REMEMBERING-POS-P is not nil remembering their positions in a buffer-local
-`vterm--copy-mode-fake-newlines'."
- (let (fake-newline
- (inhibit-read-only t)
- (inhibit-redisplay t))
- (when remembering-pos-p
- (setq vterm--copy-mode-fake-newlines nil))
-
- (goto-char (point-max))
- (when (and (bolp)
- (not (bobp))
- (get-text-property (1- (point)) 'vterm-line-wrap))
- (forward-char -1)
- (when remembering-pos-p
- (setq vterm--copy-mode-fake-newlines
- (cons (point) vterm--copy-mode-fake-newlines)))
- (vterm--delete-char 1))
-
- (while (and (not (bobp))
- (setq fake-newline (previous-single-property-change
- (point) 'vterm-line-wrap)))
- (goto-char (1- fake-newline))
- (cl-assert (eq ?\n (char-after)))
- (when remembering-pos-p
- (setq vterm--copy-mode-fake-newlines
- (cons (point) vterm--copy-mode-fake-newlines)))
- (vterm--delete-char 1))))
-
-(defun vterm--filter-buffer-substring (content)
- "Filter string CONTENT of fake/injected newlines."
- (with-temp-buffer
- (vterm--insert content)
- (vterm--remove-fake-newlines nil)
- (buffer-string)))
-
-
-(provide 'vterm)
-;; Local Variables:
-;; indent-tabs-mode: nil
-;; End:
-;;; vterm.el ends here
--- /dev/null
+cmake_minimum_required(VERSION 3.11)
+include(ExternalProject)
+
+project(emacs-libvterm C)
+
+if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
+ set(LIBVTERM_BUILD_COMMAND "gmake")
+else()
+ set(LIBVTERM_BUILD_COMMAND "make")
+endif()
+
+add_library(vterm-module MODULE vterm-module.c utf8.c elisp.c)
+set_target_properties(vterm-module PROPERTIES
+ C_STANDARD 99
+ C_VISIBILITY_PRESET "hidden"
+ POSITION_INDEPENDENT_CODE ON
+ PREFIX ""
+ LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}
+ )
+
+# Set RelWithDebInfo as default build type
+if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ message(STATUS "No build type selected, defaulting to RelWithDebInfo")
+ set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Build type (default RelWithDebInfo)" FORCE)
+endif()
+
+# Look for the header file.
+option(USE_SYSTEM_LIBVTERM "Use system libvterm instead of the vendored version." ON)
+
+# Try to find the libvterm in system.
+if (USE_SYSTEM_LIBVTERM)
+ # try to find the vterm.h header file.
+ find_path(LIBVTERM_INCLUDE_DIR
+ NAMES vterm.h
+ )
+
+ # vterm.h is found.
+ if (LIBVTERM_INCLUDE_DIR)
+ message(STATUS "System libvterm detected")
+ execute_process(COMMAND grep -c "VTermStringFragment" "${LIBVTERM_INCLUDE_DIR}/vterm.h" OUTPUT_VARIABLE VTermStringFragmentExists)
+ if (${VTermStringFragmentExists} EQUAL "0")
+# add_compile_definitions(VTermStringFragmentNotExists)
+ add_definitions(-DVTermStringFragmentNotExists)
+ endif()
+ execute_process(COMMAND grep -c "VTermSelectionMask" "${LIBVTERM_INCLUDE_DIR}/vterm.h" OUTPUT_VARIABLE VTermSelectionMaskExists)
+ if (${VTermSelectionMaskExists} EQUAL "0")
+# add_compile_definitions(VTermStringFragmentNotExists)
+ add_definitions(-DVTermSelectionMaskNotExists)
+ endif()
+ execute_process(COMMAND grep -c "sb_clear" "${LIBVTERM_INCLUDE_DIR}/vterm.h" OUTPUT_VARIABLE VTermSBClearExists)
+ if (${VTermSBClearExists} EQUAL "0")
+ add_definitions(-DVTermSBClearNotExists)
+ endif()
+ else()
+ message(STATUS "System libvterm not found: libvterm will be downloaded and compiled as part of the build process")
+ endif()
+endif()
+
+if (LIBVTERM_INCLUDE_DIR)
+ find_library(LIBVTERM_LIBRARY NAMES
+ vterm
+ libvterm
+ )
+
+ if(NOT LIBVTERM_LIBRARY)
+ message(FATAL_ERROR "libvterm not found")
+ endif()
+else()
+ find_program(LIBTOOL NAMES libtool glibtool)
+ if(NOT LIBTOOL)
+ message(FATAL_ERROR "libtool not found. Please install libtool")
+ endif()
+
+ ExternalProject_add(libvterm
+ GIT_REPOSITORY https://github.com/Sbozzolo/libvterm-mirror.git
+ GIT_TAG 64f1775952dbe001e989f2ab679563b54f2fca55
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND ${LIBVTERM_BUILD_COMMAND} "CFLAGS='-fPIC'" "LDFLAGS='-static'"
+ BUILD_IN_SOURCE ON
+ INSTALL_COMMAND "")
+
+ ExternalProject_Get_property(libvterm SOURCE_DIR)
+
+ set(LIBVTERM_INCLUDE_DIR ${SOURCE_DIR}/include)
+ set(LIBVTERM_LIBRARY ${SOURCE_DIR}/.libs/libvterm.a)
+
+ add_dependencies(vterm-module libvterm)
+
+ # Workaround for https://gitlab.kitware.com/cmake/cmake/issues/15052
+ file(MAKE_DIRECTORY ${LIBVTERM_INCLUDE_DIR})
+endif()
+
+add_library(vterm STATIC IMPORTED)
+set_target_properties(vterm PROPERTIES IMPORTED_LOCATION ${LIBVTERM_LIBRARY})
+target_include_directories(vterm INTERFACE ${LIBVTERM_INCLUDE_DIR})
+
+# Link with libvterm
+target_link_libraries(vterm-module PUBLIC vterm)
+
+# Custom run command for testing
+add_custom_target(run
+ COMMAND emacs -Q -L ${CMAKE_SOURCE_DIR} -L ${CMAKE_BINARY_DIR} --eval "\\(require \\'vterm\\)" --eval "\\(vterm\\)"
+ DEPENDS vterm-module
+ )
--- /dev/null
+#include "elisp.h"
+#include <stdio.h>
+
+// Emacs symbols
+emacs_value Qt;
+emacs_value Qnil;
+emacs_value Qnormal;
+emacs_value Qbold;
+emacs_value Qitalic;
+emacs_value Qforeground;
+emacs_value Qbackground;
+emacs_value Qweight;
+emacs_value Qunderline;
+emacs_value Qslant;
+emacs_value Qreverse;
+emacs_value Qstrike;
+emacs_value Qextend;
+emacs_value Qface;
+emacs_value Qbox;
+emacs_value Qbar;
+emacs_value Qhbar;
+emacs_value Qcursor_type;
+emacs_value Qemacs_major_version;
+emacs_value Qvterm_line_wrap;
+emacs_value Qrear_nonsticky;
+emacs_value Qvterm_prompt;
+
+// Emacs functions
+emacs_value Fblink_cursor_mode;
+emacs_value Fsymbol_value;
+emacs_value Flength;
+emacs_value Flist;
+emacs_value Fnth;
+emacs_value Ferase_buffer;
+emacs_value Finsert;
+emacs_value Fgoto_char;
+emacs_value Fforward_char;
+emacs_value Fforward_line;
+emacs_value Fgoto_line;
+emacs_value Fdelete_lines;
+emacs_value Frecenter;
+emacs_value Fset_window_point;
+emacs_value Fwindow_body_height;
+emacs_value Fpoint;
+emacs_value Fapply;
+
+emacs_value Fput_text_property;
+emacs_value Fadd_text_properties;
+emacs_value Fset;
+emacs_value Fvterm_flush_output;
+emacs_value Fget_buffer_window_list;
+emacs_value Fselected_window;
+emacs_value Fvterm_set_title;
+emacs_value Fvterm_set_directory;
+emacs_value Fvterm_invalidate;
+emacs_value Feq;
+emacs_value Fvterm_get_color;
+emacs_value Fvterm_eval;
+emacs_value Fvterm_set_selection;
+
+/* Set the function cell of the symbol named NAME to SFUN using
+ the 'fset' function. */
+void bind_function(emacs_env *env, const char *name, emacs_value Sfun) {
+ emacs_value Qfset = env->intern(env, "fset");
+ emacs_value Qsym = env->intern(env, name);
+
+ env->funcall(env, Qfset, 2, (emacs_value[]){Qsym, Sfun});
+}
+
+/* Provide FEATURE to Emacs. */
+void provide(emacs_env *env, const char *feature) {
+ emacs_value Qfeat = env->intern(env, feature);
+ emacs_value Qprovide = env->intern(env, "provide");
+
+ env->funcall(env, Qprovide, 1, (emacs_value[]){Qfeat});
+}
+
+emacs_value symbol_value(emacs_env *env, emacs_value symbol) {
+ return env->funcall(env, Fsymbol_value, 1, (emacs_value[]){symbol});
+}
+
+int string_bytes(emacs_env *env, emacs_value string) {
+ ptrdiff_t size = 0;
+ env->copy_string_contents(env, string, NULL, &size);
+ return size;
+}
+
+emacs_value length(emacs_env *env, emacs_value string) {
+ return env->funcall(env, Flength, 1, (emacs_value[]){string});
+}
+
+emacs_value list(emacs_env *env, emacs_value elements[], ptrdiff_t len) {
+ return env->funcall(env, Flist, len, elements);
+}
+emacs_value nth(emacs_env *env, int idx, emacs_value list) {
+ emacs_value eidx = env->make_integer(env, idx);
+ return env->funcall(env, Fnth, 2, (emacs_value[]){eidx, list});
+}
+
+void put_text_property(emacs_env *env, emacs_value string, emacs_value property,
+ emacs_value value) {
+ emacs_value start = env->make_integer(env, 0);
+ emacs_value end = length(env, string);
+
+ env->funcall(env, Fput_text_property, 5,
+ (emacs_value[]){start, end, property, value, string});
+}
+
+void add_text_properties(emacs_env *env, emacs_value string,
+ emacs_value property) {
+ emacs_value start = env->make_integer(env, 0);
+ emacs_value end = length(env, string);
+
+ env->funcall(env, Fadd_text_properties, 4,
+ (emacs_value[]){start, end, property, string});
+}
+
+void erase_buffer(emacs_env *env) { env->funcall(env, Ferase_buffer, 0, NULL); }
+
+void insert(emacs_env *env, emacs_value string) {
+ env->funcall(env, Finsert, 1, (emacs_value[]){string});
+}
+
+void goto_char(emacs_env *env, int pos) {
+ emacs_value point = env->make_integer(env, pos);
+ env->funcall(env, Fgoto_char, 1, (emacs_value[]){point});
+}
+
+void forward_line(emacs_env *env, int n) {
+ emacs_value nline = env->make_integer(env, n);
+ env->funcall(env, Fforward_line, 1, (emacs_value[]){nline});
+}
+void goto_line(emacs_env *env, int n) {
+ emacs_value nline = env->make_integer(env, n);
+ env->funcall(env, Fgoto_line, 1, (emacs_value[]){nline});
+}
+void delete_lines(emacs_env *env, int linenum, int count, bool del_whole_line) {
+ emacs_value Qlinenum = env->make_integer(env, linenum);
+ emacs_value Qcount = env->make_integer(env, count);
+ if (del_whole_line) {
+ env->funcall(env, Fdelete_lines, 3, (emacs_value[]){Qlinenum, Qcount, Qt});
+ } else {
+ env->funcall(env, Fdelete_lines, 3,
+ (emacs_value[]){Qlinenum, Qcount, Qnil});
+ }
+}
+void recenter(emacs_env *env, emacs_value pos) {
+ env->funcall(env, Frecenter, 1, (emacs_value[]){pos});
+}
+emacs_value point(emacs_env *env) { return env->funcall(env, Fpoint, 0, NULL); }
+
+void set_window_point(emacs_env *env, emacs_value win, emacs_value point) {
+ env->funcall(env, Fset_window_point, 2, (emacs_value[]){win, point});
+}
+emacs_value window_body_height(emacs_env *env, emacs_value win) {
+ return env->funcall(env, Fwindow_body_height, 1, (emacs_value[]){win});
+}
+
+bool eq(emacs_env *env, emacs_value e1, emacs_value e2) {
+ emacs_value Qeq = env->funcall(env, Feq, 2, (emacs_value[]){e1, e2});
+ return env->is_not_nil(env, Qeq);
+}
+
+void forward_char(emacs_env *env, emacs_value n) {
+ env->funcall(env, Fforward_char, 1, (emacs_value[]){n});
+}
+
+emacs_value get_buffer_window_list(emacs_env *env) {
+ return env->funcall(env, Fget_buffer_window_list, 3,
+ (emacs_value[]){Qnil, Qnil, Qt});
+}
+
+emacs_value selected_window(emacs_env *env) {
+ return env->funcall(env, Fselected_window, 0, (emacs_value[]){});
+}
+
+void set_cursor_type(emacs_env *env, emacs_value cursor_type) {
+ env->funcall(env, Fset, 2, (emacs_value[]){Qcursor_type, cursor_type});
+}
+
+void set_cursor_blink(emacs_env *env, bool blink) {
+ env->funcall(env, Fblink_cursor_mode, 1,
+ (emacs_value[]){env->make_integer(env, blink)});
+}
+
+emacs_value vterm_get_color(emacs_env *env, int index, emacs_value args) {
+ emacs_value idx = env->make_integer(env, index);
+ return env->funcall(env, Fapply, 3, (emacs_value[]){ Fvterm_get_color, idx, args });
+}
+
+void set_title(emacs_env *env, emacs_value string) {
+ env->funcall(env, Fvterm_set_title, 1, (emacs_value[]){string});
+}
+
+void set_directory(emacs_env *env, emacs_value string) {
+ env->funcall(env, Fvterm_set_directory, 1, (emacs_value[]){string});
+}
+
+void vterm_invalidate(emacs_env *env) {
+ env->funcall(env, Fvterm_invalidate, 0, NULL);
+}
+emacs_value vterm_eval(emacs_env *env, emacs_value string) {
+ return env->funcall(env, Fvterm_eval, 1, (emacs_value[]){string});
+}
+
+emacs_value vterm_set_selection(emacs_env *env, emacs_value selection_target,
+ emacs_value selection_data) {
+ return env->funcall(env, Fvterm_set_selection, 2,
+ (emacs_value[]){selection_target, selection_data});
+}
--- /dev/null
+#ifndef ELISP_H
+#define ELISP_H
+
+#include "emacs-module.h"
+#include "vterm.h"
+
+// Emacs symbols
+extern emacs_value Qt;
+extern emacs_value Qnil;
+extern emacs_value Qnormal;
+extern emacs_value Qbold;
+extern emacs_value Qitalic;
+extern emacs_value Qforeground;
+extern emacs_value Qbackground;
+extern emacs_value Qweight;
+extern emacs_value Qunderline;
+extern emacs_value Qslant;
+extern emacs_value Qreverse;
+extern emacs_value Qstrike;
+extern emacs_value Qextend;
+extern emacs_value Qface;
+extern emacs_value Qbox;
+extern emacs_value Qbar;
+extern emacs_value Qhbar;
+extern emacs_value Qcursor_type;
+extern emacs_value Qemacs_major_version;
+extern emacs_value Qvterm_line_wrap;
+extern emacs_value Qrear_nonsticky;
+extern emacs_value Qvterm_prompt;
+
+// Emacs functions
+extern emacs_value Fapply;
+extern emacs_value Fblink_cursor_mode;
+extern emacs_value Fsymbol_value;
+extern emacs_value Flength;
+extern emacs_value Flist;
+extern emacs_value Fnth;
+extern emacs_value Ferase_buffer;
+extern emacs_value Finsert;
+extern emacs_value Fgoto_char;
+extern emacs_value Fforward_char;
+extern emacs_value Fforward_line;
+extern emacs_value Fgoto_line;
+extern emacs_value Fdelete_lines;
+extern emacs_value Frecenter;
+extern emacs_value Fset_window_point;
+extern emacs_value Fwindow_body_height;
+extern emacs_value Fpoint;
+
+extern emacs_value Fput_text_property;
+extern emacs_value Fadd_text_properties;
+extern emacs_value Fset;
+extern emacs_value Fvterm_flush_output;
+extern emacs_value Fget_buffer_window_list;
+extern emacs_value Fselected_window;
+extern emacs_value Fvterm_set_title;
+extern emacs_value Fvterm_set_directory;
+extern emacs_value Fvterm_invalidate;
+extern emacs_value Feq;
+extern emacs_value Fvterm_get_color;
+extern emacs_value Fvterm_eval;
+extern emacs_value Fvterm_set_selection;
+
+// Utils
+void bind_function(emacs_env *env, const char *name, emacs_value Sfun);
+void provide(emacs_env *env, const char *feature);
+emacs_value symbol_value(emacs_env *env, emacs_value symbol);
+int string_bytes(emacs_env *env, emacs_value string);
+emacs_value length(emacs_env *env, emacs_value string);
+emacs_value list(emacs_env *env, emacs_value elements[], ptrdiff_t len);
+emacs_value nth(emacs_env *env, int idx, emacs_value list);
+void put_text_property(emacs_env *env, emacs_value string, emacs_value property,
+ emacs_value value);
+void add_text_properties(emacs_env *env, emacs_value string,
+ emacs_value property);
+void erase_buffer(emacs_env *env);
+void insert(emacs_env *env, emacs_value string);
+void goto_char(emacs_env *env, int pos);
+void forward_line(emacs_env *env, int n);
+void goto_line(emacs_env *env, int n);
+void set_cursor_type(emacs_env *env, emacs_value cursor_type);
+void set_cursor_blink(emacs_env *env, bool blink);
+void delete_lines(emacs_env *env, int linenum, int count, bool del_whole_line);
+void recenter(emacs_env *env, emacs_value pos);
+void set_window_point(emacs_env *env, emacs_value win, emacs_value point);
+emacs_value window_body_height(emacs_env *env, emacs_value win);
+emacs_value point(emacs_env *env);
+bool eq(emacs_env *env, emacs_value e1, emacs_value e2);
+void forward_char(emacs_env *env, emacs_value n);
+emacs_value get_buffer_window_list(emacs_env *env);
+emacs_value selected_window(emacs_env *env);
+void set_title(emacs_env *env, emacs_value string);
+void set_directory(emacs_env *env, emacs_value string);
+void vterm_invalidate(emacs_env *env);
+emacs_value vterm_get_color(emacs_env *env, int index, emacs_value args);
+emacs_value vterm_eval(emacs_env *env, emacs_value string);
+emacs_value vterm_set_selection(emacs_env *env, emacs_value selection_target,
+ emacs_value selection_data);
+
+#endif /* ELISP_H */
--- /dev/null
+/* emacs-module.h - GNU Emacs module API.
+
+Copyright (C) 2015-2018 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
+
+#ifndef EMACS_MODULE_H
+#define EMACS_MODULE_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifndef __cplusplus
+#include <stdbool.h>
+#endif
+
+#if defined __cplusplus && __cplusplus >= 201103L
+#define EMACS_NOEXCEPT noexcept
+#else
+#define EMACS_NOEXCEPT
+#endif
+
+#ifdef __has_attribute
+#if __has_attribute(__nonnull__)
+#define EMACS_ATTRIBUTE_NONNULL(...) __attribute__((__nonnull__(__VA_ARGS__)))
+#endif
+#endif
+#ifndef EMACS_ATTRIBUTE_NONNULL
+#define EMACS_ATTRIBUTE_NONNULL(...)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Current environment. */
+typedef struct emacs_env_25 emacs_env;
+
+/* Opaque pointer representing an Emacs Lisp value.
+ BEWARE: Do not assume NULL is a valid value! */
+typedef struct emacs_value_tag *emacs_value;
+
+enum { emacs_variadic_function = -2 };
+
+/* Struct passed to a module init function (emacs_module_init). */
+struct emacs_runtime {
+ /* Structure size (for version checking). */
+ ptrdiff_t size;
+
+ /* Private data; users should not touch this. */
+ struct emacs_runtime_private *private_members;
+
+ /* Return an environment pointer. */
+ emacs_env *(*get_environment)(struct emacs_runtime *ert)
+ EMACS_ATTRIBUTE_NONNULL(1);
+};
+
+/* Possible Emacs function call outcomes. */
+enum emacs_funcall_exit {
+ /* Function has returned normally. */
+ emacs_funcall_exit_return = 0,
+
+ /* Function has signaled an error using `signal'. */
+ emacs_funcall_exit_signal = 1,
+
+ /* Function has exit using `throw'. */
+ emacs_funcall_exit_throw = 2
+};
+
+struct emacs_env_25 {
+ /* Structure size (for version checking). */
+ ptrdiff_t size;
+
+ /* Private data; users should not touch this. */
+ struct emacs_env_private *private_members;
+
+ /* Memory management. */
+
+ emacs_value (*make_global_ref)(emacs_env *env, emacs_value any_reference)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*free_global_ref)(emacs_env *env, emacs_value global_reference)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Non-local exit handling. */
+
+ enum emacs_funcall_exit (*non_local_exit_check)(emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_clear)(emacs_env *env) EMACS_ATTRIBUTE_NONNULL(1);
+
+ enum emacs_funcall_exit (*non_local_exit_get)(
+ emacs_env *env, emacs_value *non_local_exit_symbol_out,
+ emacs_value *non_local_exit_data_out) EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
+
+ void (*non_local_exit_signal)(emacs_env *env,
+ emacs_value non_local_exit_symbol,
+ emacs_value non_local_exit_data)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_throw)(emacs_env *env, emacs_value tag,
+ emacs_value value) EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Function registration. */
+
+ emacs_value (*make_function)(
+ emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity,
+ emacs_value (*function)(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *)
+ EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1),
+ const char *documentation, void *data) EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ emacs_value (*funcall)(emacs_env *env, emacs_value function, ptrdiff_t nargs,
+ emacs_value args[]) EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*intern)(emacs_env *env, const char *symbol_name)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Type conversion. */
+
+ emacs_value (*type_of)(emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*is_not_nil)(emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*eq)(emacs_env *env, emacs_value a, emacs_value b)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ intmax_t (*extract_integer)(emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_integer)(emacs_env *env, intmax_t value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ double (*extract_float)(emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_float)(emacs_env *env, double value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
+ null-terminated string.
+
+ SIZE must point to the total size of the buffer. If BUFFER is
+ NULL or if SIZE is not big enough, write the required buffer size
+ to SIZE and return true.
+
+ Note that SIZE must include the last null byte (e.g. "abc" needs
+ a buffer of size 4).
+
+ Return true if the string was successfully copied. */
+
+ bool (*copy_string_contents)(emacs_env *env, emacs_value value, char *buffer,
+ ptrdiff_t *size_inout)
+ EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ /* Create a Lisp string from a utf8 encoded string. */
+ emacs_value (*make_string)(emacs_env *env, const char *contents,
+ ptrdiff_t length) EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Embedded pointer type. */
+ emacs_value (*make_user_ptr)(emacs_env *env,
+ void (*fin)(void *) EMACS_NOEXCEPT, void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void *(*get_user_ptr)(emacs_env *env,
+ emacs_value uptr)EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_ptr)(emacs_env *env, emacs_value uptr, void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*(*get_user_finalizer)(emacs_env *env,
+ emacs_value uptr))(void *) EMACS_NOEXCEPT
+ EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_finalizer)(emacs_env *env, emacs_value uptr,
+ void (*fin)(void *) EMACS_NOEXCEPT)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Vector functions. */
+ emacs_value (*vec_get)(emacs_env *env, emacs_value vec, ptrdiff_t i)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*vec_set)(emacs_env *env, emacs_value vec, ptrdiff_t i, emacs_value val)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ ptrdiff_t (*vec_size)(emacs_env *env, emacs_value vec)
+ EMACS_ATTRIBUTE_NONNULL(1);
+};
+
+struct emacs_env_26 {
+ /* Structure size (for version checking). */
+ ptrdiff_t size;
+
+ /* Private data; users should not touch this. */
+ struct emacs_env_private *private_members;
+
+ /* Memory management. */
+
+ emacs_value (*make_global_ref)(emacs_env *env, emacs_value any_reference)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*free_global_ref)(emacs_env *env, emacs_value global_reference)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Non-local exit handling. */
+
+ enum emacs_funcall_exit (*non_local_exit_check)(emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_clear)(emacs_env *env) EMACS_ATTRIBUTE_NONNULL(1);
+
+ enum emacs_funcall_exit (*non_local_exit_get)(
+ emacs_env *env, emacs_value *non_local_exit_symbol_out,
+ emacs_value *non_local_exit_data_out) EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
+
+ void (*non_local_exit_signal)(emacs_env *env,
+ emacs_value non_local_exit_symbol,
+ emacs_value non_local_exit_data)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_throw)(emacs_env *env, emacs_value tag,
+ emacs_value value) EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Function registration. */
+
+ emacs_value (*make_function)(
+ emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity,
+ emacs_value (*function)(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *)
+ EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1),
+ const char *documentation, void *data) EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ emacs_value (*funcall)(emacs_env *env, emacs_value function, ptrdiff_t nargs,
+ emacs_value args[]) EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*intern)(emacs_env *env, const char *symbol_name)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Type conversion. */
+
+ emacs_value (*type_of)(emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*is_not_nil)(emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*eq)(emacs_env *env, emacs_value a, emacs_value b)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ intmax_t (*extract_integer)(emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_integer)(emacs_env *env, intmax_t value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ double (*extract_float)(emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_float)(emacs_env *env, double value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
+ null-terminated string.
+
+ SIZE must point to the total size of the buffer. If BUFFER is
+ NULL or if SIZE is not big enough, write the required buffer size
+ to SIZE and return true.
+
+ Note that SIZE must include the last null byte (e.g. "abc" needs
+ a buffer of size 4).
+
+ Return true if the string was successfully copied. */
+
+ bool (*copy_string_contents)(emacs_env *env, emacs_value value, char *buffer,
+ ptrdiff_t *size_inout)
+ EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ /* Create a Lisp string from a utf8 encoded string. */
+ emacs_value (*make_string)(emacs_env *env, const char *contents,
+ ptrdiff_t length) EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Embedded pointer type. */
+ emacs_value (*make_user_ptr)(emacs_env *env,
+ void (*fin)(void *) EMACS_NOEXCEPT, void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void *(*get_user_ptr)(emacs_env *env,
+ emacs_value uptr)EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_ptr)(emacs_env *env, emacs_value uptr, void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*(*get_user_finalizer)(emacs_env *env,
+ emacs_value uptr))(void *) EMACS_NOEXCEPT
+ EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_finalizer)(emacs_env *env, emacs_value uptr,
+ void (*fin)(void *) EMACS_NOEXCEPT)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Vector functions. */
+ emacs_value (*vec_get)(emacs_env *env, emacs_value vec, ptrdiff_t i)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*vec_set)(emacs_env *env, emacs_value vec, ptrdiff_t i, emacs_value val)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ ptrdiff_t (*vec_size)(emacs_env *env, emacs_value vec)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Returns whether a quit is pending. */
+ bool (*should_quit)(emacs_env *env) EMACS_ATTRIBUTE_NONNULL(1);
+};
+
+/* Every module should define a function as follows. */
+extern int emacs_module_init(struct emacs_runtime *ert) EMACS_NOEXCEPT
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EMACS_MODULE_H */
--- /dev/null
+# Some of the most useful features in emacs-libvterm require shell-side
+# configurations. The main goal of these additional functions is to enable the
+# shell to send information to `vterm` via properly escaped sequences. A
+# function that helps in this task, `vterm_printf`, is defined below.
+
+function vterm_printf(){
+ if [ -n "$TMUX" ] && ([ "${TERM%%-*}" = "tmux" ] || [ "${TERM%%-*}" = "screen" ] ); then
+ # Tell tmux to pass the escape sequences through
+ printf "\ePtmux;\e\e]%s\007\e\\" "$1"
+ elif [ "${TERM%%-*}" = "screen" ]; then
+ # GNU screen (screen, screen-256color, screen-256color-bce)
+ printf "\eP\e]%s\007\e\\" "$1"
+ else
+ printf "\e]%s\e\\" "$1"
+ fi
+}
+
+# Completely clear the buffer. With this, everything that is not on screen
+# is erased.
+if [[ "$INSIDE_EMACS" = 'vterm' ]]; then
+ function clear(){
+ vterm_printf "51;Evterm-clear-scrollback";
+ tput clear;
+ }
+fi
+
+# With vterm_cmd you can execute Emacs commands directly from the shell.
+# For example, vterm_cmd message "HI" will print "HI".
+# To enable new commands, you have to customize Emacs's variable
+# vterm-eval-cmds.
+vterm_cmd() {
+ local vterm_elisp
+ vterm_elisp=""
+ while [ $# -gt 0 ]; do
+ vterm_elisp="$vterm_elisp""$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")"
+ shift
+ done
+ vterm_printf "51;E$vterm_elisp"
+}
+
+# This is to change the title of the buffer based on information provided by the
+# shell. See, http://tldp.org/HOWTO/Xterm-Title-4.html, for the meaning of the
+# various symbols.
+PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }"'echo -ne "\033]0;${HOSTNAME}:${PWD}\007"'
+
+# Sync directory and host in the shell with Emacs's current directory.
+# You may need to manually specify the hostname instead of $(hostname) in case
+# $(hostname) does not return the correct string to connect to the server.
+#
+# The escape sequence "51;A" has also the role of identifying the end of the
+# prompt
+vterm_prompt_end(){
+ vterm_printf "51;A$(whoami)@$(hostname):$(pwd)"
+}
+PS1=$PS1'\[$(vterm_prompt_end)\]'
--- /dev/null
+# Some of the most useful features in emacs-libvterm require shell-side
+# configurations. The main goal of these additional functions is to enable the
+# shell to send information to `vterm` via properly escaped sequences. A
+# function that helps in this task, `vterm_printf`, is defined below.
+
+function vterm_printf(){
+ if [ -n "$TMUX" ] && ([ "${TERM%%-*}" = "tmux" ] || [ "${TERM%%-*}" = "screen" ] ); then
+ # Tell tmux to pass the escape sequences through
+ printf "\ePtmux;\e\e]%s\007\e\\" "$1"
+ elif [ "${TERM%%-*}" = "screen" ]; then
+ # GNU screen (screen, screen-256color, screen-256color-bce)
+ printf "\eP\e]%s\007\e\\" "$1"
+ else
+ printf "\e]%s\e\\" "$1"
+ fi
+}
+
+# Completely clear the buffer. With this, everything that is not on screen
+# is erased.
+if [[ "$INSIDE_EMACS" = 'vterm' ]]; then
+ alias clear='vterm_printf "51;Evterm-clear-scrollback";tput clear'
+fi
+
+# With vterm_cmd you can execute Emacs commands directly from the shell.
+# For example, vterm_cmd message "HI" will print "HI".
+# To enable new commands, you have to customize Emacs's variable
+# vterm-eval-cmds.
+vterm_cmd() {
+ local vterm_elisp
+ vterm_elisp=""
+ while [ $# -gt 0 ]; do
+ vterm_elisp="$vterm_elisp""$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")"
+ shift
+ done
+ vterm_printf "51;E$vterm_elisp"
+}
+
+# This is to change the title of the buffer based on information provided by the
+# shell. See, http://tldp.org/HOWTO/Xterm-Title-4.html, for the meaning of the
+# various symbols.
+autoload -U add-zsh-hook
+add-zsh-hook -Uz chpwd (){ print -Pn "\e]2;%m:%2~\a" }
+
+# Sync directory and host in the shell with Emacs's current directory.
+# You may need to manually specify the hostname instead of $(hostname) in case
+# $(hostname) does not return the correct string to connect to the server.
+#
+# The escape sequence "51;A" has also the role of identifying the end of the
+# prompt
+vterm_prompt_end() {
+ vterm_printf "51;A$(whoami)@$(hostname):$(pwd)"
+}
+setopt PROMPT_SUBST
+PROMPT=$PROMPT'%{$(vterm_prompt_end)%}'
--- /dev/null
+# Some of the most useful features in emacs-libvterm require shell-side
+# configurations. The main goal of these additional functions is to enable the
+# shell to send information to `vterm` via properly escaped sequences. A
+# function that helps in this task, `vterm_printf`, is defined below.
+
+function vterm_printf;
+ if begin; [ -n "$TMUX" ] ; and string match -q -r "screen|tmux" "$TERM"; end
+ # tell tmux to pass the escape sequences through
+ printf "\ePtmux;\e\e]%s\007\e\\" "$argv"
+ else if string match -q -- "screen*" "$TERM"
+ # GNU screen (screen, screen-256color, screen-256color-bce)
+ printf "\eP\e]%s\007\e\\" "$argv"
+ else
+ printf "\e]%s\e\\" "$argv"
+ end
+end
+
+# Completely clear the buffer. With this, everything that is not on screen
+# is erased.
+if [ "$INSIDE_EMACS" = 'vterm' ]
+ function clear
+ vterm_printf "51;Evterm-clear-scrollback";
+ tput clear;
+ end
+end
+
+# This is to change the title of the buffer based on information provided by the
+# shell. See, http://tldp.org/HOWTO/Xterm-Title-4.html, for the meaning of the
+# various symbols.
+function fish_title
+ hostname
+ echo ":"
+ prompt_pwd
+end
+
+# With vterm_cmd you can execute Emacs commands directly from the shell.
+# For example, vterm_cmd message "HI" will print "HI".
+# To enable new commands, you have to customize Emacs's variable
+# vterm-eval-cmds.
+function vterm_cmd --description 'Run an Emacs command among the ones defined in vterm-eval-cmds.'
+ set -l vterm_elisp ()
+ for arg in $argv
+ set -a vterm_elisp (printf '"%s" ' (string replace -a -r '([\\\\"])' '\\\\\\\\$1' $arg))
+ end
+ vterm_printf '51;E'(string join '' $vterm_elisp)
+end
+
+# Sync directory and host in the shell with Emacs's current directory.
+# You may need to manually specify the hostname instead of $(hostname) in case
+# $(hostname) does not return the correct string to connect to the server.
+#
+# The escape sequence "51;A" has also the role of identifying the end of the
+# prompt
+function vterm_prompt_end;
+ vterm_printf '51;A'(whoami)'@'(hostname)':'(pwd)
+end
+
+# We are going to add a portion to the prompt, so we copy the old one
+functions --copy fish_prompt vterm_old_fish_prompt
+
+function fish_prompt --description 'Write out the prompt; do not replace this. Instead, put this at end of your file.'
+ # Remove the trailing newline from the original prompt. This is done
+ # using the string builtin from fish, but to make sure any escape codes
+ # are correctly interpreted, use %b for printf.
+ printf "%b" (string join "\n" (vterm_old_fish_prompt))
+ vterm_prompt_end
+end
--- /dev/null
+#include "utf8.h"
+
+size_t codepoint_to_utf8(const uint32_t codepoint, unsigned char buffer[4]) {
+ if (codepoint <= 0x7F) {
+ buffer[0] = codepoint;
+ return 1;
+ }
+ if (codepoint >= 0x80 && codepoint <= 0x07FF) {
+ buffer[0] = 0xC0 | (codepoint >> 6);
+ buffer[1] = 0x80 | (codepoint & 0x3F);
+ return 2;
+ }
+ if (codepoint >= 0x0800 && codepoint <= 0xFFFF) {
+ buffer[0] = 0xE0 | (codepoint >> 12);
+ buffer[1] = 0x80 | ((codepoint >> 6) & 0x3F);
+ buffer[2] = 0x80 | (codepoint & 0x3F);
+ return 3;
+ }
+
+ if (codepoint >= 0x10000 && codepoint <= 0x10FFFF) {
+ buffer[0] = 0xF0 | (codepoint >> 18);
+ buffer[1] = 0x80 | ((codepoint >> 12) & 0x3F);
+ buffer[2] = 0x80 | ((codepoint >> 6) & 0x3F);
+ buffer[3] = 0x80 | (codepoint & 0x3F);
+ return 4;
+ }
+ return 0;
+}
+
+bool utf8_to_codepoint(const unsigned char buffer[4], const size_t len,
+ uint32_t *codepoint) {
+ *codepoint = 0;
+ if (len == 1 && buffer[0] <= 0x7F) {
+ *codepoint = buffer[0];
+ return true;
+ }
+ if (len == 2 && (buffer[0] >= 0xC0 && buffer[0] <= 0xDF) &&
+ (buffer[1] >= 0x80 && buffer[1] <= 0xBF)) {
+ *codepoint = buffer[0] & 0x1F;
+ *codepoint = *codepoint << 6;
+ *codepoint = *codepoint | (buffer[1] & 0x3F);
+ return true;
+ }
+ if (len == 3 && (buffer[0] >= 0xE0 && buffer[0] <= 0xEF) &&
+ (buffer[1] >= 0x80 && buffer[1] <= 0xBF) &&
+ (buffer[2] >= 0x80 && buffer[2] <= 0xBF)) {
+ *codepoint = buffer[0] & 0xF;
+ *codepoint = *codepoint << 6;
+ *codepoint = *codepoint | (buffer[1] & 0x3F);
+ *codepoint = *codepoint << 6;
+ *codepoint = *codepoint | (buffer[2] & 0x3F);
+ return true;
+ }
+ if (len == 4 && (buffer[0] >= 0xF0 && buffer[0] <= 0xF7) &&
+ (buffer[1] >= 0x80 && buffer[1] <= 0xBF) &&
+ (buffer[2] >= 0x80 && buffer[2] <= 0xBF) &&
+ (buffer[3] >= 0x80 && buffer[3] <= 0xBF)) {
+ *codepoint = buffer[0] & 7;
+ *codepoint = *codepoint << 6;
+ *codepoint = *codepoint | (buffer[1] & 0x3F);
+ *codepoint = *codepoint << 6;
+ *codepoint = *codepoint | (buffer[2] & 0x3F);
+ *codepoint = *codepoint << 6;
+ *codepoint = *codepoint | (buffer[3] & 0x3F);
+ return true;
+ }
+
+ return false;
+}
--- /dev/null
+#ifndef UTF8_H
+#define UTF8_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+size_t codepoint_to_utf8(const uint32_t codepoint, unsigned char buffer[4]);
+bool utf8_to_codepoint(const unsigned char buffer[4], const size_t len,
+ uint32_t *codepoint);
+
+#endif /* UTF8_H */
--- /dev/null
+;;; vterm-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from vterm.el
+
+(autoload 'vterm-module-compile "vterm" "\
+Compile vterm-module." t)
+(autoload 'vterm--bookmark-handler "vterm" "\
+Handler to restore a vterm bookmark BMK.
+
+If a vterm buffer of the same name does not exist, the function will create a
+new vterm buffer of the name. It also checks the current directory and sets
+it to the bookmarked directory if needed.
+
+(fn BMK)")
+(autoload 'vterm-next-error-function "vterm" "\
+Advance to the next error message and visit the file where the error was.
+This is the value of `next-error-function' in Compilation
+buffers. Prefix arg N says how many error messages to move
+forwards (or backwards, if negative).
+
+Optional argument RESET clears all the errors.
+
+(fn N &optional RESET)" t)
+(autoload 'vterm "vterm" "\
+Create an interactive Vterm buffer.
+Start a new Vterm session, or switch to an already active
+session. Return the buffer selected (or created).
+
+With a nonnumeric prefix arg, create a new session.
+
+With a string prefix arg, create a new session with arg as buffer name.
+
+With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch
+to the session with that number, or create it if it doesn't
+already exist.
+
+The buffer name used for Vterm sessions is determined by the
+value of `vterm-buffer-name'.
+
+(fn &optional ARG)" t)
+(autoload 'vterm-other-window "vterm" "\
+Create an interactive Vterm buffer in another window.
+Start a new Vterm session, or switch to an already active
+session. Return the buffer selected (or created).
+
+With a nonnumeric prefix arg, create a new session.
+
+With a string prefix arg, create a new session with arg as buffer name.
+
+With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch
+to the session with that number, or create it if it doesn't
+already exist.
+
+The buffer name used for Vterm sessions is determined by the
+value of `vterm-buffer-name'.
+
+(fn &optional ARG)" t)
+(register-definition-prefixes "vterm" '("vterm-"))
+\f
+;;; End of scraped data
+
+(provide 'vterm-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; vterm-autoloads.el ends here
--- /dev/null
+#include "vterm-module.h"
+#include "elisp.h"
+#include "utf8.h"
+#include <assert.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <vterm.h>
+
+static LineInfo *alloc_lineinfo() {
+ LineInfo *info = malloc(sizeof(LineInfo));
+ info->directory = NULL;
+ info->prompt_col = -1;
+ return info;
+}
+void free_lineinfo(LineInfo *line) {
+ if (line == NULL) {
+ return;
+ }
+ if (line->directory != NULL) {
+ free(line->directory);
+ line->directory = NULL;
+ }
+ free(line);
+}
+static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) {
+ Term *term = (Term *)data;
+
+ if (!term->sb_size) {
+ return 0;
+ }
+
+ // copy vterm cells into sb_buffer
+ size_t c = (size_t)cols;
+ ScrollbackLine *sbrow = NULL;
+ if (term->sb_current == term->sb_size) {
+ if (term->sb_buffer[term->sb_current - 1]->cols == c) {
+ // Recycle old row if it's the right size
+ sbrow = term->sb_buffer[term->sb_current - 1];
+ } else {
+ if (term->sb_buffer[term->sb_current - 1]->info != NULL) {
+ free_lineinfo(term->sb_buffer[term->sb_current - 1]->info);
+ term->sb_buffer[term->sb_current - 1]->info = NULL;
+ }
+ free(term->sb_buffer[term->sb_current - 1]);
+ }
+
+ // Make room at the start by shifting to the right.
+ memmove(term->sb_buffer + 1, term->sb_buffer,
+ sizeof(term->sb_buffer[0]) * (term->sb_current - 1));
+
+ } else if (term->sb_current > 0) {
+ // Make room at the start by shifting to the right.
+ memmove(term->sb_buffer + 1, term->sb_buffer,
+ sizeof(term->sb_buffer[0]) * term->sb_current);
+ }
+
+ if (!sbrow) {
+ sbrow = malloc(sizeof(ScrollbackLine) + c * sizeof(sbrow->cells[0]));
+ sbrow->cols = c;
+ sbrow->info = NULL;
+ }
+ if (sbrow->info != NULL) {
+ free_lineinfo(sbrow->info);
+ }
+ sbrow->info = term->lines[0];
+ memmove(term->lines, term->lines + 1,
+ sizeof(term->lines[0]) * (term->lines_len - 1));
+ if (term->resizing) {
+ /* pushed by window height decr */
+ if (term->lines[term->lines_len - 1] != NULL) {
+ /* do not need free here ,it is reused ,we just need set null */
+ term->lines[term->lines_len - 1] = NULL;
+ }
+ term->lines_len--;
+ } else {
+ LineInfo *lastline = term->lines[term->lines_len - 1];
+ if (lastline != NULL) {
+ LineInfo *line = alloc_lineinfo();
+ if (lastline->directory != NULL) {
+ line->directory = malloc(1 + strlen(lastline->directory));
+ strcpy(line->directory, lastline->directory);
+ }
+ term->lines[term->lines_len - 1] = line;
+ }
+ }
+
+ // New row is added at the start of the storage buffer.
+ term->sb_buffer[0] = sbrow;
+ if (term->sb_current < term->sb_size) {
+ term->sb_current++;
+ }
+
+ if (term->sb_pending < term->sb_size) {
+ term->sb_pending++;
+ /* when window height decreased */
+ if (term->height_resize < 0 &&
+ term->sb_pending_by_height_decr < -term->height_resize) {
+ term->sb_pending_by_height_decr++;
+ }
+ }
+
+ memcpy(sbrow->cells, cells, c * sizeof(cells[0]));
+
+ return 1;
+}
+/// Scrollback pop handler (from pangoterm).
+///
+/// @param cols
+/// @param cells VTerm state to update.
+/// @param data Term
+static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) {
+ Term *term = (Term *)data;
+
+ if (!term->sb_current) {
+ return 0;
+ }
+
+ if (term->sb_pending) {
+ term->sb_pending--;
+ }
+
+ ScrollbackLine *sbrow = term->sb_buffer[0];
+ term->sb_current--;
+ // Forget the "popped" row by shifting the rest onto it.
+ memmove(term->sb_buffer, term->sb_buffer + 1,
+ sizeof(term->sb_buffer[0]) * (term->sb_current));
+
+ size_t cols_to_copy = (size_t)cols;
+ if (cols_to_copy > sbrow->cols) {
+ cols_to_copy = sbrow->cols;
+ }
+
+ // copy to vterm state
+ memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy);
+ size_t col;
+ for (col = cols_to_copy; col < (size_t)cols; col++) {
+ cells[col].chars[0] = 0;
+ cells[col].width = 1;
+ }
+
+ LineInfo **lines = malloc(sizeof(LineInfo *) * (term->lines_len + 1));
+
+ memmove(lines + 1, term->lines, sizeof(term->lines[0]) * term->lines_len);
+ lines[0] = sbrow->info;
+ free(sbrow);
+ term->lines_len += 1;
+ free(term->lines);
+ term->lines = lines;
+
+ return 1;
+}
+
+static int term_sb_clear(void *data) {
+ Term *term = (Term *)data;
+
+ if (term->sb_clear_pending) {
+ // Another scrollback clear is already pending, so skip this one.
+ return 0;
+ }
+
+ for (int i = 0; i < term->sb_current; i++) {
+ if (term->sb_buffer[i]->info != NULL) {
+ free_lineinfo(term->sb_buffer[i]->info);
+ term->sb_buffer[i]->info = NULL;
+ }
+ free(term->sb_buffer[i]);
+ }
+ free(term->sb_buffer);
+ term->sb_buffer = malloc(sizeof(ScrollbackLine *) * term->sb_size);
+ term->sb_clear_pending = true;
+ term->sb_current = 0;
+ term->sb_pending = 0;
+ term->sb_pending_by_height_decr = 0;
+ invalidate_terminal(term, -1, -1);
+
+ return 0;
+}
+
+static int row_to_linenr(Term *term, int row) {
+ return row != INT_MAX ? row + (int)term->sb_current + 1 : INT_MAX;
+}
+
+static int linenr_to_row(Term *term, int linenr) {
+ return linenr - (int)term->sb_current - 1;
+}
+
+static void fetch_cell(Term *term, int row, int col, VTermScreenCell *cell) {
+ if (row < 0) {
+ ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
+ if ((size_t)col < sbrow->cols) {
+ *cell = sbrow->cells[col];
+ } else {
+ // fill the pointer with an empty cell
+ VTermColor fg, bg;
+ VTermState *state = vterm_obtain_state(term->vt);
+ vterm_state_get_default_colors(state, &fg, &bg);
+
+ *cell = (VTermScreenCell){.chars = {0}, .width = 1, .bg = bg};
+ }
+ } else {
+ vterm_screen_get_cell(term->vts, (VTermPos){.row = row, .col = col}, cell);
+ }
+}
+
+static char *get_row_directory(Term *term, int row) {
+ if (row < 0) {
+ ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
+ return sbrow->info->directory;
+ /* return term->dirs[0]; */
+ } else {
+ LineInfo *line = term->lines[row];
+ return line ? line->directory : NULL;
+ }
+}
+static LineInfo *get_lineinfo(Term *term, int row) {
+ if (row < 0) {
+ ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
+ return sbrow->info;
+ /* return term->dirs[0]; */
+ } else {
+ return term->lines[row];
+ }
+}
+static bool is_eol(Term *term, int end_col, int row, int col) {
+ /* This cell is EOL if this and every cell to the right is black */
+ if (row >= 0) {
+ VTermPos pos = {.row = row, .col = col};
+ return vterm_screen_is_eol(term->vts, pos);
+ }
+
+ ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
+ int c;
+ for (c = col; c < end_col && c < sbrow->cols;) {
+ if (sbrow->cells[c].chars[0]) {
+ return 0;
+ }
+ c += sbrow->cells[c].width;
+ }
+ return 1;
+}
+static int is_end_of_prompt(Term *term, int end_col, int row, int col) {
+ LineInfo *info = get_lineinfo(term, row);
+ if (info == NULL) {
+ return 0;
+ }
+ if (info->prompt_col < 0) {
+ return 0;
+ }
+ if (info->prompt_col == col) {
+ return 1;
+ }
+ if (is_eol(term, end_col, row, col) && info->prompt_col >= col) {
+ return 1;
+ }
+ return 0;
+}
+
+static void goto_col(Term *term, emacs_env *env, int row, int end_col) {
+ int col = 0;
+ size_t offset = 0;
+ size_t beyond_eol = 0;
+
+ int height;
+ int width;
+ vterm_get_size(term->vt, &height, &width);
+
+ while (col < end_col) {
+ VTermScreenCell cell;
+ fetch_cell(term, row, col, &cell);
+ if (cell.chars[0]) {
+ if (cell.width > 1) {
+ offset += cell.width - 1;
+ }
+ } else {
+ if (is_eol(term, term->width, row, col)) {
+ offset += cell.width;
+ beyond_eol += cell.width;
+ }
+ }
+ col += cell.width;
+ }
+
+ forward_char(env, env->make_integer(env, end_col - offset));
+ emacs_value space = env->make_string(env, " ", 1);
+ for (int i = 0; i < beyond_eol; i += 1)
+ insert(env, space);
+}
+
+static void refresh_lines(Term *term, emacs_env *env, int start_row,
+ int end_row, int end_col) {
+ if (end_row < start_row) {
+ return;
+ }
+ int i, j;
+
+#define PUSH_BUFFER(c) \
+ do { \
+ if (length == capacity) { \
+ capacity += end_col * 4; \
+ buffer = realloc(buffer, capacity * sizeof(char)); \
+ } \
+ buffer[length] = (c); \
+ length++; \
+ } while (0)
+
+ int capacity = ((end_row - start_row + 1) * end_col) * 4;
+ int length = 0;
+ char *buffer = malloc(capacity * sizeof(char));
+ VTermScreenCell cell;
+ VTermScreenCell lastCell;
+ fetch_cell(term, start_row, 0, &lastCell);
+
+ for (i = start_row; i < end_row; i++) {
+
+ int newline = 0;
+ int isprompt = 0;
+ for (j = 0; j < end_col; j++) {
+ fetch_cell(term, i, j, &cell);
+ if (isprompt && length > 0) {
+ emacs_value text = render_text(env, term, buffer, length, &lastCell);
+ insert(env, render_prompt(env, text));
+ length = 0;
+ }
+
+ isprompt = is_end_of_prompt(term, end_col, i, j);
+ if (isprompt && length > 0) {
+ insert(env, render_text(env, term, buffer, length, &lastCell));
+ length = 0;
+ }
+
+ if (!compare_cells(&cell, &lastCell)) {
+ emacs_value text = render_text(env, term, buffer, length, &lastCell);
+ insert(env, text);
+ length = 0;
+ }
+
+ lastCell = cell;
+ if (cell.chars[0] == 0) {
+ if (is_eol(term, end_col, i, j)) {
+ /* This cell is EOL if this and every cell to the right is black */
+ PUSH_BUFFER('\n');
+ newline = 1;
+ break;
+ }
+ PUSH_BUFFER(' ');
+ } else {
+ for (int k = 0; k < VTERM_MAX_CHARS_PER_CELL && cell.chars[k]; ++k) {
+ unsigned char bytes[4];
+ size_t count = codepoint_to_utf8(cell.chars[k], bytes);
+ for (int l = 0; l < count; l++) {
+ PUSH_BUFFER(bytes[l]);
+ }
+ }
+ }
+
+ if (cell.width > 1) {
+ int w = cell.width - 1;
+ j = j + w;
+ }
+ }
+ if (isprompt && length > 0) {
+ emacs_value text = render_text(env, term, buffer, length, &lastCell);
+ insert(env, render_prompt(env, text));
+ length = 0;
+ isprompt = 0;
+ }
+
+ if (!newline) {
+ emacs_value text = render_text(env, term, buffer, length, &lastCell);
+ insert(env, text);
+ length = 0;
+ text = render_fake_newline(env, term);
+ insert(env, text);
+ }
+ }
+ emacs_value text = render_text(env, term, buffer, length, &lastCell);
+ insert(env, text);
+
+#undef PUSH_BUFFER
+ free(buffer);
+
+ return;
+}
+// Refresh the screen (visible part of the buffer when the terminal is
+// focused) of a invalidated terminal
+static void refresh_screen(Term *term, emacs_env *env) {
+ // Term height may have decreased before `invalid_end` reflects it.
+ term->invalid_end = MIN(term->invalid_end, term->height);
+
+ if (term->invalid_end >= term->invalid_start) {
+ int startrow = -(term->height - term->invalid_start - term->linenum_added);
+ /* startrow is negative,so we backward -startrow lines from end of buffer
+ then delete lines there.
+ */
+ goto_line(env, startrow);
+ delete_lines(env, startrow, term->invalid_end - term->invalid_start, true);
+ refresh_lines(term, env, term->invalid_start, term->invalid_end,
+ term->width);
+
+ /* term->linenum_added is lines added by window height increased */
+ term->linenum += term->linenum_added;
+ term->linenum_added = 0;
+ }
+
+ term->invalid_start = INT_MAX;
+ term->invalid_end = -1;
+}
+
+static int term_resize(int rows, int cols, void *user_data) {
+ /* can not use invalidate_terminal here */
+ /* when the window height decreased, */
+ /* the value of term->invalid_end can't bigger than window height */
+ Term *term = (Term *)user_data;
+ term->invalid_start = 0;
+ term->invalid_end = rows;
+
+ /* if rows=term->lines_len, that means term_sb_pop already resize term->lines
+ */
+ /* if rows<term->lines_len, term_sb_push would resize term->lines there */
+ /* we noly need to take care of rows>term->height */
+
+ if (rows > term->height) {
+ if (rows > term->lines_len) {
+ LineInfo **infos = term->lines;
+ term->lines = malloc(sizeof(LineInfo *) * rows);
+ memmove(term->lines, infos, sizeof(infos[0]) * term->lines_len);
+
+ LineInfo *lastline = term->lines[term->lines_len - 1];
+ for (int i = term->lines_len; i < rows; i++) {
+ if (lastline != NULL) {
+ LineInfo *line = alloc_lineinfo();
+ if (lastline->directory != NULL) {
+ line->directory =
+ malloc(1 + strlen(term->lines[term->lines_len - 1]->directory));
+ strcpy(line->directory,
+ term->lines[term->lines_len - 1]->directory);
+ }
+ term->lines[i] = line;
+ } else {
+ term->lines[i] = NULL;
+ }
+ }
+ term->lines_len = rows;
+ free(infos);
+ }
+ }
+
+ term->width = cols;
+ term->height = rows;
+
+ invalidate_terminal(term, -1, -1);
+ term->resizing = false;
+
+ return 1;
+}
+
+// Refresh the scrollback of an invalidated terminal.
+static void refresh_scrollback(Term *term, emacs_env *env) {
+ int max_line_count = (int)term->sb_current + term->height;
+ int del_cnt = 0;
+ if (term->sb_clear_pending) {
+ del_cnt = term->linenum - term->height;
+ if (del_cnt > 0) {
+ delete_lines(env, 1, del_cnt, true);
+ term->linenum -= del_cnt;
+ }
+ term->sb_clear_pending = false;
+ }
+ if (term->sb_pending > 0) {
+ // This means that either the window height has decreased or the screen
+ // became full and libvterm had to push all rows up. Convert the first
+ // pending scrollback row into a string and append it just above the visible
+ // section of the buffer
+
+ del_cnt = term->linenum - term->height - (int)term->sb_size +
+ term->sb_pending - term->sb_pending_by_height_decr;
+ if (del_cnt > 0) {
+ delete_lines(env, 1, del_cnt, true);
+ term->linenum -= del_cnt;
+ }
+
+ term->linenum += term->sb_pending;
+ del_cnt = term->linenum - max_line_count; /* extra lines at the bottom */
+ /* buf_index is negative,so we move to end of buffer,then backward
+ -buf_index lines. goto lines backward is effectively when
+ vterm-max-scrollback is a large number.
+ */
+ int buf_index = -(term->height + del_cnt);
+ goto_line(env, buf_index);
+ refresh_lines(term, env, -term->sb_pending, 0, term->width);
+
+ term->sb_pending = 0;
+ }
+
+ // Remove extra lines at the bottom
+ del_cnt = term->linenum - max_line_count;
+ if (del_cnt > 0) {
+ term->linenum -= del_cnt;
+ /* -del_cnt is negative,so we delete_lines from end of buffer.
+ this line means: delete del_cnt count of lines at end of buffer.
+ */
+ delete_lines(env, -del_cnt, del_cnt, true);
+ }
+
+ term->sb_pending_by_height_decr = 0;
+ term->height_resize = 0;
+}
+
+static void adjust_topline(Term *term, emacs_env *env) {
+ VTermState *state = vterm_obtain_state(term->vt);
+ VTermPos pos;
+ vterm_state_get_cursorpos(state, &pos);
+
+ /* pos.row-term->height is negative,so we backward term->height-pos.row
+ * lines from end of buffer
+ */
+
+ goto_line(env, pos.row - term->height);
+ goto_col(term, env, pos.row, pos.col);
+
+ emacs_value windows = get_buffer_window_list(env);
+ emacs_value swindow = selected_window(env);
+ int winnum = env->extract_integer(env, length(env, windows));
+ for (int i = 0; i < winnum; i++) {
+ emacs_value window = nth(env, i, windows);
+ if (eq(env, window, swindow)) {
+ int win_body_height =
+ env->extract_integer(env, window_body_height(env, window));
+
+ /* recenter:If ARG is negative, it counts up from the bottom of the
+ * window. (ARG should be less than the height of the window ) */
+ if (term->height - pos.row <= win_body_height) {
+ recenter(env, env->make_integer(env, pos.row - term->height));
+ } else {
+ recenter(env, env->make_integer(env, pos.row));
+ }
+ } else {
+ if (env->is_not_nil(env, window)) {
+ set_window_point(env, window, point(env));
+ }
+ }
+ }
+}
+
+static void invalidate_terminal(Term *term, int start_row, int end_row) {
+ if (start_row != -1 && end_row != -1) {
+ term->invalid_start = MIN(term->invalid_start, start_row);
+ term->invalid_end = MAX(term->invalid_end, end_row);
+ }
+ term->is_invalidated = true;
+}
+
+static int term_damage(VTermRect rect, void *data) {
+ invalidate_terminal(data, rect.start_row, rect.end_row);
+ return 1;
+}
+
+static int term_moverect(VTermRect dest, VTermRect src, void *data) {
+ invalidate_terminal(data, MIN(dest.start_row, src.start_row),
+ MAX(dest.end_row, src.end_row));
+ return 1;
+}
+
+static int term_movecursor(VTermPos new, VTermPos old, int visible,
+ void *data) {
+ Term *term = data;
+ term->cursor.row = new.row;
+ term->cursor.col = new.col;
+ invalidate_terminal(term, old.row, old.row + 1);
+ invalidate_terminal(term, new.row, new.row + 1);
+
+ return 1;
+}
+
+static void term_redraw_cursor(Term *term, emacs_env *env) {
+ if (term->cursor.cursor_blink_changed) {
+ term->cursor.cursor_blink_changed = false;
+ set_cursor_blink(env, term->cursor.cursor_blink);
+ }
+
+ if (term->cursor.cursor_type_changed) {
+ term->cursor.cursor_type_changed = false;
+
+ if (!term->cursor.cursor_visible) {
+ set_cursor_type(env, Qnil);
+ return;
+ }
+
+ switch (term->cursor.cursor_type) {
+ case VTERM_PROP_CURSORSHAPE_BLOCK:
+ set_cursor_type(env, Qbox);
+ break;
+ case VTERM_PROP_CURSORSHAPE_UNDERLINE:
+ set_cursor_type(env, Qhbar);
+ break;
+ case VTERM_PROP_CURSORSHAPE_BAR_LEFT:
+ set_cursor_type(env, Qbar);
+ break;
+ default:
+ set_cursor_type(env, Qt);
+ break;
+ }
+ }
+}
+
+static void term_redraw(Term *term, emacs_env *env) {
+ term_redraw_cursor(term, env);
+
+ if (term->is_invalidated) {
+ int oldlinenum = term->linenum;
+ refresh_scrollback(term, env);
+ refresh_screen(term, env);
+ term->linenum_added = term->linenum - oldlinenum;
+ adjust_topline(term, env);
+ term->linenum_added = 0;
+ }
+
+ if (term->title_changed) {
+ set_title(env, env->make_string(env, term->title, strlen(term->title)));
+ term->title_changed = false;
+ }
+
+ if (term->directory_changed) {
+ set_directory(
+ env, env->make_string(env, term->directory, strlen(term->directory)));
+ term->directory_changed = false;
+ }
+
+ while (term->elisp_code_first) {
+ ElispCodeListNode *node = term->elisp_code_first;
+ term->elisp_code_first = node->next;
+ emacs_value elisp_code = env->make_string(env, node->code, node->code_len);
+ vterm_eval(env, elisp_code);
+
+ free(node->code);
+ free(node);
+ }
+ term->elisp_code_p_insert = &term->elisp_code_first;
+
+ if (term->selection_data) {
+ emacs_value selection_mask = env->make_integer(env, term->selection_mask);
+ emacs_value selection_data = env->make_string(env, term->selection_data,
+ strlen(term->selection_data));
+ vterm_set_selection(env, selection_mask, selection_data);
+ free(term->selection_data);
+ term->selection_data = NULL;
+ term->selection_mask = 0;
+ }
+
+ term->is_invalidated = false;
+}
+
+static VTermScreenCallbacks vterm_screen_callbacks = {
+ .damage = term_damage,
+ .moverect = term_moverect,
+ .movecursor = term_movecursor,
+ .settermprop = term_settermprop,
+ .resize = term_resize,
+ .sb_pushline = term_sb_push,
+ .sb_popline = term_sb_pop,
+#if !defined(VTermSBClearNotExists)
+ .sb_clear = term_sb_clear,
+#endif
+};
+
+static bool compare_cells(VTermScreenCell *a, VTermScreenCell *b) {
+ bool equal = true;
+ equal = equal && vterm_color_is_equal(&a->fg, &b->fg);
+ equal = equal && vterm_color_is_equal(&a->bg, &b->bg);
+ equal = equal && (a->attrs.bold == b->attrs.bold);
+ equal = equal && (a->attrs.underline == b->attrs.underline);
+ equal = equal && (a->attrs.italic == b->attrs.italic);
+ equal = equal && (a->attrs.reverse == b->attrs.reverse);
+ equal = equal && (a->attrs.strike == b->attrs.strike);
+ return equal;
+}
+
+static bool is_key(unsigned char *key, size_t len, char *key_description) {
+ return (len == strlen(key_description) &&
+ memcmp(key, key_description, len) == 0);
+}
+
+/* str1=concat(str1,str2,str2_len,true); */
+/* str1 can be NULL */
+static char *concat(char *str1, const char *str2, size_t str2_len,
+ bool free_str1) {
+ if (str1 == NULL) {
+ str1 = malloc(str2_len + 1);
+ memcpy(str1, str2, str2_len);
+ str1[str2_len] = '\0';
+ return str1;
+ }
+ size_t str1_len = strlen(str1);
+ char *buf = malloc(str1_len + str2_len + 1);
+ memcpy(buf, str1, str1_len);
+ memcpy(&buf[str1_len], str2, str2_len);
+ buf[str1_len + str2_len] = '\0';
+ if (free_str1) {
+ free(str1);
+ }
+ return buf;
+}
+static void term_set_title(Term *term, const char *title, size_t len,
+ bool initial, bool final) {
+ if (term->title && initial) {
+ free(term->title);
+ term->title = NULL;
+ term->title_changed = false;
+ }
+ term->title = concat(term->title, title, len, true);
+ if (final) {
+ term->title_changed = true;
+ }
+ return;
+}
+
+static int term_settermprop(VTermProp prop, VTermValue *val, void *user_data) {
+ Term *term = (Term *)user_data;
+ switch (prop) {
+ case VTERM_PROP_CURSORVISIBLE:
+ invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
+ term->cursor.cursor_visible = val->boolean;
+ term->cursor.cursor_type_changed = true;
+ break;
+ case VTERM_PROP_CURSORBLINK:
+ if (term->ignore_blink_cursor)
+ break;
+ invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
+ term->cursor.cursor_blink = val->boolean;
+ term->cursor.cursor_blink_changed = true;
+ break;
+ case VTERM_PROP_CURSORSHAPE:
+ invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
+ term->cursor.cursor_type = val->number;
+ term->cursor.cursor_type_changed = true;
+ break;
+ case VTERM_PROP_TITLE:
+#ifdef VTermStringFragmentNotExists
+ term_set_title(term, val->string, strlen(val->string), true, true);
+#else
+ term_set_title(term, val->string.str, val->string.len, val->string.initial,
+ val->string.final);
+#endif
+ break;
+ case VTERM_PROP_ALTSCREEN:
+ invalidate_terminal(term, 0, term->height);
+ break;
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+static emacs_value render_text(emacs_env *env, Term *term, char *buffer,
+ int len, VTermScreenCell *cell) {
+ emacs_value text;
+ if (len == 0) {
+ text = env->make_string(env, "", 0);
+ return text;
+ } else {
+ text = env->make_string(env, buffer, len);
+ }
+
+ emacs_value fg = cell_rgb_color(env, term, cell, true);
+ emacs_value bg = cell_rgb_color(env, term, cell, false);
+ /* With vterm-disable-bold-font, vterm-disable-underline,
+ * vterm-disable-inverse-video, users can disable some text properties.
+ * Here, we check whether the text would require adding such properties.
+ * In case it does, and the user does not disable the attribute, we later
+ * append the property to the list props. If the text does not require
+ * such property, or the user disable it, we set the variable to nil.
+ * Properties that are marked as nil are not added to the text. */
+ emacs_value bold =
+ cell->attrs.bold && !term->disable_bold_font ? Qbold : Qnil;
+ emacs_value underline =
+ cell->attrs.underline && !term->disable_underline ? Qt : Qnil;
+ emacs_value italic = cell->attrs.italic ? Qitalic : Qnil;
+ emacs_value reverse =
+ cell->attrs.reverse && !term->disable_inverse_video ? Qt : Qnil;
+ emacs_value strike = cell->attrs.strike ? Qt : Qnil;
+
+ // TODO: Blink, font, dwl, dhl is missing
+ int emacs_major_version =
+ env->extract_integer(env, symbol_value(env, Qemacs_major_version));
+ emacs_value properties;
+ emacs_value props[64];
+ int props_len = 0;
+ if (env->is_not_nil(env, fg))
+ props[props_len++] = Qforeground, props[props_len++] = fg;
+ if (env->is_not_nil(env, bg))
+ props[props_len++] = Qbackground, props[props_len++] = bg;
+ if (bold != Qnil)
+ props[props_len++] = Qweight, props[props_len++] = bold;
+ if (underline != Qnil)
+ props[props_len++] = Qunderline, props[props_len++] = underline;
+ if (italic != Qnil)
+ props[props_len++] = Qslant, props[props_len++] = italic;
+ if (reverse != Qnil)
+ props[props_len++] = Qreverse, props[props_len++] = reverse;
+ if (strike != Qnil)
+ props[props_len++] = Qstrike, props[props_len++] = strike;
+ if (emacs_major_version >= 27)
+ props[props_len++] = Qextend, props[props_len++] = Qt;
+
+ properties = list(env, props, props_len);
+
+ if (props_len)
+ put_text_property(env, text, Qface, properties);
+
+ return text;
+}
+static emacs_value render_prompt(emacs_env *env, emacs_value text) {
+
+ emacs_value properties;
+
+ properties =
+ list(env, (emacs_value[]){Qvterm_prompt, Qt, Qrear_nonsticky, Qt}, 4);
+
+ add_text_properties(env, text, properties);
+
+ return text;
+}
+
+static emacs_value render_fake_newline(emacs_env *env, Term *term) {
+
+ emacs_value text;
+ text = env->make_string(env, "\n", 1);
+
+ emacs_value properties;
+
+ properties =
+ list(env, (emacs_value[]){Qvterm_line_wrap, Qt, Qrear_nonsticky, Qt}, 4);
+
+ add_text_properties(env, text, properties);
+
+ return text;
+}
+
+static emacs_value cell_rgb_color(emacs_env *env, Term *term,
+ VTermScreenCell *cell, bool is_foreground) {
+ VTermColor *color = is_foreground ? &cell->fg : &cell->bg;
+
+ int props_len = 0;
+ emacs_value props[3];
+ if (is_foreground)
+ props[props_len++] = Qforeground;
+ if (cell->attrs.underline)
+ props[props_len++] = Qunderline;
+ if (cell->attrs.reverse)
+ props[props_len++] = Qreverse;
+
+ emacs_value args = list(env, props, props_len);
+
+ /** NOTE: -10 is used as index offset for special indexes,
+ * see C-h f vterm--get-color RET
+ */
+ if (VTERM_COLOR_IS_DEFAULT_FG(color) || VTERM_COLOR_IS_DEFAULT_BG(color)) {
+ return vterm_get_color(env, -1, args);
+ }
+ if (VTERM_COLOR_IS_INDEXED(color)) {
+ if (color->indexed.idx < 16) {
+ return vterm_get_color(env, color->indexed.idx, args);
+ } else {
+ VTermState *state = vterm_obtain_state(term->vt);
+ vterm_state_get_palette_color(state, color->indexed.idx, color);
+ }
+ } else if (VTERM_COLOR_IS_RGB(color)) {
+ /* do nothing just use the argument color directly */
+ }
+
+ char buffer[8];
+ snprintf(buffer, 8, "#%02X%02X%02X", color->rgb.red, color->rgb.green,
+ color->rgb.blue);
+ return env->make_string(env, buffer, 7);
+}
+
+static void term_flush_output(Term *term, emacs_env *env) {
+ size_t len = vterm_output_get_buffer_current(term->vt);
+ if (len) {
+ char buffer[len];
+ len = vterm_output_read(term->vt, buffer, len);
+
+ emacs_value output = env->make_string(env, buffer, len);
+ env->funcall(env, Fvterm_flush_output, 1, (emacs_value[]){output});
+ }
+}
+
+static void term_clear_scrollback(Term *term, emacs_env *env) {
+ term_sb_clear(term);
+ vterm_screen_flush_damage(term->vts);
+ term_redraw(term, env);
+}
+
+static void term_process_key(Term *term, emacs_env *env, unsigned char *key,
+ size_t len, VTermModifier modifier) {
+ if (is_key(key, len, "<clear_scrollback>")) {
+ term_clear_scrollback(term, env);
+ } else if (is_key(key, len, "<start>")) {
+ tcflow(term->pty_fd, TCOON);
+ } else if (is_key(key, len, "<stop>")) {
+ tcflow(term->pty_fd, TCOOFF);
+ } else if (is_key(key, len, "<start_paste>")) {
+ vterm_keyboard_start_paste(term->vt);
+ } else if (is_key(key, len, "<end_paste>")) {
+ vterm_keyboard_end_paste(term->vt);
+ } else if (is_key(key, len, "<tab>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_TAB, modifier);
+ } else if (is_key(key, len, "<backtab>") ||
+ is_key(key, len, "<iso-lefttab>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_TAB, VTERM_MOD_SHIFT);
+ } else if (is_key(key, len, "<backspace>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_BACKSPACE, modifier);
+ } else if (is_key(key, len, "<escape>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_ESCAPE, modifier);
+ } else if (is_key(key, len, "<up>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_UP, modifier);
+ } else if (is_key(key, len, "<down>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_DOWN, modifier);
+ } else if (is_key(key, len, "<left>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_LEFT, modifier);
+ } else if (is_key(key, len, "<right>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_RIGHT, modifier);
+ } else if (is_key(key, len, "<insert>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_INS, modifier);
+ } else if (is_key(key, len, "<delete>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_DEL, modifier);
+ } else if (is_key(key, len, "<home>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_HOME, modifier);
+ } else if (is_key(key, len, "<end>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_END, modifier);
+ } else if (is_key(key, len, "<prior>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_PAGEUP, modifier);
+ } else if (is_key(key, len, "<next>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_PAGEDOWN, modifier);
+ } else if (is_key(key, len, "<f0>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(0), modifier);
+ } else if (is_key(key, len, "<f1>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(1), modifier);
+ } else if (is_key(key, len, "<f2>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(2), modifier);
+ } else if (is_key(key, len, "<f3>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(3), modifier);
+ } else if (is_key(key, len, "<f4>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(4), modifier);
+ } else if (is_key(key, len, "<f5>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(5), modifier);
+ } else if (is_key(key, len, "<f6>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(6), modifier);
+ } else if (is_key(key, len, "<f7>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(7), modifier);
+ } else if (is_key(key, len, "<f8>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(8), modifier);
+ } else if (is_key(key, len, "<f9>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(9), modifier);
+ } else if (is_key(key, len, "<f10>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(10), modifier);
+ } else if (is_key(key, len, "<f11>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(11), modifier);
+ } else if (is_key(key, len, "<f12>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(12), modifier);
+ } else if (is_key(key, len, "<kp-0>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_0, modifier);
+ } else if (is_key(key, len, "<kp-1>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_1, modifier);
+ } else if (is_key(key, len, "<kp-2>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_2, modifier);
+ } else if (is_key(key, len, "<kp-3>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_3, modifier);
+ } else if (is_key(key, len, "<kp-4>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_4, modifier);
+ } else if (is_key(key, len, "<kp-5>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_5, modifier);
+ } else if (is_key(key, len, "<kp-6>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_6, modifier);
+ } else if (is_key(key, len, "<kp-7>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_7, modifier);
+ } else if (is_key(key, len, "<kp-8>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_8, modifier);
+ } else if (is_key(key, len, "<kp-9>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_9, modifier);
+ } else if (is_key(key, len, "<kp-add>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_PLUS, modifier);
+ } else if (is_key(key, len, "<kp-subtract>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_MINUS, modifier);
+ } else if (is_key(key, len, "<kp-multiply>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_MULT, modifier);
+ } else if (is_key(key, len, "<kp-divide>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_DIVIDE, modifier);
+ } else if (is_key(key, len, "<kp-equal>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_EQUAL, modifier);
+ } else if (is_key(key, len, "<kp-decimal>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_PERIOD, modifier);
+ } else if (is_key(key, len, "<kp-separator>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_COMMA, modifier);
+ } else if (is_key(key, len, "<kp-enter>")) {
+ vterm_keyboard_key(term->vt, VTERM_KEY_KP_ENTER, modifier);
+ } else if (is_key(key, len, "j") && (modifier == VTERM_MOD_CTRL)) {
+ vterm_keyboard_unichar(term->vt, '\n', 0);
+ } else if (is_key(key, len, "SPC")) {
+ vterm_keyboard_unichar(term->vt, ' ', modifier);
+ } else if (len <= 4) {
+ uint32_t codepoint;
+ if (utf8_to_codepoint(key, len, &codepoint)) {
+ vterm_keyboard_unichar(term->vt, codepoint, modifier);
+ }
+ }
+}
+
+void term_finalize(void *object) {
+ Term *term = (Term *)object;
+ for (int i = 0; i < term->sb_current; i++) {
+ if (term->sb_buffer[i]->info != NULL) {
+ free_lineinfo(term->sb_buffer[i]->info);
+ term->sb_buffer[i]->info = NULL;
+ }
+ free(term->sb_buffer[i]);
+ }
+ if (term->title) {
+ free(term->title);
+ term->title = NULL;
+ }
+
+ if (term->directory) {
+ free(term->directory);
+ term->directory = NULL;
+ }
+
+ while (term->elisp_code_first) {
+ ElispCodeListNode *node = term->elisp_code_first;
+ term->elisp_code_first = node->next;
+ free(node->code);
+ free(node);
+ }
+ term->elisp_code_p_insert = &term->elisp_code_first;
+
+ if (term->cmd_buffer) {
+ free(term->cmd_buffer);
+ term->cmd_buffer = NULL;
+ }
+ if (term->selection_data) {
+ free(term->selection_data);
+ term->selection_data = NULL;
+ }
+
+ for (int i = 0; i < term->lines_len; i++) {
+ if (term->lines[i] != NULL) {
+ free_lineinfo(term->lines[i]);
+ term->lines[i] = NULL;
+ }
+ }
+
+ if (term->pty_fd > 0) {
+ close(term->pty_fd);
+ }
+
+ free(term->sb_buffer);
+ free(term->lines);
+ vterm_free(term->vt);
+ free(term);
+}
+
+static int handle_osc_cmd_51(Term *term, char subCmd, char *buffer) {
+ if (subCmd == 'A') {
+ /* "51;A" sets the current directory */
+ /* "51;A" has also the role of identifying the end of the prompt */
+ if (term->directory != NULL) {
+ free(term->directory);
+ term->directory = NULL;
+ }
+ term->directory = malloc(strlen(buffer) + 1);
+ strcpy(term->directory, buffer);
+ term->directory_changed = true;
+
+ for (int i = term->cursor.row; i < term->lines_len; i++) {
+ if (term->lines[i] == NULL) {
+ term->lines[i] = alloc_lineinfo();
+ }
+
+ if (term->lines[i]->directory != NULL) {
+ free(term->lines[i]->directory);
+ }
+ term->lines[i]->directory = malloc(strlen(buffer) + 1);
+ strcpy(term->lines[i]->directory, buffer);
+ if (i == term->cursor.row) {
+ term->lines[i]->prompt_col = term->cursor.col;
+ } else {
+ term->lines[i]->prompt_col = -1;
+ }
+ }
+ return 1;
+ } else if (subCmd == 'E') {
+ /* "51;E" executes elisp code */
+ /* The elisp code is executed in term_redraw */
+ ElispCodeListNode *node = malloc(sizeof(ElispCodeListNode));
+ node->code_len = strlen(buffer);
+ node->code = malloc(node->code_len + 1);
+ strcpy(node->code, buffer);
+ node->next = NULL;
+
+ *(term->elisp_code_p_insert) = node;
+ term->elisp_code_p_insert = &(node->next);
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_osc_cmd(Term *term, int cmd, char *buffer) {
+ if (cmd == 51) {
+ char subCmd = '0';
+ if (strlen(buffer) == 0) {
+ return 0;
+ }
+ subCmd = buffer[0];
+ /* ++ skip the subcmd char */
+ return handle_osc_cmd_51(term, subCmd, ++buffer);
+ }
+ return 0;
+}
+/* maybe we should drop support of libvterm < v0.2 */
+/* VTermStringFragmentNotExists was introduced when libvterm is not released */
+#ifdef VTermStringFragmentNotExists
+static int osc_callback(const char *command, size_t cmdlen, void *user) {
+ Term *term = (Term *)user;
+ char buffer[cmdlen + 1];
+ buffer[cmdlen] = '\0';
+ memcpy(buffer, command, cmdlen);
+
+ if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' && buffer[2] == ';' &&
+ buffer[3] == 'A') {
+ return handle_osc_cmd_51(term, 'A', &buffer[4]);
+ } else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' &&
+ buffer[2] == ';' && buffer[3] == 'E') {
+ return handle_osc_cmd_51(term, 'E', &buffer[4]);
+ }
+ return 0;
+}
+static VTermParserCallbacks parser_callbacks = {
+ .text = NULL,
+ .control = NULL,
+ .escape = NULL,
+ .csi = NULL,
+ .osc = &osc_callback,
+ .dcs = NULL,
+};
+#else
+
+static int osc_callback(int cmd, VTermStringFragment frag, void *user) {
+ /* osc_callback (OSC = Operating System Command) */
+
+ /* We interpret escape codes that start with "51;" */
+ /* "51;A" sets the current directory */
+ /* "51;A" has also the role of identifying the end of the prompt */
+ /* "51;E" executes elisp code */
+ /* The elisp code is executed in term_redraw */
+ Term *term = (Term *)user;
+
+ if (frag.initial) {
+ /* drop old fragment,because this is a initial fragment */
+ if (term->cmd_buffer) {
+ free(term->cmd_buffer);
+ term->cmd_buffer = NULL;
+ }
+ }
+
+ if (!frag.initial && !frag.final && frag.len == 0) {
+ return 0;
+ }
+
+ term->cmd_buffer = concat(term->cmd_buffer, frag.str, frag.len, true);
+ if (frag.final) {
+ handle_osc_cmd(term, cmd, term->cmd_buffer);
+ free(term->cmd_buffer);
+ term->cmd_buffer = NULL;
+ }
+ return 0;
+}
+static VTermStateFallbacks parser_callbacks = {
+ .control = NULL,
+ .csi = NULL,
+ .osc = &osc_callback,
+ .dcs = NULL,
+};
+#ifndef VTermSelectionMaskNotExists
+static int set_selection(VTermSelectionMask mask, VTermStringFragment frag,
+ void *user) {
+ Term *term = (Term *)user;
+
+ if (frag.initial) {
+ term->selection_mask = mask;
+ if (term->selection_data) {
+ free(term->selection_data);
+ }
+ term->selection_data = NULL;
+ }
+
+ if (frag.len) {
+ term->selection_data =
+ concat(term->selection_data, frag.str, frag.len, true);
+ }
+ return 1;
+}
+/* OSC 52 ; Pc ; Pd BEL */
+/* Manipulate Selection Data */
+/* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html */
+/* test by printf "\033]52;c;$(printf "%s" "blabla" | base64)\a" */
+/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
+/* for clipboard, primary, secondary, select, or cut buffers 0 through 7 */
+/* respectively */
+static VTermSelectionCallbacks selection_callbacks = {
+ .set = &set_selection,
+ .query = NULL,
+};
+#endif /* VTermSelectionMaskNotExists */
+
+#endif
+
+emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
+ void *data) {
+ Term *term = malloc(sizeof(Term));
+
+ int rows = env->extract_integer(env, args[0]);
+ int cols = env->extract_integer(env, args[1]);
+ int sb_size = env->extract_integer(env, args[2]);
+ int disable_bold_font = env->is_not_nil(env, args[3]);
+ int disable_underline = env->is_not_nil(env, args[4]);
+ int disable_inverse_video = env->is_not_nil(env, args[5]);
+ int ignore_blink_cursor = env->is_not_nil(env, args[6]);
+ int set_bold_hightbright = env->is_not_nil(env, args[7]);
+
+ term->vt = vterm_new(rows, cols);
+ vterm_set_utf8(term->vt, 1);
+
+ term->vts = vterm_obtain_screen(term->vt);
+
+ VTermState *state = vterm_obtain_state(term->vt);
+ vterm_state_set_unrecognised_fallbacks(state, &parser_callbacks, term);
+
+#ifndef VTermSelectionMaskNotExists
+ vterm_state_set_selection_callbacks(state, &selection_callbacks, term,
+ term->selection_buf, SELECTION_BUF_LEN);
+#endif
+ vterm_state_set_bold_highbright(state, set_bold_hightbright);
+
+ vterm_screen_reset(term->vts, 1);
+ vterm_screen_set_callbacks(term->vts, &vterm_screen_callbacks, term);
+ vterm_screen_set_damage_merge(term->vts, VTERM_DAMAGE_SCROLL);
+ vterm_screen_enable_altscreen(term->vts, true);
+ term->sb_size = MIN(SB_MAX, sb_size);
+ term->sb_current = 0;
+ term->sb_pending = 0;
+ term->sb_clear_pending = false;
+ term->sb_pending_by_height_decr = 0;
+ term->sb_buffer = malloc(sizeof(ScrollbackLine *) * term->sb_size);
+ term->invalid_start = 0;
+ term->invalid_end = rows;
+ term->is_invalidated = false;
+ term->width = cols;
+ term->height = rows;
+ term->height_resize = 0;
+ term->disable_bold_font = disable_bold_font;
+ term->disable_underline = disable_underline;
+ term->disable_inverse_video = disable_inverse_video;
+ term->ignore_blink_cursor = ignore_blink_cursor;
+ emacs_value newline = env->make_string(env, "\n", 1);
+ for (int i = 0; i < term->height; i++) {
+ insert(env, newline);
+ }
+ term->linenum = term->height;
+ term->linenum_added = 0;
+ term->resizing = false;
+
+ term->pty_fd = -1;
+
+ term->title = NULL;
+ term->title_changed = false;
+
+ term->cursor.row = 0;
+ term->cursor.col = 0;
+ term->cursor.cursor_type = -1;
+ term->cursor.cursor_visible = true;
+ term->cursor.cursor_type_changed = false;
+ term->cursor.cursor_blink = false;
+ term->cursor.cursor_blink_changed = false;
+ term->directory = NULL;
+ term->directory_changed = false;
+ term->elisp_code_first = NULL;
+ term->elisp_code_p_insert = &term->elisp_code_first;
+ term->selection_data = NULL;
+ term->selection_mask = 0;
+
+ term->cmd_buffer = NULL;
+
+ term->lines = malloc(sizeof(LineInfo *) * rows);
+ term->lines_len = rows;
+ for (int i = 0; i < rows; i++) {
+ term->lines[i] = NULL;
+ }
+
+ return env->make_user_ptr(env, term_finalize, term);
+}
+
+emacs_value Fvterm_update(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
+ void *data) {
+ Term *term = env->get_user_ptr(env, args[0]);
+
+ // Process keys
+ if (nargs > 1) {
+ ptrdiff_t len = string_bytes(env, args[1]);
+ unsigned char key[len];
+ env->copy_string_contents(env, args[1], (char *)key, &len);
+ VTermModifier modifier = VTERM_MOD_NONE;
+ if (nargs > 2 && env->is_not_nil(env, args[2]))
+ modifier = modifier | VTERM_MOD_SHIFT;
+ if (nargs > 3 && env->is_not_nil(env, args[3]))
+ modifier = modifier | VTERM_MOD_ALT;
+ if (nargs > 4 && env->is_not_nil(env, args[4]))
+ modifier = modifier | VTERM_MOD_CTRL;
+
+ // Ignore the final zero byte
+ term_process_key(term, env, key, len - 1, modifier);
+ }
+
+ // Flush output
+ term_flush_output(term, env);
+ if (term->is_invalidated) {
+ vterm_invalidate(env);
+ }
+
+ return env->make_integer(env, 0);
+}
+
+emacs_value Fvterm_redraw(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
+ void *data) {
+ Term *term = env->get_user_ptr(env, args[0]);
+ term_redraw(term, env);
+ return env->make_integer(env, 0);
+}
+
+emacs_value Fvterm_write_input(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *data) {
+ Term *term = env->get_user_ptr(env, args[0]);
+ ptrdiff_t len = string_bytes(env, args[1]);
+ char bytes[len];
+
+ env->copy_string_contents(env, args[1], bytes, &len);
+
+ vterm_input_write(term->vt, bytes, len);
+ vterm_screen_flush_damage(term->vts);
+
+ return env->make_integer(env, 0);
+}
+
+emacs_value Fvterm_set_size(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
+ void *data) {
+ Term *term = env->get_user_ptr(env, args[0]);
+ int rows = env->extract_integer(env, args[1]);
+ int cols = env->extract_integer(env, args[2]);
+
+ if (cols != term->width || rows != term->height) {
+ term->height_resize = rows - term->height;
+ if (rows > term->height) {
+ if (rows - term->height > term->sb_current) {
+ term->linenum_added = rows - term->height - term->sb_current;
+ }
+ }
+ term->resizing = true;
+ vterm_set_size(term->vt, rows, cols);
+ vterm_screen_flush_damage(term->vts);
+
+ term_redraw(term, env);
+ }
+
+ return Qnil;
+}
+
+emacs_value Fvterm_set_pty_name(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *data) {
+ Term *term = env->get_user_ptr(env, args[0]);
+
+ if (nargs > 1) {
+ ptrdiff_t len = string_bytes(env, args[1]);
+ char filename[len];
+
+ env->copy_string_contents(env, args[1], filename, &len);
+
+ term->pty_fd = open(filename, O_RDONLY);
+ }
+ return Qnil;
+}
+emacs_value Fvterm_get_pwd(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
+ void *data) {
+ Term *term = env->get_user_ptr(env, args[0]);
+ int linenum = env->extract_integer(env, args[1]);
+ int row = linenr_to_row(term, linenum);
+ char *dir = get_row_directory(term, row);
+
+ return dir ? env->make_string(env, dir, strlen(dir)) : Qnil;
+}
+
+emacs_value Fvterm_get_icrnl(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *data) {
+ Term *term = env->get_user_ptr(env, args[0]);
+
+ if (term->pty_fd > 0) {
+ struct termios keys;
+ tcgetattr(term->pty_fd, &keys);
+
+ if (keys.c_iflag & ICRNL)
+ return Qt;
+ else
+ return Qnil;
+ }
+ return Qnil;
+}
+
+emacs_value Fvterm_reset_cursor_point(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *data) {
+ Term *term = env->get_user_ptr(env, args[0]);
+ int line = row_to_linenr(term, term->cursor.row);
+ goto_line(env, line);
+ goto_col(term, env, term->cursor.row, term->cursor.col);
+ return point(env);
+}
+
+int emacs_module_init(struct emacs_runtime *ert) {
+ emacs_env *env = ert->get_environment(ert);
+
+ // Symbols;
+ Qt = env->make_global_ref(env, env->intern(env, "t"));
+ Qnil = env->make_global_ref(env, env->intern(env, "nil"));
+ Qnormal = env->make_global_ref(env, env->intern(env, "normal"));
+ Qbold = env->make_global_ref(env, env->intern(env, "bold"));
+ Qitalic = env->make_global_ref(env, env->intern(env, "italic"));
+ Qforeground = env->make_global_ref(env, env->intern(env, ":foreground"));
+ Qbackground = env->make_global_ref(env, env->intern(env, ":background"));
+ Qweight = env->make_global_ref(env, env->intern(env, ":weight"));
+ Qunderline = env->make_global_ref(env, env->intern(env, ":underline"));
+ Qslant = env->make_global_ref(env, env->intern(env, ":slant"));
+ Qreverse = env->make_global_ref(env, env->intern(env, ":inverse-video"));
+ Qstrike = env->make_global_ref(env, env->intern(env, ":strike-through"));
+ Qextend = env->make_global_ref(env, env->intern(env, ":extend"));
+ Qemacs_major_version =
+ env->make_global_ref(env, env->intern(env, "emacs-major-version"));
+ Qvterm_line_wrap =
+ env->make_global_ref(env, env->intern(env, "vterm-line-wrap"));
+ Qrear_nonsticky =
+ env->make_global_ref(env, env->intern(env, "rear-nonsticky"));
+ Qvterm_prompt = env->make_global_ref(env, env->intern(env, "vterm-prompt"));
+
+ Qface = env->make_global_ref(env, env->intern(env, "font-lock-face"));
+ Qbox = env->make_global_ref(env, env->intern(env, "box"));
+ Qbar = env->make_global_ref(env, env->intern(env, "bar"));
+ Qhbar = env->make_global_ref(env, env->intern(env, "hbar"));
+ Qcursor_type = env->make_global_ref(env, env->intern(env, "cursor-type"));
+
+ // Functions
+ Fapply = env->make_global_ref(env, env->intern(env, "apply"));
+ Fblink_cursor_mode =
+ env->make_global_ref(env, env->intern(env, "blink-cursor-mode"));
+ Fsymbol_value = env->make_global_ref(env, env->intern(env, "symbol-value"));
+ Flength = env->make_global_ref(env, env->intern(env, "length"));
+ Flist = env->make_global_ref(env, env->intern(env, "list"));
+ Fnth = env->make_global_ref(env, env->intern(env, "nth"));
+ Ferase_buffer = env->make_global_ref(env, env->intern(env, "erase-buffer"));
+ Finsert = env->make_global_ref(env, env->intern(env, "vterm--insert"));
+ Fgoto_char = env->make_global_ref(env, env->intern(env, "goto-char"));
+ Fput_text_property =
+ env->make_global_ref(env, env->intern(env, "put-text-property"));
+ Fadd_text_properties =
+ env->make_global_ref(env, env->intern(env, "add-text-properties"));
+ Fset = env->make_global_ref(env, env->intern(env, "set"));
+ Fvterm_flush_output =
+ env->make_global_ref(env, env->intern(env, "vterm--flush-output"));
+ Fforward_line = env->make_global_ref(env, env->intern(env, "forward-line"));
+ Fgoto_line = env->make_global_ref(env, env->intern(env, "vterm--goto-line"));
+ Fdelete_lines =
+ env->make_global_ref(env, env->intern(env, "vterm--delete-lines"));
+ Frecenter = env->make_global_ref(env, env->intern(env, "recenter"));
+ Fset_window_point =
+ env->make_global_ref(env, env->intern(env, "set-window-point"));
+ Fwindow_body_height =
+ env->make_global_ref(env, env->intern(env, "window-body-height"));
+
+ Fpoint = env->make_global_ref(env, env->intern(env, "point"));
+ Fforward_char = env->make_global_ref(env, env->intern(env, "forward-char"));
+ Fget_buffer_window_list =
+ env->make_global_ref(env, env->intern(env, "get-buffer-window-list"));
+ Fselected_window =
+ env->make_global_ref(env, env->intern(env, "selected-window"));
+
+ Fvterm_set_title =
+ env->make_global_ref(env, env->intern(env, "vterm--set-title"));
+ Fvterm_set_directory =
+ env->make_global_ref(env, env->intern(env, "vterm--set-directory"));
+ Fvterm_invalidate =
+ env->make_global_ref(env, env->intern(env, "vterm--invalidate"));
+ Feq = env->make_global_ref(env, env->intern(env, "eq"));
+ Fvterm_get_color =
+ env->make_global_ref(env, env->intern(env, "vterm--get-color"));
+ Fvterm_eval = env->make_global_ref(env, env->intern(env, "vterm--eval"));
+ Fvterm_set_selection =
+ env->make_global_ref(env, env->intern(env, "vterm--set-selection"));
+
+ // Exported functions
+ emacs_value fun;
+ fun =
+ env->make_function(env, 4, 8, Fvterm_new, "Allocate a new vterm.", NULL);
+ bind_function(env, "vterm--new", fun);
+
+ fun = env->make_function(env, 1, 5, Fvterm_update,
+ "Process io and update the screen.", NULL);
+ bind_function(env, "vterm--update", fun);
+
+ fun =
+ env->make_function(env, 1, 1, Fvterm_redraw, "Redraw the screen.", NULL);
+ bind_function(env, "vterm--redraw", fun);
+
+ fun = env->make_function(env, 2, 2, Fvterm_write_input,
+ "Write input to vterm.", NULL);
+ bind_function(env, "vterm--write-input", fun);
+
+ fun = env->make_function(env, 3, 3, Fvterm_set_size,
+ "Set the size of the terminal.", NULL);
+ bind_function(env, "vterm--set-size", fun);
+
+ fun = env->make_function(env, 2, 2, Fvterm_set_pty_name,
+ "Set the name of the pty.", NULL);
+ bind_function(env, "vterm--set-pty-name", fun);
+ fun = env->make_function(env, 2, 2, Fvterm_get_pwd,
+ "Get the working directory of at line n.", NULL);
+ bind_function(env, "vterm--get-pwd-raw", fun);
+ fun = env->make_function(env, 1, 1, Fvterm_reset_cursor_point,
+ "Reset cursor position.", NULL);
+ bind_function(env, "vterm--reset-point", fun);
+
+ fun = env->make_function(env, 1, 1, Fvterm_get_icrnl,
+ "Get the icrnl state of the pty", NULL);
+ bind_function(env, "vterm--get-icrnl", fun);
+
+ provide(env, "vterm-module");
+
+ return 0;
+}
--- /dev/null
+#ifndef VTERM_MODULE_H
+#define VTERM_MODULE_H
+
+#include "emacs-module.h"
+#include <inttypes.h>
+#include <stdbool.h>
+#include <vterm.h>
+
+// https://gcc.gnu.org/wiki/Visibility
+#if defined _WIN32 || defined __CYGWIN__
+#ifdef __GNUC__
+#define VTERM_EXPORT __attribute__((dllexport))
+#else
+#define VTERM_EXPORT __declspec(dllexport)
+#endif
+#else
+#if __GNUC__ >= 4
+#define VTERM_EXPORT __attribute__((visibility("default")))
+#else
+#define VTERM_EXPORT
+#endif
+#endif
+
+VTERM_EXPORT int plugin_is_GPL_compatible;
+
+#ifndef SB_MAX
+#define SB_MAX 100000 // Maximum 'scrollback' value.
+#endif
+
+#ifndef MIN
+#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
+#endif
+#ifndef MAX
+#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
+#endif
+
+typedef struct LineInfo {
+ char *directory; /* working directory */
+
+ int prompt_col; /* end column of the prompt, if the current line contains the
+ * prompt */
+} LineInfo;
+
+typedef struct ScrollbackLine {
+ size_t cols;
+ LineInfo *info;
+ VTermScreenCell cells[];
+} ScrollbackLine;
+
+typedef struct ElispCodeListNode {
+ char *code;
+ size_t code_len;
+ struct ElispCodeListNode *next;
+} ElispCodeListNode;
+
+/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
+/* clipboard, primary, secondary, select, or cut buffers 0 through 7 */
+#define SELECTION_BUF_LEN 4096
+
+typedef struct Cursor {
+ int row, col;
+ int cursor_type;
+ bool cursor_visible;
+ bool cursor_blink;
+ bool cursor_type_changed;
+ bool cursor_blink_changed;
+} Cursor;
+
+typedef struct Term {
+ VTerm *vt;
+ VTermScreen *vts;
+ // buffer used to:
+ // - convert VTermScreen cell arrays into utf8 strings
+ // - receive data from libvterm as a result of key presses.
+ ScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm
+ size_t sb_current; // number of rows pushed to sb_buffer
+ size_t sb_size; // sb_buffer size
+ // "virtual index" that points to the first sb_buffer row that we need to
+ // push to the terminal buffer when refreshing the scrollback. When negative,
+ // it actually points to entries that are no longer in sb_buffer (because the
+ // window height has increased) and must be deleted from the terminal buffer
+ int sb_pending;
+ int sb_pending_by_height_decr;
+ bool sb_clear_pending;
+ long linenum;
+ long linenum_added;
+
+ int invalid_start, invalid_end; // invalid rows in libvterm screen
+ bool is_invalidated;
+
+ Cursor cursor;
+ char *title;
+ bool title_changed;
+
+ char *directory;
+ bool directory_changed;
+
+ // Single-linked list of elisp_code.
+ // Newer commands are added at the tail.
+ ElispCodeListNode *elisp_code_first;
+ ElispCodeListNode **elisp_code_p_insert; // pointer to the position where new
+ // node should be inserted
+
+ /* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
+ /* clipboard, primary, secondary, select, or cut buffers 0 through 7 */
+ int selection_mask; /* see VTermSelectionMask in vterm.h */
+ char *selection_data;
+ char selection_buf[SELECTION_BUF_LEN];
+
+ /* the size of dirs almost = window height, value = directory of that line */
+ LineInfo **lines;
+ int lines_len;
+
+ int width, height;
+ int height_resize;
+ bool resizing;
+ bool disable_bold_font;
+ bool disable_underline;
+ bool disable_inverse_video;
+ bool ignore_blink_cursor;
+
+ char *cmd_buffer;
+
+ int pty_fd;
+} Term;
+
+static bool compare_cells(VTermScreenCell *a, VTermScreenCell *b);
+static bool is_key(unsigned char *key, size_t len, char *key_description);
+static emacs_value render_text(emacs_env *env, Term *term, char *string,
+ int len, VTermScreenCell *cell);
+static emacs_value render_fake_newline(emacs_env *env, Term *term);
+static emacs_value render_prompt(emacs_env *env, emacs_value text);
+static emacs_value cell_rgb_color(emacs_env *env, Term *term,
+ VTermScreenCell *cell, bool is_foreground);
+
+static int term_settermprop(VTermProp prop, VTermValue *val, void *user_data);
+
+static void term_redraw(Term *term, emacs_env *env);
+static void term_flush_output(Term *term, emacs_env *env);
+static void term_process_key(Term *term, emacs_env *env, unsigned char *key,
+ size_t len, VTermModifier modifier);
+static void invalidate_terminal(Term *term, int start_row, int end_row);
+
+void term_finalize(void *object);
+
+emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
+ void *data);
+emacs_value Fvterm_update(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
+ void *data);
+emacs_value Fvterm_redraw(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
+ void *data);
+emacs_value Fvterm_write_input(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *data);
+emacs_value Fvterm_set_size(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
+ void *data);
+emacs_value Fvterm_set_pty_name(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *data);
+emacs_value Fvterm_get_icrnl(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *data);
+
+emacs_value Fvterm_get_pwd(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
+ void *data);
+
+emacs_value Fvterm_get_prompt_point(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *data);
+emacs_value Fvterm_reset_cursor_point(emacs_env *env, ptrdiff_t nargs,
+ emacs_value args[], void *data);
+
+VTERM_EXPORT int emacs_module_init(struct emacs_runtime *ert);
+
+#endif /* VTERM_MODULE_H */
--- /dev/null
+(define-package "vterm" "20240520.231" "Fully-featured terminal emulator"
+ '((emacs "25.1"))
+ :commit "df057b1af2bb89a1deb288086f13be296af42090" :authors
+ '(("Lukas Fürmetz" . "fuermetzNOSPAMmailbox.org"))
+ :maintainers
+ '(("Lukas Fürmetz" . "fuermetzNOSPAMmailbox.org"))
+ :maintainer
+ '("Lukas Fürmetz" . "fuermetzNOSPAMmailbox.org")
+ :keywords
+ '("terminals")
+ :url "https://github.com/akermu/emacs-libvterm")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; vterm.el --- Fully-featured terminal emulator -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2017-2020 by Lukas Fürmetz & Contributors
+;;
+;; Author: Lukas Fürmetz <fuermetzNOSPAMmailbox.org>
+;; Version: 0.0.2
+;; URL: https://github.com/akermu/emacs-libvterm
+;; Keywords: terminals
+;; Package-Requires: ((emacs "25.1"))
+
+
+;; This file is not part of GNU Emacs.
+
+;; This file 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 2, or (at your option)
+;; any later version.
+
+;; This file 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+
+;;; Commentary:
+;;
+;; Emacs-libvterm (vterm) is fully-fledged terminal emulator based on an
+;; external library (libvterm) loaded as a dynamic module. As a result of using
+;; compiled code (instead of elisp), emacs-libvterm is fully capable, fast, and
+;; it can seamlessly handle large outputs.
+
+;;; Installation
+
+;; Emacs-libvterm requires support for loading modules. You can check if your
+;; Emacs supports modules by inspecting the variable module-file-suffix. If it
+;; nil, than, you need to recompile Emacs or obtain a copy of Emacs with this
+;; option enabled.
+
+;; Emacs-libvterm requires CMake and libvterm. If libvterm is not available,
+;; emacs-libvterm will downloaded and compiled. In this case, libtool is
+;; needed.
+
+;; The reccomended way to install emacs-libvterm is from MELPA.
+
+;;; Usage
+
+;; To open a terminal, simply use the command M-x vterm.
+
+;;; Tips and tricks
+
+;; Adding some shell-side configuration enables a large set of additional
+;; features, including, directory tracking, prompt recognition, message passing.
+
+;;; Code:
+
+(require 'term/xterm)
+
+(unless module-file-suffix
+ (error "VTerm needs module support. Please compile Emacs with
+ the --with-modules option!"))
+
+(defvar vterm-copy-mode)
+
+;;; Compilation of the module
+
+(defcustom vterm-module-cmake-args ""
+ "Arguments given to CMake to compile vterm-module.
+
+Currently, vterm defines the following flags (in addition to the
+ones already available in CMake):
+
+`USE_SYSTEM_LIBVTERM'. Set it to `Off' to use the vendored version of
+libvterm instead of the one installed on your system.
+
+This string is given verbatim to CMake, so it has to have the
+correct syntax. An example of meaningful value for this variable
+is `-DUSE_SYSTEM_LIBVTERM=Off'."
+ :type 'string
+ :group 'vterm)
+
+(defcustom vterm-always-compile-module nil
+ "If not nil, if `vterm-module' is not found, compile it without asking.
+
+When `vterm-always-compile-module' is nil, vterm will ask for
+confirmation before compiling."
+ :type 'boolean
+ :group 'vterm)
+
+(defvar vterm-install-buffer-name " *Install vterm* "
+ "Name of the buffer used for compiling vterm-module.")
+
+(defun vterm-module--cmake-is-available ()
+ "Return t if cmake is available.
+CMake is needed to build vterm, here we check that we can find
+the executable."
+
+ (unless (executable-find "cmake")
+ (error "Vterm needs CMake to be compiled. Please, install CMake"))
+ t)
+
+;;;###autoload
+(defun vterm-module-compile ()
+ "Compile vterm-module."
+ (interactive)
+ (when (vterm-module--cmake-is-available)
+ (let* ((vterm-directory
+ (shell-quote-argument
+ ;; NOTE: This is a workaround to fix an issue with how the Emacs
+ ;; feature/native-comp branch changes the result of
+ ;; `(locate-library "vterm")'. See emacs-devel thread
+ ;; https://lists.gnu.org/archive/html/emacs-devel/2020-07/msg00306.html
+ ;; for a discussion.
+ (file-name-directory (locate-library "vterm.el" t))))
+ (make-commands
+ (concat
+ "cd " vterm-directory "; \
+ mkdir -p build; \
+ cd build; \
+ cmake -G 'Unix Makefiles' "
+ vterm-module-cmake-args
+ " ..; \
+ make; \
+ cd -"))
+ (buffer (get-buffer-create vterm-install-buffer-name)))
+ (pop-to-buffer buffer)
+ (compilation-mode)
+ (if (zerop (let ((inhibit-read-only t))
+ (call-process "sh" nil buffer t "-c" make-commands)))
+ (message "Compilation of `emacs-libvterm' module succeeded")
+ (error "Compilation of `emacs-libvterm' module failed!")))))
+
+;; If the vterm-module is not compiled yet, compile it
+(unless (require 'vterm-module nil t)
+ (if (or vterm-always-compile-module
+ (y-or-n-p "Vterm needs `vterm-module' to work. Compile it now? "))
+ (progn
+ (vterm-module-compile)
+ (require 'vterm-module))
+ (error "Vterm will not work until `vterm-module' is compiled!")))
+
+;;; Dependencies
+
+;; Generate this list with:
+;; awk -F\" '/bind_function*/ {print "(declare-function", $2, "\"vterm-module\")"}' vterm-module.c
+(declare-function vterm--new "vterm-module")
+(declare-function vterm--update "vterm-module")
+(declare-function vterm--redraw "vterm-module")
+(declare-function vterm--write-input "vterm-module")
+(declare-function vterm--set-size "vterm-module")
+(declare-function vterm--set-pty-name "vterm-module")
+(declare-function vterm--get-pwd-raw "vterm-module")
+(declare-function vterm--reset-point "vterm-module")
+(declare-function vterm--get-icrnl "vterm-module")
+
+(require 'subr-x)
+(require 'find-func)
+(require 'cl-lib)
+(require 'term)
+(require 'color)
+(require 'compile)
+(require 'face-remap)
+(require 'tramp)
+(require 'bookmark)
+
+;;; Options
+
+(defcustom vterm-shell shell-file-name
+ "The shell that gets run in the vterm."
+ :type 'string
+ :group 'vterm)
+
+(defcustom vterm-tramp-shells '(("docker" "/bin/sh"))
+ "The shell that gets run in the vterm for tramp.
+
+`vterm-tramp-shells' has to be a list of pairs of the format:
+\(TRAMP-METHOD SHELL)"
+ :type '(alist :key-type string :value-type string)
+ :group 'vterm)
+
+(defcustom vterm-buffer-name "*vterm*"
+ "The basename used for vterm buffers.
+This is the default name used when running `vterm' or
+`vterm-other-window'.
+
+With a numeric prefix argument to `vterm', the buffer name will
+be the value of this variable followed by the number. For
+example, with the numeric prefix argument 2, the buffer would be
+named \"*vterm*<2>\"."
+ :type 'string
+ :group 'vterm)
+
+(defcustom vterm-max-scrollback 1000
+ "Maximum \\='scrollback\\=' value.
+
+The maximum allowed is 100000. This value can modified by
+changing the SB_MAX variable in vterm-module.h and recompiling
+the module."
+ :type 'number
+ :group 'vterm)
+
+(defcustom vterm-min-window-width 80
+ "Minimum window width."
+ :type 'number
+ :group 'vterm)
+
+(defcustom vterm-kill-buffer-on-exit t
+ "If not nil vterm buffers are killed when the attached process is terminated.
+
+If `vterm-kill-buffer-on-exit' is set to t, when the process
+associated to a vterm buffer quits, the buffer is killed. When
+nil, the buffer will still be available as if it were in
+`fundamental-mode'."
+ :type 'boolean
+ :group 'vterm)
+
+(define-obsolete-variable-alias 'vterm-clear-scrollback
+ 'vterm-clear-scrollback-when-clearing "0.0.1")
+
+(define-obsolete-variable-alias 'vterm-use-vterm-prompt
+ 'vterm-use-vterm-prompt-detection-method "0.0.1")
+
+(defcustom vterm-clear-scrollback-when-clearing nil
+ "If not nil `vterm-clear' clears both screen and scrollback.
+
+The scrollback is everything that is not current visible on
+screen in vterm buffers.
+
+If `vterm-clear-scrollback-when-clearing' is nil, `vterm-clear'
+clears only the screen, so the scrollback is accessible moving
+the point up."
+ :type 'boolean
+ :group 'vterm)
+
+(defcustom vterm-keymap-exceptions
+ '("C-c" "C-x" "C-u" "C-g" "C-h" "C-l" "M-x" "M-o" "C-y" "M-y")
+ "Exceptions for `vterm-keymap'.
+
+If you use a keybinding with a prefix-key, add that prefix-key to
+this list. Note that after doing so that prefix-key cannot be sent
+to the terminal anymore.
+
+The mapping is done by the macro `vterm-define-key', and the
+function `vterm--exclude-keys' removes the keybindings defined in
+`vterm-keymap-exceptions'."
+ :type '(repeat string)
+ :set (lambda (sym val)
+ (set sym val)
+ (when (and (fboundp 'vterm--exclude-keys)
+ (boundp 'vterm-mode-map))
+ (vterm--exclude-keys vterm-mode-map val)))
+ :group 'vterm)
+
+(defcustom vterm-exit-functions nil
+ "List of functions called when a vterm process exits.
+
+Each function is called with two arguments: the vterm buffer of
+the process if any, and a string describing the event passed from
+the sentinel.
+
+This hook applies only to new vterms, created after setting this
+value with `add-hook'.
+
+Note that this hook will not work if another package like
+`shell-pop' sets its own sentinel to the `vterm' process."
+ :type 'hook
+ :group 'vterm)
+
+(make-obsolete-variable 'vterm-set-title-functions
+ "This variable was substituted by `vterm-buffer-name-string'."
+ "0.0.1")
+
+(defcustom vterm-buffer-name-string nil
+ "Format string for the title of vterm buffers.
+
+If `vterm-buffer-name-string' is nil, vterm will not set the
+title of its buffers. If not nil, `vterm-buffer-name-string' has
+to be a format control string (see `format') containing one
+instance of %s which will be substituted with the string TITLE.
+The argument TITLE is provided by the shell. This requires shell
+side configuration.
+
+For example, if `vterm-buffer-name-string' is set to \"vterm %s\",
+and the shell properly configured to set TITLE=$(pwd), than vterm
+buffers will be named \"vterm\" followed by the current path.
+
+See URL http://tldp.org/HOWTO/Xterm-Title-4.html for additional
+information on the how to configure the shell."
+ :type 'string
+ :group 'vterm)
+
+(defcustom vterm-term-environment-variable "xterm-256color"
+ "TERM value for terminal."
+ :type 'string
+ :group 'vterm)
+
+(defcustom vterm-environment nil
+ "List of extra environment variables to the vterm shell processes only.
+
+demo: \\='(\"env1=v1\" \"env2=v2\")"
+ :type '(repeat string)
+ :group 'vterm)
+
+
+(defcustom vterm-enable-manipulate-selection-data-by-osc52 nil
+ "Support OSC 52 MANIPULATE SELECTION DATA(libvterm 0.2 is needed).
+
+Support copy text to Emacs kill ring and system clipboard by using OSC 52.
+For example: send base64 encoded \\='foo\\=' to kill ring: echo -en \\='\\e]52;c;Zm9v\\a\\=',
+tmux can share its copy buffer to terminals by supporting osc52(like iterm2
+ xterm) you can enable this feature for tmux by :
+set -g set-clipboard on #osc 52 copy paste share with iterm
+set -ga terminal-overrides \\=',xterm*:XT:Ms=\\E]52;%p1%s;%p2%s\\007\\='
+set -ga terminal-overrides \\=',screen*:XT:Ms=\\E]52;%p1%s;%p2%s\\007\\='
+
+The clipboard querying/clearing functionality offered by OSC 52 is not
+implemented here,And for security reason, this feature is disabled
+by default."
+ :type 'boolean
+ :group 'vterm)
+
+;; TODO: Improve doc string, it should not point to the readme but it should
+;; be self-contained.
+(defcustom vterm-eval-cmds '(("find-file" find-file)
+ ("message" message)
+ ("vterm-clear-scrollback" vterm-clear-scrollback))
+ "Whitelisted Emacs functions that can be executed from vterm.
+
+You can execute Emacs functions directly from vterm buffers. To do this,
+you have to escape the name of the function and its arguments with \e]51;E.
+
+See Message passing in README.
+
+The function you want to execute has to be in `vterm-eval-cmds'.
+
+`vterm-eval-cmds' has to be a list of pairs of the format:
+\(NAME-OF-COMMAND-IN-SHELL EMACS-FUNCTION)
+
+The need for an explicit map is to avoid arbitrary code execution."
+ :type '(alist :key-type string)
+ :group 'vterm)
+
+(defcustom vterm-disable-underline nil
+ "When not-nil, underline text properties are ignored.
+
+This means that vterm will render underlined text as if it was not
+underlined."
+ :type 'boolean
+ :group 'vterm)
+
+(defcustom vterm-disable-inverse-video nil
+ "When not-nil, inverse video text properties are ignored.
+
+This means that vterm will render reversed video text as if it was not
+such."
+ :type 'boolean
+ :group 'vterm)
+
+(define-obsolete-variable-alias 'vterm-disable-bold-font
+ 'vterm-disable-bold "0.0.1")
+
+(defcustom vterm-disable-bold-font nil
+ "When not-nil, bold text properties are ignored.
+
+This means that vterm will render bold with the default face weight."
+ :type 'boolean
+ :group 'vterm)
+
+(defcustom vterm-set-bold-hightbright nil
+ "When not-nil, using hightbright colors for bolded text, see #549."
+ :type 'boolean
+ :group 'vterm)
+
+(defcustom vterm-ignore-blink-cursor t
+ "When t, vterm will ignore request from application to turn on/off cursor blink.
+
+If nil, cursor in any window may begin to blink or not blink because
+`blink-cursor-mode`is a global minor mode in Emacs,
+you can use `M-x blink-cursor-mode` to toggle."
+ :type 'boolean
+ :group 'vterm)
+
+(defcustom vterm-copy-exclude-prompt t
+ "When not-nil, the prompt is not included by `vterm-copy-mode-done'."
+ :type 'boolean
+ :group 'vterm)
+
+(defcustom vterm-use-vterm-prompt-detection-method t
+ "When not-nil, the prompt is detected through the shell.
+
+Vterm needs to know where the shell prompt is to enable all the
+available features. There are two supported ways to do this.
+First, the shell can inform vterm on the location of the prompt.
+This requires shell-side configuration: the escape code 51;A is
+used to set the current directory and prompt location. This
+detection method is the most-reliable. To use it, you have
+to change your shell prompt to print 51;A.
+
+The second method is using a regular expression. This method does
+not require any shell-side configuration. See
+`term-prompt-regexp', for more information."
+ :type 'boolean
+ :group 'vterm)
+
+(defcustom vterm-bookmark-check-dir t
+ "When set to non-nil, also restore directory when restoring a vterm bookmark."
+ :type 'boolean
+ :group 'vterm)
+
+(defcustom vterm-copy-mode-remove-fake-newlines nil
+ "When not-nil fake newlines are removed on entering copy mode.
+
+vterm inserts \\='fake\\=' newlines purely for rendering. When using
+vterm-copy-mode these are in conflict with many emacs functions
+like isearch-forward. if this varialbe is not-nil the
+fake-newlines are removed on entering copy-mode and re-inserted
+on leaving copy mode. Also truncate-lines is set to t on entering
+copy-mode and set to nil on leaving."
+ :type 'boolean
+ :group 'vterm)
+
+;;; Faces
+
+(defface vterm-color-black
+ `((t :inherit term-color-black))
+ "Face used to render black color code."
+ :group 'vterm)
+
+(defface vterm-color-red
+ `((t :inherit term-color-red))
+ "Face used to render red color code."
+ :group 'vterm)
+
+(defface vterm-color-green
+ `((t :inherit term-color-green))
+ "Face used to render green color code."
+ :group 'vterm)
+
+(defface vterm-color-yellow
+ `((t :inherit term-color-yellow))
+ "Face used to render yellow color code."
+ :group 'vterm)
+
+(defface vterm-color-blue
+ `((t :inherit term-color-blue))
+ "Face used to render blue color code."
+ :group 'vterm)
+
+(defface vterm-color-magenta
+ `((t :inherit term-color-magenta))
+ "Face used to render magenta color code."
+ :group 'vterm)
+
+(defface vterm-color-cyan
+ `((t :inherit term-color-cyan))
+ "Face used to render cyan color code."
+ :group 'vterm)
+
+(defface vterm-color-white
+ `((t :inherit term-color-white))
+ "Face used to render white color code."
+ :group 'vterm)
+
+(defface vterm-color-bright-black
+ `((t :inherit ,(if (facep 'term-color-bright-black)
+ 'term-color-bright-black
+ 'term-color-black)))
+ "Face used to render bright black color code."
+ :group 'vterm)
+
+(defface vterm-color-bright-red
+ `((t :inherit ,(if (facep 'term-color-bright-red)
+ 'term-color-bright-red
+ 'term-color-red)))
+ "Face used to render bright red color code."
+ :group 'vterm)
+
+(defface vterm-color-bright-green
+ `((t :inherit ,(if (facep 'term-color-bright-green)
+ 'term-color-bright-green
+ 'term-color-green)))
+ "Face used to render bright green color code."
+ :group 'vterm)
+
+(defface vterm-color-bright-yellow
+ `((t :inherit ,(if (facep 'term-color-bright-yellow)
+ 'term-color-bright-yellow
+ 'term-color-yellow)))
+ "Face used to render bright yellow color code."
+ :group 'vterm)
+
+(defface vterm-color-bright-blue
+ `((t :inherit ,(if (facep 'term-color-bright-blue)
+ 'term-color-bright-blue
+ 'term-color-blue)))
+ "Face used to render bright blue color code."
+ :group 'vterm)
+
+(defface vterm-color-bright-magenta
+ `((t :inherit ,(if (facep 'term-color-bright-magenta)
+ 'term-color-bright-magenta
+ 'term-color-magenta)))
+ "Face used to render bright magenta color code."
+ :group 'vterm)
+
+(defface vterm-color-bright-cyan
+ `((t :inherit ,(if (facep 'term-color-bright-cyan)
+ 'term-color-bright-cyan
+ 'term-color-cyan)))
+ "Face used to render bright cyan color code."
+ :group 'vterm)
+
+(defface vterm-color-bright-white
+ `((t :inherit ,(if (facep 'term-color-bright-white)
+ 'term-color-bright-white
+ 'term-color-white)))
+ "Face used to render bright white color code."
+ :group 'vterm)
+
+(defface vterm-color-underline
+ `((t :inherit default))
+ "Face used to render cells with underline attribute.
+Only foreground is used."
+ :group 'vterm)
+
+(defface vterm-color-inverse-video
+ `((t :inherit default))
+ "Face used to render cells with inverse video attribute.
+Only background is used."
+ :group 'vterm)
+
+;;; Variables
+
+(defvar vterm-color-palette
+ [vterm-color-black
+ vterm-color-red
+ vterm-color-green
+ vterm-color-yellow
+ vterm-color-blue
+ vterm-color-magenta
+ vterm-color-cyan
+ vterm-color-white
+ vterm-color-bright-black
+ vterm-color-bright-red
+ vterm-color-bright-green
+ vterm-color-bright-yellow
+ vterm-color-bright-blue
+ vterm-color-bright-magenta
+ vterm-color-bright-cyan
+ vterm-color-bright-white]
+ "Color palette for the foreground and background.")
+
+(defvar-local vterm--term nil
+ "Pointer to Term.")
+
+(defvar-local vterm--process nil
+ "Shell process of current term.")
+
+(defvar-local vterm--redraw-timer nil)
+(defvar-local vterm--redraw-immididately nil)
+(defvar-local vterm--linenum-remapping nil)
+(defvar-local vterm--prompt-tracking-enabled-p nil)
+(defvar-local vterm--insert-function (symbol-function #'insert))
+(defvar-local vterm--delete-char-function (symbol-function #'delete-char))
+(defvar-local vterm--delete-region-function (symbol-function #'delete-region))
+(defvar-local vterm--undecoded-bytes nil)
+(defvar-local vterm--copy-mode-fake-newlines nil)
+
+
+(defvar vterm-timer-delay 0.1
+ "Delay for refreshing the buffer after receiving updates from libvterm.
+
+A larger delary improves performance when receiving large bursts
+of data. If nil, never delay. The units are seconds.")
+
+;;; Keybindings
+
+;; We have many functions defined by vterm-define-key. Later, we will bind some
+;; of the functions. If the following is not evaluated during compilation, the compiler
+;; will complain that some functions are not defined (eg, vterm-send-C-c)
+(eval-and-compile
+ (defmacro vterm-define-key (key)
+ "Define a command that sends KEY with modifiers C and M to vterm."
+ (declare (indent defun)
+ (doc-string 3))
+ `(progn (defun ,(intern (format "vterm-send-%s" key))()
+ ,(format "Sends %s to the libvterm." key)
+ (interactive)
+ (vterm-send-key ,(char-to-string (get-byte (1- (length key)) key))
+ ,(let ((case-fold-search nil))
+ (or (string-match-p "[A-Z]$" key)
+ (string-match-p "S-" key)))
+ ,(string-match-p "M-" key)
+ ,(string-match-p "C-" key)))
+ (make-obsolete ',(intern (format "vterm-send-%s" key))
+ "use `vterm--self-insert' or `vterm-send' or `vterm-send-key'."
+ "v0.1")))
+ (make-obsolete 'vterm-define-key "" "v0.1")
+ (mapc (lambda (key)
+ (eval `(vterm-define-key ,key)))
+ (cl-loop for prefix in '("M-")
+ append (cl-loop for char from ?A to ?Z
+ for key = (format "%s%c" prefix char)
+ collect key)))
+ (mapc (lambda (key)
+ (eval `(vterm-define-key ,key)))
+ (cl-loop for prefix in '("C-" "M-" "C-S-")
+ append (cl-loop for char from ?a to ?z
+ for key = (format "%s%c" prefix char)
+ collect key))))
+
+;; Function keys and most of C- and M- bindings
+(defun vterm--exclude-keys (map exceptions)
+ "Remove EXCEPTIONS from the keys bound by `vterm-define-keys'.
+
+Exceptions are defined by `vterm-keymap-exceptions'."
+ (mapc (lambda (key)
+ (define-key map (kbd key) nil))
+ exceptions)
+ (mapc (lambda (key)
+ (define-key map (kbd key) #'vterm--self-insert))
+ (cl-loop for number from 1 to 12
+ for key = (format "<f%i>" number)
+ unless (member key exceptions)
+ collect key))
+ (let ((esc-map (lookup-key map "\e"))
+ (i 0)
+ key)
+ (unless esc-map (setq esc-map (make-keymap)))
+ (while (< i 128)
+ (setq key (make-string 1 i))
+ (unless (member (key-description key) exceptions)
+ (define-key map key 'vterm--self-insert))
+ ;; Avoid O and [. They are used in escape sequences for various keys.
+ (unless (or (eq i ?O) (eq i 91))
+ (unless (member (key-description key "\e") exceptions)
+ (define-key esc-map key 'vterm--self-insert-meta)))
+ (setq i (1+ i)))
+ (define-key map "\e" esc-map)))
+
+(defun vterm-xterm-paste (event)
+ "Handle xterm paste EVENT in vterm."
+ (interactive "e")
+ (with-temp-buffer
+ (xterm-paste event)
+ (kill-new (buffer-string)))
+ (vterm-yank))
+
+(defvar vterm-mode-map
+ (let ((map (make-sparse-keymap)))
+ (vterm--exclude-keys map vterm-keymap-exceptions)
+ (define-key map (kbd "C-]") #'vterm--self-insert)
+ (define-key map (kbd "M-<") #'vterm--self-insert)
+ (define-key map (kbd "M->") #'vterm--self-insert)
+ (define-key map [tab] #'vterm-send-tab)
+ (define-key map (kbd "TAB") #'vterm-send-tab)
+ (define-key map [backtab] #'vterm--self-insert)
+ (define-key map [backspace] #'vterm-send-backspace)
+ (define-key map (kbd "DEL") #'vterm-send-backspace)
+ (define-key map [delete] #'vterm-send-delete)
+ (define-key map [M-backspace] #'vterm-send-meta-backspace)
+ (define-key map (kbd "M-DEL") #'vterm-send-meta-backspace)
+ (define-key map [C-backspace] #'vterm-send-meta-backspace)
+ (define-key map [return] #'vterm-send-return)
+ (define-key map (kbd "RET") #'vterm-send-return)
+ (define-key map [C-left] #'vterm--self-insert)
+ (define-key map [M-left] #'vterm--self-insert)
+ (define-key map [C-right] #'vterm--self-insert)
+ (define-key map [M-right] #'vterm--self-insert)
+ (define-key map [C-up] #'vterm--self-insert)
+ (define-key map [C-down] #'vterm--self-insert)
+ (define-key map [M-up] #'vterm--self-insert)
+ (define-key map [M-down] #'vterm--self-insert)
+ (define-key map [left] #'vterm--self-insert)
+ (define-key map [right] #'vterm--self-insert)
+ (define-key map [up] #'vterm--self-insert)
+ (define-key map [down] #'vterm--self-insert)
+ (define-key map [prior] #'vterm--self-insert)
+ (define-key map [S-prior] #'scroll-down-command)
+ (define-key map [next] #'vterm--self-insert)
+ (define-key map [S-next] #'scroll-up-command)
+ (define-key map [home] #'vterm--self-insert)
+ (define-key map [end] #'vterm--self-insert)
+ (define-key map [C-home] #'vterm--self-insert)
+ (define-key map [C-end] #'vterm--self-insert)
+ (define-key map [escape] #'vterm--self-insert)
+ (define-key map [remap yank] #'vterm-yank)
+ (define-key map [remap xterm-paste] #'vterm-xterm-paste)
+ (define-key map [remap yank-pop] #'vterm-yank-pop)
+ (define-key map [remap mouse-yank-primary] #'vterm-yank-primary)
+ (define-key map [mouse-1] #'vterm-mouse-set-point)
+ (define-key map (kbd "C-SPC") #'vterm--self-insert)
+ (define-key map (kbd "S-SPC") #'vterm-send-space)
+ (define-key map (kbd "C-_") #'vterm--self-insert)
+ (define-key map [remap undo] #'vterm-undo)
+ (define-key map (kbd "M-.") #'vterm--self-insert)
+ (define-key map (kbd "M-,") #'vterm--self-insert)
+ (define-key map (kbd "C-c C-y") #'vterm--self-insert)
+ (define-key map (kbd "C-c C-c") #'vterm--self-insert)
+ (define-key map (kbd "C-c C-l") #'vterm-clear-scrollback)
+ (define-key map (kbd "C-l") #'vterm-clear)
+ (define-key map (kbd "C-\\") #'vterm--self-insert)
+ (define-key map (kbd "C-c C-g") #'vterm--self-insert)
+ (define-key map (kbd "C-c C-u") #'vterm--self-insert)
+ (define-key map [remap self-insert-command] #'vterm--self-insert)
+ (define-key map (kbd "C-c C-r") #'vterm-reset-cursor-point)
+ (define-key map (kbd "C-c C-n") #'vterm-next-prompt)
+ (define-key map (kbd "C-c C-p") #'vterm-previous-prompt)
+ (define-key map (kbd "C-c C-t") #'vterm-copy-mode)
+ map))
+
+(defvar vterm-copy-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "C-c C-t") #'vterm-copy-mode)
+ (define-key map [return] #'vterm-copy-mode-done)
+ (define-key map (kbd "RET") #'vterm-copy-mode-done)
+ (define-key map (kbd "C-c C-r") #'vterm-reset-cursor-point)
+ (define-key map (kbd "C-a") #'vterm-beginning-of-line)
+ (define-key map (kbd "C-e") #'vterm-end-of-line)
+ (define-key map (kbd "C-c C-n") #'vterm-next-prompt)
+ (define-key map (kbd "C-c C-p") #'vterm-previous-prompt)
+ map))
+
+
+;;; Mode
+
+(define-derived-mode vterm-mode fundamental-mode "VTerm"
+ "Major mode for vterm buffer."
+ (buffer-disable-undo)
+ (and (boundp 'display-line-numbers)
+ (let ((font-height (expt text-scale-mode-step text-scale-mode-amount)))
+ (setq vterm--linenum-remapping
+ (face-remap-add-relative 'line-number :height font-height))))
+ (hack-dir-local-variables)
+ (let ((vterm-env (assq 'vterm-environment dir-local-variables-alist)))
+ (when vterm-env
+ (make-local-variable 'vterm-environment)
+ (setq vterm-environment (cdr vterm-env))))
+ (let ((process-environment (append vterm-environment
+ `(,(concat "TERM="
+ vterm-term-environment-variable)
+ ,(concat "EMACS_VTERM_PATH="
+ (file-name-directory (find-library-name "vterm")))
+ "INSIDE_EMACS=vterm"
+ "LINES"
+ "COLUMNS")
+ process-environment))
+ ;; TODO: Figure out why inhibit is needed for curses to render correctly.
+ (inhibit-eol-conversion nil)
+ (coding-system-for-read 'binary)
+ (process-adaptive-read-buffering nil)
+ (width (max (- (window-max-chars-per-line) (vterm--get-margin-width))
+ vterm-min-window-width)))
+ (setq vterm--term (vterm--new (window-body-height)
+ width vterm-max-scrollback
+ vterm-disable-bold-font
+ vterm-disable-underline
+ vterm-disable-inverse-video
+ vterm-ignore-blink-cursor
+ vterm-set-bold-hightbright))
+ (setq buffer-read-only t)
+ (setq-local scroll-conservatively 101)
+ (setq-local scroll-margin 0)
+ (setq-local hscroll-margin 0)
+ (setq-local hscroll-step 1)
+ (setq-local truncate-lines t)
+
+
+ ;; Disable all automatic fontification
+ (setq-local font-lock-defaults '(nil t))
+
+ (add-function :filter-return
+ (local 'filter-buffer-substring-function)
+ #'vterm--filter-buffer-substring)
+ (setq vterm--process
+ (make-process
+ :name "vterm"
+ :buffer (current-buffer)
+ :command
+ `("/bin/sh" "-c"
+ ,(format
+ "stty -nl sane %s erase ^? rows %d columns %d >/dev/null && exec %s"
+ ;; Some stty implementations (i.e. that of *BSD) do not
+ ;; support the iutf8 option. to handle that, we run some
+ ;; heuristics to work out if the system supports that
+ ;; option and set the arg string accordingly. This is a
+ ;; gross hack but FreeBSD doesn't seem to want to fix it.
+ ;;
+ ;; See: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=220009
+ (if (eq system-type 'berkeley-unix) "" "iutf8")
+ (window-body-height)
+ width (vterm--get-shell)))
+ ;; :coding 'no-conversion
+ :connection-type 'pty
+ :file-handler t
+ :filter #'vterm--filter
+ ;; The sentinel is needed if there are exit functions or if
+ ;; vterm-kill-buffer-on-exit is set to t. In this latter case,
+ ;; vterm--sentinel will kill the buffer
+ :sentinel (when (or vterm-exit-functions
+ vterm-kill-buffer-on-exit)
+ #'vterm--sentinel))))
+
+ ;; Change major-mode is not allowed
+ ;; Vterm interfaces with an underlying process. Changing the major
+ ;; mode can break this, leading to segmentation faults.
+ (add-hook 'change-major-mode-hook
+ (lambda () (interactive)
+ (user-error "You cannot change major mode in vterm buffers")) nil t)
+
+ (vterm--set-pty-name vterm--term (process-tty-name vterm--process))
+ (process-put vterm--process 'adjust-window-size-function
+ #'vterm--window-adjust-process-window-size)
+ ;; Support to compilation-shell-minor-mode
+ ;; Is this necessary? See vterm--compilation-setup
+ (setq next-error-function 'vterm-next-error-function)
+ (setq-local bookmark-make-record-function 'vterm--bookmark-make-record))
+
+(defun vterm--get-shell ()
+ "Get the shell that gets run in the vterm."
+ (if (ignore-errors (file-remote-p default-directory))
+ (with-parsed-tramp-file-name default-directory nil
+ (or (cadr (assoc method vterm-tramp-shells))
+ (with-connection-local-variables shell-file-name)
+ vterm-shell))
+ vterm-shell))
+
+(defun vterm--bookmark-make-record ()
+ "Create a vterm bookmark.
+
+Notes down the current directory and buffer name."
+ `(nil
+ (handler . vterm--bookmark-handler)
+ (thisdir . ,default-directory)
+ (buf-name . ,(buffer-name))
+ (defaults . nil)))
+
+
+;;;###autoload
+(defun vterm--bookmark-handler (bmk)
+ "Handler to restore a vterm bookmark BMK.
+
+If a vterm buffer of the same name does not exist, the function will create a
+new vterm buffer of the name. It also checks the current directory and sets
+it to the bookmarked directory if needed."
+ (let* ((thisdir (bookmark-prop-get bmk 'thisdir))
+ (buf-name (bookmark-prop-get bmk 'buf-name))
+ (buf (get-buffer buf-name))
+ (thismode (and buf (with-current-buffer buf major-mode))))
+ ;; create if no such vterm buffer exists
+ (when (or (not buf) (not (eq thismode 'vterm-mode)))
+ (setq buf (generate-new-buffer buf-name))
+ (with-current-buffer buf
+ (when vterm-bookmark-check-dir
+ (setq default-directory thisdir))
+ (vterm-mode)))
+ ;; check the current directory
+ (with-current-buffer buf
+ (when (and vterm-bookmark-check-dir
+ (not (string-equal default-directory thisdir)))
+ (when vterm-copy-mode
+ (vterm-copy-mode-done nil))
+ (vterm-insert (concat "cd " thisdir))
+ (vterm-send-return)))
+ ;; set to this vterm buf
+ (set-buffer buf)))
+
+(defun vterm--compilation-setup ()
+ "Function to enable the option `compilation-shell-minor-mode' for vterm.
+`'compilation-shell-minor-mode' would change the value of local
+variable `next-error-function', so we should call this function in
+`compilation-shell-minor-mode-hook'."
+ (when (eq major-mode 'vterm-mode)
+ (setq next-error-function 'vterm-next-error-function)))
+
+(add-hook 'compilation-shell-minor-mode-hook #'vterm--compilation-setup)
+
+;;;###autoload
+(defun vterm-next-error-function (n &optional reset)
+ "Advance to the next error message and visit the file where the error was.
+This is the value of `next-error-function' in Compilation
+buffers. Prefix arg N says how many error messages to move
+forwards (or backwards, if negative).
+
+Optional argument RESET clears all the errors."
+ (interactive "p")
+ (let* ((pt (point))
+ (default-directory default-directory)
+ (pwd (vterm--get-pwd)))
+ (when pwd
+ (setq default-directory pwd))
+ (goto-char pt)
+ (compilation-next-error-function n reset)))
+
+;;; Copy Mode
+
+(defun vterm--enter-copy-mode ()
+ (use-local-map nil)
+ (vterm-send-stop)
+ (when vterm-copy-mode-remove-fake-newlines
+ (save-excursion
+ (setq truncate-lines nil)
+ (vterm--remove-fake-newlines t))))
+
+
+(defun vterm--exit-copy-mode ()
+ (when vterm-copy-mode-remove-fake-newlines
+ (save-excursion
+ (setq truncate-lines t)
+ (vterm--reinsert-fake-newlines)))
+ (vterm-reset-cursor-point)
+ (use-local-map vterm-mode-map)
+ (vterm-send-start))
+
+(define-minor-mode vterm-copy-mode
+ "Toggle `vterm-copy-mode'.
+
+When `vterm-copy-mode' is enabled, the terminal will not display
+additional output received from the underlying process and will
+behave similarly to buffer in `fundamental-mode'. This mode is
+typically used to copy text from vterm buffers.
+
+A conventient way to exit `vterm-copy-mode' is with
+`vterm-copy-mode-done', which copies the selected text and exit
+`vterm-copy-mode'."
+ :group 'vterm
+ :lighter " VTermCopy"
+ :keymap vterm-copy-mode-map
+ (if (equal major-mode 'vterm-mode)
+ (if vterm-copy-mode
+ (vterm--enter-copy-mode)
+ (vterm--exit-copy-mode))
+ (user-error "You cannot enable vterm-copy-mode outside vterm buffers")))
+
+(defun vterm-copy-mode-done (arg)
+ "Save the active region or line to the kill ring and exit `vterm-copy-mode'.
+
+If a region is defined then that region is killed, with no region then
+current line is killed from start to end.
+
+The option `vterm-copy-exclude-prompt' controls if the prompt
+should be included in a line copy. Using the universal prefix ARG
+will invert `vterm-copy-exclude-prompt' for that call."
+ (interactive "P")
+ (unless vterm-copy-mode
+ (user-error "This command is effective only in vterm-copy-mode"))
+ (unless (use-region-p)
+ (goto-char (vterm--get-beginning-of-line))
+ ;; Are we excluding the prompt?
+ (if (or (and vterm-copy-exclude-prompt (not arg))
+ (and (not vterm-copy-exclude-prompt) arg))
+ (goto-char (max (or (vterm--get-prompt-point) 0)
+ (vterm--get-beginning-of-line))))
+ (set-mark (point))
+ (goto-char (vterm--get-end-of-line)))
+ (kill-ring-save (region-beginning) (region-end))
+ (vterm-copy-mode -1))
+
+;;; Commands
+
+(defun vterm--self-insert-meta ()
+ (interactive)
+ (when vterm--term
+ (dolist (key (vterm--translate-event-to-args
+ last-command-event :meta))
+ (apply #'vterm-send-key key))))
+
+(defun vterm--self-insert ()
+ "Send invoking key to libvterm."
+ (interactive)
+ (when vterm--term
+ (dolist (key (vterm--translate-event-to-args
+ last-command-event))
+ (apply #'vterm-send-key key))))
+
+(defun vterm-send-key (key &optional shift meta ctrl accept-proc-output)
+ "Send KEY to libvterm with optional modifiers SHIFT, META and CTRL."
+ (deactivate-mark)
+ (when vterm--term
+ (let ((inhibit-redisplay t)
+ (inhibit-read-only t))
+ (vterm--update vterm--term key shift meta ctrl)
+ (setq vterm--redraw-immididately t)
+ (when accept-proc-output
+ (accept-process-output vterm--process vterm-timer-delay nil t)))))
+
+(defun vterm-send (key)
+ "Send KEY to libvterm. KEY can be anything `kbd' understands."
+ (dolist (key (vterm--translate-event-to-args
+ (listify-key-sequence (kbd key))))
+ (apply #'vterm-send-key key)))
+
+(defun vterm-send-next-key ()
+ "Read next input event and send it to the libvterm.
+
+With this you can directly send modified keys to applications
+running in the terminal (like Emacs or Nano)."
+ (interactive)
+ (dolist (key (vterm--translate-event-to-args
+ (read-event)))
+ (apply #'vterm-send-key key)))
+
+(defun vterm-send-start ()
+ "Output from the system is started when the system receives START."
+ (interactive)
+ (vterm-send-key "<start>"))
+
+(defun vterm-send-stop ()
+ "Output from the system is stopped when the system receives STOP."
+ (interactive)
+ (vterm-send-key "<stop>"))
+
+(defun vterm-send-return ()
+ "Send `C-m' to the libvterm."
+ (interactive)
+ (deactivate-mark)
+ (when vterm--term
+ (if (vterm--get-icrnl vterm--term)
+ (process-send-string vterm--process "\C-j")
+ (process-send-string vterm--process "\C-m"))))
+
+(defun vterm-send-tab ()
+ "Send `<tab>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<tab>"))
+
+(defun vterm-send-space ()
+ "Send `<space>' to the libvterm."
+ (interactive)
+ (vterm-send-key " "))
+
+(defun vterm-send-backspace ()
+ "Send `<backspace>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<backspace>"))
+
+(defun vterm-send-delete ()
+ "Send `<delete>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<delete>"))
+
+(defun vterm-send-meta-backspace ()
+ "Send `M-<backspace>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<backspace>" nil t))
+
+(defun vterm-send-up ()
+ "Send `<up>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<up>"))
+(make-obsolete 'vterm-send-up 'vterm--self-insert "v0.1")
+
+(defun vterm-send-down ()
+ "Send `<down>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<down>"))
+(make-obsolete 'vterm-send-down 'vterm--self-insert "v0.1")
+
+(defun vterm-send-left ()
+ "Send `<left>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<left>"))
+(make-obsolete 'vterm-send-left 'vterm--self-insert "v0.1")
+
+(defun vterm-send-right ()
+ "Send `<right>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<right>"))
+(make-obsolete 'vterm-send-right 'vterm--self-insert "v0.1")
+
+(defun vterm-send-prior ()
+ "Send `<prior>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<prior>"))
+(make-obsolete 'vterm-send-prior 'vterm--self-insert "v0.1")
+
+(defun vterm-send-next ()
+ "Send `<next>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<next>"))
+(make-obsolete 'vterm-send-next 'vterm--self-insert "v0.1")
+
+(defun vterm-send-meta-dot ()
+ "Send `M-.' to the libvterm."
+ (interactive)
+ (vterm-send-key "." nil t))
+(make-obsolete 'vterm-send-meta-dot 'vterm--self-insert "v0.1")
+
+(defun vterm-send-meta-comma ()
+ "Send `M-,' to the libvterm."
+ (interactive)
+ (vterm-send-key "," nil t))
+(make-obsolete 'vterm-send-meta-comma 'vterm--self-insert "v0.1")
+
+(defun vterm-send-ctrl-slash ()
+ "Send `C-\' to the libvterm."
+ (interactive)
+ (vterm-send-key "\\" nil nil t))
+(make-obsolete 'vterm-send-ctrl-slash 'vterm--self-insert "v0.1")
+
+(defun vterm-send-escape ()
+ "Send `<escape>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<escape>"))
+
+(defun vterm-clear-scrollback ()
+ "Send `<clear-scrollback>' to the libvterm."
+ (interactive)
+ (vterm-send-key "<clear_scrollback>"))
+
+(defun vterm-clear (&optional arg)
+ "Send `<clear>' to the libvterm.
+
+`vterm-clear-scrollback' determines whether
+`vterm-clear' should also clear the scrollback or not.
+
+This behavior can be altered by calling `vterm-clear' with a
+prefix argument ARG or with \\[universal-argument]."
+ (interactive "P")
+ (if (or
+ (and vterm-clear-scrollback-when-clearing (not arg))
+ (and arg (not vterm-clear-scrollback-when-clearing)))
+ (vterm-clear-scrollback))
+ (vterm-send-key "l" nil nil :ctrl))
+
+(defun vterm-undo ()
+ "Send `C-_' to the libvterm."
+ (interactive)
+ (vterm-send-key "_" nil nil t))
+
+(defun vterm-yank (&optional arg)
+ "Yank (paste) text in vterm.
+
+Argument ARG is passed to `yank'."
+ (interactive "P")
+ (deactivate-mark)
+ (vterm-goto-char (point))
+ (let ((inhibit-read-only t))
+ (cl-letf (((symbol-function 'insert-for-yank) #'vterm-insert))
+ (yank arg))))
+
+(defun vterm-yank-primary ()
+ "Yank text from the primary selection in vterm."
+ (interactive)
+ (vterm-goto-char (point))
+ (let ((inhibit-read-only t)
+ (primary (gui-get-primary-selection)))
+ (cl-letf (((symbol-function 'insert-for-yank) #'vterm-insert))
+ (insert-for-yank primary))))
+
+(defun vterm-yank-pop (&optional arg)
+ "Replaced text just yanked with the next entry in the kill ring.
+
+Argument ARG is passed to `yank'"
+ (interactive "p")
+ (vterm-goto-char (point))
+ (let ((inhibit-read-only t)
+ (yank-undo-function #'(lambda (_start _end) (vterm-undo))))
+ (cl-letf (((symbol-function 'insert-for-yank) #'vterm-insert))
+ (yank-pop arg))))
+
+(defun vterm-mouse-set-point (event &optional promote-to-region)
+ "Move point to the position clicked on with the mouse.
+But when clicking to the unused area below the last prompt,
+move the cursor to the prompt area."
+ (interactive "e\np")
+ (let ((pt (mouse-set-point event promote-to-region)))
+ (if (= (count-words pt (point-max)) 0)
+ (vterm-reset-cursor-point)
+ pt))
+ ;; Otherwise it selects text for every other click
+ (keyboard-quit))
+
+(defun vterm-send-string (string &optional paste-p)
+ "Send the string STRING to vterm.
+Optional argument PASTE-P paste-p."
+ (when vterm--term
+ (when paste-p
+ (vterm--update vterm--term "<start_paste>" ))
+ (dolist (char (string-to-list string))
+ (vterm--update vterm--term (char-to-string char)))
+ (when paste-p
+ (vterm--update vterm--term "<end_paste>")))
+ (setq vterm--redraw-immididately t)
+ (accept-process-output vterm--process vterm-timer-delay nil t))
+
+(defun vterm-insert (&rest contents)
+ "Insert the arguments, either strings or characters, at point.
+
+Provide similar behavior as `insert' for vterm."
+ (when vterm--term
+ (vterm--update vterm--term "<start_paste>")
+ (dolist (c contents)
+ (if (characterp c)
+ (vterm--update vterm--term (char-to-string c))
+ (dolist (char (string-to-list c))
+ (vterm--update vterm--term (char-to-string char)))))
+ (vterm--update vterm--term "<end_paste>")
+ (setq vterm--redraw-immididately t)
+ (accept-process-output vterm--process vterm-timer-delay nil t)))
+
+(defun vterm-delete-region (start end)
+ "Delete the text between START and END for vterm. "
+ (when vterm--term
+ (save-excursion
+ (when (get-text-property start 'vterm-line-wrap)
+ ;; skip over the fake newline when start there.
+ (setq start (1+ start))))
+ ;; count of chars after fake newline removed
+ (let ((count (length (filter-buffer-substring start end))))
+ (if (vterm-goto-char start)
+ (cl-loop repeat count do
+ (vterm-send-key "<delete>" nil nil nil t))
+ (let ((inhibit-read-only nil))
+ (vterm--delete-region start end))))))
+
+(defun vterm-goto-char (pos)
+ "Set point to POSITION for vterm.
+
+The return value is `t' when point moved successfully."
+ (when (and vterm--term
+ (vterm-cursor-in-command-buffer-p)
+ (vterm-cursor-in-command-buffer-p pos))
+ (vterm-reset-cursor-point)
+ (let ((diff (- pos (point))))
+ (cond
+ ((zerop diff) t) ;do not need move
+ ((< diff 0) ;backward
+ (while (and
+ (vterm--backward-char)
+ (> (point) pos)))
+ (<= (point) pos))
+ (t
+ (while (and (vterm--forward-char)
+ (< (point) pos)))
+ (>= (point) pos))))))
+
+;;; Internal
+
+(defun vterm--forward-char ()
+ "Move point 1 character forward ().
+
+the return value is `t' when cursor moved."
+ (vterm-reset-cursor-point)
+ (let ((pt (point)))
+ (vterm-send-key "<right>" nil nil nil t)
+ (cond
+ ((= (point) (1+ pt)) t)
+ ((and (> (point) pt)
+ ;; move over the fake newline
+ (get-text-property (1- (point)) 'vterm-line-wrap))
+ t)
+ ((and (= (point) (+ 4 pt))
+ (looking-back (regexp-quote "^[[C") nil)) ;escape code for <right>
+ (dotimes (_ 3) (vterm-send-key "<backspace>" nil nil nil t)) ;;delete "^[[C"
+ nil)
+ ((> (point) (1+ pt)) ;auto suggest
+ (vterm-send-key "_" nil nil t t) ;undo C-_
+ nil)
+ (t nil))))
+
+
+
+(defun vterm--backward-char ()
+ "Move point N characters backward.
+
+Return count of moved characeters."
+ (vterm-reset-cursor-point)
+ (let ((pt (point)))
+ (vterm-send-key "<left>" nil nil nil t)
+ (cond
+ ((= (point) (1- pt)) t)
+ ((and (= (point) (- pt 2))
+ ;; backward cross fake newline
+ (string-equal (buffer-substring-no-properties
+ (1+ (point)) (+ 2 (point)))
+ "\n"))
+ t)
+ ((and (= (point) (+ 4 pt))
+ (looking-back (regexp-quote "^[[D") nil)) ;escape code for <left>
+ (dotimes (_ 3) (vterm-send-key "<backspace>" nil nil nil t)) ;;delete "^[[D"
+ nil)
+ (t nil))))
+
+(defun vterm--delete-region(start end)
+ "A wrapper for `delete-region'."
+ (funcall vterm--delete-region-function start end))
+
+(defun vterm--insert(&rest content)
+ "A wrapper for `insert'."
+ (apply vterm--insert-function content))
+
+(defun vterm--delete-char(n &optional killflag)
+ "A wrapper for `delete-char'."
+ (funcall vterm--delete-char-function n killflag))
+
+(defun vterm--translate-event-to-args (event &optional meta)
+ "Translate EVENT as list of args for `vterm-send-key'.
+
+When some input method is enabled, one key may generate
+several characters, so the result of this function is a list,
+looks like: ((\"m\" :shift ))"
+ (let* ((modifiers (event-modifiers event))
+ (shift (memq 'shift modifiers))
+ (meta (or meta (memq 'meta modifiers)))
+ (ctrl (memq 'control modifiers))
+ (raw-key (event-basic-type event))
+ (ev-keys) keys)
+ (if input-method-function
+ (let ((inhibit-read-only t))
+ (setq ev-keys (funcall input-method-function raw-key))
+ (when (listp ev-keys)
+ (dolist (k ev-keys)
+ (when-let ((key (key-description (vector k))))
+ (when (and (not (symbolp event)) shift (not meta) (not ctrl))
+ (setq key (upcase key)))
+ (setq keys (append keys (list (list key shift meta ctrl))))))))
+ (when-let ((key (key-description (vector raw-key))))
+ (when (and (not (symbolp event)) shift (not meta) (not ctrl))
+ (setq key (upcase key)))
+ (setq keys (list (list key shift meta ctrl)))))
+ keys))
+
+(defun vterm--invalidate ()
+ "The terminal buffer is invalidated, the buffer needs redrawing."
+ (if (and (not vterm--redraw-immididately)
+ vterm-timer-delay)
+ (unless vterm--redraw-timer
+ (setq vterm--redraw-timer
+ (run-with-timer vterm-timer-delay nil
+ #'vterm--delayed-redraw (current-buffer))))
+ (vterm--delayed-redraw (current-buffer))
+ (setq vterm--redraw-immididately nil)))
+
+(defun vterm-check-proc (&optional buffer)
+ "Check if there is a running process associated to the vterm buffer BUFFER.
+
+BUFFER can be either a buffer or the name of one."
+ (let* ((buffer (get-buffer (or buffer (current-buffer))))
+ (proc (get-buffer-process buffer)))
+ (and proc
+ (memq (process-status proc) '(run stop open listen connect))
+ (buffer-local-value 'vterm--term buffer))))
+
+(defun vterm--delayed-redraw (buffer)
+ "Redraw the terminal buffer.
+Argument BUFFER the terminal buffer."
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (let ((inhibit-redisplay t)
+ (inhibit-read-only t)
+ (windows (get-buffer-window-list)))
+ (setq vterm--redraw-timer nil)
+ (when vterm--term
+ (vterm--redraw vterm--term)
+ (unless (zerop (window-hscroll))
+ (when (cl-member (selected-window) windows :test #'eq)
+ (set-window-hscroll (selected-window) 0))))))))
+
+;; see VTermSelectionMask in vterm.el
+;; VTERM_SELECTION_CLIPBOARD = (1<<0),
+;; VTERM_SELECTION_PRIMARY = (1<<1),
+(defconst vterm--selection-clipboard 1) ;(1<<0)
+(defconst vterm--selection-primary 2) ;(1<<1)
+(defun vterm--set-selection (mask data)
+ "OSC 52 Manipulate Selection Data.
+Search Manipulate Selection Data in
+ https://invisible-island.net/xterm/ctlseqs/ctlseqs.html ."
+ (when vterm-enable-manipulate-selection-data-by-osc52
+ (let ((select-enable-clipboard select-enable-clipboard)
+ (select-enable-primary select-enable-primary))
+ (setq select-enable-clipboard
+ (logand mask vterm--selection-clipboard))
+ (setq select-enable-primary
+ (logand mask vterm--selection-primary))
+ (kill-new data)
+ (message "kill-ring is updated by vterm OSC 52(Manipulate Selection Data)"))
+ ))
+
+;;; Entry Points
+
+;;;###autoload
+(defun vterm (&optional arg)
+ "Create an interactive Vterm buffer.
+Start a new Vterm session, or switch to an already active
+session. Return the buffer selected (or created).
+
+With a nonnumeric prefix arg, create a new session.
+
+With a string prefix arg, create a new session with arg as buffer name.
+
+With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch
+to the session with that number, or create it if it doesn't
+already exist.
+
+The buffer name used for Vterm sessions is determined by the
+value of `vterm-buffer-name'."
+ (interactive "P")
+ (vterm--internal #'pop-to-buffer-same-window arg))
+
+;;;###autoload
+(defun vterm-other-window (&optional arg)
+ "Create an interactive Vterm buffer in another window.
+Start a new Vterm session, or switch to an already active
+session. Return the buffer selected (or created).
+
+With a nonnumeric prefix arg, create a new session.
+
+With a string prefix arg, create a new session with arg as buffer name.
+
+With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch
+to the session with that number, or create it if it doesn't
+already exist.
+
+The buffer name used for Vterm sessions is determined by the
+value of `vterm-buffer-name'."
+ (interactive "P")
+ (vterm--internal #'pop-to-buffer arg))
+
+(defun vterm--internal (pop-to-buf-fun &optional arg)
+ (cl-assert vterm-buffer-name)
+ (let ((buf (cond ((numberp arg)
+ (get-buffer-create (format "%s<%d>"
+ vterm-buffer-name
+ arg)))
+ ((stringp arg) (generate-new-buffer arg))
+ (arg (generate-new-buffer vterm-buffer-name))
+ (t
+ (get-buffer-create vterm-buffer-name)))))
+ (cl-assert (and buf (buffer-live-p buf)))
+ (funcall pop-to-buf-fun buf)
+ (with-current-buffer buf
+ (unless (derived-mode-p 'vterm-mode)
+ (vterm-mode)))
+ buf))
+
+;;; Internal
+
+(defun vterm--flush-output (output)
+ "Send the virtual terminal's OUTPUT to the shell."
+ (process-send-string vterm--process output))
+;; Terminal emulation
+;; This is the standard process filter for term buffers.
+;; It emulates (most of the features of) a VT100/ANSI-style terminal.
+
+;; References:
+;; [ctlseqs]: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+;; [ECMA-48]: https://www.ecma-international.org/publications/standards/Ecma-048.htm
+;; [vt100]: https://vt100.net/docs/vt100-ug/chapter3.html
+
+(defconst vterm-control-seq-regexp
+ (concat
+ ;; A control character,
+ "\\(?:[\r\n\000\007\t\b\016\017]\\|"
+ ;; a C1 escape coded character (see [ECMA-48] section 5.3 "Elements
+ ;; of the C1 set"),
+ "\e\\(?:[DM78c=]\\|"
+ ;; another Emacs specific control sequence for term.el,
+ "AnSiT[^\n]+\n\\|"
+ ;; another Emacs specific control sequence for vterm.el
+ ;; printf "\e]%s\e\\"
+ "\\][^\e]+\e\\\\\\|"
+ ;; or an escape sequence (section 5.4 "Control Sequences"),
+ "\\[\\([\x30-\x3F]*\\)[\x20-\x2F]*[\x40-\x7E]\\)\\)")
+ "Regexp matching control sequences handled by term.el.")
+
+(defconst vterm-control-seq-prefix-regexp
+ "[\032\e]")
+
+(defun vterm--filter (process input)
+ "I/O Event. Feeds PROCESS's INPUT to the virtual terminal.
+
+Then triggers a redraw from the module."
+ (let ((inhibit-redisplay t)
+ (inhibit-eol-conversion t)
+ (inhibit-read-only t)
+ (buf (process-buffer process))
+ (i 0)
+ (str-length (length input))
+ decoded-substring
+ funny)
+ (when (buffer-live-p buf)
+ (with-current-buffer buf
+ ;; borrowed from term.el
+ ;; Handle non-control data. Decode the string before
+ ;; counting characters, to avoid garbling of certain
+ ;; multibyte characters (https://github.com/akermu/emacs-libvterm/issues/394).
+ ;; same bug of term.el https://debbugs.gnu.org/cgi/bugreport.cgi?bug=1006
+ (when vterm--undecoded-bytes
+ (setq input (concat vterm--undecoded-bytes input))
+ (setq vterm--undecoded-bytes nil)
+ (setq str-length (length input)))
+ (while (< i str-length)
+ (setq funny (string-match vterm-control-seq-regexp input i))
+ (let ((ctl-end (if funny (match-end 0)
+ (setq funny (string-match vterm-control-seq-prefix-regexp input i))
+ (if funny
+ (setq vterm--undecoded-bytes
+ (substring input funny))
+ (setq funny str-length))
+ ;; The control sequence ends somewhere
+ ;; past the end of this string.
+ (1+ str-length))))
+ (when (> funny i)
+ ;; Handle non-control data. Decode the string before
+ ;; counting characters, to avoid garbling of certain
+ ;; multibyte characters (emacs bug#1006).
+ (setq decoded-substring
+ (decode-coding-string
+ (substring input i funny)
+ locale-coding-system t))
+ ;; Check for multibyte characters that ends
+ ;; before end of string, and save it for
+ ;; next time.
+ (when (= funny str-length)
+ (let ((partial 0)
+ (count (length decoded-substring)))
+ (while (and (< partial count)
+ (eq (char-charset (aref decoded-substring
+ (- count 1 partial)))
+ 'eight-bit))
+ (cl-incf partial))
+ (when (> count partial 0)
+ (setq vterm--undecoded-bytes
+ (substring decoded-substring (- partial)))
+ (setq decoded-substring
+ (substring decoded-substring 0 (- partial)))
+ (cl-decf str-length partial)
+ (cl-decf funny partial))))
+ (ignore-errors (vterm--write-input vterm--term decoded-substring))
+ (setq i funny))
+ (when (<= ctl-end str-length)
+ (ignore-errors (vterm--write-input vterm--term (substring input i ctl-end))))
+ (setq i ctl-end)))
+ (vterm--update vterm--term)))))
+
+(defun vterm--sentinel (process event)
+ "Sentinel of vterm PROCESS.
+Argument EVENT process event."
+ (let ((buf (process-buffer process)))
+ (run-hook-with-args 'vterm-exit-functions
+ (if (buffer-live-p buf) buf nil)
+ event)
+ (if (and vterm-kill-buffer-on-exit (buffer-live-p buf))
+ (kill-buffer buf))))
+
+(defun vterm--text-scale-mode (&optional _argv)
+ "Fix `line-number' height for scaled text."
+ (and text-scale-mode
+ (equal major-mode 'vterm-mode)
+ (boundp 'display-line-numbers)
+ (let ((height (expt text-scale-mode-step
+ text-scale-mode-amount)))
+ (when vterm--linenum-remapping
+ (face-remap-remove-relative vterm--linenum-remapping))
+ (setq vterm--linenum-remapping
+ (face-remap-add-relative 'line-number :height height))))
+ (window--adjust-process-windows))
+
+(advice-add #'text-scale-mode :after #'vterm--text-scale-mode)
+
+(defun vterm--window-adjust-process-window-size (process windows)
+ "Adjust width of window WINDOWS associated to process PROCESS.
+
+`vterm-min-window-width' determines the minimum width allowed."
+ ;; We want `vterm-copy-mode' to resemble a fundamental buffer as much as
+ ;; possible. Hence, we must not call this function when the minor mode is
+ ;; enabled, otherwise the buffer would be redrawn, messing around with the
+ ;; position of the point.
+ (unless vterm-copy-mode
+ (let* ((size (funcall window-adjust-process-window-size-function
+ process windows))
+ (width (car size))
+ (height (cdr size))
+ (inhibit-read-only t))
+ (setq width (- width (vterm--get-margin-width)))
+ (setq width (max width vterm-min-window-width))
+ (when (and (processp process)
+ (process-live-p process)
+ (> width 0)
+ (> height 0))
+ (vterm--set-size vterm--term height width)
+ (cons width height)))))
+
+(defun vterm--get-margin-width ()
+ "Get margin width of vterm buffer when `display-line-numbers-mode' is enabled."
+ (let ((width 0)
+ (max-line-num (+ (frame-height) vterm-max-scrollback)))
+ (when (bound-and-true-p display-line-numbers)
+ (setq width (+ width 4
+ (string-width (number-to-string max-line-num)))))
+ width))
+
+(defun vterm--delete-lines (line-num count &optional delete-whole-line)
+ "Delete COUNT lines from LINE-NUM.
+If LINE-NUM is negative backward-line from end of buffer.
+If option DELETE-WHOLE-LINE is non-nil, then this command kills
+the whole line including its terminating newline"
+ (save-excursion
+ (when (vterm--goto-line line-num)
+ (vterm--delete-region (point) (line-end-position count))
+ (when (and delete-whole-line
+ (looking-at "\n"))
+ (vterm--delete-char 1)))))
+
+(defun vterm--goto-line (n)
+ "Go to line N and return true on success.
+If N is negative backward-line from end of buffer."
+ (cond
+ ((> n 0)
+ (goto-char (point-min))
+ (eq 0 (forward-line (1- n))))
+ (t
+ (goto-char (point-max))
+ (eq 0 (forward-line n)))))
+
+(defun vterm--set-title (title)
+ "Use TITLE to set the buffer name according to `vterm-buffer-name-string'."
+ (when vterm-buffer-name-string
+ (rename-buffer (format vterm-buffer-name-string title) t)))
+
+(defun vterm--set-directory (path)
+ "Set `default-directory' to PATH."
+ (let ((dir (vterm--get-directory path)))
+ (when dir (setq default-directory dir))))
+
+(defun vterm--get-directory (path)
+ "Get normalized directory to PATH."
+ (when path
+ (let (directory)
+ (if (string-match "^\\(.*?\\)@\\(.*?\\):\\(.*?\\)$" path)
+ (progn
+ (let ((user (match-string 1 path))
+ (host (match-string 2 path))
+ (dir (match-string 3 path)))
+ (if (and (string-equal user user-login-name)
+ (string-equal host (system-name)))
+ (progn
+ (when (file-directory-p dir)
+ (setq directory (file-name-as-directory dir))))
+ (setq directory (file-name-as-directory (concat "/-:" path))))))
+ (when (file-directory-p path)
+ (setq directory (file-name-as-directory path))))
+ directory)))
+
+(defun vterm--get-pwd (&optional linenum)
+ "Get working directory at LINENUM."
+ (when vterm--term
+ (let ((raw-pwd (vterm--get-pwd-raw
+ vterm--term
+ (or linenum (line-number-at-pos)))))
+ (when raw-pwd
+ (vterm--get-directory raw-pwd)))))
+
+(defun vterm--get-color (index &rest args)
+ "Get color by INDEX from `vterm-color-palette'.
+
+Special INDEX of -1 is used to represent default colors. ARGS
+may optionally contain `:underline' or `:inverse-video' for cells
+with underline or inverse video attribute. If ARGS contains
+`:foreground', use foreground color of the respective face
+instead of background."
+ (let ((foreground (member :foreground args))
+ (underline (member :underline args))
+ (inverse-video (member :inverse-video args)))
+ (funcall (if foreground #'face-foreground #'face-background)
+ (cond
+ ((and (>= index 0) (< index 16))
+ (elt vterm-color-palette index))
+ ((and (= index -1) foreground underline)
+ 'vterm-color-underline)
+ ((and (= index -1) (not foreground) inverse-video)
+ 'vterm-color-inverse-video)
+ (t 'default))
+ nil 'default)))
+
+(defun vterm--eval (str)
+ "Check if string STR is `vterm-eval-cmds' and execute command.
+
+All passed in arguments are strings and forwarded as string to
+the called functions."
+ (let* ((parts (split-string-and-unquote str))
+ (command (car parts))
+ (args (cdr parts))
+ (f (assoc command vterm-eval-cmds)))
+ (if f
+ (apply (cadr f) args)
+ (message "Failed to find command: %s. To execute a command,
+ add it to the `vterm-eval-cmd' list" command))))
+
+;; TODO: Improve doc string, it should not point to the readme but it should
+;; be self-contained.
+(defun vterm--prompt-tracking-enabled-p ()
+ "Return t if tracking the prompt is enabled.
+
+Prompt tracking need shell side configurations.
+
+For zsh user, this is done by PROMPT=$PROMPT'%{$(vterm_prompt_end)%}'.
+
+The shell send semantic information about where the prompt ends via properly
+escaped sequences to Emacs.
+
+More information see `Shell-side configuration' and `Directory tracking'
+in README."
+ (or vterm--prompt-tracking-enabled-p
+ (save-excursion
+ (setq vterm--prompt-tracking-enabled-p
+ (next-single-property-change (point-min) 'vterm-prompt)))))
+
+(defun vterm-next-prompt (n)
+ "Move to end of Nth next prompt in the buffer."
+ (interactive "p")
+ (if (and vterm-use-vterm-prompt-detection-method
+ (vterm--prompt-tracking-enabled-p))
+ (let ((pt (point))
+ (promp-pt (vterm--get-prompt-point)))
+ (when promp-pt (goto-char promp-pt))
+ (cl-loop repeat (or n 1) do
+ (setq pt (next-single-property-change (line-beginning-position 2) 'vterm-prompt))
+ (when pt (goto-char pt))))
+ (term-next-prompt n)))
+
+(defun vterm-previous-prompt (n)
+ "Move to end of Nth previous prompt in the buffer."
+ (interactive "p")
+ (if (and vterm-use-vterm-prompt-detection-method
+ (vterm--prompt-tracking-enabled-p))
+ (let ((pt (point))
+ (prompt-pt (vterm--get-prompt-point)))
+ (when prompt-pt
+ (goto-char prompt-pt)
+ (when (> pt (point))
+ (setq n (1- (or n 1))))
+ (cl-loop repeat n do
+ (setq pt (previous-single-property-change (1- (point)) 'vterm-prompt))
+ (when pt (goto-char (1- pt))))))
+ (term-previous-prompt n)))
+
+(defun vterm--get-beginning-of-line (&optional pt)
+ "Find the start of the line, bypassing line wraps.
+If PT is specified, find it's beginning of the line instead of the beginning
+of the line at cursor."
+ (save-excursion
+ (when pt (goto-char pt))
+ (beginning-of-line)
+ (while (and (not (bobp))
+ (get-text-property (1- (point)) 'vterm-line-wrap))
+ (forward-char -1)
+ (beginning-of-line))
+ (point)))
+
+(defun vterm--get-end-of-line (&optional pt)
+ "Find the start of the line, bypassing line wraps.
+If PT is specified, find it's end of the line instead of the end
+of the line at cursor."
+ (save-excursion
+ (when pt (goto-char pt))
+ (end-of-line)
+ (while (get-text-property (point) 'vterm-line-wrap)
+ (forward-char)
+ (end-of-line))
+ (point)))
+
+;; TODO: Improve doc string, it should not point to the readme but it should
+;; be self-contained.
+(defun vterm--get-prompt-point ()
+ "Get the position of the end of current prompt.
+More information see `vterm--prompt-tracking-enabled-p' and
+`Directory tracking and Prompt tracking'in README."
+ (let ((end-point (vterm--get-end-of-line))
+ prompt-point)
+ (save-excursion
+ (if (and vterm-use-vterm-prompt-detection-method
+ (vterm--prompt-tracking-enabled-p))
+ (if (get-text-property end-point 'vterm-prompt)
+ end-point
+ (setq prompt-point (previous-single-property-change end-point 'vterm-prompt))
+ (when prompt-point (setq prompt-point (1- prompt-point))))
+ (goto-char end-point)
+ (if (search-backward-regexp term-prompt-regexp nil t)
+ (goto-char (match-end 0))
+ (vterm--get-beginning-of-line))))))
+
+(defun vterm--at-prompt-p ()
+ "Return t if the cursor position is at shell prompt."
+ (= (point) (or (vterm--get-prompt-point) 0)))
+
+(defun vterm-cursor-in-command-buffer-p (&optional pt)
+ "Check whether cursor in command buffer area."
+ (save-excursion
+ (vterm-reset-cursor-point)
+ (let ((promp-pt (vterm--get-prompt-point)))
+ (when promp-pt
+ (<= promp-pt (or pt (vterm--get-cursor-point)))))))
+
+(defun vterm-beginning-of-line ()
+ "Move point to the beginning of the line.
+
+Move the point to the first character after the shell prompt on this line.
+If the point is already there, move to the beginning of the line.
+Effectively toggle between the two positions."
+ (interactive "^")
+ (if (vterm--at-prompt-p)
+ (goto-char (vterm--get-beginning-of-line))
+ (goto-char (max (or (vterm--get-prompt-point) 0)
+ (vterm--get-beginning-of-line)))))
+
+(defun vterm-end-of-line ()
+ "Move point to the end of the line, bypassing line wraps."
+ (interactive "^")
+ (goto-char (vterm--get-end-of-line)))
+
+(defun vterm-reset-cursor-point ()
+ "Make sure the cursor at the right position."
+ (interactive)
+ (when vterm--term
+ (let ((inhibit-read-only t))
+ (vterm--reset-point vterm--term))))
+
+(defun vterm--get-cursor-point ()
+ "Get term cursor position."
+ (when vterm--term
+ (save-excursion
+ (vterm-reset-cursor-point))))
+
+(defun vterm--reinsert-fake-newlines ()
+ "Reinsert fake newline from `vterm--copy-mode-fake-newlines'."
+ (let ((inhibit-read-only t)
+ (inhibit-redisplay t)
+ (fake-newline-text "\n")
+ fake-newline-pos)
+ (add-text-properties 0 1 '(vterm-line-wrap t rear-nonsticky t)
+ fake-newline-text)
+ (while vterm--copy-mode-fake-newlines
+ (setq fake-newline-pos (car vterm--copy-mode-fake-newlines))
+ (setq vterm--copy-mode-fake-newlines (cdr vterm--copy-mode-fake-newlines))
+ (goto-char fake-newline-pos)
+ (insert fake-newline-text))))
+
+(defun vterm--remove-fake-newlines (&optional remembering-pos-p)
+ "Filter out injected newlines were injected when rendering the terminal.
+
+These newlines were tagged with \\='vterm-line-wrap property so we
+can find them and remove them.
+If REMEMBERING-POS-P is not nil remembering their positions in a buffer-local
+`vterm--copy-mode-fake-newlines'."
+ (let (fake-newline
+ (inhibit-read-only t)
+ (inhibit-redisplay t))
+ (when remembering-pos-p
+ (setq vterm--copy-mode-fake-newlines nil))
+
+ (goto-char (point-max))
+ (when (and (bolp)
+ (not (bobp))
+ (get-text-property (1- (point)) 'vterm-line-wrap))
+ (forward-char -1)
+ (when remembering-pos-p
+ (setq vterm--copy-mode-fake-newlines
+ (cons (point) vterm--copy-mode-fake-newlines)))
+ (vterm--delete-char 1))
+
+ (while (and (not (bobp))
+ (setq fake-newline (previous-single-property-change
+ (point) 'vterm-line-wrap)))
+ (goto-char (1- fake-newline))
+ (cl-assert (eq ?\n (char-after)))
+ (when remembering-pos-p
+ (setq vterm--copy-mode-fake-newlines
+ (cons (point) vterm--copy-mode-fake-newlines)))
+ (vterm--delete-char 1))))
+
+(defun vterm--filter-buffer-substring (content)
+ "Filter string CONTENT of fake/injected newlines."
+ (with-temp-buffer
+ (vterm--insert content)
+ (vterm--remove-fake-newlines nil)
+ (buffer-string)))
+
+
+(provide 'vterm)
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
+;;; vterm.el ends here