From: Andreas Gerler Date: Thu, 11 May 2023 16:16:28 +0000 (+0200) Subject: add vterm X-Git-Url: http://git.bundesbrandschatzamt.de/?a=commitdiff_plain;h=c1cc61e0621b215e27c2268a78aebed49ee4c8c2;p=emacsconfig.git add vterm Signed-off-by: Andreas Gerler --- diff --git a/elpa/vterm-20230417.424/CMakeLists.txt b/elpa/vterm-20230417.424/CMakeLists.txt new file mode 100644 index 00000000..4917264c --- /dev/null +++ b/elpa/vterm-20230417.424/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-20230417.424/elisp.c b/elpa/vterm-20230417.424/elisp.c new file mode 100644 index 00000000..12d48d0e --- /dev/null +++ b/elpa/vterm-20230417.424/elisp.c @@ -0,0 +1,209 @@ +#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 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 idx = env->make_integer(env, index); + return env->funcall(env, Fvterm_get_color, 1, (emacs_value[]){idx}); +} + +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-20230417.424/elisp.h b/elpa/vterm-20230417.424/elisp.h new file mode 100644 index 00000000..22d7e605 --- /dev/null +++ b/elpa/vterm-20230417.424/elisp.h @@ -0,0 +1,99 @@ +#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 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 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-20230417.424/emacs-module.h b/elpa/vterm-20230417.424/emacs-module.h new file mode 100644 index 00000000..5a7d603c --- /dev/null +++ b/elpa/vterm-20230417.424/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-20230417.424/etc/emacs-vterm-bash.sh b/elpa/vterm-20230417.424/etc/emacs-vterm-bash.sh new file mode 100644 index 00000000..94b012c1 --- /dev/null +++ b/elpa/vterm-20230417.424/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-20230417.424/etc/emacs-vterm-zsh.sh b/elpa/vterm-20230417.424/etc/emacs-vterm-zsh.sh new file mode 100644 index 00000000..1dcd20e6 --- /dev/null +++ b/elpa/vterm-20230417.424/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-20230417.424/etc/emacs-vterm.fish b/elpa/vterm-20230417.424/etc/emacs-vterm.fish new file mode 100644 index 00000000..6fa04676 --- /dev/null +++ b/elpa/vterm-20230417.424/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-20230417.424/utf8.c b/elpa/vterm-20230417.424/utf8.c new file mode 100644 index 00000000..49e1477a --- /dev/null +++ b/elpa/vterm-20230417.424/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-20230417.424/utf8.h b/elpa/vterm-20230417.424/utf8.h new file mode 100644 index 00000000..8ec5f3e0 --- /dev/null +++ b/elpa/vterm-20230417.424/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-20230417.424/vterm-autoloads.el b/elpa/vterm-20230417.424/vterm-autoloads.el new file mode 100644 index 00000000..26a35211 --- /dev/null +++ b/elpa/vterm-20230417.424/vterm-autoloads.el @@ -0,0 +1,84 @@ +;;; vterm-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- +;; +;;; Code: + +(add-to-list 'load-path (directory-file-name + (or (file-name-directory #$) (car load-path)))) + + +;;;### (autoloads nil "vterm" "vterm.el" (0 0 0 0)) +;;; Generated autoloads from vterm.el + +(autoload 'vterm-module-compile "vterm" "\ +Compile vterm-module." t nil) + +(autoload 'vterm--bookmark-handler "vterm" "\ +Handler to restore a vterm bookmark BMK. + +If a vterm buffer of the same name does not exist, the function will create a +new vterm buffer of the name. It also checks the current directory and sets +it to the bookmarked directory if needed. + +\(fn BMK)" nil nil) + +(autoload 'vterm-next-error-function "vterm" "\ +Advance to the next error message and visit the file where the error was. +This is the value of `next-error-function' in Compilation +buffers. Prefix arg N says how many error messages to move +forwards (or backwards, if negative). + +Optional argument RESET clears all the errors. + +\(fn N &optional RESET)" t nil) + +(autoload 'vterm "vterm" "\ +Create an interactive Vterm buffer. +Start a new Vterm session, or switch to an already active +session. Return the buffer selected (or created). + +With a nonnumeric prefix arg, create a new session. + +With a string prefix arg, create a new session with arg as buffer name. + +With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch +to the session with that number, or create it if it doesn't +already exist. + +The buffer name used for Vterm sessions is determined by the +value of `vterm-buffer-name'. + +\(fn &optional ARG)" t nil) + +(autoload 'vterm-other-window "vterm" "\ +Create an interactive Vterm buffer in another window. +Start a new Vterm session, or switch to an already active +session. Return the buffer selected (or created). + +With a nonnumeric prefix arg, create a new session. + +With a string prefix arg, create a new session with arg as buffer name. + +With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch +to the session with that number, or create it if it doesn't +already exist. + +The buffer name used for Vterm sessions is determined by the +value of `vterm-buffer-name'. + +\(fn &optional ARG)" t nil) + +(register-definition-prefixes "vterm" '("vterm-")) + +;;;*** + +;;;### (autoloads nil nil ("vterm-pkg.el") (0 0 0 0)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; coding: utf-8 +;; End: +;;; vterm-autoloads.el ends here diff --git a/elpa/vterm-20230417.424/vterm-module.c b/elpa/vterm-20230417.424/vterm-module.c new file mode 100644 index 00000000..b599a37f --- /dev/null +++ b/elpa/vterm-20230417.424/vterm-module.c @@ -0,0 +1,1539 @@ +#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; + + /** 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)) { + return vterm_get_color(env, -1 + (cell->attrs.underline ? -10 : 0)); + } + if (VTERM_COLOR_IS_DEFAULT_BG(color)) { + return vterm_get_color(env, -2 + (cell->attrs.reverse ? -10 : 0)); + } + if (VTERM_COLOR_IS_INDEXED(color)) { + if (color->indexed.idx < 16) { + return vterm_get_color(env, color->indexed.idx); + } 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 + Fblink_cursor_mode = + env->make_global_ref(env, env->intern(env, "blink-cursor-mode")); + Fsymbol_value = env->make_global_ref(env, env->intern(env, "symbol-value")); + Flength = env->make_global_ref(env, env->intern(env, "length")); + Flist = env->make_global_ref(env, env->intern(env, "list")); + Fnth = env->make_global_ref(env, env->intern(env, "nth")); + Ferase_buffer = env->make_global_ref(env, env->intern(env, "erase-buffer")); + Finsert = env->make_global_ref(env, env->intern(env, "vterm--insert")); + Fgoto_char = env->make_global_ref(env, env->intern(env, "goto-char")); + Fput_text_property = + env->make_global_ref(env, env->intern(env, "put-text-property")); + Fadd_text_properties = + env->make_global_ref(env, env->intern(env, "add-text-properties")); + Fset = env->make_global_ref(env, env->intern(env, "set")); + Fvterm_flush_output = + env->make_global_ref(env, env->intern(env, "vterm--flush-output")); + Fforward_line = env->make_global_ref(env, env->intern(env, "forward-line")); + Fgoto_line = env->make_global_ref(env, env->intern(env, "vterm--goto-line")); + Fdelete_lines = + env->make_global_ref(env, env->intern(env, "vterm--delete-lines")); + Frecenter = env->make_global_ref(env, env->intern(env, "recenter")); + Fset_window_point = + env->make_global_ref(env, env->intern(env, "set-window-point")); + Fwindow_body_height = + env->make_global_ref(env, env->intern(env, "window-body-height")); + + Fpoint = env->make_global_ref(env, env->intern(env, "point")); + Fforward_char = env->make_global_ref(env, env->intern(env, "forward-char")); + Fget_buffer_window_list = + env->make_global_ref(env, env->intern(env, "get-buffer-window-list")); + Fselected_window = + env->make_global_ref(env, env->intern(env, "selected-window")); + + Fvterm_set_title = + env->make_global_ref(env, env->intern(env, "vterm--set-title")); + Fvterm_set_directory = + env->make_global_ref(env, env->intern(env, "vterm--set-directory")); + Fvterm_invalidate = + env->make_global_ref(env, env->intern(env, "vterm--invalidate")); + Feq = env->make_global_ref(env, env->intern(env, "eq")); + Fvterm_get_color = + env->make_global_ref(env, env->intern(env, "vterm--get-color")); + Fvterm_eval = env->make_global_ref(env, env->intern(env, "vterm--eval")); + Fvterm_set_selection = + env->make_global_ref(env, env->intern(env, "vterm--set-selection")); + + // Exported functions + emacs_value fun; + fun = + env->make_function(env, 4, 8, Fvterm_new, "Allocate a new vterm.", NULL); + bind_function(env, "vterm--new", fun); + + fun = env->make_function(env, 1, 5, Fvterm_update, + "Process io and update the screen.", NULL); + bind_function(env, "vterm--update", fun); + + fun = + env->make_function(env, 1, 1, Fvterm_redraw, "Redraw the screen.", NULL); + bind_function(env, "vterm--redraw", fun); + + fun = env->make_function(env, 2, 2, Fvterm_write_input, + "Write input to vterm.", NULL); + bind_function(env, "vterm--write-input", fun); + + fun = env->make_function(env, 3, 3, Fvterm_set_size, + "Set the size of the terminal.", NULL); + bind_function(env, "vterm--set-size", fun); + + fun = env->make_function(env, 2, 2, Fvterm_set_pty_name, + "Set the name of the pty.", NULL); + bind_function(env, "vterm--set-pty-name", fun); + fun = env->make_function(env, 2, 2, Fvterm_get_pwd, + "Get the working directory of at line n.", NULL); + bind_function(env, "vterm--get-pwd-raw", fun); + fun = env->make_function(env, 1, 1, Fvterm_reset_cursor_point, + "Reset cursor postion.", NULL); + bind_function(env, "vterm--reset-point", fun); + + fun = env->make_function(env, 1, 1, Fvterm_get_icrnl, + "Get the icrnl state of the pty", NULL); + bind_function(env, "vterm--get-icrnl", fun); + + provide(env, "vterm-module"); + + return 0; +} diff --git a/elpa/vterm-20230417.424/vterm-module.h b/elpa/vterm-20230417.424/vterm-module.h new file mode 100644 index 00000000..1699cbc9 --- /dev/null +++ b/elpa/vterm-20230417.424/vterm-module.h @@ -0,0 +1,169 @@ +#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; + +#define SB_MAX 100000 // Maximum 'scrollback' value. + +#ifndef MIN +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#endif +#ifndef MAX +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#endif + +typedef struct LineInfo { + char *directory; /* working directory */ + + int prompt_col; /* end column of the prompt, if the current line contains the + * prompt */ +} LineInfo; + +typedef struct ScrollbackLine { + size_t cols; + LineInfo *info; + VTermScreenCell cells[]; +} ScrollbackLine; + +typedef struct ElispCodeListNode { + char *code; + size_t code_len; + struct ElispCodeListNode *next; +} ElispCodeListNode; + +/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */ +/* clipboard, primary, secondary, select, or cut buffers 0 through 7 */ +#define SELECTION_BUF_LEN 4096 + +typedef struct Cursor { + int row, col; + int cursor_type; + bool cursor_visible; + bool cursor_blink; + bool cursor_type_changed; + bool cursor_blink_changed; +} Cursor; + +typedef struct Term { + VTerm *vt; + VTermScreen *vts; + // buffer used to: + // - convert VTermScreen cell arrays into utf8 strings + // - receive data from libvterm as a result of key presses. + ScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm + size_t sb_current; // number of rows pushed to sb_buffer + size_t sb_size; // sb_buffer size + // "virtual index" that points to the first sb_buffer row that we need to + // push to the terminal buffer when refreshing the scrollback. When negative, + // it actually points to entries that are no longer in sb_buffer (because the + // window height has increased) and must be deleted from the terminal buffer + int sb_pending; + int sb_pending_by_height_decr; + bool sb_clear_pending; + long linenum; + long linenum_added; + + int invalid_start, invalid_end; // invalid rows in libvterm screen + bool is_invalidated; + + Cursor cursor; + char *title; + bool title_changed; + + char *directory; + bool directory_changed; + + // Single-linked list of elisp_code. + // Newer commands are added at the tail. + ElispCodeListNode *elisp_code_first; + ElispCodeListNode **elisp_code_p_insert; // pointer to the position where new + // node should be inserted + + /* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */ + /* clipboard, primary, secondary, select, or cut buffers 0 through 7 */ + int selection_mask; /* see VTermSelectionMask in vterm.h */ + char *selection_data; + char selection_buf[SELECTION_BUF_LEN]; + + /* the size of dirs almost = window height, value = directory of that line */ + LineInfo **lines; + int lines_len; + + int width, height; + int height_resize; + bool resizing; + bool disable_bold_font; + bool disable_underline; + bool disable_inverse_video; + bool ignore_blink_cursor; + + char *cmd_buffer; + + int pty_fd; +} Term; + +static bool compare_cells(VTermScreenCell *a, VTermScreenCell *b); +static bool is_key(unsigned char *key, size_t len, char *key_description); +static emacs_value render_text(emacs_env *env, Term *term, char *string, + int len, VTermScreenCell *cell); +static emacs_value render_fake_newline(emacs_env *env, Term *term); +static emacs_value render_prompt(emacs_env *env, emacs_value text); +static emacs_value cell_rgb_color(emacs_env *env, Term *term, + VTermScreenCell *cell, bool is_foreground); + +static int term_settermprop(VTermProp prop, VTermValue *val, void *user_data); + +static void term_redraw(Term *term, emacs_env *env); +static void term_flush_output(Term *term, emacs_env *env); +static void term_process_key(Term *term, emacs_env *env, unsigned char *key, + size_t len, VTermModifier modifier); +static void invalidate_terminal(Term *term, int start_row, int end_row); + +void term_finalize(void *object); + +emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[], + void *data); +emacs_value Fvterm_update(emacs_env *env, ptrdiff_t nargs, emacs_value args[], + void *data); +emacs_value Fvterm_redraw(emacs_env *env, ptrdiff_t nargs, emacs_value args[], + void *data); +emacs_value Fvterm_write_input(emacs_env *env, ptrdiff_t nargs, + emacs_value args[], void *data); +emacs_value Fvterm_set_size(emacs_env *env, ptrdiff_t nargs, emacs_value args[], + void *data); +emacs_value Fvterm_set_pty_name(emacs_env *env, ptrdiff_t nargs, + emacs_value args[], void *data); +emacs_value Fvterm_get_icrnl(emacs_env *env, ptrdiff_t nargs, + emacs_value args[], void *data); + +emacs_value Fvterm_get_pwd(emacs_env *env, ptrdiff_t nargs, emacs_value args[], + void *data); + +emacs_value Fvterm_get_prompt_point(emacs_env *env, ptrdiff_t nargs, + emacs_value args[], void *data); +emacs_value Fvterm_reset_cursor_point(emacs_env *env, ptrdiff_t nargs, + emacs_value args[], void *data); + +VTERM_EXPORT int emacs_module_init(struct emacs_runtime *ert); + +#endif /* VTERM_MODULE_H */ diff --git a/elpa/vterm-20230417.424/vterm-pkg.el b/elpa/vterm-20230417.424/vterm-pkg.el new file mode 100644 index 00000000..9dbdb15d --- /dev/null +++ b/elpa/vterm-20230417.424/vterm-pkg.el @@ -0,0 +1,14 @@ +(define-package "vterm" "20230417.424" "Fully-featured terminal emulator" + '((emacs "25.1")) + :commit "94e2b0b2b4a750e7907dacd5b4c0584900846dd1" :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-20230417.424/vterm.el b/elpa/vterm-20230417.424/vterm.el new file mode 100644 index 00000000..a0600ad6 --- /dev/null +++ b/elpa/vterm-20230417.424/vterm.el @@ -0,0 +1,1841 @@ +;;; 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. +The foreground color is used as ANSI color 0 and the background +color is used as ANSI color 8." + :group 'vterm) + +(defface vterm-color-red + `((t :inherit term-color-red)) + "Face used to render red color code. +The foreground color is used as ANSI color 1 and the background +color is used as ANSI color 9." + :group 'vterm) + +(defface vterm-color-green + `((t :inherit term-color-green)) + "Face used to render green color code. +The foreground color is used as ANSI color 2 and the background +color is used as ANSI color 10." + :group 'vterm) + +(defface vterm-color-yellow + `((t :inherit term-color-yellow)) + "Face used to render yellow color code. +The foreground color is used as ANSI color 3 and the background +color is used as ANSI color 11." + :group 'vterm) + +(defface vterm-color-blue + `((t :inherit term-color-blue)) + "Face used to render blue color code. +The foreground color is used as ANSI color 4 and the background +color is used as ANSI color 12." + :group 'vterm) + +(defface vterm-color-magenta + `((t :inherit term-color-magenta)) + "Face used to render magenta color code. +The foreground color is used as ansi color 5 and the background +color is used as ansi color 13." + :group 'vterm) + +(defface vterm-color-cyan + `((t :inherit term-color-cyan)) + "Face used to render cyan color code. +The foreground color is used as ansi color 6 and the background +color is used as ansi color 14." + :group 'vterm) + +(defface vterm-color-white + `((t :inherit term-color-white)) + "Face used to render white color code. +The foreground color is used as ansi color 7 and the background +color is used as ansi color 15." + :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] + "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-body-width) (vterm--get-margin-width)) + vterm-min-window-width))) + (setq vterm--term (vterm--new (window-body-height) + width vterm-max-scrollback + vterm-disable-bold-font + vterm-disable-underline + vterm-disable-inverse-video + vterm-ignore-blink-cursor + vterm-set-bold-hightbright)) + (setq buffer-read-only t) + (setq-local scroll-conservatively 101) + (setq-local scroll-margin 0) + (setq-local hscroll-margin 0) + (setq-local hscroll-step 1) + (setq-local truncate-lines t) + + + ;; Disable all automatic fontification + (setq-local font-lock-defaults '(nil t)) + + (add-function :filter-return + (local 'filter-buffer-substring-function) + #'vterm--filter-buffer-substring) + (setq vterm--process + (make-process + :name "vterm" + :buffer (current-buffer) + :command + `("/bin/sh" "-c" + ,(format + "stty -nl sane %s erase ^? rows %d columns %d >/dev/null && exec %s" + ;; Some stty implementations (i.e. that of *BSD) do not + ;; support the iutf8 option. to handle that, we run some + ;; heuristics to work out if the system supports that + ;; option and set the arg string accordingly. This is a + ;; gross hack but FreeBSD doesn't seem to want to fix it. + ;; + ;; See: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=220009 + (if (eq system-type 'berkeley-unix) "" "iutf8") + (window-body-height) + width (vterm--get-shell))) + ;; :coding 'no-conversion + :connection-type 'pty + :file-handler t + :filter #'vterm--filter + ;; The sentinel is needed if there are exit functions or if + ;; vterm-kill-buffer-on-exit is set to t. In this latter case, + ;; vterm--sentinel will kill the buffer + :sentinel (when (or vterm-exit-functions + vterm-kill-buffer-on-exit) + #'vterm--sentinel)))) + + ;; Change major-mode is not allowed + ;; Vterm interfaces with an underlying process. Changing the major + ;; mode can break this, leading to segmentation faults. + (add-hook 'change-major-mode-hook + (lambda () (interactive) + (user-error "You cannot change major mode in vterm buffers")) nil t) + + (vterm--set-pty-name vterm--term (process-tty-name vterm--process)) + (process-put vterm--process 'adjust-window-size-function + #'vterm--window-adjust-process-window-size) + ;; Support to compilation-shell-minor-mode + ;; Is this necessary? See vterm--compilation-setup + (setq next-error-function 'vterm-next-error-function) + (setq-local bookmark-make-record-function 'vterm--bookmark-make-record)) + +(defun vterm--get-shell () + "Get the shell that gets run in the vterm." + (if (ignore-errors (file-remote-p default-directory)) + (with-parsed-tramp-file-name default-directory nil + (or (cadr (assoc method vterm-tramp-shells)) + (with-connection-local-variables shell-file-name) + vterm-shell)) + vterm-shell)) + +(defun vterm--bookmark-make-record () + "Create a vterm bookmark. + +Notes down the current directory and buffer name." + `(nil + (handler . vterm--bookmark-handler) + (thisdir . ,default-directory) + (buf-name . ,(buffer-name)) + (defaults . nil))) + + +;;;###autoload +(defun vterm--bookmark-handler (bmk) + "Handler to restore a vterm bookmark BMK. + +If a vterm buffer of the same name does not exist, the function will create a +new vterm buffer of the name. It also checks the current directory and sets +it to the bookmarked directory if needed." + (let* ((thisdir (bookmark-prop-get bmk 'thisdir)) + (buf-name (bookmark-prop-get bmk 'buf-name)) + (buf (get-buffer buf-name)) + (thismode (and buf (with-current-buffer buf major-mode)))) + ;; create if no such vterm buffer exists + (when (or (not buf) (not (eq thismode 'vterm-mode))) + (setq buf (generate-new-buffer buf-name)) + (with-current-buffer buf + (when vterm-bookmark-check-dir + (setq default-directory thisdir)) + (vterm-mode))) + ;; check the current directory + (with-current-buffer buf + (when (and vterm-bookmark-check-dir + (not (string-equal default-directory thisdir))) + (when vterm-copy-mode + (vterm-copy-mode-done nil)) + (vterm-insert (concat "cd " thisdir)) + (vterm-send-return))) + ;; set to this vterm buf + (set-buffer buf))) + +(defun vterm--compilation-setup () + "Function to enable the option `compilation-shell-minor-mode' for vterm. +`'compilation-shell-minor-mode' would change the value of local +variable `next-error-function', so we should call this function in +`compilation-shell-minor-mode-hook'." + (when (eq major-mode 'vterm-mode) + (setq next-error-function 'vterm-next-error-function))) + +(add-hook 'compilation-shell-minor-mode-hook #'vterm--compilation-setup) + +;;;###autoload +(defun vterm-next-error-function (n &optional reset) + "Advance to the next error message and visit the file where the error was. +This is the value of `next-error-function' in Compilation +buffers. Prefix arg N says how many error messages to move +forwards (or backwards, if negative). + +Optional argument RESET clears all the errors." + (interactive "p") + (let* ((pt (point)) + (default-directory default-directory) + (pwd (vterm--get-pwd))) + (when pwd + (setq default-directory pwd)) + (goto-char pt) + (compilation-next-error-function n reset))) + +;;; Copy Mode + +(defun vterm--enter-copy-mode () + (use-local-map nil) + (vterm-send-stop) + (when vterm-copy-mode-remove-fake-newlines + (save-excursion + (setq truncate-lines nil) + (vterm--remove-fake-newlines t)))) + + +(defun vterm--exit-copy-mode () + (when vterm-copy-mode-remove-fake-newlines + (save-excursion + (setq truncate-lines t) + (vterm--reinsert-fake-newlines))) + (vterm-reset-cursor-point) + (use-local-map vterm-mode-map) + (vterm-send-start)) + +(define-minor-mode vterm-copy-mode + "Toggle `vterm-copy-mode'. + +When `vterm-copy-mode' is enabled, the terminal will not display +additional output received from the underlying process and will +behave similarly to buffer in `fundamental-mode'. This mode is +typically used to copy text from vterm buffers. + +A conventient way to exit `vterm-copy-mode' is with +`vterm-copy-mode-done', which copies the selected text and exit +`vterm-copy-mode'." + :group 'vterm + :lighter " VTermCopy" + :keymap vterm-copy-mode-map + (if (equal major-mode 'vterm-mode) + (if vterm-copy-mode + (vterm--enter-copy-mode) + (vterm--exit-copy-mode)) + (user-error "You cannot enable vterm-copy-mode outside vterm buffers"))) + +(defun vterm-copy-mode-done (arg) + "Save the active region or line to the kill ring and exit `vterm-copy-mode'. + +If a region is defined then that region is killed, with no region then +current line is killed from start to end. + +The option `vterm-copy-exclude-prompt' controls if the prompt +should be included in a line copy. Using the universal prefix ARG +will invert `vterm-copy-exclude-prompt' for that call." + (interactive "P") + (unless vterm-copy-mode + (user-error "This command is effective only in vterm-copy-mode")) + (unless (use-region-p) + (goto-char (vterm--get-beginning-of-line)) + ;; Are we excluding the prompt? + (if (or (and vterm-copy-exclude-prompt (not arg)) + (and (not vterm-copy-exclude-prompt) arg)) + (goto-char (max (or (vterm--get-prompt-point) 0) + (vterm--get-beginning-of-line)))) + (set-mark (point)) + (goto-char (vterm--get-end-of-line))) + (kill-ring-save (region-beginning) (region-end)) + (vterm-copy-mode -1)) + +;;; Commands + +(defun vterm--self-insert-meta () + (interactive) + (when vterm--term + (dolist (key (vterm--translate-event-to-args + last-command-event :meta)) + (apply #'vterm-send-key key)))) + +(defun vterm--self-insert () + "Send invoking key to libvterm." + (interactive) + (when vterm--term + (dolist (key (vterm--translate-event-to-args + last-command-event)) + (apply #'vterm-send-key key)))) + +(defun vterm-send-key (key &optional shift meta ctrl accept-proc-output) + "Send KEY to libvterm with optional modifiers SHIFT, META and CTRL." + (deactivate-mark) + (when vterm--term + (let ((inhibit-redisplay t) + (inhibit-read-only t)) + (vterm--update vterm--term key shift meta ctrl) + (setq vterm--redraw-immididately t) + (when accept-proc-output + (accept-process-output vterm--process vterm-timer-delay nil t))))) + +(defun vterm-send (key) + "Send KEY to libvterm. KEY can be anything `kbd' understands." + (dolist (key (vterm--translate-event-to-args + (listify-key-sequence (kbd key)))) + (apply #'vterm-send-key key))) + +(defun vterm-send-next-key () + "Read next input event and send it to the libvterm. + +With this you can directly send modified keys to applications +running in the terminal (like Emacs or Nano)." + (interactive) + (dolist (key (vterm--translate-event-to-args + (read-event))) + (apply #'vterm-send-key key))) + +(defun vterm-send-start () + "Output from the system is started when the system receives START." + (interactive) + (vterm-send-key "")) + +(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) + "Get color by index from `vterm-color-palette'. +Argument INDEX index of the terminal color. +Special values for INDEX are: +-11 foreground for cells with underline attribute, foreground of +the `vterm-color-underline' face is used in this case. +-12 background for cells with inverse video attribute, background +of the `vterm-color-inverse-video' face is used in this case." + (cond + ((and (>= index 0) (< index 8)) + (face-foreground + (elt vterm-color-palette index) + nil 'default)) + ((and (>= index 8) (< index 16)) + (face-background + (elt vterm-color-palette (% index 8)) + nil 'default)) + ((= index -11) + (face-foreground 'vterm-color-underline nil 'default)) + ((= index -12) + (face-background 'vterm-color-inverse-video nil 'default)) + (t + nil))) + +(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