From: Andreas Gerler Date: Sun, 22 Dec 2024 12:22:00 +0000 (+0100) Subject: update vterm X-Git-Url: http://git.bundesbrandschatzamt.de/?a=commitdiff_plain;h=d8dba5d2fe7d73d6e7e4120e4e1f1d76fe0181f0;p=emacsconfig.git update vterm Signed-off-by: Andreas Gerler --- diff --git a/elpa/vterm-20240520.231/CMakeLists.txt b/elpa/vterm-20240520.231/CMakeLists.txt deleted file mode 100644 index 4917264c..00000000 --- a/elpa/vterm-20240520.231/CMakeLists.txt +++ /dev/null @@ -1,104 +0,0 @@ -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 - ) diff --git a/elpa/vterm-20240520.231/elisp.c b/elpa/vterm-20240520.231/elisp.c deleted file mode 100644 index bf08fbd2..00000000 --- a/elpa/vterm-20240520.231/elisp.c +++ /dev/null @@ -1,210 +0,0 @@ -#include "elisp.h" -#include - -// 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}); -} diff --git a/elpa/vterm-20240520.231/elisp.h b/elpa/vterm-20240520.231/elisp.h deleted file mode 100644 index ba5e5fe6..00000000 --- a/elpa/vterm-20240520.231/elisp.h +++ /dev/null @@ -1,100 +0,0 @@ -#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 */ diff --git a/elpa/vterm-20240520.231/emacs-module.h b/elpa/vterm-20240520.231/emacs-module.h deleted file mode 100644 index 5a7d603c..00000000 --- a/elpa/vterm-20240520.231/emacs-module.h +++ /dev/null @@ -1,334 +0,0 @@ -/* 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 . */ - -#ifndef EMACS_MODULE_H -#define EMACS_MODULE_H - -#include -#include - -#ifndef __cplusplus -#include -#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 */ diff --git a/elpa/vterm-20240520.231/etc/emacs-vterm-bash.sh b/elpa/vterm-20240520.231/etc/emacs-vterm-bash.sh deleted file mode 100644 index 94b012c1..00000000 --- a/elpa/vterm-20240520.231/etc/emacs-vterm-bash.sh +++ /dev/null @@ -1,55 +0,0 @@ -# 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)\]' diff --git a/elpa/vterm-20240520.231/etc/emacs-vterm-zsh.sh b/elpa/vterm-20240520.231/etc/emacs-vterm-zsh.sh deleted file mode 100644 index 1dcd20e6..00000000 --- a/elpa/vterm-20240520.231/etc/emacs-vterm-zsh.sh +++ /dev/null @@ -1,54 +0,0 @@ -# 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)%}' diff --git a/elpa/vterm-20240520.231/etc/emacs-vterm.fish b/elpa/vterm-20240520.231/etc/emacs-vterm.fish deleted file mode 100644 index 6fa04676..00000000 --- a/elpa/vterm-20240520.231/etc/emacs-vterm.fish +++ /dev/null @@ -1,67 +0,0 @@ -# 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 diff --git a/elpa/vterm-20240520.231/utf8.c b/elpa/vterm-20240520.231/utf8.c deleted file mode 100644 index 49e1477a..00000000 --- a/elpa/vterm-20240520.231/utf8.c +++ /dev/null @@ -1,69 +0,0 @@ -#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; -} diff --git a/elpa/vterm-20240520.231/utf8.h b/elpa/vterm-20240520.231/utf8.h deleted file mode 100644 index 8ec5f3e0..00000000 --- a/elpa/vterm-20240520.231/utf8.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef UTF8_H -#define UTF8_H - -#include -#include -#include - -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 */ diff --git a/elpa/vterm-20240520.231/vterm-autoloads.el b/elpa/vterm-20240520.231/vterm-autoloads.el deleted file mode 100644 index fe69da20..00000000 --- a/elpa/vterm-20240520.231/vterm-autoloads.el +++ /dev/null @@ -1,81 +0,0 @@ -;;; 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))) - - - -;;; 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-")) - -;;; 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 diff --git a/elpa/vterm-20240520.231/vterm-module.c b/elpa/vterm-20240520.231/vterm-module.c deleted file mode 100644 index 05f40f0a..00000000 --- a/elpa/vterm-20240520.231/vterm-module.c +++ /dev/null @@ -1,1548 +0,0 @@ -#include "vterm-module.h" -#include "elisp.h" -#include "utf8.h" -#include -#include -#include -#include -#include -#include -#include -#include - -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 rowslines_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, "")) { - term_clear_scrollback(term, env); - } else if (is_key(key, len, "")) { - tcflow(term->pty_fd, TCOON); - } else if (is_key(key, len, "")) { - tcflow(term->pty_fd, TCOOFF); - } else if (is_key(key, len, "")) { - vterm_keyboard_start_paste(term->vt); - } else if (is_key(key, len, "")) { - vterm_keyboard_end_paste(term->vt); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_TAB, modifier); - } else if (is_key(key, len, "") || - is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_TAB, VTERM_MOD_SHIFT); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_BACKSPACE, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_ESCAPE, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_UP, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_DOWN, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_LEFT, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_RIGHT, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_INS, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_DEL, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_HOME, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_END, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_PAGEUP, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_PAGEDOWN, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(0), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(1), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(2), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(3), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(4), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(5), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(6), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(7), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(8), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(9), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(10), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(11), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(12), modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_0, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_1, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_2, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_3, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_4, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_5, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_6, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_7, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_8, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_9, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_PLUS, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_MINUS, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_MULT, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_DIVIDE, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_EQUAL, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_PERIOD, modifier); - } else if (is_key(key, len, "")) { - vterm_keyboard_key(term->vt, VTERM_KEY_KP_COMMA, modifier); - } else if (is_key(key, len, "")) { - 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; -} diff --git a/elpa/vterm-20240520.231/vterm-module.h b/elpa/vterm-20240520.231/vterm-module.h deleted file mode 100644 index ebf36249..00000000 --- a/elpa/vterm-20240520.231/vterm-module.h +++ /dev/null @@ -1,171 +0,0 @@ -#ifndef VTERM_MODULE_H -#define VTERM_MODULE_H - -#include "emacs-module.h" -#include -#include -#include - -// 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 */ diff --git a/elpa/vterm-20240520.231/vterm-pkg.el b/elpa/vterm-20240520.231/vterm-pkg.el deleted file mode 100644 index c64fcbf4..00000000 --- a/elpa/vterm-20240520.231/vterm-pkg.el +++ /dev/null @@ -1,14 +0,0 @@ -(define-package "vterm" "20240520.231" "Fully-featured terminal emulator" - '((emacs "25.1")) - :commit "df057b1af2bb89a1deb288086f13be296af42090" :authors - '(("Lukas Fürmetz" . "fuermetz@mailbox.org")) - :maintainers - '(("Lukas Fürmetz" . "fuermetz@mailbox.org")) - :maintainer - '("Lukas Fürmetz" . "fuermetz@mailbox.org") - :keywords - '("terminals") - :url "https://github.com/akermu/emacs-libvterm") -;; Local Variables: -;; no-byte-compile: t -;; End: diff --git a/elpa/vterm-20240520.231/vterm.el b/elpa/vterm-20240520.231/vterm.el deleted file mode 100644 index 04c90587..00000000 --- a/elpa/vterm-20240520.231/vterm.el +++ /dev/null @@ -1,1887 +0,0 @@ -;;; vterm.el --- Fully-featured terminal emulator -*- lexical-binding: t; -*- - -;; Copyright (C) 2017-2020 by Lukas Fürmetz & Contributors -;; -;; Author: Lukas Fürmetz -;; 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 . - - -;;; 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 "" 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 "")) - -(defun vterm-send-stop () - "Output from the system is stopped when the system receives STOP." - (interactive) - (vterm-send-key "")) - -(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 `' to the libvterm." - (interactive) - (vterm-send-key "")) - -(defun vterm-send-space () - "Send `' to the libvterm." - (interactive) - (vterm-send-key " ")) - -(defun vterm-send-backspace () - "Send `' to the libvterm." - (interactive) - (vterm-send-key "")) - -(defun vterm-send-delete () - "Send `' to the libvterm." - (interactive) - (vterm-send-key "")) - -(defun vterm-send-meta-backspace () - "Send `M-' to the libvterm." - (interactive) - (vterm-send-key "" nil t)) - -(defun vterm-send-up () - "Send `' to the libvterm." - (interactive) - (vterm-send-key "")) -(make-obsolete 'vterm-send-up 'vterm--self-insert "v0.1") - -(defun vterm-send-down () - "Send `' to the libvterm." - (interactive) - (vterm-send-key "")) -(make-obsolete 'vterm-send-down 'vterm--self-insert "v0.1") - -(defun vterm-send-left () - "Send `' to the libvterm." - (interactive) - (vterm-send-key "")) -(make-obsolete 'vterm-send-left 'vterm--self-insert "v0.1") - -(defun vterm-send-right () - "Send `' to the libvterm." - (interactive) - (vterm-send-key "")) -(make-obsolete 'vterm-send-right 'vterm--self-insert "v0.1") - -(defun vterm-send-prior () - "Send `' to the libvterm." - (interactive) - (vterm-send-key "")) -(make-obsolete 'vterm-send-prior 'vterm--self-insert "v0.1") - -(defun vterm-send-next () - "Send `' to the libvterm." - (interactive) - (vterm-send-key "")) -(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 `' to the libvterm." - (interactive) - (vterm-send-key "")) - -(defun vterm-clear-scrollback () - "Send `' to the libvterm." - (interactive) - (vterm-send-key "")) - -(defun vterm-clear (&optional arg) - "Send `' 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 "" )) - (dolist (char (string-to-list string)) - (vterm--update vterm--term (char-to-string char))) - (when paste-p - (vterm--update vterm--term ""))) - (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 "") - (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 "") - (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 "" 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 "" 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 - (dotimes (_ 3) (vterm-send-key "" 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 "" 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 - (dotimes (_ 3) (vterm-send-key "" 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 diff --git a/elpa/vterm-20241218.331/CMakeLists.txt b/elpa/vterm-20241218.331/CMakeLists.txt new file mode 100644 index 00000000..4917264c --- /dev/null +++ b/elpa/vterm-20241218.331/CMakeLists.txt @@ -0,0 +1,104 @@ +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 + ) diff --git a/elpa/vterm-20241218.331/elisp.c b/elpa/vterm-20241218.331/elisp.c new file mode 100644 index 00000000..bf08fbd2 --- /dev/null +++ b/elpa/vterm-20241218.331/elisp.c @@ -0,0 +1,210 @@ +#include "elisp.h" +#include + +// 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}); +} diff --git a/elpa/vterm-20241218.331/elisp.h b/elpa/vterm-20241218.331/elisp.h new file mode 100644 index 00000000..ba5e5fe6 --- /dev/null +++ b/elpa/vterm-20241218.331/elisp.h @@ -0,0 +1,100 @@ +#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 */ diff --git a/elpa/vterm-20241218.331/emacs-module.h b/elpa/vterm-20241218.331/emacs-module.h new file mode 100644 index 00000000..5a7d603c --- /dev/null +++ b/elpa/vterm-20241218.331/emacs-module.h @@ -0,0 +1,334 @@ +/* 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 . */ + +#ifndef EMACS_MODULE_H +#define EMACS_MODULE_H + +#include +#include + +#ifndef __cplusplus +#include +#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 */ diff --git a/elpa/vterm-20241218.331/etc/emacs-vterm-bash.sh b/elpa/vterm-20241218.331/etc/emacs-vterm-bash.sh new file mode 100644 index 00000000..94b012c1 --- /dev/null +++ b/elpa/vterm-20241218.331/etc/emacs-vterm-bash.sh @@ -0,0 +1,55 @@ +# 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)\]' diff --git a/elpa/vterm-20241218.331/etc/emacs-vterm-zsh.sh b/elpa/vterm-20241218.331/etc/emacs-vterm-zsh.sh new file mode 100644 index 00000000..1dcd20e6 --- /dev/null +++ b/elpa/vterm-20241218.331/etc/emacs-vterm-zsh.sh @@ -0,0 +1,54 @@ +# 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)%}' diff --git a/elpa/vterm-20241218.331/etc/emacs-vterm.fish b/elpa/vterm-20241218.331/etc/emacs-vterm.fish new file mode 100644 index 00000000..6fa04676 --- /dev/null +++ b/elpa/vterm-20241218.331/etc/emacs-vterm.fish @@ -0,0 +1,67 @@ +# 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 diff --git a/elpa/vterm-20241218.331/utf8.c b/elpa/vterm-20241218.331/utf8.c new file mode 100644 index 00000000..49e1477a --- /dev/null +++ b/elpa/vterm-20241218.331/utf8.c @@ -0,0 +1,69 @@ +#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; +} diff --git a/elpa/vterm-20241218.331/utf8.h b/elpa/vterm-20241218.331/utf8.h new file mode 100644 index 00000000..8ec5f3e0 --- /dev/null +++ b/elpa/vterm-20241218.331/utf8.h @@ -0,0 +1,12 @@ +#ifndef UTF8_H +#define UTF8_H + +#include +#include +#include + +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 */ diff --git a/elpa/vterm-20241218.331/vterm-autoloads.el b/elpa/vterm-20241218.331/vterm-autoloads.el new file mode 100644 index 00000000..fe69da20 --- /dev/null +++ b/elpa/vterm-20241218.331/vterm-autoloads.el @@ -0,0 +1,81 @@ +;;; 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))) + + + +;;; 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-")) + +;;; 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 diff --git a/elpa/vterm-20241218.331/vterm-module.c b/elpa/vterm-20241218.331/vterm-module.c new file mode 100644 index 00000000..2294ef47 --- /dev/null +++ b/elpa/vterm-20241218.331/vterm-module.c @@ -0,0 +1,1550 @@ +#include "vterm-module.h" +#include "elisp.h" +#include "utf8.h" +#include +#include +#include +#include +#include +#include +#include +#include + +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 rowslines_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, "")) { + term_clear_scrollback(term, env); + } else if (is_key(key, len, "")) { + tcflow(term->pty_fd, TCOON); + } else if (is_key(key, len, "")) { + tcflow(term->pty_fd, TCOOFF); + } else if (is_key(key, len, "")) { + vterm_keyboard_start_paste(term->vt); + } else if (is_key(key, len, "")) { + vterm_keyboard_end_paste(term->vt); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_TAB, modifier); + } else if (is_key(key, len, "") || + is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_TAB, VTERM_MOD_SHIFT); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_BACKSPACE, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_ESCAPE, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_UP, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_DOWN, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_LEFT, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_RIGHT, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_INS, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_DEL, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_HOME, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_END, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_PAGEUP, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_PAGEDOWN, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(0), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(1), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(2), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(3), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(4), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(5), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(6), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(7), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(8), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(9), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(10), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(11), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(12), modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_0, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_1, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_2, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_3, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_4, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_5, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_6, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_7, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_8, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_9, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_PLUS, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_MINUS, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_MULT, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_DIVIDE, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_EQUAL, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_PERIOD, modifier); + } else if (is_key(key, len, "")) { + vterm_keyboard_key(term->vt, VTERM_KEY_KP_COMMA, modifier); + } else if (is_key(key, len, "")) { + 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]); + + if (len > 0) { + 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; +} diff --git a/elpa/vterm-20241218.331/vterm-module.h b/elpa/vterm-20241218.331/vterm-module.h new file mode 100644 index 00000000..ebf36249 --- /dev/null +++ b/elpa/vterm-20241218.331/vterm-module.h @@ -0,0 +1,171 @@ +#ifndef VTERM_MODULE_H +#define VTERM_MODULE_H + +#include "emacs-module.h" +#include +#include +#include + +// 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 */ diff --git a/elpa/vterm-20241218.331/vterm-pkg.el b/elpa/vterm-20241218.331/vterm-pkg.el new file mode 100644 index 00000000..945d6e20 --- /dev/null +++ b/elpa/vterm-20241218.331/vterm-pkg.el @@ -0,0 +1,10 @@ +;; -*- no-byte-compile: t; lexical-binding: nil -*- +(define-package "vterm" "20241218.331" + "Fully-featured terminal emulator." + '((emacs "25.1")) + :url "https://github.com/akermu/emacs-libvterm" + :commit "f64729ed8b59e46ce827d28222c4087c538de562" + :revdesc "f64729ed8b59" + :keywords '("terminals") + :authors '(("Lukas Fürmetz" . "fuermetz@mailbox.org")) + :maintainers '(("Lukas Fürmetz" . "fuermetz@mailbox.org"))) diff --git a/elpa/vterm-20241218.331/vterm.el b/elpa/vterm-20241218.331/vterm.el new file mode 100644 index 00000000..ee452b23 --- /dev/null +++ b/elpa/vterm-20241218.331/vterm.el @@ -0,0 +1,1962 @@ +;;; vterm.el --- Fully-featured terminal emulator -*- lexical-binding: t; -*- + +;; Copyright (C) 2017-2020 by Lukas Fürmetz & Contributors +;; +;; Author: Lukas Fürmetz +;; Package-Version: 20241218.331 +;; Package-Revision: f64729ed8b59 +;; 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 . + + +;;; 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 + '(("ssh" login-shell) ("scp" login-shell) ("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) + +Use t as TRAMP-METHOD to specify a default shell for all methods. +Specific methods always take precedence over t. + +Set SHELL to \\='login-shell to use the user's login shell on the host. +The login-shell detection currently works for POSIX-compliant remote +hosts that have the getent command (regular GNU/Linux distros, *BSDs, +but not MacOS X unfortunately). + +You can specify an additional second SHELL command as a fallback +that is used when the login-shell detection fails, e.g., +\\='((\"ssh\" login-shell \"/bin/bash\") ...) +If no second SHELL command is specified with \\='login-shell, vterm will +fall back to tramp's 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 "" 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) + + ;; Set the truncation slot for 'buffer-display-table' to the ASCII code for a + ;; space character (32) to make the vterm buffer display a space instead of + ;; the default truncation character ($) when a line is truncated. + (let* ((display-table (or buffer-display-table (make-display-table)))) + (set-display-table-slot display-table 'truncation 32) + (setq buffer-display-table display-table)) + + ;; 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--tramp-get-shell (method) + "Get the shell for a remote location as specified in `vterm-tramp-shells'. +The argument METHOD is the method string (as used by tramp) to get the shell +for, or t to get the default shell for all methods." + (let* ((specs (cdr (assoc method vterm-tramp-shells))) + (first (car specs)) + (second (cadr specs))) + ;; Allow '(... login-shell) or '(... 'login-shell). + (if (or (eq first 'login-shell) + (and (consp first) (eq (cadr first) 'login-shell))) + ;; If the first element is 'login-shell, try to determine the user's + ;; login shell on the remote host. This should work for all + ;; POSIX-compliant systems with the getent command in PATH. This + ;; includes regular GNU/Linux distros, *BSDs, but not MacOS X. If + ;; the login-shell determination fails at any point, the second + ;; element in the shell spec is used (if present, otherwise nil is + ;; returned). + (let* ((entry (ignore-errors + (with-output-to-string + (with-current-buffer standard-output + ;; The getent command returns the passwd entry + ;; for the specified user independently of the + ;; used name service (i.e., not only for static + ;; passwd files, but also for LDAP, etc). + ;; + ;; Use a shell command here to get $LOGNAME. + ;; Using the tramp user does not always work as + ;; it can be nil, e.g., with ssh host configs. + ;; $LOGNAME is defined in all POSIX-compliant + ;; systems. + (unless (= 0 (process-file-shell-command + "getent passwd $LOGNAME" + nil (current-buffer) nil)) + (error "Unexpected return value")) + ;; If we have more than one line, the output is + ;; not the expected single passwd entry. + ;; Most likely, $LOGNAME is not set. + (when (> (count-lines (point-min) (point-max)) 1) + (error "Unexpected output")))))) + (shell (when entry + ;; The returned Unix passwd entry is a colon- + ;; separated line. The 6th (last) element specifies + ;; the user's shell. + (nth 6 (split-string entry ":" nil "[ \t\n\r]+"))))) + (or shell second)) + first))) + +(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 (vterm--tramp-get-shell method) + (vterm--tramp-get-shell t) + (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 (or (eq major-mode 'vterm-mode) + (derived-mode-p '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 (or (equal major-mode 'vterm-mode) + (derived-mode-p '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 "")) + +(defun vterm-send-stop () + "Output from the system is stopped when the system receives STOP." + (interactive) + (vterm-send-key "")) + +(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 `' to the libvterm." + (interactive) + (vterm-send-key "")) + +(defun vterm-send-space () + "Send `' to the libvterm." + (interactive) + (vterm-send-key " ")) + +(defun vterm-send-backspace () + "Send `' to the libvterm." + (interactive) + (vterm-send-key "")) + +(defun vterm-send-delete () + "Send `' to the libvterm." + (interactive) + (vterm-send-key "")) + +(defun vterm-send-meta-backspace () + "Send `M-' to the libvterm." + (interactive) + (vterm-send-key "" nil t)) + +(defun vterm-send-up () + "Send `' to the libvterm." + (interactive) + (vterm-send-key "")) +(make-obsolete 'vterm-send-up 'vterm--self-insert "v0.1") + +(defun vterm-send-down () + "Send `' to the libvterm." + (interactive) + (vterm-send-key "")) +(make-obsolete 'vterm-send-down 'vterm--self-insert "v0.1") + +(defun vterm-send-left () + "Send `' to the libvterm." + (interactive) + (vterm-send-key "")) +(make-obsolete 'vterm-send-left 'vterm--self-insert "v0.1") + +(defun vterm-send-right () + "Send `' to the libvterm." + (interactive) + (vterm-send-key "")) +(make-obsolete 'vterm-send-right 'vterm--self-insert "v0.1") + +(defun vterm-send-prior () + "Send `' to the libvterm." + (interactive) + (vterm-send-key "")) +(make-obsolete 'vterm-send-prior 'vterm--self-insert "v0.1") + +(defun vterm-send-next () + "Send `' to the libvterm." + (interactive) + (vterm-send-key "")) +(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 `' to the libvterm." + (interactive) + (vterm-send-key "")) + +(defun vterm-clear-scrollback () + "Send `' to the libvterm." + (interactive) + (vterm-send-key "")) + +(defun vterm-clear (&optional arg) + "Send `' 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 "" )) + (dolist (char (string-to-list string)) + (vterm--update vterm--term (char-to-string char))) + (when paste-p + (vterm--update vterm--term ""))) + (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 "") + (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 "") + (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 "" 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 "" 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 + (dotimes (_ 3) (vterm-send-key "" 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 "" 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 + (dotimes (_ 3) (vterm-send-key "" 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 (> (1+ 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 + (or (equal major-mode 'vterm-mode) + (derived-mode-p '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