├── .clang-format ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── elisp.c ├── elisp.h ├── emacs-module.h ├── etc ├── emacs-vterm-bash.sh ├── emacs-vterm-zsh.sh └── emacs-vterm.fish ├── utf8.c ├── utf8.h ├── vterm-module.c ├── vterm-module.h └── vterm.el /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: LLVM 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /vterm-module.so 3 | *.elc 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akermu/emacs-libvterm/056ad74653704bc353d8ec8ab52ac75267b7d373/.gitmodules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Template from https://github.com/flycheck/emacs-travis 2 | 3 | language: c 4 | sudo: required 5 | dist: trusty 6 | cache: 7 | - directories: 8 | - "$HOME/emacs/" 9 | 10 | addons: 11 | apt: 12 | packages: 13 | - bzr 14 | 15 | matrix: 16 | fast_finish: true 17 | 18 | env: 19 | - EMACS_VERSION=25.3 20 | - EMACS_VERSION=26.1 21 | 22 | before_install: 23 | - export PATH="$HOME/bin:$PATH" 24 | - wget 'https://raw.githubusercontent.com/flycheck/emacs-travis/master/emacs-travis.mk' 25 | - make -f emacs-travis.mk EMACSCONFFLAGS="--with-x-toolkit=no --without-x --without-all --with-xml2 --with-modules CFLAGS='-O2 -march=native' CXXFLAGS='-O2 -march=native'" install_emacs 26 | # cmake 27 | - wget 'https://github.com/Kitware/CMake/releases/download/v3.15.0/cmake-3.15.0-Linux-x86_64.tar.gz' 28 | - tar -xf cmake-3.15.0-Linux-x86_64.tar.gz 29 | - export CMAKE="$(pwd)/cmake-3.15.0-Linux-x86_64/bin/cmake" 30 | 31 | script: 32 | - mkdir build 33 | - cd build 34 | - ${CMAKE} .. 35 | - make 36 | 37 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | include(ExternalProject) 3 | 4 | project(emacs-libvterm C) 5 | 6 | if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR CMAKE_SYSTEM_NAME STREQUAL "NetBSD") 7 | set(LIBVTERM_BUILD_COMMAND "gmake") 8 | else() 9 | set(LIBVTERM_BUILD_COMMAND "make") 10 | endif() 11 | 12 | add_library(vterm-module MODULE vterm-module.c utf8.c elisp.c) 13 | set_target_properties(vterm-module PROPERTIES 14 | C_STANDARD 99 15 | C_VISIBILITY_PRESET "hidden" 16 | POSITION_INDEPENDENT_CODE ON 17 | PREFIX "" 18 | LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR} 19 | ) 20 | 21 | # Set RelWithDebInfo as default build type 22 | if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 23 | message(STATUS "No build type selected, defaulting to RelWithDebInfo") 24 | set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Build type (default RelWithDebInfo)" FORCE) 25 | endif() 26 | 27 | # Look for the header file. 28 | option(USE_SYSTEM_LIBVTERM "Use system libvterm instead of the vendored version." ON) 29 | 30 | # Try to find the libvterm in system. 31 | if (USE_SYSTEM_LIBVTERM) 32 | # try to find the vterm.h header file. 33 | find_path(LIBVTERM_INCLUDE_DIR 34 | NAMES vterm.h 35 | ) 36 | 37 | # vterm.h is found. 38 | if (LIBVTERM_INCLUDE_DIR) 39 | message(STATUS "System libvterm detected") 40 | execute_process(COMMAND grep -c "VTermStringFragment" "${LIBVTERM_INCLUDE_DIR}/vterm.h" OUTPUT_VARIABLE VTermStringFragmentExists) 41 | if (${VTermStringFragmentExists} EQUAL "0") 42 | # add_compile_definitions(VTermStringFragmentNotExists) 43 | add_definitions(-DVTermStringFragmentNotExists) 44 | endif() 45 | execute_process(COMMAND grep -c "VTermSelectionMask" "${LIBVTERM_INCLUDE_DIR}/vterm.h" OUTPUT_VARIABLE VTermSelectionMaskExists) 46 | if (${VTermSelectionMaskExists} EQUAL "0") 47 | # add_compile_definitions(VTermStringFragmentNotExists) 48 | add_definitions(-DVTermSelectionMaskNotExists) 49 | endif() 50 | execute_process(COMMAND grep -c "sb_clear" "${LIBVTERM_INCLUDE_DIR}/vterm.h" OUTPUT_VARIABLE VTermSBClearExists) 51 | if (${VTermSBClearExists} EQUAL "0") 52 | add_definitions(-DVTermSBClearNotExists) 53 | endif() 54 | else() 55 | message(STATUS "System libvterm not found: libvterm will be downloaded and compiled as part of the build process") 56 | endif() 57 | endif() 58 | 59 | if (LIBVTERM_INCLUDE_DIR) 60 | find_library(LIBVTERM_LIBRARY NAMES 61 | vterm 62 | libvterm 63 | ) 64 | 65 | if(NOT LIBVTERM_LIBRARY) 66 | message(FATAL_ERROR "libvterm not found") 67 | endif() 68 | else() 69 | find_program(LIBTOOL NAMES libtool glibtool) 70 | if(NOT LIBTOOL) 71 | message(FATAL_ERROR "libtool not found. Please install libtool") 72 | endif() 73 | 74 | ExternalProject_add(libvterm 75 | GIT_REPOSITORY https://github.com/Sbozzolo/libvterm-mirror.git 76 | GIT_TAG 64f1775952dbe001e989f2ab679563b54f2fca55 77 | CONFIGURE_COMMAND "" 78 | BUILD_COMMAND ${LIBVTERM_BUILD_COMMAND} "CFLAGS='-fPIC'" "LDFLAGS='-static'" 79 | BUILD_IN_SOURCE ON 80 | INSTALL_COMMAND "") 81 | 82 | ExternalProject_Get_property(libvterm SOURCE_DIR) 83 | 84 | set(LIBVTERM_INCLUDE_DIR ${SOURCE_DIR}/include) 85 | set(LIBVTERM_LIBRARY ${SOURCE_DIR}/.libs/libvterm.a) 86 | 87 | add_dependencies(vterm-module libvterm) 88 | 89 | # Workaround for https://gitlab.kitware.com/cmake/cmake/issues/15052 90 | file(MAKE_DIRECTORY ${LIBVTERM_INCLUDE_DIR}) 91 | endif() 92 | 93 | add_library(vterm STATIC IMPORTED) 94 | set_target_properties(vterm PROPERTIES IMPORTED_LOCATION ${LIBVTERM_LIBRARY}) 95 | target_include_directories(vterm INTERFACE ${LIBVTERM_INCLUDE_DIR}) 96 | 97 | # Link with libvterm 98 | target_link_libraries(vterm-module PUBLIC vterm) 99 | 100 | # Custom run command for testing 101 | add_custom_target(run 102 | COMMAND emacs -Q -L ${CMAKE_SOURCE_DIR} -L ${CMAKE_BINARY_DIR} --eval "\\(require \\'vterm\\)" --eval "\\(vterm\\)" 103 | DEPENDS vterm-module 104 | ) 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MELPA](https://melpa.org/packages/vterm-badge.svg)](https://melpa.org/#/vterm) 2 | 3 | # Introduction 4 | 5 | Emacs-libvterm (_vterm_) is fully-fledged terminal emulator inside GNU Emacs 6 | based on [libvterm](https://github.com/neovim/libvterm), a C library. As a 7 | result of using compiled code (instead of elisp), emacs-libvterm is fully 8 | capable, fast, and it can seamlessly handle large outputs. 9 | 10 | ## Warning 11 | 12 | This package is in active development and, while being stable enough to be used 13 | as a daily-driver, it is currently in **alpha** stage. This means that 14 | occasionally the public interface will change (for example names of options or 15 | functions). A list of recent breaking changes is in 16 | [appendix](#breaking-changes). Moreover, emacs-libvterm deals directly with some 17 | low-level operations, hence, bugs can lead to segmentation faults and crashes. 18 | If that happens, please [report the 19 | problem](https://github.com/akermu/emacs-libvterm/issues/new). 20 | 21 | ## Given that eshell, shell, and (ansi-)term are Emacs built-in, why should I use vterm? 22 | 23 | The short answer is: unparalleled performance and compatibility with standard 24 | command-line tools. 25 | 26 | For the long answer, let us discuss the differences between `eshell`, `shell`, 27 | `term` and `vterm`: 28 | - `eshell`: it is a shell completely implemented in Emacs Lisp. It is 29 | well-integrated in Emacs and it runs on Windows. It does not support command line 30 | tools that require terminal manipulation capabilities (e.g., `ncdu`, `nmtui`, 31 | ...). 32 | - `shell`: it interfaces with a standard shell (e.g., `bash`). It reads an input 33 | from Emacs, sends it to the shell, and reports back the output from the shell. 34 | As such, like `eshell`, it does not support interactive commands, especially 35 | those that directly handle how the output should be displayed (e.g., `htop`). 36 | - `term`: it is a terminal emulator written in elisp. `term` runs a shell 37 | (similarly to other terminal emulators like Gnome Terminal) and programs can 38 | directly manipulate the output using escape codes. Hence, many interactive 39 | applications (like the one aforementioned) work with `term`. However, `term` 40 | and `ansi-term` do not implement all the escapes codes needed, so some 41 | programs do not work properly. Moreover, `term` has inferior performance 42 | compared to standalone terminals, especially with large bursts of output. 43 | - `vterm`: like `term` it is a terminal emulator. Unlike `term`, the core of 44 | `vterm` is an external library written in C, `libvterm`. For this reason, 45 | `vterm` outperforms `term` and has a nearly universal compatibility with 46 | terminal applications. 47 | 48 | Vterm is not for you [if you are using Windows](https://github.com/akermu/emacs-libvterm/issues/12), or if you cannot set up Emacs 49 | with support for modules. Otherwise, you should try vterm, as it provides a 50 | superior terminal experience in Emacs. 51 | 52 | Using `vterm` is like using Gnome Terminal inside Emacs: Vterm is fully-featured 53 | and fast, but is not as well integrated in Emacs as `eshell` (yet), so some of 54 | the editing keybinding you are used to using may not work. For example, 55 | `evil-mode` is currently not supported (though, users can enable VI emulation in 56 | their shells). This is because keys are sent directly to the shell. We are 57 | constantly working to improve this. 58 | 59 | # Installation 60 | 61 | ## Requirements 62 | 63 | Before installing emacs-libvterm, you need to make sure you have installed 64 | 1. GNU Emacs (>= 25.1) with [module 65 | support](https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Modules.html). 66 | You can check that, by verifying that `module-file-suffix` is not `nil`. 67 | 2. cmake (>= 3.11) 68 | 3. libtool-bin (related issues: 69 | [#66](https://github.com/akermu/emacs-libvterm/issues/66) 70 | [#85](https://github.com/akermu/emacs-libvterm/issues/85#issuecomment-491845136)) 71 | 4. OPTIONAL: [libvterm](https://github.com/Sbozzolo/libvterm-mirror.git) (>= 0.2). This 72 | library can be found in the official repositories of most distributions 73 | (e.g., Arch, Debian, Fedora, Gentoo, openSUSE, Ubuntu). Typical names are 74 | `libvterm` (Arch, Fedora, Gentoo, openSUSE), or `libvterm-dev` (Debian, 75 | Ubuntu). If not available, `libvterm` will be downloaded during the 76 | compilation process. Some distributions (e.g. Ubuntu < 20.04, Debian < 11) 77 | have versions of `libvterm` that are too old. If you find compilation errors 78 | related to `VTERM_COLOR`, you should not use your system libvterm. See 79 | [FAQ](#frequently-asked-questions-and-problems) for more details. 80 | 81 | ## From MELPA 82 | 83 | `vterm` is available on [MELPA](https://melpa.org/), and it can be installed as 84 | a normal package. If the requirements are satisfied (mainly, Emacs was built 85 | with support for modules), `vterm` will compile the module the first time it is 86 | run. This is the recommended way to install `vterm`. 87 | 88 | `vterm` can be install from MELPA with `use-package` by adding the following 89 | lines to your `init.el`: 90 | 91 | ```elisp 92 | (use-package vterm 93 | :ensure t) 94 | ``` 95 | 96 | To take full advantage of the capabilities of `vterm`, you should configure your 97 | shell too. Read about this in the section [shell-side 98 | configuration](#shell-side-configuration). 99 | 100 | ## Manual installation 101 | 102 | Clone the repository: 103 | 104 | ```sh 105 | git clone https://github.com/akermu/emacs-libvterm.git 106 | ``` 107 | 108 | By default, vterm will try to find if libvterm is installed. If it is not found, 109 | emacs-libvterm will download the latest version available of libvterm (from 110 | [here](https://github.com/neovim/libvterm)), compile it, and use it. If you 111 | always want to use the vendored version as opposed to the one on you system, set 112 | `USE_SYSTEM_LIBVTERM` to `no`. To do this, change `cmake ..` with `cmake 113 | -DUSE_SYSTEM_LIBVTERM=no ..` in the following instructions. 114 | 115 | Build the module with: 116 | 117 | ```sh 118 | cd emacs-libvterm 119 | mkdir -p build 120 | cd build 121 | cmake .. 122 | make 123 | ``` 124 | 125 | And add this to your `init.el`: 126 | 127 | ```elisp 128 | (add-to-list 'load-path "path/to/emacs-libvterm") 129 | (require 'vterm) 130 | ``` 131 | 132 | Or, with `use-package`: 133 | 134 | ```elisp 135 | (use-package vterm 136 | :load-path "path/to/emacs-libvterm/") 137 | ``` 138 | 139 | ## vterm and Ubuntu 140 | ### 20.04 141 | Using `vterm` on Ubuntu requires additional steps. The latest LTS version 142 | (20.04) ships without CMake installed and Emacs27 is not yet available from Ubuntu's package repository. 143 | 144 | The basic steps for getting vterm to work on Ubuntu 20.04 are: 145 | * Ensure Emacs27 is installed 146 | * Install cmake, libtool, and libtool-bin 147 | 148 | There are a few options for installing Emacs27 on Ubuntu 20.04: 149 | * Compile Emacs27 from source 150 | * Install Emacs27 from Snap 151 | * Install Emacs27 from Kevin Kelley's PPA 152 | 153 | In any case, if you have an older Emacs version you will need to purge it before proceeding: 154 | 155 | #### Purge Emacs 156 | 157 | ```sh 158 | sudo apt --purge remove emacs 159 | sudo apt autoremove 160 | ``` 161 | 162 | #### Installing Emacs27 from Kevin Kelley PPA 163 | 164 | ```sh 165 | sudo add-apt-repository ppa:kelleyk/emacs 166 | sudo apt install emacs27 167 | ``` 168 | 169 | ##### If you get an error about emacs27_common during the install process: 170 | 171 | ```sh 172 | Errors were encountered while processing: 173 | /tmp/apt-dpkg-install-RVK8CA/064-emacs27-common_27.1~1.git86d8d76aa3-kk2+20.04_all.deb 174 | ``` 175 | 176 | run 177 | 178 | ```sh 179 | sudo apt --purge remove emacs-common 180 | sudo apt --fix-broken install 181 | ``` 182 | 183 | #### Installing Emacs27 from Snap 184 | I hesitate to include SNAP here, because I ran into a number of GTK Theme parsing errors, and Fontconfig errors when I tested it, and reverted to installing from Kevin Kelley's PPA. YMMV 185 | 186 | ```sh 187 | sudo snap install emacs --classic 188 | ``` 189 | 190 | #### Install CMake and Libtool 191 | In Ubuntu 20.04 CMake (v3.16.3-1ubuntu1) and Libtool can be installed with 192 | 193 | ```sh 194 | sudo apt install cmake 195 | sudo apt install libtool 196 | sudo apt install libtool-bin 197 | ``` 198 | 199 | ### 18.04 200 | 201 | Using `vterm` on Ubuntu 18.04 requires additional steps. 202 | 18.04 ships with a version of CMake that is too old for `vterm` and GNU 203 | Emacs is not compiled with support for dynamical module loading. 204 | 205 | It is possible to install GNU Emacs with module support from Kevin Kelley's PPA. 206 | The binary in Ubuntu Emacs Lisp PPA is currently broken and leads to segmentation faults 207 | (see [#185](https://github.com/akermu/emacs-libvterm/issues/185#issuecomment-562237077)). 208 | In case Emacs is already on the system, you need to purge it before proceeding 209 | with the following commands. 210 | 211 | ```sh 212 | sudo add-apt-repository ppa:kelleyk/emacs 213 | sudo apt update 214 | sudo apt-get install emacs26 215 | ``` 216 | 217 | A way to install a recent version of CMake (>= 3.11) is with linuxbrew. 218 | 219 | ```sh 220 | brew install cmake 221 | ``` 222 | 223 | 224 | In some cases, `/bin/sh` needs to be relinked to `/bin/bash` for the compilation 225 | to work (see, 226 | [#216](https://github.com/akermu/emacs-libvterm/issues/216#issuecomment-575934593)). 227 | 228 | Pull requests to improve support for Ubuntu are welcome (e.g., simplifying the 229 | installation). 230 | 231 | Some releases of Ubuntu (e.g., 18.04) ship with a old version of libvterm that 232 | can lead to compilation errors. If you have this problem, see the 233 | [FAQ](#frequently-asked-questions-and-problems) for a solution. 234 | 235 | ## GNU Guix 236 | 237 | `vterm` and its dependencies are available in GNU Guix as 238 | [emacs-vterm](https://guix.gnu.org/packages/emacs-vterm-0-1.7d7381f/). 239 | The package can be installed with `guix package -i emacs-vterm`. 240 | 241 | ## Shell-side configuration 242 | 243 | Some of the most useful features in `vterm` (e.g., [directory-tracking and 244 | prompt-tracking](#directory-tracking-and-prompt-tracking) or [message 245 | passing](#message-passing)) require shell-side configurations. The main goal of 246 | these additional functions is to enable the shell to send information to `vterm` 247 | via properly escaped sequences. A function that helps in this task, 248 | `vterm_printf`, is defined below. This function is widely used throughout this 249 | readme. 250 | 251 | For `bash` or `zsh`, put this in your `.zshrc` or `.bashrc` 252 | 253 | ```sh 254 | vterm_printf() { 255 | if [ -n "$TMUX" ] \ 256 | && { [ "${TERM%%-*}" = "tmux" ] \ 257 | || [ "${TERM%%-*}" = "screen" ]; }; then 258 | # Tell tmux to pass the escape sequences through 259 | printf "\ePtmux;\e\e]%s\007\e\\" "$1" 260 | elif [ "${TERM%%-*}" = "screen" ]; then 261 | # GNU screen (screen, screen-256color, screen-256color-bce) 262 | printf "\eP\e]%s\007\e\\" "$1" 263 | else 264 | printf "\e]%s\e\\" "$1" 265 | fi 266 | } 267 | ``` 268 | 269 | This works also for `dash`. 270 | 271 | For `fish` put this in your `~/.config/fish/config.fish`: 272 | 273 | ```fish 274 | function vterm_printf; 275 | if begin; [ -n "$TMUX" ] ; and string match -q -r "screen|tmux" "$TERM"; end 276 | # tell tmux to pass the escape sequences through 277 | printf "\ePtmux;\e\e]%s\007\e\\" "$argv" 278 | else if string match -q -- "screen*" "$TERM" 279 | # GNU screen (screen, screen-256color, screen-256color-bce) 280 | printf "\eP\e]%s\007\e\\" "$argv" 281 | else 282 | printf "\e]%s\e\\" "$argv" 283 | end 284 | end 285 | ``` 286 | 287 | # Debugging and testing 288 | 289 | If you have successfully built the module, you can test it by executing the 290 | following command in the `build` directory: 291 | 292 | ```sh 293 | make run 294 | ``` 295 | 296 | # Usage 297 | 298 | ## `vterm` 299 | 300 | Open a terminal in the current window. 301 | 302 | ## `vterm-other-window` 303 | 304 | Open a terminal in another window. 305 | 306 | ## `vterm-copy-mode` 307 | 308 | When you enable `vterm-copy-mode`, the terminal buffer behaves like a normal 309 | `read-only` text buffer: you can search, copy text, etc. The default keybinding 310 | to toggle `vterm-copy-mode` is `C-c C-t`. When a region is selected, it is 311 | possible to copy the text and leave `vterm-copy-mode` with the enter key. 312 | 313 | If no region is selected when the enter key is pressed it will copy the current 314 | line from start to end. If `vterm-copy-exclude-prompt` is true it will skip 315 | the prompt and not include it in the copy. 316 | 317 | ## `vterm-clear-scrollback` 318 | 319 | `vterm-clear-scrollback` does exactly what the name suggests: it clears the 320 | current buffer from the data that it is not currently visible. 321 | `vterm-clear-scrollback` is bound to `C-c C-l`. This function is typically used 322 | with the `clear` function provided by the shell to clear both screen and 323 | scrollback. In order to achieve this behavior, you need to add a new shell alias. 324 | 325 | For `zsh`, put this in your `.zshrc`: 326 | 327 | ```zsh 328 | if [[ "$INSIDE_EMACS" = 'vterm' ]]; then 329 | alias clear='vterm_printf "51;Evterm-clear-scrollback";tput clear' 330 | fi 331 | ``` 332 | 333 | For `bash`, put this in your `.bashrc`: 334 | 335 | ```bash 336 | if [ "$INSIDE_EMACS" = 'vterm' ]; then 337 | clear() { 338 | vterm_printf "51;Evterm-clear-scrollback"; 339 | tput clear; 340 | } 341 | fi 342 | ``` 343 | 344 | For `fish`: 345 | 346 | ```fish 347 | if [ "$INSIDE_EMACS" = 'vterm' ] 348 | function clear 349 | vterm_printf "51;Evterm-clear-scrollback"; 350 | tput clear; 351 | end 352 | end 353 | ``` 354 | 355 | These aliases take advantage of the fact that `vterm` can execute `elisp` 356 | commands, as explained below. 357 | 358 | If it possible to automatically clear the scrollback when the screen is cleared 359 | by setting the variable `vterm-clear-scrollback-when-clearing`: When 360 | `vterm-clear-scrollback-when-clearing` is non nil, `C-l` clears both the screen 361 | and the scrollback. When is nil, `C-l` only clears the screen. The opposite 362 | behavior can be achieved by using the universal prefix (i.e., calling `C-u C-l`). 363 | 364 | # Customization 365 | 366 | ## `vterm-shell` 367 | 368 | Shell to run in a new vterm. It defaults to `$SHELL`. 369 | 370 | ## `vterm-environment` 371 | 372 | to add more environment variables there is the custom vterm-environment which has 373 | a similar format than the internal Emacs variable process-environment. 374 | You can check the documentation with C-h v process-environment for more details. 375 | 376 | ## `vterm-term-environment-variable` 377 | 378 | Value for the `TERM` environment variable. It defaults to `xterm-256color`. If 379 | [eterm-256color](https://github.com/dieggsy/eterm-256color) is installed, 380 | setting `vterm-term-environment-variable` to `eterm-color` improves the 381 | rendering of colors in some systems. 382 | 383 | ## `vterm-kill-buffer-on-exit` 384 | 385 | If set to `t`, buffers are killed when the associated process is terminated (for 386 | example, by logging out the shell). Keeping buffers around it is useful if you 387 | need to copy or manipulate the content. 388 | 389 | ## `vterm-module-cmake-args` 390 | 391 | Compilation flags and arguments to be given to CMake when compiling the module. 392 | This string is directly passed to CMake, so it uses the same syntax. At the 393 | moment, it main use is for compiling vterm using the system libvterm instead of 394 | the one downloaded from GitHub. You can find all the arguments and flags 395 | available with `cmake -LA` in the build directory. 396 | 397 | ## `vterm-copy-exclude-prompt` 398 | 399 | Controls whether or not to exclude the prompt when copying a line in 400 | `vterm-copy-mode`. Using the universal prefix before calling 401 | `vterm-copy-mode-done` will invert the value for that call, allowing you to 402 | temporarily override the setting. When a prompt is not found, the whole line is 403 | copied. 404 | 405 | ## `vterm-use-vterm-prompt-detection-method` 406 | 407 | The variable `vterm-use-vterm-prompt-detection-method` determines whether to use 408 | the vterm prompt tracking, if false it use the regexp in 409 | `vterm-copy-prompt-regexp` to search for the prompt. 410 | 411 | ## `vterm-enable-manipulate-selection-data-by-osc52` 412 | 413 | Vterm support copy text to Emacs kill ring and system clipboard by using OSC 52. 414 | See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html for more info about OSC 52. 415 | For example: send 'blabla' to kill ring: printf "\033]52;c;$(printf "%s" "blabla" | base64)\a" 416 | 417 | tmux can share its copy buffer to terminals by supporting osc52(like iterm2 xterm), 418 | you can enable this feature for tmux by : 419 | set -g set-clipboard on #osc 52 copy paste share with iterm 420 | set -ga terminal-overrides ',xterm*:XT:Ms=\E]52;%p1%s;%p2%s\007' 421 | set -ga terminal-overrides ',screen*:XT:Ms=\E]52;%p1%s;%p2%s\007' 422 | 423 | The clipboard querying/clearing functionality offered by OSC 52 is not implemented here, 424 | And for security reason, this feature is disabled by default." 425 | 426 | This feature need the new way of handling strings with a struct `VTermStringFragment` 427 | in libvterm. You'd better compile emacs-libvterm with `cmake -DUSE_SYSTEM_LIBVTERM=no ..`. 428 | If you don't do that, when the content you want to copied is too long, it would be truncated 429 | by bug of libvterm. 430 | 431 | ## `vterm-buffer-name-string` 432 | 433 | When `vterm-buffer-name-string` is not nil, vterm renames automatically its own 434 | buffers with `vterm-buffer-name-string`. This string can contain the character 435 | `%s`, which is substituted with the _title_ (as defined by the shell, see 436 | below). A possible value for `vterm-buffer-name-string` is `vterm %s`, according 437 | to which all the vterm buffers will be named "vterm TITLE". 438 | 439 | This requires some shell-side configuration to print the title. For example to 440 | set the name "HOSTNAME:PWD", use can you the following: 441 | 442 | For `zsh`, 443 | 444 | ```zsh 445 | autoload -U add-zsh-hook 446 | add-zsh-hook -Uz chpwd (){ print -Pn "\e]2;%m:%2~\a" } 447 | ``` 448 | 449 | For `bash`, 450 | 451 | ```bash 452 | PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }"'echo -ne "\033]0;${HOSTNAME}:${PWD}\007"' 453 | ``` 454 | 455 | For `fish`, 456 | 457 | ```fish 458 | function fish_title 459 | hostname 460 | echo ":" 461 | prompt_pwd 462 | end 463 | ``` 464 | 465 | See [zsh and bash](http://tldp.org/HOWTO/Xterm-Title-4.html) and [fish 466 | documentations](https://fishshell.com/docs/current/#programmable-title). 467 | 468 | ## `vterm-always-compile-module` 469 | 470 | Vterm needs `vterm-module` to work. This can be compiled externally, or `vterm` 471 | will ask the user whether to build the module when `vterm` is first called. To 472 | avoid this question and always compile the module, set 473 | `vterm-always-compile-module` to `t`. 474 | 475 | ## `vterm-copy-mode-remove-fake-newlines` 476 | 477 | When not-nil fake newlines are removed on entering copy mode. 478 | vterm inserts fake-newlines purely for rendering. When using 479 | `vterm-copy-mode` these are in conflict with many emacs functions 480 | like isearch-forward. if this varialbe is not-nil the 481 | fake-newlines are removed on entering copy-mode and re-inserted 482 | on leaving copy mode. Also truncate-lines is set to t on entering 483 | copy-mode and set to nil on leaving. 484 | 485 | ## `vterm-tramp-shells` 486 | 487 | The shell that gets run in the vterm for tramp. 488 | 489 | This has to be a list of pairs of the format: 490 | `(TRAMP-METHOD SHELL)` 491 | 492 | The `TRAMP-METHOD` is a method string as used by tramp (e.g., `"ssh"`). 493 | Use t as `TRAMP-METHOD` to specify a default shell for all methods. 494 | Specific methods always take precedence over `t`. 495 | 496 | Set SHELL to `'login-shell` to use the user's login shell on the remote host. 497 | The login-shell detection currently works for POSIX-compliant remote hosts that 498 | have the `getent` command (regular GNU/Linux distros, *BSDs, but not MacOS X 499 | unfortunately). 500 | You can specify an additional second `SHELL` command as a fallback 501 | that is used when the login-shell detection fails, e.g., 502 | `'(("ssh" login-shell "/bin/bash") ...)` 503 | If no second `SHELL` command is specified with `'login-shell`, vterm will 504 | fall back to tramp's shell. 505 | 506 | Examples: 507 | - Usee the default login shell for all methods, except for docker. 508 | `'((t login-shell) ("docker" "/bin/sh"))` 509 | - Use the default login shell for ssh and scp, fall back to "/bin/bash". 510 | Use tramp's default shell for all other methods. 511 | `'(("ssh" login-shell "/bin/bash") ("scp" login-shell "/bin/bash"))` 512 | 513 | ## Keybindings 514 | 515 | If you want a key to be sent to the terminal, bind it to `vterm--self-insert`, 516 | or remove it from `vterm-mode-map`. By default, `vterm.el` binds most of the 517 | `C-` and `M-` keys, `` through `` and some special keys 518 | like `` and ``. Sending a keyboard interrupt is bound to `C-c 519 | C-c`. 520 | 521 | In order to send a keypress that is already recognized by Emacs, such as `C-g`, 522 | use the interactive function `vterm-send-next-key`. This can be bound to a key 523 | in the `vterm-mode-map` like `C-q`, in which case pressing `C-q C-g` will send a 524 | `C-g` key to the terminal, and so on for other modified keys: 525 | 526 | ```elisp 527 | (define-key vterm-mode-map (kbd "C-q") #'vterm-send-next-key) 528 | ``` 529 | 530 | This can be useful for controlling an application running in the terminal, such 531 | as Emacs or Nano. 532 | 533 | ## Fonts 534 | 535 | You can change the font (the _face_) used in a vterm with the following code: 536 | 537 | ```elisp 538 | (add-hook 'vterm-mode-hook 539 | (lambda () 540 | (set (make-local-variable 'buffer-face-mode-face) 'fixed-pitch) 541 | (buffer-face-mode t))) 542 | ``` 543 | 544 | Where instead of `'fixed-pitch` you specify the face you want to use. The 545 | example reported here can be used to force vterm to use a mono-spaced font (the 546 | `fixed-pitch` face). This is useful when your default font in Emacs is a 547 | proportional font. 548 | 549 | In addition to that, you can disable some text properties (bold, underline, 550 | reverse video) setting the relative option to `t` (`vterm-disable-bold`, 551 | `vterm-disable-underline`, or `vterm-disable-inverse-video`). 552 | 553 | You can use another font for vterm buffer 554 | 555 | ``` elisp 556 | (add-hook 'vterm-mode-hook 557 | (lambda () 558 | (set (make-local-variable 'buffer-face-mode-face) '(:family "IosevkaTerm Nerd Font")) 559 | (buffer-face-mode t)) 560 | ``` 561 | ## Blink cursor 562 | 563 | When `vterm-ignore-blink-cursor` is `t`, vterm will ignore request from application to turn on or off cursor blink. 564 | 565 | If `nil`, cursor in any window may begin to blink or not blink because `blink-cursor-mode` 566 | is a global minor mode in Emacs, you can use `M-x blink-cursor-mode` to toggle. 567 | 568 | ## Colors 569 | 570 | Customize the following faces to your liking. It is possible to specify 571 | different colors for foreground and background via the `:foreground` and 572 | `:background` attributes. 573 | 574 | - vterm-color-black 575 | - vterm-color-red 576 | - vterm-color-green 577 | - vterm-color-yellow 578 | - vterm-color-blue 579 | - vterm-color-magenta 580 | - vterm-color-cyan 581 | - vterm-color-white 582 | - vterm-color-bright-black 583 | - vterm-color-bright-red 584 | - vterm-color-bright-green 585 | - vterm-color-bright-yellow 586 | - vterm-color-bright-blue 587 | - vterm-color-bright-magenta 588 | - vterm-color-bright-cyan 589 | - vterm-color-bright-white 590 | 591 | ## Directory tracking and Prompt tracking 592 | 593 | `vterm` supports _directory tracking_. If this feature is enabled, the default 594 | directory in Emacs and the current working directory in `vterm` are synced. As a 595 | result, interactive functions that ask for a path or a file (e.g., `dired` or 596 | `find-file`) will do so starting from the current location. 597 | 598 | And `vterm` supports _prompt tracking_. If this feature is enabled, Emacs knows 599 | where the prompt ends, you needn't customize `term-prompt-regexp` any more. 600 | Then you can use `vterm-next-prompt` and `vterm-previous-prompt` 601 | moving to end of next/previous prompt. The default keybinding is `C-c C-n` and `C-c C-p`. 602 | 603 | And `vterm-beginning-of-line` would move the point to the first character after the 604 | shell prompt on this line. If the point is already there, move to the beginning of the line. 605 | The default keybinding is `C-a` in `vterm-copy-mode`. 606 | 607 | And `vterm--at-prompt-p` would check whether the cursor is at the point just after 608 | the shell prompt. 609 | 610 | Directory tracking and Prompt tracking requires some configuration, as the shell has to be 611 | instructed to share the relevant information with Emacs. The following pieces of 612 | code assume that you have the function `vterm_printf` as defined in section 613 | [shell-side configuration](#shell-side-configuration). 614 | 615 | For `zsh`, put this at the end of your `.zshrc`: 616 | 617 | ```zsh 618 | vterm_prompt_end() { 619 | vterm_printf "51;A$(whoami)@$(hostname):$(pwd)" 620 | } 621 | setopt PROMPT_SUBST 622 | PROMPT=$PROMPT'%{$(vterm_prompt_end)%}' 623 | ``` 624 | 625 | For `bash`, put this at the end of your `.bashrc`: 626 | 627 | ```bash 628 | vterm_prompt_end(){ 629 | vterm_printf "51;A$(whoami)@$(hostname):$(pwd)" 630 | } 631 | PS1=$PS1'\[$(vterm_prompt_end)\]' 632 | ``` 633 | 634 | For `fish`, put this in your `~/.config/fish/config.fish`: 635 | 636 | ```fish 637 | function vterm_prompt_end; 638 | vterm_printf '51;A'(whoami)'@'(hostname)':'(pwd) 639 | end 640 | functions --copy fish_prompt vterm_old_fish_prompt 641 | function fish_prompt --description 'Write out the prompt; do not replace this. Instead, put this at end of your file.' 642 | # Remove the trailing newline from the original prompt. This is done 643 | # using the string builtin from fish, but to make sure any escape codes 644 | # are correctly interpreted, use %b for printf. 645 | printf "%b" (string join "\n" (vterm_old_fish_prompt)) 646 | vterm_prompt_end 647 | end 648 | ``` 649 | 650 | Here we are using the function `vterm_printf` that we have discussed above, so make 651 | sure that this function is defined in your configuration file. 652 | 653 | Directory tracking works on remote servers too. In case the hostname of your 654 | remote machine does not match the actual hostname needed to connect to that 655 | server, change `$(hostname)` with the correct one. For example, if the correct 656 | hostname is `foo` and the username is `bar`, you should have something like 657 | 658 | ```bash 659 | HOSTNAME=foo 660 | USER=baz 661 | vterm_printf "51;A$USER@$HOSTNAME:$(pwd)" 662 | ``` 663 | 664 | ## Message passing 665 | 666 | `vterm` can read and execute commands. At the moment, a command is 667 | passed by providing a specific escape sequence. For example, to evaluate 668 | 669 | ```elisp 670 | (message "Hello!") 671 | ``` 672 | 673 | use 674 | 675 | ```sh 676 | printf "\e]51;Emessage \"Hello\!\"\e\\" 677 | # or 678 | vterm_printf "51;Emessage \"Hello\!\"" 679 | ``` 680 | 681 | The commands that are understood are defined in the setting `vterm-eval-cmds`. 682 | 683 | As `split-string-and-unquote` is used the parse the passed string, double quotes 684 | and backslashes need to be escaped via backslash. A convenient shell function to 685 | automate the substitution is 686 | 687 | `bash` or `zsh`: 688 | 689 | ```sh 690 | vterm_cmd() { 691 | local vterm_elisp 692 | vterm_elisp="" 693 | while [ $# -gt 0 ]; do 694 | vterm_elisp="$vterm_elisp""$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")" 695 | shift 696 | done 697 | vterm_printf "51;E$vterm_elisp" 698 | } 699 | ``` 700 | 701 | `fish`: 702 | 703 | ```fish 704 | function vterm_cmd --description 'Run an Emacs command among the ones been defined in vterm-eval-cmds.' 705 | set -l vterm_elisp () 706 | for arg in $argv 707 | set -a vterm_elisp (printf '"%s" ' (string replace -a -r '([\\\\"])' '\\\\\\\\$1' $arg)) 708 | end 709 | vterm_printf '51;E'(string join '' $vterm_elisp) 710 | end 711 | ``` 712 | 713 | Now we can write shell functions to call the ones defined in `vterm-eval-cmds`. 714 | 715 | ```sh 716 | find_file() { 717 | vterm_cmd find-file "$(realpath "${@:-.}")" 718 | } 719 | 720 | say() { 721 | vterm_cmd message "%s" "$*" 722 | } 723 | ``` 724 | 725 | Or for `fish`: 726 | 727 | ```fish 728 | function find_file 729 | set -q argv[1]; or set argv[1] "." 730 | vterm_cmd find-file (realpath "$argv") 731 | end 732 | 733 | function say 734 | vterm_cmd message "%s" "$argv" 735 | end 736 | ``` 737 | 738 | This newly defined `find_file` function can now be used inside `vterm` as 739 | 740 | ```sh 741 | find_file name_of_file_in_local_directory 742 | ``` 743 | 744 | If you call `find_file` without specifying any file (you just execute `find_file` in your shell), 745 | `dired` will open with the current directory. 746 | 747 | As an example, say you like having files opened below the current window. You 748 | could add the command to do it on the lisp side like so: 749 | 750 | ```elisp 751 | (push (list "find-file-below" 752 | (lambda (path) 753 | (if-let* ((buf (find-file-noselect path)) 754 | (window (display-buffer-below-selected buf nil))) 755 | (select-window window) 756 | (message "Failed to open file: %s" path)))) 757 | vterm-eval-cmds) 758 | ``` 759 | 760 | Then add the command in your `.bashrc` file. 761 | 762 | ```sh 763 | open_file_below() { 764 | vterm_cmd find-file-below "$(realpath "${@:-.}")" 765 | } 766 | ``` 767 | 768 | Then you can open any file from inside your shell. 769 | 770 | ```sh 771 | open_file_below ~/Documents 772 | ``` 773 | 774 | ## Shell-side configuration files 775 | 776 | The configurations described in earlier sections are combined in 777 | [`etc/`](./etc/). These can be appended to or loaded into your user 778 | configuration file. Alternatively, they can be installed system-wide, for 779 | example in `/etc/bash/bashrc.d/`, `/etc/profile.d/` (for `zsh`), or 780 | `/etc/fish/conf.d/` for `fish`. 781 | 782 | When using vterm Emacs sets the environment variable INSIDE_EMACS in the subshell to ‘vterm’. 783 | Usually the programs check this variable to determine whether they are running inside Emacs. 784 | 785 | Vterm also sets an extra variable EMACS_VTERM_PATH to the place where the vterm library is installed. 786 | This is very useful because when vterm is installed from melpa the Shell-side configuration files are 787 | in the EMACS_VTERM_PATH inside the /etc sub-directory. After a package update, the directory name changes, 788 | so, a code like this in your bashrc could be enough to load always the latest version of the file 789 | from the right location without coping any file manually. 790 | 791 | ```sh 792 | if [[ "$INSIDE_EMACS" = 'vterm' ]] \ 793 | && [[ -n ${EMACS_VTERM_PATH} ]] \ 794 | && [[ -f ${EMACS_VTERM_PATH}/etc/emacs-vterm-bash.sh ]]; then 795 | source ${EMACS_VTERM_PATH}/etc/emacs-vterm-bash.sh 796 | fi 797 | ``` 798 | 799 | ## Frequently Asked Questions and Problems 800 | 801 | ### How can I increase the size of the scrollback? 802 | 803 | By default, the scrollback can contain up to 1000 lines per each vterm buffer. 804 | You can increase this up to 100000 by changing the variable 805 | `vterm-max-scrollback`. If you want to increase it further, you have to edit the 806 | file `vterm-module.h`, change the variable `SB_MAX`, and set the new value for 807 | `vterm-max-scrollback`. The potential maximum memory consumption of vterm 808 | buffers increases with `vterm-max-scrollback`, so setting `SB_MAX` to extreme 809 | values may lead to system instabilities and crashes. 810 | 811 | ### How can I automatically close vterm buffers when the process is terminated? 812 | 813 | There is an option for that: set `vterm-kill-buffer-on-exit` to `t`. 814 | 815 | ### The package does not compile, I have errors related to `VTERM_COLOR`. 816 | 817 | The version of `libvterm` installed on your system is too old. You should let 818 | `emacs-libvterm` download `libvterm` for you. You can either uninstall your 819 | libvterm, or instruct Emacs to ignore the system libvterm. If you are compiling 820 | from Emacs, you can do this by setting: 821 | 822 | ```elisp 823 | (setq vterm-module-cmake-args "-DUSE_SYSTEM_LIBVTERM=no") 824 | ``` 825 | 826 | and compile again. If you are compiling with CMake, use the flag 827 | `-DUSE_SYSTEM_LIBVTERM=no`. 828 | 829 | ### `` doesn't kill previous word. 830 | 831 | This can be fixed by rebinding the key to what `C-w` does: 832 | 833 | ```elisp 834 | (define-key vterm-mode-map (kbd "") 835 | (lambda () (interactive) (vterm-send-key (kbd "C-w")))) 836 | ``` 837 | 838 | ### `counsel-yank-pop` doesn't work. 839 | 840 | Add this piece of code to your configuration file to make `counsel` use 841 | the correct function to yank in vterm buffers. 842 | 843 | ```elisp 844 | (defun vterm-counsel-yank-pop-action (orig-fun &rest args) 845 | (if (equal major-mode 'vterm-mode) 846 | (let ((inhibit-read-only t) 847 | (yank-undo-function (lambda (_start _end) (vterm-undo)))) 848 | (cl-letf (((symbol-function 'insert-for-yank) 849 | (lambda (str) (vterm-send-string str t)))) 850 | (apply orig-fun args))) 851 | (apply orig-fun args))) 852 | 853 | (advice-add 'counsel-yank-pop-action :around #'vterm-counsel-yank-pop-action) 854 | ``` 855 | 856 | ### How can I get the local directory without shell-side configuration? 857 | 858 | We recommend that you set up shell-side configuration for reliable directory 859 | tracking. If you cannot do it, a possible workaround is the following. 860 | 861 | On most GNU/Linux systems, you can read current directory from `/proc`: 862 | 863 | ```elisp 864 | (defun vterm-directory-sync () 865 | "Synchronize current working directory." 866 | (interactive) 867 | (when vterm--process 868 | (let* ((pid (process-id vterm--process)) 869 | (dir (file-truename (format "/proc/%d/cwd/" pid)))) 870 | (setq default-directory dir)))) 871 | ``` 872 | 873 | A possible application of this function is in combination with `find-file`: 874 | 875 | ```elisp 876 | (advice-add #'find-file :before #'vterm-directory-sync) 877 | ``` 878 | 879 | This method does not work on remote machines. 880 | 881 | ### How can I get the directory tracking in a more understandable way? 882 | 883 | If you looked at the recommended way to set-up directory tracking, you will have 884 | noticed that it requires printing obscure code like `\e]2;%m:%2~\a` (unless you 885 | are using `fish`). 886 | 887 | There is another way to achieve this behavior. Define a shell function, on a 888 | local host you can simply use 889 | 890 | ```sh 891 | vterm_set_directory() { 892 | vterm_cmd update-pwd "$PWD/" 893 | } 894 | ``` 895 | 896 | On a remote one, use instead 897 | 898 | ```sh 899 | vterm_set_directory() { 900 | vterm_cmd update-pwd "/-:""$USER""@""$HOSTNAME"":""$PWD/" 901 | } 902 | ``` 903 | 904 | Then, for `zsh`, add this function to the `chpwd` hook: 905 | 906 | ```zsh 907 | autoload -U add-zsh-hook 908 | add-zsh-hook -Uz chpwd (){ vterm_set_directory } 909 | ``` 910 | 911 | For `bash`, append it to the prompt: 912 | 913 | ```bash 914 | PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }vterm_set_directory" 915 | ``` 916 | 917 | Finally, add `update-pwd` to the list of commands that Emacs 918 | is allowed to execute from vterm: 919 | 920 | ```elisp 921 | (add-to-list 'vterm-eval-cmds '("update-pwd" (lambda (path) (setq default-directory path)))) 922 | ``` 923 | 924 | ### When evil-mode is enabled, the cursor moves back in normal state, and this messes directory tracking 925 | 926 | `evil-collection` provides a solution for this problem. If you do not want to 927 | use `evil-collection`, you can add the following code: 928 | 929 | ```elisp 930 | (defun evil-collection-vterm-escape-stay () 931 | "Go back to normal state but don't move 932 | cursor backwards. Moving cursor backwards is the default vim behavior but it is 933 | not appropriate in some cases like terminals." 934 | (setq-local evil-move-cursor-back nil)) 935 | 936 | (add-hook 'vterm-mode-hook #'evil-collection-vterm-escape-stay) 937 | ``` 938 | 939 | 940 | ## Related packages 941 | 942 | - [vterm-toggle](https://github.com/jixiuf/vterm-toggle): Toggles between a 943 | vterm and the current buffer 944 | - [multi-libvterm](https://github.com/suonlight/multi-libvterm): Multiterm for emacs-libvterm 945 | 946 | ## Appendix 947 | 948 | ### Breaking changes 949 | 950 | Obsolete variables will be removed in version 0.1. 951 | 952 | #### August 2022 953 | * `vterm-send-C-[a-z]` `vterm-send-M-[a-z]` `vterm-define-key` 954 | `vterm-send-{up/down/left/right/prior/next/meta-dot/meta-comma/ctrl-slash}` 955 | were obsolete, please use `vterm-send` or `vterm-send-key` or 956 | `vterm--self-insert` instead. 957 | 958 | #### October 2020 959 | 960 | * `vterm-disable-bold-font` was renamed to `vterm-disable-bold` to uniform it 961 | with the other similar options. 962 | 963 | #### July 2020 964 | 965 | * `vterm-use-vterm-prompt` was renamed to `vterm-use-vterm-prompt-detection-method`. 966 | * `vterm-kill-buffer-on-exit` is set to `t` by default. 967 | 968 | #### April 2020 969 | 970 | * `vterm-clear-scrollback` was renamed to `vterm-clear-scrollback-when-clearning`. 971 | * `vterm-set-title-functions` was removed. In its place, there is a new custom 972 | option `vterm-buffer-name-string`. See 973 | [vterm-buffer-name-string](vterm-buffer-name-string) for documentation. 974 | -------------------------------------------------------------------------------- /elisp.c: -------------------------------------------------------------------------------- 1 | #include "elisp.h" 2 | #include 3 | 4 | // Emacs symbols 5 | emacs_value Qt; 6 | emacs_value Qnil; 7 | emacs_value Qnormal; 8 | emacs_value Qbold; 9 | emacs_value Qitalic; 10 | emacs_value Qforeground; 11 | emacs_value Qbackground; 12 | emacs_value Qweight; 13 | emacs_value Qunderline; 14 | emacs_value Qslant; 15 | emacs_value Qreverse; 16 | emacs_value Qstrike; 17 | emacs_value Qextend; 18 | emacs_value Qface; 19 | emacs_value Qbox; 20 | emacs_value Qbar; 21 | emacs_value Qhbar; 22 | emacs_value Qcursor_type; 23 | emacs_value Qemacs_major_version; 24 | emacs_value Qvterm_line_wrap; 25 | emacs_value Qrear_nonsticky; 26 | emacs_value Qvterm_prompt; 27 | 28 | // Emacs functions 29 | emacs_value Fblink_cursor_mode; 30 | emacs_value Fsymbol_value; 31 | emacs_value Flength; 32 | emacs_value Flist; 33 | emacs_value Fnth; 34 | emacs_value Ferase_buffer; 35 | emacs_value Finsert; 36 | emacs_value Fgoto_char; 37 | emacs_value Fforward_char; 38 | emacs_value Fforward_line; 39 | emacs_value Fgoto_line; 40 | emacs_value Fdelete_lines; 41 | emacs_value Frecenter; 42 | emacs_value Fset_window_point; 43 | emacs_value Fwindow_body_height; 44 | emacs_value Fpoint; 45 | emacs_value Fapply; 46 | 47 | emacs_value Fput_text_property; 48 | emacs_value Fadd_text_properties; 49 | emacs_value Fset; 50 | emacs_value Fvterm_flush_output; 51 | emacs_value Fget_buffer_window_list; 52 | emacs_value Fselected_window; 53 | emacs_value Fvterm_set_title; 54 | emacs_value Fvterm_set_directory; 55 | emacs_value Fvterm_invalidate; 56 | emacs_value Feq; 57 | emacs_value Fvterm_get_color; 58 | emacs_value Fvterm_eval; 59 | emacs_value Fvterm_set_selection; 60 | 61 | /* Set the function cell of the symbol named NAME to SFUN using 62 | the 'fset' function. */ 63 | void bind_function(emacs_env *env, const char *name, emacs_value Sfun) { 64 | emacs_value Qfset = env->intern(env, "fset"); 65 | emacs_value Qsym = env->intern(env, name); 66 | 67 | env->funcall(env, Qfset, 2, (emacs_value[]){Qsym, Sfun}); 68 | } 69 | 70 | /* Provide FEATURE to Emacs. */ 71 | void provide(emacs_env *env, const char *feature) { 72 | emacs_value Qfeat = env->intern(env, feature); 73 | emacs_value Qprovide = env->intern(env, "provide"); 74 | 75 | env->funcall(env, Qprovide, 1, (emacs_value[]){Qfeat}); 76 | } 77 | 78 | emacs_value symbol_value(emacs_env *env, emacs_value symbol) { 79 | return env->funcall(env, Fsymbol_value, 1, (emacs_value[]){symbol}); 80 | } 81 | 82 | int string_bytes(emacs_env *env, emacs_value string) { 83 | ptrdiff_t size = 0; 84 | env->copy_string_contents(env, string, NULL, &size); 85 | return size; 86 | } 87 | 88 | emacs_value length(emacs_env *env, emacs_value string) { 89 | return env->funcall(env, Flength, 1, (emacs_value[]){string}); 90 | } 91 | 92 | emacs_value list(emacs_env *env, emacs_value elements[], ptrdiff_t len) { 93 | return env->funcall(env, Flist, len, elements); 94 | } 95 | emacs_value nth(emacs_env *env, int idx, emacs_value list) { 96 | emacs_value eidx = env->make_integer(env, idx); 97 | return env->funcall(env, Fnth, 2, (emacs_value[]){eidx, list}); 98 | } 99 | 100 | void put_text_property(emacs_env *env, emacs_value string, emacs_value property, 101 | emacs_value value) { 102 | emacs_value start = env->make_integer(env, 0); 103 | emacs_value end = length(env, string); 104 | 105 | env->funcall(env, Fput_text_property, 5, 106 | (emacs_value[]){start, end, property, value, string}); 107 | } 108 | 109 | void add_text_properties(emacs_env *env, emacs_value string, 110 | emacs_value property) { 111 | emacs_value start = env->make_integer(env, 0); 112 | emacs_value end = length(env, string); 113 | 114 | env->funcall(env, Fadd_text_properties, 4, 115 | (emacs_value[]){start, end, property, string}); 116 | } 117 | 118 | void erase_buffer(emacs_env *env) { env->funcall(env, Ferase_buffer, 0, NULL); } 119 | 120 | void insert(emacs_env *env, emacs_value string) { 121 | env->funcall(env, Finsert, 1, (emacs_value[]){string}); 122 | } 123 | 124 | void goto_char(emacs_env *env, int pos) { 125 | emacs_value point = env->make_integer(env, pos); 126 | env->funcall(env, Fgoto_char, 1, (emacs_value[]){point}); 127 | } 128 | 129 | void forward_line(emacs_env *env, int n) { 130 | emacs_value nline = env->make_integer(env, n); 131 | env->funcall(env, Fforward_line, 1, (emacs_value[]){nline}); 132 | } 133 | void goto_line(emacs_env *env, int n) { 134 | emacs_value nline = env->make_integer(env, n); 135 | env->funcall(env, Fgoto_line, 1, (emacs_value[]){nline}); 136 | } 137 | void delete_lines(emacs_env *env, int linenum, int count, bool del_whole_line) { 138 | emacs_value Qlinenum = env->make_integer(env, linenum); 139 | emacs_value Qcount = env->make_integer(env, count); 140 | if (del_whole_line) { 141 | env->funcall(env, Fdelete_lines, 3, (emacs_value[]){Qlinenum, Qcount, Qt}); 142 | } else { 143 | env->funcall(env, Fdelete_lines, 3, 144 | (emacs_value[]){Qlinenum, Qcount, Qnil}); 145 | } 146 | } 147 | void recenter(emacs_env *env, emacs_value pos) { 148 | env->funcall(env, Frecenter, 1, (emacs_value[]){pos}); 149 | } 150 | emacs_value point(emacs_env *env) { return env->funcall(env, Fpoint, 0, NULL); } 151 | 152 | void set_window_point(emacs_env *env, emacs_value win, emacs_value point) { 153 | env->funcall(env, Fset_window_point, 2, (emacs_value[]){win, point}); 154 | } 155 | emacs_value window_body_height(emacs_env *env, emacs_value win) { 156 | return env->funcall(env, Fwindow_body_height, 1, (emacs_value[]){win}); 157 | } 158 | 159 | bool eq(emacs_env *env, emacs_value e1, emacs_value e2) { 160 | emacs_value Qeq = env->funcall(env, Feq, 2, (emacs_value[]){e1, e2}); 161 | return env->is_not_nil(env, Qeq); 162 | } 163 | 164 | void forward_char(emacs_env *env, emacs_value n) { 165 | env->funcall(env, Fforward_char, 1, (emacs_value[]){n}); 166 | } 167 | 168 | emacs_value get_buffer_window_list(emacs_env *env) { 169 | return env->funcall(env, Fget_buffer_window_list, 3, 170 | (emacs_value[]){Qnil, Qnil, Qt}); 171 | } 172 | 173 | emacs_value selected_window(emacs_env *env) { 174 | return env->funcall(env, Fselected_window, 0, (emacs_value[]){}); 175 | } 176 | 177 | void set_cursor_type(emacs_env *env, emacs_value cursor_type) { 178 | env->funcall(env, Fset, 2, (emacs_value[]){Qcursor_type, cursor_type}); 179 | } 180 | 181 | void set_cursor_blink(emacs_env *env, bool blink) { 182 | env->funcall(env, Fblink_cursor_mode, 1, 183 | (emacs_value[]){env->make_integer(env, blink)}); 184 | } 185 | 186 | emacs_value vterm_get_color(emacs_env *env, int index, emacs_value args) { 187 | emacs_value idx = env->make_integer(env, index); 188 | return env->funcall(env, Fapply, 3, (emacs_value[]){ Fvterm_get_color, idx, args }); 189 | } 190 | 191 | void set_title(emacs_env *env, emacs_value string) { 192 | env->funcall(env, Fvterm_set_title, 1, (emacs_value[]){string}); 193 | } 194 | 195 | void set_directory(emacs_env *env, emacs_value string) { 196 | env->funcall(env, Fvterm_set_directory, 1, (emacs_value[]){string}); 197 | } 198 | 199 | void vterm_invalidate(emacs_env *env) { 200 | env->funcall(env, Fvterm_invalidate, 0, NULL); 201 | } 202 | emacs_value vterm_eval(emacs_env *env, emacs_value string) { 203 | return env->funcall(env, Fvterm_eval, 1, (emacs_value[]){string}); 204 | } 205 | 206 | emacs_value vterm_set_selection(emacs_env *env, emacs_value selection_target, 207 | emacs_value selection_data) { 208 | return env->funcall(env, Fvterm_set_selection, 2, 209 | (emacs_value[]){selection_target, selection_data}); 210 | } 211 | -------------------------------------------------------------------------------- /elisp.h: -------------------------------------------------------------------------------- 1 | #ifndef ELISP_H 2 | #define ELISP_H 3 | 4 | #include "emacs-module.h" 5 | #include "vterm.h" 6 | 7 | // Emacs symbols 8 | extern emacs_value Qt; 9 | extern emacs_value Qnil; 10 | extern emacs_value Qnormal; 11 | extern emacs_value Qbold; 12 | extern emacs_value Qitalic; 13 | extern emacs_value Qforeground; 14 | extern emacs_value Qbackground; 15 | extern emacs_value Qweight; 16 | extern emacs_value Qunderline; 17 | extern emacs_value Qslant; 18 | extern emacs_value Qreverse; 19 | extern emacs_value Qstrike; 20 | extern emacs_value Qextend; 21 | extern emacs_value Qface; 22 | extern emacs_value Qbox; 23 | extern emacs_value Qbar; 24 | extern emacs_value Qhbar; 25 | extern emacs_value Qcursor_type; 26 | extern emacs_value Qemacs_major_version; 27 | extern emacs_value Qvterm_line_wrap; 28 | extern emacs_value Qrear_nonsticky; 29 | extern emacs_value Qvterm_prompt; 30 | 31 | // Emacs functions 32 | extern emacs_value Fapply; 33 | extern emacs_value Fblink_cursor_mode; 34 | extern emacs_value Fsymbol_value; 35 | extern emacs_value Flength; 36 | extern emacs_value Flist; 37 | extern emacs_value Fnth; 38 | extern emacs_value Ferase_buffer; 39 | extern emacs_value Finsert; 40 | extern emacs_value Fgoto_char; 41 | extern emacs_value Fforward_char; 42 | extern emacs_value Fforward_line; 43 | extern emacs_value Fgoto_line; 44 | extern emacs_value Fdelete_lines; 45 | extern emacs_value Frecenter; 46 | extern emacs_value Fset_window_point; 47 | extern emacs_value Fwindow_body_height; 48 | extern emacs_value Fpoint; 49 | 50 | extern emacs_value Fput_text_property; 51 | extern emacs_value Fadd_text_properties; 52 | extern emacs_value Fset; 53 | extern emacs_value Fvterm_flush_output; 54 | extern emacs_value Fget_buffer_window_list; 55 | extern emacs_value Fselected_window; 56 | extern emacs_value Fvterm_set_title; 57 | extern emacs_value Fvterm_set_directory; 58 | extern emacs_value Fvterm_invalidate; 59 | extern emacs_value Feq; 60 | extern emacs_value Fvterm_get_color; 61 | extern emacs_value Fvterm_eval; 62 | extern emacs_value Fvterm_set_selection; 63 | 64 | // Utils 65 | void bind_function(emacs_env *env, const char *name, emacs_value Sfun); 66 | void provide(emacs_env *env, const char *feature); 67 | emacs_value symbol_value(emacs_env *env, emacs_value symbol); 68 | int string_bytes(emacs_env *env, emacs_value string); 69 | emacs_value length(emacs_env *env, emacs_value string); 70 | emacs_value list(emacs_env *env, emacs_value elements[], ptrdiff_t len); 71 | emacs_value nth(emacs_env *env, int idx, emacs_value list); 72 | void put_text_property(emacs_env *env, emacs_value string, emacs_value property, 73 | emacs_value value); 74 | void add_text_properties(emacs_env *env, emacs_value string, 75 | emacs_value property); 76 | void erase_buffer(emacs_env *env); 77 | void insert(emacs_env *env, emacs_value string); 78 | void goto_char(emacs_env *env, int pos); 79 | void forward_line(emacs_env *env, int n); 80 | void goto_line(emacs_env *env, int n); 81 | void set_cursor_type(emacs_env *env, emacs_value cursor_type); 82 | void set_cursor_blink(emacs_env *env, bool blink); 83 | void delete_lines(emacs_env *env, int linenum, int count, bool del_whole_line); 84 | void recenter(emacs_env *env, emacs_value pos); 85 | void set_window_point(emacs_env *env, emacs_value win, emacs_value point); 86 | emacs_value window_body_height(emacs_env *env, emacs_value win); 87 | emacs_value point(emacs_env *env); 88 | bool eq(emacs_env *env, emacs_value e1, emacs_value e2); 89 | void forward_char(emacs_env *env, emacs_value n); 90 | emacs_value get_buffer_window_list(emacs_env *env); 91 | emacs_value selected_window(emacs_env *env); 92 | void set_title(emacs_env *env, emacs_value string); 93 | void set_directory(emacs_env *env, emacs_value string); 94 | void vterm_invalidate(emacs_env *env); 95 | emacs_value vterm_get_color(emacs_env *env, int index, emacs_value args); 96 | emacs_value vterm_eval(emacs_env *env, emacs_value string); 97 | emacs_value vterm_set_selection(emacs_env *env, emacs_value selection_target, 98 | emacs_value selection_data); 99 | 100 | #endif /* ELISP_H */ 101 | -------------------------------------------------------------------------------- /emacs-module.h: -------------------------------------------------------------------------------- 1 | /* emacs-module.h - GNU Emacs module API. 2 | 3 | Copyright (C) 2015-2018 Free Software Foundation, Inc. 4 | 5 | This file is part of GNU Emacs. 6 | 7 | GNU Emacs is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or (at 10 | your option) any later version. 11 | 12 | GNU Emacs is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with GNU Emacs. If not, see . */ 19 | 20 | #ifndef EMACS_MODULE_H 21 | #define EMACS_MODULE_H 22 | 23 | #include 24 | #include 25 | 26 | #ifndef __cplusplus 27 | #include 28 | #endif 29 | 30 | #if defined __cplusplus && __cplusplus >= 201103L 31 | #define EMACS_NOEXCEPT noexcept 32 | #else 33 | #define EMACS_NOEXCEPT 34 | #endif 35 | 36 | #ifdef __has_attribute 37 | #if __has_attribute(__nonnull__) 38 | #define EMACS_ATTRIBUTE_NONNULL(...) __attribute__((__nonnull__(__VA_ARGS__))) 39 | #endif 40 | #endif 41 | #ifndef EMACS_ATTRIBUTE_NONNULL 42 | #define EMACS_ATTRIBUTE_NONNULL(...) 43 | #endif 44 | 45 | #ifdef __cplusplus 46 | extern "C" { 47 | #endif 48 | 49 | /* Current environment. */ 50 | typedef struct emacs_env_25 emacs_env; 51 | 52 | /* Opaque pointer representing an Emacs Lisp value. 53 | BEWARE: Do not assume NULL is a valid value! */ 54 | typedef struct emacs_value_tag *emacs_value; 55 | 56 | enum { emacs_variadic_function = -2 }; 57 | 58 | /* Struct passed to a module init function (emacs_module_init). */ 59 | struct emacs_runtime { 60 | /* Structure size (for version checking). */ 61 | ptrdiff_t size; 62 | 63 | /* Private data; users should not touch this. */ 64 | struct emacs_runtime_private *private_members; 65 | 66 | /* Return an environment pointer. */ 67 | emacs_env *(*get_environment)(struct emacs_runtime *ert) 68 | EMACS_ATTRIBUTE_NONNULL(1); 69 | }; 70 | 71 | /* Possible Emacs function call outcomes. */ 72 | enum emacs_funcall_exit { 73 | /* Function has returned normally. */ 74 | emacs_funcall_exit_return = 0, 75 | 76 | /* Function has signaled an error using `signal'. */ 77 | emacs_funcall_exit_signal = 1, 78 | 79 | /* Function has exit using `throw'. */ 80 | emacs_funcall_exit_throw = 2 81 | }; 82 | 83 | struct emacs_env_25 { 84 | /* Structure size (for version checking). */ 85 | ptrdiff_t size; 86 | 87 | /* Private data; users should not touch this. */ 88 | struct emacs_env_private *private_members; 89 | 90 | /* Memory management. */ 91 | 92 | emacs_value (*make_global_ref)(emacs_env *env, emacs_value any_reference) 93 | EMACS_ATTRIBUTE_NONNULL(1); 94 | 95 | void (*free_global_ref)(emacs_env *env, emacs_value global_reference) 96 | EMACS_ATTRIBUTE_NONNULL(1); 97 | 98 | /* Non-local exit handling. */ 99 | 100 | enum emacs_funcall_exit (*non_local_exit_check)(emacs_env *env) 101 | EMACS_ATTRIBUTE_NONNULL(1); 102 | 103 | void (*non_local_exit_clear)(emacs_env *env) EMACS_ATTRIBUTE_NONNULL(1); 104 | 105 | enum emacs_funcall_exit (*non_local_exit_get)( 106 | emacs_env *env, emacs_value *non_local_exit_symbol_out, 107 | emacs_value *non_local_exit_data_out) EMACS_ATTRIBUTE_NONNULL(1, 2, 3); 108 | 109 | void (*non_local_exit_signal)(emacs_env *env, 110 | emacs_value non_local_exit_symbol, 111 | emacs_value non_local_exit_data) 112 | EMACS_ATTRIBUTE_NONNULL(1); 113 | 114 | void (*non_local_exit_throw)(emacs_env *env, emacs_value tag, 115 | emacs_value value) EMACS_ATTRIBUTE_NONNULL(1); 116 | 117 | /* Function registration. */ 118 | 119 | emacs_value (*make_function)( 120 | emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity, 121 | emacs_value (*function)(emacs_env *env, ptrdiff_t nargs, 122 | emacs_value args[], void *) 123 | EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1), 124 | const char *documentation, void *data) EMACS_ATTRIBUTE_NONNULL(1, 4); 125 | 126 | emacs_value (*funcall)(emacs_env *env, emacs_value function, ptrdiff_t nargs, 127 | emacs_value args[]) EMACS_ATTRIBUTE_NONNULL(1); 128 | 129 | emacs_value (*intern)(emacs_env *env, const char *symbol_name) 130 | EMACS_ATTRIBUTE_NONNULL(1, 2); 131 | 132 | /* Type conversion. */ 133 | 134 | emacs_value (*type_of)(emacs_env *env, emacs_value value) 135 | EMACS_ATTRIBUTE_NONNULL(1); 136 | 137 | bool (*is_not_nil)(emacs_env *env, emacs_value value) 138 | EMACS_ATTRIBUTE_NONNULL(1); 139 | 140 | bool (*eq)(emacs_env *env, emacs_value a, emacs_value b) 141 | EMACS_ATTRIBUTE_NONNULL(1); 142 | 143 | intmax_t (*extract_integer)(emacs_env *env, emacs_value value) 144 | EMACS_ATTRIBUTE_NONNULL(1); 145 | 146 | emacs_value (*make_integer)(emacs_env *env, intmax_t value) 147 | EMACS_ATTRIBUTE_NONNULL(1); 148 | 149 | double (*extract_float)(emacs_env *env, emacs_value value) 150 | EMACS_ATTRIBUTE_NONNULL(1); 151 | 152 | emacs_value (*make_float)(emacs_env *env, double value) 153 | EMACS_ATTRIBUTE_NONNULL(1); 154 | 155 | /* Copy the content of the Lisp string VALUE to BUFFER as an utf8 156 | null-terminated string. 157 | 158 | SIZE must point to the total size of the buffer. If BUFFER is 159 | NULL or if SIZE is not big enough, write the required buffer size 160 | to SIZE and return true. 161 | 162 | Note that SIZE must include the last null byte (e.g. "abc" needs 163 | a buffer of size 4). 164 | 165 | Return true if the string was successfully copied. */ 166 | 167 | bool (*copy_string_contents)(emacs_env *env, emacs_value value, char *buffer, 168 | ptrdiff_t *size_inout) 169 | EMACS_ATTRIBUTE_NONNULL(1, 4); 170 | 171 | /* Create a Lisp string from a utf8 encoded string. */ 172 | emacs_value (*make_string)(emacs_env *env, const char *contents, 173 | ptrdiff_t length) EMACS_ATTRIBUTE_NONNULL(1, 2); 174 | 175 | /* Embedded pointer type. */ 176 | emacs_value (*make_user_ptr)(emacs_env *env, 177 | void (*fin)(void *) EMACS_NOEXCEPT, void *ptr) 178 | EMACS_ATTRIBUTE_NONNULL(1); 179 | 180 | void *(*get_user_ptr)(emacs_env *env, 181 | emacs_value uptr)EMACS_ATTRIBUTE_NONNULL(1); 182 | void (*set_user_ptr)(emacs_env *env, emacs_value uptr, void *ptr) 183 | EMACS_ATTRIBUTE_NONNULL(1); 184 | 185 | void (*(*get_user_finalizer)(emacs_env *env, 186 | emacs_value uptr))(void *) EMACS_NOEXCEPT 187 | EMACS_ATTRIBUTE_NONNULL(1); 188 | void (*set_user_finalizer)(emacs_env *env, emacs_value uptr, 189 | void (*fin)(void *) EMACS_NOEXCEPT) 190 | EMACS_ATTRIBUTE_NONNULL(1); 191 | 192 | /* Vector functions. */ 193 | emacs_value (*vec_get)(emacs_env *env, emacs_value vec, ptrdiff_t i) 194 | EMACS_ATTRIBUTE_NONNULL(1); 195 | 196 | void (*vec_set)(emacs_env *env, emacs_value vec, ptrdiff_t i, emacs_value val) 197 | EMACS_ATTRIBUTE_NONNULL(1); 198 | 199 | ptrdiff_t (*vec_size)(emacs_env *env, emacs_value vec) 200 | EMACS_ATTRIBUTE_NONNULL(1); 201 | }; 202 | 203 | struct emacs_env_26 { 204 | /* Structure size (for version checking). */ 205 | ptrdiff_t size; 206 | 207 | /* Private data; users should not touch this. */ 208 | struct emacs_env_private *private_members; 209 | 210 | /* Memory management. */ 211 | 212 | emacs_value (*make_global_ref)(emacs_env *env, emacs_value any_reference) 213 | EMACS_ATTRIBUTE_NONNULL(1); 214 | 215 | void (*free_global_ref)(emacs_env *env, emacs_value global_reference) 216 | EMACS_ATTRIBUTE_NONNULL(1); 217 | 218 | /* Non-local exit handling. */ 219 | 220 | enum emacs_funcall_exit (*non_local_exit_check)(emacs_env *env) 221 | EMACS_ATTRIBUTE_NONNULL(1); 222 | 223 | void (*non_local_exit_clear)(emacs_env *env) EMACS_ATTRIBUTE_NONNULL(1); 224 | 225 | enum emacs_funcall_exit (*non_local_exit_get)( 226 | emacs_env *env, emacs_value *non_local_exit_symbol_out, 227 | emacs_value *non_local_exit_data_out) EMACS_ATTRIBUTE_NONNULL(1, 2, 3); 228 | 229 | void (*non_local_exit_signal)(emacs_env *env, 230 | emacs_value non_local_exit_symbol, 231 | emacs_value non_local_exit_data) 232 | EMACS_ATTRIBUTE_NONNULL(1); 233 | 234 | void (*non_local_exit_throw)(emacs_env *env, emacs_value tag, 235 | emacs_value value) EMACS_ATTRIBUTE_NONNULL(1); 236 | 237 | /* Function registration. */ 238 | 239 | emacs_value (*make_function)( 240 | emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity, 241 | emacs_value (*function)(emacs_env *env, ptrdiff_t nargs, 242 | emacs_value args[], void *) 243 | EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1), 244 | const char *documentation, void *data) EMACS_ATTRIBUTE_NONNULL(1, 4); 245 | 246 | emacs_value (*funcall)(emacs_env *env, emacs_value function, ptrdiff_t nargs, 247 | emacs_value args[]) EMACS_ATTRIBUTE_NONNULL(1); 248 | 249 | emacs_value (*intern)(emacs_env *env, const char *symbol_name) 250 | EMACS_ATTRIBUTE_NONNULL(1, 2); 251 | 252 | /* Type conversion. */ 253 | 254 | emacs_value (*type_of)(emacs_env *env, emacs_value value) 255 | EMACS_ATTRIBUTE_NONNULL(1); 256 | 257 | bool (*is_not_nil)(emacs_env *env, emacs_value value) 258 | EMACS_ATTRIBUTE_NONNULL(1); 259 | 260 | bool (*eq)(emacs_env *env, emacs_value a, emacs_value b) 261 | EMACS_ATTRIBUTE_NONNULL(1); 262 | 263 | intmax_t (*extract_integer)(emacs_env *env, emacs_value value) 264 | EMACS_ATTRIBUTE_NONNULL(1); 265 | 266 | emacs_value (*make_integer)(emacs_env *env, intmax_t value) 267 | EMACS_ATTRIBUTE_NONNULL(1); 268 | 269 | double (*extract_float)(emacs_env *env, emacs_value value) 270 | EMACS_ATTRIBUTE_NONNULL(1); 271 | 272 | emacs_value (*make_float)(emacs_env *env, double value) 273 | EMACS_ATTRIBUTE_NONNULL(1); 274 | 275 | /* Copy the content of the Lisp string VALUE to BUFFER as an utf8 276 | null-terminated string. 277 | 278 | SIZE must point to the total size of the buffer. If BUFFER is 279 | NULL or if SIZE is not big enough, write the required buffer size 280 | to SIZE and return true. 281 | 282 | Note that SIZE must include the last null byte (e.g. "abc" needs 283 | a buffer of size 4). 284 | 285 | Return true if the string was successfully copied. */ 286 | 287 | bool (*copy_string_contents)(emacs_env *env, emacs_value value, char *buffer, 288 | ptrdiff_t *size_inout) 289 | EMACS_ATTRIBUTE_NONNULL(1, 4); 290 | 291 | /* Create a Lisp string from a utf8 encoded string. */ 292 | emacs_value (*make_string)(emacs_env *env, const char *contents, 293 | ptrdiff_t length) EMACS_ATTRIBUTE_NONNULL(1, 2); 294 | 295 | /* Embedded pointer type. */ 296 | emacs_value (*make_user_ptr)(emacs_env *env, 297 | void (*fin)(void *) EMACS_NOEXCEPT, void *ptr) 298 | EMACS_ATTRIBUTE_NONNULL(1); 299 | 300 | void *(*get_user_ptr)(emacs_env *env, 301 | emacs_value uptr)EMACS_ATTRIBUTE_NONNULL(1); 302 | void (*set_user_ptr)(emacs_env *env, emacs_value uptr, void *ptr) 303 | EMACS_ATTRIBUTE_NONNULL(1); 304 | 305 | void (*(*get_user_finalizer)(emacs_env *env, 306 | emacs_value uptr))(void *) EMACS_NOEXCEPT 307 | EMACS_ATTRIBUTE_NONNULL(1); 308 | void (*set_user_finalizer)(emacs_env *env, emacs_value uptr, 309 | void (*fin)(void *) EMACS_NOEXCEPT) 310 | EMACS_ATTRIBUTE_NONNULL(1); 311 | 312 | /* Vector functions. */ 313 | emacs_value (*vec_get)(emacs_env *env, emacs_value vec, ptrdiff_t i) 314 | EMACS_ATTRIBUTE_NONNULL(1); 315 | 316 | void (*vec_set)(emacs_env *env, emacs_value vec, ptrdiff_t i, emacs_value val) 317 | EMACS_ATTRIBUTE_NONNULL(1); 318 | 319 | ptrdiff_t (*vec_size)(emacs_env *env, emacs_value vec) 320 | EMACS_ATTRIBUTE_NONNULL(1); 321 | 322 | /* Returns whether a quit is pending. */ 323 | bool (*should_quit)(emacs_env *env) EMACS_ATTRIBUTE_NONNULL(1); 324 | }; 325 | 326 | /* Every module should define a function as follows. */ 327 | extern int emacs_module_init(struct emacs_runtime *ert) EMACS_NOEXCEPT 328 | EMACS_ATTRIBUTE_NONNULL(1); 329 | 330 | #ifdef __cplusplus 331 | } 332 | #endif 333 | 334 | #endif /* EMACS_MODULE_H */ 335 | -------------------------------------------------------------------------------- /etc/emacs-vterm-bash.sh: -------------------------------------------------------------------------------- 1 | # Some of the most useful features in emacs-libvterm require shell-side 2 | # configurations. The main goal of these additional functions is to enable the 3 | # shell to send information to `vterm` via properly escaped sequences. A 4 | # function that helps in this task, `vterm_printf`, is defined below. 5 | 6 | function vterm_printf(){ 7 | if [ -n "$TMUX" ] && ([ "${TERM%%-*}" = "tmux" ] || [ "${TERM%%-*}" = "screen" ] ); then 8 | # Tell tmux to pass the escape sequences through 9 | printf "\ePtmux;\e\e]%s\007\e\\" "$1" 10 | elif [ "${TERM%%-*}" = "screen" ]; then 11 | # GNU screen (screen, screen-256color, screen-256color-bce) 12 | printf "\eP\e]%s\007\e\\" "$1" 13 | else 14 | printf "\e]%s\e\\" "$1" 15 | fi 16 | } 17 | 18 | # Completely clear the buffer. With this, everything that is not on screen 19 | # is erased. 20 | if [[ "$INSIDE_EMACS" = 'vterm' ]]; then 21 | function clear(){ 22 | vterm_printf "51;Evterm-clear-scrollback"; 23 | tput clear; 24 | } 25 | fi 26 | 27 | # With vterm_cmd you can execute Emacs commands directly from the shell. 28 | # For example, vterm_cmd message "HI" will print "HI". 29 | # To enable new commands, you have to customize Emacs's variable 30 | # vterm-eval-cmds. 31 | vterm_cmd() { 32 | local vterm_elisp 33 | vterm_elisp="" 34 | while [ $# -gt 0 ]; do 35 | vterm_elisp="$vterm_elisp""$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")" 36 | shift 37 | done 38 | vterm_printf "51;E$vterm_elisp" 39 | } 40 | 41 | # This is to change the title of the buffer based on information provided by the 42 | # shell. See, http://tldp.org/HOWTO/Xterm-Title-4.html, for the meaning of the 43 | # various symbols. 44 | PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }"'echo -ne "\033]0;${HOSTNAME}:${PWD}\007"' 45 | 46 | # Sync directory and host in the shell with Emacs's current directory. 47 | # You may need to manually specify the hostname instead of $(hostname) in case 48 | # $(hostname) does not return the correct string to connect to the server. 49 | # 50 | # The escape sequence "51;A" has also the role of identifying the end of the 51 | # prompt 52 | vterm_prompt_end(){ 53 | vterm_printf "51;A$(whoami)@$(hostname):$(pwd)" 54 | } 55 | PS1=$PS1'\[$(vterm_prompt_end)\]' 56 | -------------------------------------------------------------------------------- /etc/emacs-vterm-zsh.sh: -------------------------------------------------------------------------------- 1 | # Some of the most useful features in emacs-libvterm require shell-side 2 | # configurations. The main goal of these additional functions is to enable the 3 | # shell to send information to `vterm` via properly escaped sequences. A 4 | # function that helps in this task, `vterm_printf`, is defined below. 5 | 6 | function vterm_printf(){ 7 | if [ -n "$TMUX" ] && ([ "${TERM%%-*}" = "tmux" ] || [ "${TERM%%-*}" = "screen" ] ); then 8 | # Tell tmux to pass the escape sequences through 9 | printf "\ePtmux;\e\e]%s\007\e\\" "$1" 10 | elif [ "${TERM%%-*}" = "screen" ]; then 11 | # GNU screen (screen, screen-256color, screen-256color-bce) 12 | printf "\eP\e]%s\007\e\\" "$1" 13 | else 14 | printf "\e]%s\e\\" "$1" 15 | fi 16 | } 17 | 18 | # Completely clear the buffer. With this, everything that is not on screen 19 | # is erased. 20 | if [[ "$INSIDE_EMACS" = 'vterm' ]]; then 21 | alias clear='vterm_printf "51;Evterm-clear-scrollback";tput clear' 22 | fi 23 | 24 | # With vterm_cmd you can execute Emacs commands directly from the shell. 25 | # For example, vterm_cmd message "HI" will print "HI". 26 | # To enable new commands, you have to customize Emacs's variable 27 | # vterm-eval-cmds. 28 | vterm_cmd() { 29 | local vterm_elisp 30 | vterm_elisp="" 31 | while [ $# -gt 0 ]; do 32 | vterm_elisp="$vterm_elisp""$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")" 33 | shift 34 | done 35 | vterm_printf "51;E$vterm_elisp" 36 | } 37 | 38 | # This is to change the title of the buffer based on information provided by the 39 | # shell. See, http://tldp.org/HOWTO/Xterm-Title-4.html, for the meaning of the 40 | # various symbols. 41 | autoload -U add-zsh-hook 42 | add-zsh-hook -Uz chpwd (){ print -Pn "\e]2;%m:%2~\a" } 43 | 44 | # Sync directory and host in the shell with Emacs's current directory. 45 | # You may need to manually specify the hostname instead of $(hostname) in case 46 | # $(hostname) does not return the correct string to connect to the server. 47 | # 48 | # The escape sequence "51;A" has also the role of identifying the end of the 49 | # prompt 50 | vterm_prompt_end() { 51 | vterm_printf "51;A$(whoami)@$(hostname):$(pwd)" 52 | } 53 | setopt PROMPT_SUBST 54 | PROMPT=$PROMPT'%{$(vterm_prompt_end)%}' 55 | -------------------------------------------------------------------------------- /etc/emacs-vterm.fish: -------------------------------------------------------------------------------- 1 | # Some of the most useful features in emacs-libvterm require shell-side 2 | # configurations. The main goal of these additional functions is to enable the 3 | # shell to send information to `vterm` via properly escaped sequences. A 4 | # function that helps in this task, `vterm_printf`, is defined below. 5 | 6 | function vterm_printf; 7 | if begin; [ -n "$TMUX" ] ; and string match -q -r "screen|tmux" "$TERM"; end 8 | # tell tmux to pass the escape sequences through 9 | printf "\ePtmux;\e\e]%s\007\e\\" "$argv" 10 | else if string match -q -- "screen*" "$TERM" 11 | # GNU screen (screen, screen-256color, screen-256color-bce) 12 | printf "\eP\e]%s\007\e\\" "$argv" 13 | else 14 | printf "\e]%s\e\\" "$argv" 15 | end 16 | end 17 | 18 | # Completely clear the buffer. With this, everything that is not on screen 19 | # is erased. 20 | if [ "$INSIDE_EMACS" = 'vterm' ] 21 | function clear 22 | vterm_printf "51;Evterm-clear-scrollback"; 23 | tput clear; 24 | end 25 | end 26 | 27 | # This is to change the title of the buffer based on information provided by the 28 | # shell. See, http://tldp.org/HOWTO/Xterm-Title-4.html, for the meaning of the 29 | # various symbols. 30 | function fish_title 31 | hostname 32 | echo ":" 33 | prompt_pwd 34 | end 35 | 36 | # With vterm_cmd you can execute Emacs commands directly from the shell. 37 | # For example, vterm_cmd message "HI" will print "HI". 38 | # To enable new commands, you have to customize Emacs's variable 39 | # vterm-eval-cmds. 40 | function vterm_cmd --description 'Run an Emacs command among the ones defined in vterm-eval-cmds.' 41 | set -l vterm_elisp () 42 | for arg in $argv 43 | set -a vterm_elisp (printf '"%s" ' (string replace -a -r '([\\\\"])' '\\\\\\\\$1' $arg)) 44 | end 45 | vterm_printf '51;E'(string join '' $vterm_elisp) 46 | end 47 | 48 | # Sync directory and host in the shell with Emacs's current directory. 49 | # You may need to manually specify the hostname instead of $(hostname) in case 50 | # $(hostname) does not return the correct string to connect to the server. 51 | # 52 | # The escape sequence "51;A" has also the role of identifying the end of the 53 | # prompt 54 | function vterm_prompt_end; 55 | vterm_printf '51;A'(whoami)'@'(hostname)':'(pwd) 56 | end 57 | 58 | # We are going to add a portion to the prompt, so we copy the old one 59 | functions --copy fish_prompt vterm_old_fish_prompt 60 | 61 | function fish_prompt --description 'Write out the prompt; do not replace this. Instead, put this at end of your file.' 62 | # Remove the trailing newline from the original prompt. This is done 63 | # using the string builtin from fish, but to make sure any escape codes 64 | # are correctly interpreted, use %b for printf. 65 | printf "%b" (string join "\n" (vterm_old_fish_prompt)) 66 | vterm_prompt_end 67 | end 68 | -------------------------------------------------------------------------------- /utf8.c: -------------------------------------------------------------------------------- 1 | #include "utf8.h" 2 | 3 | size_t codepoint_to_utf8(const uint32_t codepoint, unsigned char buffer[4]) { 4 | if (codepoint <= 0x7F) { 5 | buffer[0] = codepoint; 6 | return 1; 7 | } 8 | if (codepoint >= 0x80 && codepoint <= 0x07FF) { 9 | buffer[0] = 0xC0 | (codepoint >> 6); 10 | buffer[1] = 0x80 | (codepoint & 0x3F); 11 | return 2; 12 | } 13 | if (codepoint >= 0x0800 && codepoint <= 0xFFFF) { 14 | buffer[0] = 0xE0 | (codepoint >> 12); 15 | buffer[1] = 0x80 | ((codepoint >> 6) & 0x3F); 16 | buffer[2] = 0x80 | (codepoint & 0x3F); 17 | return 3; 18 | } 19 | 20 | if (codepoint >= 0x10000 && codepoint <= 0x10FFFF) { 21 | buffer[0] = 0xF0 | (codepoint >> 18); 22 | buffer[1] = 0x80 | ((codepoint >> 12) & 0x3F); 23 | buffer[2] = 0x80 | ((codepoint >> 6) & 0x3F); 24 | buffer[3] = 0x80 | (codepoint & 0x3F); 25 | return 4; 26 | } 27 | return 0; 28 | } 29 | 30 | bool utf8_to_codepoint(const unsigned char buffer[4], const size_t len, 31 | uint32_t *codepoint) { 32 | *codepoint = 0; 33 | if (len == 1 && buffer[0] <= 0x7F) { 34 | *codepoint = buffer[0]; 35 | return true; 36 | } 37 | if (len == 2 && (buffer[0] >= 0xC0 && buffer[0] <= 0xDF) && 38 | (buffer[1] >= 0x80 && buffer[1] <= 0xBF)) { 39 | *codepoint = buffer[0] & 0x1F; 40 | *codepoint = *codepoint << 6; 41 | *codepoint = *codepoint | (buffer[1] & 0x3F); 42 | return true; 43 | } 44 | if (len == 3 && (buffer[0] >= 0xE0 && buffer[0] <= 0xEF) && 45 | (buffer[1] >= 0x80 && buffer[1] <= 0xBF) && 46 | (buffer[2] >= 0x80 && buffer[2] <= 0xBF)) { 47 | *codepoint = buffer[0] & 0xF; 48 | *codepoint = *codepoint << 6; 49 | *codepoint = *codepoint | (buffer[1] & 0x3F); 50 | *codepoint = *codepoint << 6; 51 | *codepoint = *codepoint | (buffer[2] & 0x3F); 52 | return true; 53 | } 54 | if (len == 4 && (buffer[0] >= 0xF0 && buffer[0] <= 0xF7) && 55 | (buffer[1] >= 0x80 && buffer[1] <= 0xBF) && 56 | (buffer[2] >= 0x80 && buffer[2] <= 0xBF) && 57 | (buffer[3] >= 0x80 && buffer[3] <= 0xBF)) { 58 | *codepoint = buffer[0] & 7; 59 | *codepoint = *codepoint << 6; 60 | *codepoint = *codepoint | (buffer[1] & 0x3F); 61 | *codepoint = *codepoint << 6; 62 | *codepoint = *codepoint | (buffer[2] & 0x3F); 63 | *codepoint = *codepoint << 6; 64 | *codepoint = *codepoint | (buffer[3] & 0x3F); 65 | return true; 66 | } 67 | 68 | return false; 69 | } 70 | -------------------------------------------------------------------------------- /utf8.h: -------------------------------------------------------------------------------- 1 | #ifndef UTF8_H 2 | #define UTF8_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | size_t codepoint_to_utf8(const uint32_t codepoint, unsigned char buffer[4]); 9 | bool utf8_to_codepoint(const unsigned char buffer[4], const size_t len, 10 | uint32_t *codepoint); 11 | 12 | #endif /* UTF8_H */ 13 | -------------------------------------------------------------------------------- /vterm-module.c: -------------------------------------------------------------------------------- 1 | #include "vterm-module.h" 2 | #include "elisp.h" 3 | #include "utf8.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static LineInfo *alloc_lineinfo() { 14 | LineInfo *info = malloc(sizeof(LineInfo)); 15 | info->directory = NULL; 16 | info->prompt_col = -1; 17 | return info; 18 | } 19 | void free_lineinfo(LineInfo *line) { 20 | if (line == NULL) { 21 | return; 22 | } 23 | if (line->directory != NULL) { 24 | free(line->directory); 25 | line->directory = NULL; 26 | } 27 | free(line); 28 | } 29 | static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) { 30 | Term *term = (Term *)data; 31 | 32 | if (!term->sb_size) { 33 | return 0; 34 | } 35 | 36 | // copy vterm cells into sb_buffer 37 | size_t c = (size_t)cols; 38 | ScrollbackLine *sbrow = NULL; 39 | if (term->sb_current == term->sb_size) { 40 | if (term->sb_buffer[term->sb_current - 1]->cols == c) { 41 | // Recycle old row if it's the right size 42 | sbrow = term->sb_buffer[term->sb_current - 1]; 43 | } else { 44 | if (term->sb_buffer[term->sb_current - 1]->info != NULL) { 45 | free_lineinfo(term->sb_buffer[term->sb_current - 1]->info); 46 | term->sb_buffer[term->sb_current - 1]->info = NULL; 47 | } 48 | free(term->sb_buffer[term->sb_current - 1]); 49 | } 50 | 51 | // Make room at the start by shifting to the right. 52 | memmove(term->sb_buffer + 1, term->sb_buffer, 53 | sizeof(term->sb_buffer[0]) * (term->sb_current - 1)); 54 | 55 | } else if (term->sb_current > 0) { 56 | // Make room at the start by shifting to the right. 57 | memmove(term->sb_buffer + 1, term->sb_buffer, 58 | sizeof(term->sb_buffer[0]) * term->sb_current); 59 | } 60 | 61 | if (!sbrow) { 62 | sbrow = malloc(sizeof(ScrollbackLine) + c * sizeof(sbrow->cells[0])); 63 | sbrow->cols = c; 64 | sbrow->info = NULL; 65 | } 66 | if (sbrow->info != NULL) { 67 | free_lineinfo(sbrow->info); 68 | } 69 | sbrow->info = term->lines[0]; 70 | memmove(term->lines, term->lines + 1, 71 | sizeof(term->lines[0]) * (term->lines_len - 1)); 72 | if (term->resizing) { 73 | /* pushed by window height decr */ 74 | if (term->lines[term->lines_len - 1] != NULL) { 75 | /* do not need free here ,it is reused ,we just need set null */ 76 | term->lines[term->lines_len - 1] = NULL; 77 | } 78 | term->lines_len--; 79 | } else { 80 | LineInfo *lastline = term->lines[term->lines_len - 1]; 81 | if (lastline != NULL) { 82 | LineInfo *line = alloc_lineinfo(); 83 | if (lastline->directory != NULL) { 84 | line->directory = malloc(1 + strlen(lastline->directory)); 85 | strcpy(line->directory, lastline->directory); 86 | } 87 | term->lines[term->lines_len - 1] = line; 88 | } 89 | } 90 | 91 | // New row is added at the start of the storage buffer. 92 | term->sb_buffer[0] = sbrow; 93 | if (term->sb_current < term->sb_size) { 94 | term->sb_current++; 95 | } 96 | 97 | if (term->sb_pending < term->sb_size) { 98 | term->sb_pending++; 99 | /* when window height decreased */ 100 | if (term->height_resize < 0 && 101 | term->sb_pending_by_height_decr < -term->height_resize) { 102 | term->sb_pending_by_height_decr++; 103 | } 104 | } 105 | 106 | memcpy(sbrow->cells, cells, c * sizeof(cells[0])); 107 | 108 | return 1; 109 | } 110 | /// Scrollback pop handler (from pangoterm). 111 | /// 112 | /// @param cols 113 | /// @param cells VTerm state to update. 114 | /// @param data Term 115 | static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) { 116 | Term *term = (Term *)data; 117 | 118 | if (!term->sb_current) { 119 | return 0; 120 | } 121 | 122 | if (term->sb_pending) { 123 | term->sb_pending--; 124 | } 125 | 126 | ScrollbackLine *sbrow = term->sb_buffer[0]; 127 | term->sb_current--; 128 | // Forget the "popped" row by shifting the rest onto it. 129 | memmove(term->sb_buffer, term->sb_buffer + 1, 130 | sizeof(term->sb_buffer[0]) * (term->sb_current)); 131 | 132 | size_t cols_to_copy = (size_t)cols; 133 | if (cols_to_copy > sbrow->cols) { 134 | cols_to_copy = sbrow->cols; 135 | } 136 | 137 | // copy to vterm state 138 | memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy); 139 | size_t col; 140 | for (col = cols_to_copy; col < (size_t)cols; col++) { 141 | cells[col].chars[0] = 0; 142 | cells[col].width = 1; 143 | } 144 | 145 | LineInfo **lines = malloc(sizeof(LineInfo *) * (term->lines_len + 1)); 146 | 147 | memmove(lines + 1, term->lines, sizeof(term->lines[0]) * term->lines_len); 148 | lines[0] = sbrow->info; 149 | free(sbrow); 150 | term->lines_len += 1; 151 | free(term->lines); 152 | term->lines = lines; 153 | 154 | return 1; 155 | } 156 | 157 | static int term_sb_clear(void *data) { 158 | Term *term = (Term *)data; 159 | 160 | if (term->sb_clear_pending) { 161 | // Another scrollback clear is already pending, so skip this one. 162 | return 0; 163 | } 164 | 165 | for (int i = 0; i < term->sb_current; i++) { 166 | if (term->sb_buffer[i]->info != NULL) { 167 | free_lineinfo(term->sb_buffer[i]->info); 168 | term->sb_buffer[i]->info = NULL; 169 | } 170 | free(term->sb_buffer[i]); 171 | } 172 | free(term->sb_buffer); 173 | term->sb_buffer = malloc(sizeof(ScrollbackLine *) * term->sb_size); 174 | term->sb_clear_pending = true; 175 | term->sb_current = 0; 176 | term->sb_pending = 0; 177 | term->sb_pending_by_height_decr = 0; 178 | invalidate_terminal(term, -1, -1); 179 | 180 | return 0; 181 | } 182 | 183 | static int row_to_linenr(Term *term, int row) { 184 | return row != INT_MAX ? row + (int)term->sb_current + 1 : INT_MAX; 185 | } 186 | 187 | static int linenr_to_row(Term *term, int linenr) { 188 | return linenr - (int)term->sb_current - 1; 189 | } 190 | 191 | static void fetch_cell(Term *term, int row, int col, VTermScreenCell *cell) { 192 | if (row < 0) { 193 | ScrollbackLine *sbrow = term->sb_buffer[-row - 1]; 194 | if ((size_t)col < sbrow->cols) { 195 | *cell = sbrow->cells[col]; 196 | } else { 197 | // fill the pointer with an empty cell 198 | VTermColor fg, bg; 199 | VTermState *state = vterm_obtain_state(term->vt); 200 | vterm_state_get_default_colors(state, &fg, &bg); 201 | 202 | *cell = (VTermScreenCell){.chars = {0}, .width = 1, .bg = bg}; 203 | } 204 | } else { 205 | vterm_screen_get_cell(term->vts, (VTermPos){.row = row, .col = col}, cell); 206 | } 207 | } 208 | 209 | static char *get_row_directory(Term *term, int row) { 210 | if (row < 0) { 211 | ScrollbackLine *sbrow = term->sb_buffer[-row - 1]; 212 | return sbrow->info->directory; 213 | /* return term->dirs[0]; */ 214 | } else { 215 | LineInfo *line = term->lines[row]; 216 | return line ? line->directory : NULL; 217 | } 218 | } 219 | static LineInfo *get_lineinfo(Term *term, int row) { 220 | if (row < 0) { 221 | ScrollbackLine *sbrow = term->sb_buffer[-row - 1]; 222 | return sbrow->info; 223 | /* return term->dirs[0]; */ 224 | } else { 225 | return term->lines[row]; 226 | } 227 | } 228 | static bool is_eol(Term *term, int end_col, int row, int col) { 229 | /* This cell is EOL if this and every cell to the right is black */ 230 | if (row >= 0) { 231 | VTermPos pos = {.row = row, .col = col}; 232 | return vterm_screen_is_eol(term->vts, pos); 233 | } 234 | 235 | ScrollbackLine *sbrow = term->sb_buffer[-row - 1]; 236 | int c; 237 | for (c = col; c < end_col && c < sbrow->cols;) { 238 | if (sbrow->cells[c].chars[0]) { 239 | return 0; 240 | } 241 | c += sbrow->cells[c].width; 242 | } 243 | return 1; 244 | } 245 | static int is_end_of_prompt(Term *term, int end_col, int row, int col) { 246 | LineInfo *info = get_lineinfo(term, row); 247 | if (info == NULL) { 248 | return 0; 249 | } 250 | if (info->prompt_col < 0) { 251 | return 0; 252 | } 253 | if (info->prompt_col == col) { 254 | return 1; 255 | } 256 | if (is_eol(term, end_col, row, col) && info->prompt_col >= col) { 257 | return 1; 258 | } 259 | return 0; 260 | } 261 | 262 | static void goto_col(Term *term, emacs_env *env, int row, int end_col) { 263 | int col = 0; 264 | size_t offset = 0; 265 | size_t beyond_eol = 0; 266 | 267 | int height; 268 | int width; 269 | vterm_get_size(term->vt, &height, &width); 270 | 271 | while (col < end_col) { 272 | VTermScreenCell cell; 273 | fetch_cell(term, row, col, &cell); 274 | if (cell.chars[0]) { 275 | if (cell.width > 1) { 276 | offset += cell.width - 1; 277 | } 278 | } else { 279 | if (is_eol(term, term->width, row, col)) { 280 | offset += cell.width; 281 | beyond_eol += cell.width; 282 | } 283 | } 284 | col += cell.width; 285 | } 286 | 287 | forward_char(env, env->make_integer(env, end_col - offset)); 288 | emacs_value space = env->make_string(env, " ", 1); 289 | for (int i = 0; i < beyond_eol; i += 1) 290 | insert(env, space); 291 | } 292 | 293 | static void refresh_lines(Term *term, emacs_env *env, int start_row, 294 | int end_row, int end_col) { 295 | if (end_row < start_row) { 296 | return; 297 | } 298 | int i, j; 299 | 300 | #define PUSH_BUFFER(c) \ 301 | do { \ 302 | if (length == capacity) { \ 303 | capacity += end_col * 4; \ 304 | buffer = realloc(buffer, capacity * sizeof(char)); \ 305 | } \ 306 | buffer[length] = (c); \ 307 | length++; \ 308 | } while (0) 309 | 310 | int capacity = ((end_row - start_row + 1) * end_col) * 4; 311 | int length = 0; 312 | char *buffer = malloc(capacity * sizeof(char)); 313 | VTermScreenCell cell; 314 | VTermScreenCell lastCell; 315 | fetch_cell(term, start_row, 0, &lastCell); 316 | 317 | for (i = start_row; i < end_row; i++) { 318 | 319 | int newline = 0; 320 | int isprompt = 0; 321 | for (j = 0; j < end_col; j++) { 322 | fetch_cell(term, i, j, &cell); 323 | if (isprompt && length > 0) { 324 | emacs_value text = render_text(env, term, buffer, length, &lastCell); 325 | insert(env, render_prompt(env, text)); 326 | length = 0; 327 | } 328 | 329 | isprompt = is_end_of_prompt(term, end_col, i, j); 330 | if (isprompt && length > 0) { 331 | insert(env, render_text(env, term, buffer, length, &lastCell)); 332 | length = 0; 333 | } 334 | 335 | if (!compare_cells(&cell, &lastCell)) { 336 | emacs_value text = render_text(env, term, buffer, length, &lastCell); 337 | insert(env, text); 338 | length = 0; 339 | } 340 | 341 | lastCell = cell; 342 | if (cell.chars[0] == 0) { 343 | if (is_eol(term, end_col, i, j)) { 344 | /* This cell is EOL if this and every cell to the right is black */ 345 | PUSH_BUFFER('\n'); 346 | newline = 1; 347 | break; 348 | } 349 | PUSH_BUFFER(' '); 350 | } else { 351 | for (int k = 0; k < VTERM_MAX_CHARS_PER_CELL && cell.chars[k]; ++k) { 352 | unsigned char bytes[4]; 353 | size_t count = codepoint_to_utf8(cell.chars[k], bytes); 354 | for (int l = 0; l < count; l++) { 355 | PUSH_BUFFER(bytes[l]); 356 | } 357 | } 358 | } 359 | 360 | if (cell.width > 1) { 361 | int w = cell.width - 1; 362 | j = j + w; 363 | } 364 | } 365 | if (isprompt && length > 0) { 366 | emacs_value text = render_text(env, term, buffer, length, &lastCell); 367 | insert(env, render_prompt(env, text)); 368 | length = 0; 369 | isprompt = 0; 370 | } 371 | 372 | if (!newline) { 373 | emacs_value text = render_text(env, term, buffer, length, &lastCell); 374 | insert(env, text); 375 | length = 0; 376 | text = render_fake_newline(env, term); 377 | insert(env, text); 378 | } 379 | } 380 | emacs_value text = render_text(env, term, buffer, length, &lastCell); 381 | insert(env, text); 382 | 383 | #undef PUSH_BUFFER 384 | free(buffer); 385 | 386 | return; 387 | } 388 | // Refresh the screen (visible part of the buffer when the terminal is 389 | // focused) of a invalidated terminal 390 | static void refresh_screen(Term *term, emacs_env *env) { 391 | // Term height may have decreased before `invalid_end` reflects it. 392 | term->invalid_end = MIN(term->invalid_end, term->height); 393 | 394 | if (term->invalid_end >= term->invalid_start) { 395 | int startrow = -(term->height - term->invalid_start - term->linenum_added); 396 | /* startrow is negative,so we backward -startrow lines from end of buffer 397 | then delete lines there. 398 | */ 399 | goto_line(env, startrow); 400 | delete_lines(env, startrow, term->invalid_end - term->invalid_start, true); 401 | refresh_lines(term, env, term->invalid_start, term->invalid_end, 402 | term->width); 403 | 404 | /* term->linenum_added is lines added by window height increased */ 405 | term->linenum += term->linenum_added; 406 | term->linenum_added = 0; 407 | } 408 | 409 | term->invalid_start = INT_MAX; 410 | term->invalid_end = -1; 411 | } 412 | 413 | static int term_resize(int rows, int cols, void *user_data) { 414 | /* can not use invalidate_terminal here */ 415 | /* when the window height decreased, */ 416 | /* the value of term->invalid_end can't bigger than window height */ 417 | Term *term = (Term *)user_data; 418 | term->invalid_start = 0; 419 | term->invalid_end = rows; 420 | 421 | /* if rows=term->lines_len, that means term_sb_pop already resize term->lines 422 | */ 423 | /* if rowslines_len, term_sb_push would resize term->lines there */ 424 | /* we noly need to take care of rows>term->height */ 425 | 426 | if (rows > term->height) { 427 | if (rows > term->lines_len) { 428 | LineInfo **infos = term->lines; 429 | term->lines = malloc(sizeof(LineInfo *) * rows); 430 | memmove(term->lines, infos, sizeof(infos[0]) * term->lines_len); 431 | 432 | LineInfo *lastline = term->lines[term->lines_len - 1]; 433 | for (int i = term->lines_len; i < rows; i++) { 434 | if (lastline != NULL) { 435 | LineInfo *line = alloc_lineinfo(); 436 | if (lastline->directory != NULL) { 437 | line->directory = 438 | malloc(1 + strlen(term->lines[term->lines_len - 1]->directory)); 439 | strcpy(line->directory, 440 | term->lines[term->lines_len - 1]->directory); 441 | } 442 | term->lines[i] = line; 443 | } else { 444 | term->lines[i] = NULL; 445 | } 446 | } 447 | term->lines_len = rows; 448 | free(infos); 449 | } 450 | } 451 | 452 | term->width = cols; 453 | term->height = rows; 454 | 455 | invalidate_terminal(term, -1, -1); 456 | term->resizing = false; 457 | 458 | return 1; 459 | } 460 | 461 | // Refresh the scrollback of an invalidated terminal. 462 | static void refresh_scrollback(Term *term, emacs_env *env) { 463 | int max_line_count = (int)term->sb_current + term->height; 464 | int del_cnt = 0; 465 | if (term->sb_clear_pending) { 466 | del_cnt = term->linenum - term->height; 467 | if (del_cnt > 0) { 468 | delete_lines(env, 1, del_cnt, true); 469 | term->linenum -= del_cnt; 470 | } 471 | term->sb_clear_pending = false; 472 | } 473 | if (term->sb_pending > 0) { 474 | // This means that either the window height has decreased or the screen 475 | // became full and libvterm had to push all rows up. Convert the first 476 | // pending scrollback row into a string and append it just above the visible 477 | // section of the buffer 478 | 479 | del_cnt = term->linenum - term->height - (int)term->sb_size + 480 | term->sb_pending - term->sb_pending_by_height_decr; 481 | if (del_cnt > 0) { 482 | delete_lines(env, 1, del_cnt, true); 483 | term->linenum -= del_cnt; 484 | } 485 | 486 | term->linenum += term->sb_pending; 487 | del_cnt = term->linenum - max_line_count; /* extra lines at the bottom */ 488 | /* buf_index is negative,so we move to end of buffer,then backward 489 | -buf_index lines. goto lines backward is effectively when 490 | vterm-max-scrollback is a large number. 491 | */ 492 | int buf_index = -(term->height + del_cnt); 493 | goto_line(env, buf_index); 494 | refresh_lines(term, env, -term->sb_pending, 0, term->width); 495 | 496 | term->sb_pending = 0; 497 | } 498 | 499 | // Remove extra lines at the bottom 500 | del_cnt = term->linenum - max_line_count; 501 | if (del_cnt > 0) { 502 | term->linenum -= del_cnt; 503 | /* -del_cnt is negative,so we delete_lines from end of buffer. 504 | this line means: delete del_cnt count of lines at end of buffer. 505 | */ 506 | delete_lines(env, -del_cnt, del_cnt, true); 507 | } 508 | 509 | term->sb_pending_by_height_decr = 0; 510 | term->height_resize = 0; 511 | } 512 | 513 | static void adjust_topline(Term *term, emacs_env *env) { 514 | VTermState *state = vterm_obtain_state(term->vt); 515 | VTermPos pos; 516 | vterm_state_get_cursorpos(state, &pos); 517 | 518 | /* pos.row-term->height is negative,so we backward term->height-pos.row 519 | * lines from end of buffer 520 | */ 521 | 522 | goto_line(env, pos.row - term->height); 523 | goto_col(term, env, pos.row, pos.col); 524 | 525 | emacs_value windows = get_buffer_window_list(env); 526 | emacs_value swindow = selected_window(env); 527 | int winnum = env->extract_integer(env, length(env, windows)); 528 | for (int i = 0; i < winnum; i++) { 529 | emacs_value window = nth(env, i, windows); 530 | if (eq(env, window, swindow)) { 531 | int win_body_height = 532 | env->extract_integer(env, window_body_height(env, window)); 533 | 534 | /* recenter:If ARG is negative, it counts up from the bottom of the 535 | * window. (ARG should be less than the height of the window ) */ 536 | if (term->height - pos.row <= win_body_height) { 537 | recenter(env, env->make_integer(env, pos.row - term->height)); 538 | } else { 539 | recenter(env, env->make_integer(env, pos.row)); 540 | } 541 | } else { 542 | if (env->is_not_nil(env, window)) { 543 | set_window_point(env, window, point(env)); 544 | } 545 | } 546 | } 547 | } 548 | 549 | static void invalidate_terminal(Term *term, int start_row, int end_row) { 550 | if (start_row != -1 && end_row != -1) { 551 | term->invalid_start = MIN(term->invalid_start, start_row); 552 | term->invalid_end = MAX(term->invalid_end, end_row); 553 | } 554 | term->is_invalidated = true; 555 | } 556 | 557 | static int term_damage(VTermRect rect, void *data) { 558 | invalidate_terminal(data, rect.start_row, rect.end_row); 559 | return 1; 560 | } 561 | 562 | static int term_moverect(VTermRect dest, VTermRect src, void *data) { 563 | invalidate_terminal(data, MIN(dest.start_row, src.start_row), 564 | MAX(dest.end_row, src.end_row)); 565 | return 1; 566 | } 567 | 568 | static int term_movecursor(VTermPos new, VTermPos old, int visible, 569 | void *data) { 570 | Term *term = data; 571 | term->cursor.row = new.row; 572 | term->cursor.col = new.col; 573 | invalidate_terminal(term, old.row, old.row + 1); 574 | invalidate_terminal(term, new.row, new.row + 1); 575 | 576 | return 1; 577 | } 578 | 579 | static void term_redraw_cursor(Term *term, emacs_env *env) { 580 | if (term->cursor.cursor_blink_changed) { 581 | term->cursor.cursor_blink_changed = false; 582 | set_cursor_blink(env, term->cursor.cursor_blink); 583 | } 584 | 585 | if (term->cursor.cursor_type_changed) { 586 | term->cursor.cursor_type_changed = false; 587 | 588 | if (!term->cursor.cursor_visible) { 589 | set_cursor_type(env, Qnil); 590 | return; 591 | } 592 | 593 | switch (term->cursor.cursor_type) { 594 | case VTERM_PROP_CURSORSHAPE_BLOCK: 595 | set_cursor_type(env, Qbox); 596 | break; 597 | case VTERM_PROP_CURSORSHAPE_UNDERLINE: 598 | set_cursor_type(env, Qhbar); 599 | break; 600 | case VTERM_PROP_CURSORSHAPE_BAR_LEFT: 601 | set_cursor_type(env, Qbar); 602 | break; 603 | default: 604 | set_cursor_type(env, Qt); 605 | break; 606 | } 607 | } 608 | } 609 | 610 | static void term_redraw(Term *term, emacs_env *env) { 611 | term_redraw_cursor(term, env); 612 | 613 | if (term->is_invalidated) { 614 | int oldlinenum = term->linenum; 615 | refresh_scrollback(term, env); 616 | refresh_screen(term, env); 617 | term->linenum_added = term->linenum - oldlinenum; 618 | adjust_topline(term, env); 619 | term->linenum_added = 0; 620 | } 621 | 622 | if (term->title_changed) { 623 | set_title(env, env->make_string(env, term->title, strlen(term->title))); 624 | term->title_changed = false; 625 | } 626 | 627 | if (term->directory_changed) { 628 | set_directory( 629 | env, env->make_string(env, term->directory, strlen(term->directory))); 630 | term->directory_changed = false; 631 | } 632 | 633 | while (term->elisp_code_first) { 634 | ElispCodeListNode *node = term->elisp_code_first; 635 | term->elisp_code_first = node->next; 636 | emacs_value elisp_code = env->make_string(env, node->code, node->code_len); 637 | vterm_eval(env, elisp_code); 638 | 639 | free(node->code); 640 | free(node); 641 | } 642 | term->elisp_code_p_insert = &term->elisp_code_first; 643 | 644 | if (term->selection_data) { 645 | emacs_value selection_mask = env->make_integer(env, term->selection_mask); 646 | emacs_value selection_data = env->make_string(env, term->selection_data, 647 | strlen(term->selection_data)); 648 | vterm_set_selection(env, selection_mask, selection_data); 649 | free(term->selection_data); 650 | term->selection_data = NULL; 651 | term->selection_mask = 0; 652 | } 653 | 654 | term->is_invalidated = false; 655 | } 656 | 657 | static VTermScreenCallbacks vterm_screen_callbacks = { 658 | .damage = term_damage, 659 | .moverect = term_moverect, 660 | .movecursor = term_movecursor, 661 | .settermprop = term_settermprop, 662 | .resize = term_resize, 663 | .sb_pushline = term_sb_push, 664 | .sb_popline = term_sb_pop, 665 | #if !defined(VTermSBClearNotExists) 666 | .sb_clear = term_sb_clear, 667 | #endif 668 | }; 669 | 670 | static bool compare_cells(VTermScreenCell *a, VTermScreenCell *b) { 671 | bool equal = true; 672 | equal = equal && vterm_color_is_equal(&a->fg, &b->fg); 673 | equal = equal && vterm_color_is_equal(&a->bg, &b->bg); 674 | equal = equal && (a->attrs.bold == b->attrs.bold); 675 | equal = equal && (a->attrs.underline == b->attrs.underline); 676 | equal = equal && (a->attrs.italic == b->attrs.italic); 677 | equal = equal && (a->attrs.reverse == b->attrs.reverse); 678 | equal = equal && (a->attrs.strike == b->attrs.strike); 679 | return equal; 680 | } 681 | 682 | static bool is_key(unsigned char *key, size_t len, char *key_description) { 683 | return (len == strlen(key_description) && 684 | memcmp(key, key_description, len) == 0); 685 | } 686 | 687 | /* str1=concat(str1,str2,str2_len,true); */ 688 | /* str1 can be NULL */ 689 | static char *concat(char *str1, const char *str2, size_t str2_len, 690 | bool free_str1) { 691 | if (str1 == NULL) { 692 | str1 = malloc(str2_len + 1); 693 | memcpy(str1, str2, str2_len); 694 | str1[str2_len] = '\0'; 695 | return str1; 696 | } 697 | size_t str1_len = strlen(str1); 698 | char *buf = malloc(str1_len + str2_len + 1); 699 | memcpy(buf, str1, str1_len); 700 | memcpy(&buf[str1_len], str2, str2_len); 701 | buf[str1_len + str2_len] = '\0'; 702 | if (free_str1) { 703 | free(str1); 704 | } 705 | return buf; 706 | } 707 | static void term_set_title(Term *term, const char *title, size_t len, 708 | bool initial, bool final) { 709 | if (term->title && initial) { 710 | free(term->title); 711 | term->title = NULL; 712 | term->title_changed = false; 713 | } 714 | term->title = concat(term->title, title, len, true); 715 | if (final) { 716 | term->title_changed = true; 717 | } 718 | return; 719 | } 720 | 721 | static int term_settermprop(VTermProp prop, VTermValue *val, void *user_data) { 722 | Term *term = (Term *)user_data; 723 | switch (prop) { 724 | case VTERM_PROP_CURSORVISIBLE: 725 | invalidate_terminal(term, term->cursor.row, term->cursor.row + 1); 726 | term->cursor.cursor_visible = val->boolean; 727 | term->cursor.cursor_type_changed = true; 728 | break; 729 | case VTERM_PROP_CURSORBLINK: 730 | if (term->ignore_blink_cursor) 731 | break; 732 | invalidate_terminal(term, term->cursor.row, term->cursor.row + 1); 733 | term->cursor.cursor_blink = val->boolean; 734 | term->cursor.cursor_blink_changed = true; 735 | break; 736 | case VTERM_PROP_CURSORSHAPE: 737 | invalidate_terminal(term, term->cursor.row, term->cursor.row + 1); 738 | term->cursor.cursor_type = val->number; 739 | term->cursor.cursor_type_changed = true; 740 | break; 741 | case VTERM_PROP_TITLE: 742 | #ifdef VTermStringFragmentNotExists 743 | term_set_title(term, val->string, strlen(val->string), true, true); 744 | #else 745 | term_set_title(term, val->string.str, val->string.len, val->string.initial, 746 | val->string.final); 747 | #endif 748 | break; 749 | case VTERM_PROP_ALTSCREEN: 750 | invalidate_terminal(term, 0, term->height); 751 | break; 752 | default: 753 | return 0; 754 | } 755 | 756 | return 1; 757 | } 758 | 759 | static emacs_value render_text(emacs_env *env, Term *term, char *buffer, 760 | int len, VTermScreenCell *cell) { 761 | emacs_value text; 762 | if (len == 0) { 763 | text = env->make_string(env, "", 0); 764 | return text; 765 | } else { 766 | text = env->make_string(env, buffer, len); 767 | } 768 | 769 | emacs_value fg = cell_rgb_color(env, term, cell, true); 770 | emacs_value bg = cell_rgb_color(env, term, cell, false); 771 | /* With vterm-disable-bold-font, vterm-disable-underline, 772 | * vterm-disable-inverse-video, users can disable some text properties. 773 | * Here, we check whether the text would require adding such properties. 774 | * In case it does, and the user does not disable the attribute, we later 775 | * append the property to the list props. If the text does not require 776 | * such property, or the user disable it, we set the variable to nil. 777 | * Properties that are marked as nil are not added to the text. */ 778 | emacs_value bold = 779 | cell->attrs.bold && !term->disable_bold_font ? Qbold : Qnil; 780 | emacs_value underline = 781 | cell->attrs.underline && !term->disable_underline ? Qt : Qnil; 782 | emacs_value italic = cell->attrs.italic ? Qitalic : Qnil; 783 | emacs_value reverse = 784 | cell->attrs.reverse && !term->disable_inverse_video ? Qt : Qnil; 785 | emacs_value strike = cell->attrs.strike ? Qt : Qnil; 786 | 787 | // TODO: Blink, font, dwl, dhl is missing 788 | int emacs_major_version = 789 | env->extract_integer(env, symbol_value(env, Qemacs_major_version)); 790 | emacs_value properties; 791 | emacs_value props[64]; 792 | int props_len = 0; 793 | if (env->is_not_nil(env, fg)) 794 | props[props_len++] = Qforeground, props[props_len++] = fg; 795 | if (env->is_not_nil(env, bg)) 796 | props[props_len++] = Qbackground, props[props_len++] = bg; 797 | if (bold != Qnil) 798 | props[props_len++] = Qweight, props[props_len++] = bold; 799 | if (underline != Qnil) 800 | props[props_len++] = Qunderline, props[props_len++] = underline; 801 | if (italic != Qnil) 802 | props[props_len++] = Qslant, props[props_len++] = italic; 803 | if (reverse != Qnil) 804 | props[props_len++] = Qreverse, props[props_len++] = reverse; 805 | if (strike != Qnil) 806 | props[props_len++] = Qstrike, props[props_len++] = strike; 807 | if (emacs_major_version >= 27) 808 | props[props_len++] = Qextend, props[props_len++] = Qt; 809 | 810 | properties = list(env, props, props_len); 811 | 812 | if (props_len) 813 | put_text_property(env, text, Qface, properties); 814 | 815 | return text; 816 | } 817 | static emacs_value render_prompt(emacs_env *env, emacs_value text) { 818 | 819 | emacs_value properties; 820 | 821 | properties = 822 | list(env, (emacs_value[]){Qvterm_prompt, Qt, Qrear_nonsticky, Qt}, 4); 823 | 824 | add_text_properties(env, text, properties); 825 | 826 | return text; 827 | } 828 | 829 | static emacs_value render_fake_newline(emacs_env *env, Term *term) { 830 | 831 | emacs_value text; 832 | text = env->make_string(env, "\n", 1); 833 | 834 | emacs_value properties; 835 | 836 | properties = 837 | list(env, (emacs_value[]){Qvterm_line_wrap, Qt, Qrear_nonsticky, Qt}, 4); 838 | 839 | add_text_properties(env, text, properties); 840 | 841 | return text; 842 | } 843 | 844 | static emacs_value cell_rgb_color(emacs_env *env, Term *term, 845 | VTermScreenCell *cell, bool is_foreground) { 846 | VTermColor *color = is_foreground ? &cell->fg : &cell->bg; 847 | 848 | int props_len = 0; 849 | emacs_value props[3]; 850 | if (is_foreground) 851 | props[props_len++] = Qforeground; 852 | if (cell->attrs.underline) 853 | props[props_len++] = Qunderline; 854 | if (cell->attrs.reverse) 855 | props[props_len++] = Qreverse; 856 | 857 | emacs_value args = list(env, props, props_len); 858 | 859 | /** NOTE: -10 is used as index offset for special indexes, 860 | * see C-h f vterm--get-color RET 861 | */ 862 | if (VTERM_COLOR_IS_DEFAULT_FG(color) || VTERM_COLOR_IS_DEFAULT_BG(color)) { 863 | return vterm_get_color(env, -1, args); 864 | } 865 | if (VTERM_COLOR_IS_INDEXED(color)) { 866 | if (color->indexed.idx < 16) { 867 | return vterm_get_color(env, color->indexed.idx, args); 868 | } else { 869 | VTermState *state = vterm_obtain_state(term->vt); 870 | vterm_state_get_palette_color(state, color->indexed.idx, color); 871 | } 872 | } else if (VTERM_COLOR_IS_RGB(color)) { 873 | /* do nothing just use the argument color directly */ 874 | } 875 | 876 | char buffer[8]; 877 | snprintf(buffer, 8, "#%02X%02X%02X", color->rgb.red, color->rgb.green, 878 | color->rgb.blue); 879 | return env->make_string(env, buffer, 7); 880 | } 881 | 882 | static void term_flush_output(Term *term, emacs_env *env) { 883 | size_t len = vterm_output_get_buffer_current(term->vt); 884 | if (len) { 885 | char buffer[len]; 886 | len = vterm_output_read(term->vt, buffer, len); 887 | 888 | emacs_value output = env->make_string(env, buffer, len); 889 | env->funcall(env, Fvterm_flush_output, 1, (emacs_value[]){output}); 890 | } 891 | } 892 | 893 | static void term_clear_scrollback(Term *term, emacs_env *env) { 894 | term_sb_clear(term); 895 | vterm_screen_flush_damage(term->vts); 896 | term_redraw(term, env); 897 | } 898 | 899 | static void term_process_key(Term *term, emacs_env *env, unsigned char *key, 900 | size_t len, VTermModifier modifier) { 901 | if (is_key(key, len, "")) { 902 | term_clear_scrollback(term, env); 903 | } else if (is_key(key, len, "")) { 904 | tcflow(term->pty_fd, TCOON); 905 | } else if (is_key(key, len, "")) { 906 | tcflow(term->pty_fd, TCOOFF); 907 | } else if (is_key(key, len, "")) { 908 | vterm_keyboard_start_paste(term->vt); 909 | } else if (is_key(key, len, "")) { 910 | vterm_keyboard_end_paste(term->vt); 911 | } else if (is_key(key, len, "")) { 912 | vterm_keyboard_key(term->vt, VTERM_KEY_TAB, modifier); 913 | } else if (is_key(key, len, "") || 914 | is_key(key, len, "")) { 915 | vterm_keyboard_key(term->vt, VTERM_KEY_TAB, VTERM_MOD_SHIFT); 916 | } else if (is_key(key, len, "")) { 917 | vterm_keyboard_key(term->vt, VTERM_KEY_BACKSPACE, modifier); 918 | } else if (is_key(key, len, "")) { 919 | vterm_keyboard_key(term->vt, VTERM_KEY_ESCAPE, modifier); 920 | } else if (is_key(key, len, "")) { 921 | vterm_keyboard_key(term->vt, VTERM_KEY_UP, modifier); 922 | } else if (is_key(key, len, "")) { 923 | vterm_keyboard_key(term->vt, VTERM_KEY_DOWN, modifier); 924 | } else if (is_key(key, len, "")) { 925 | vterm_keyboard_key(term->vt, VTERM_KEY_LEFT, modifier); 926 | } else if (is_key(key, len, "")) { 927 | vterm_keyboard_key(term->vt, VTERM_KEY_RIGHT, modifier); 928 | } else if (is_key(key, len, "")) { 929 | vterm_keyboard_key(term->vt, VTERM_KEY_INS, modifier); 930 | } else if (is_key(key, len, "")) { 931 | vterm_keyboard_key(term->vt, VTERM_KEY_DEL, modifier); 932 | } else if (is_key(key, len, "")) { 933 | vterm_keyboard_key(term->vt, VTERM_KEY_HOME, modifier); 934 | } else if (is_key(key, len, "")) { 935 | vterm_keyboard_key(term->vt, VTERM_KEY_END, modifier); 936 | } else if (is_key(key, len, "")) { 937 | vterm_keyboard_key(term->vt, VTERM_KEY_PAGEUP, modifier); 938 | } else if (is_key(key, len, "")) { 939 | vterm_keyboard_key(term->vt, VTERM_KEY_PAGEDOWN, modifier); 940 | } else if (is_key(key, len, "")) { 941 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(0), modifier); 942 | } else if (is_key(key, len, "")) { 943 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(1), modifier); 944 | } else if (is_key(key, len, "")) { 945 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(2), modifier); 946 | } else if (is_key(key, len, "")) { 947 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(3), modifier); 948 | } else if (is_key(key, len, "")) { 949 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(4), modifier); 950 | } else if (is_key(key, len, "")) { 951 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(5), modifier); 952 | } else if (is_key(key, len, "")) { 953 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(6), modifier); 954 | } else if (is_key(key, len, "")) { 955 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(7), modifier); 956 | } else if (is_key(key, len, "")) { 957 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(8), modifier); 958 | } else if (is_key(key, len, "")) { 959 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(9), modifier); 960 | } else if (is_key(key, len, "")) { 961 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(10), modifier); 962 | } else if (is_key(key, len, "")) { 963 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(11), modifier); 964 | } else if (is_key(key, len, "")) { 965 | vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(12), modifier); 966 | } else if (is_key(key, len, "")) { 967 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_0, modifier); 968 | } else if (is_key(key, len, "")) { 969 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_1, modifier); 970 | } else if (is_key(key, len, "")) { 971 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_2, modifier); 972 | } else if (is_key(key, len, "")) { 973 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_3, modifier); 974 | } else if (is_key(key, len, "")) { 975 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_4, modifier); 976 | } else if (is_key(key, len, "")) { 977 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_5, modifier); 978 | } else if (is_key(key, len, "")) { 979 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_6, modifier); 980 | } else if (is_key(key, len, "")) { 981 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_7, modifier); 982 | } else if (is_key(key, len, "")) { 983 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_8, modifier); 984 | } else if (is_key(key, len, "")) { 985 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_9, modifier); 986 | } else if (is_key(key, len, "")) { 987 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_PLUS, modifier); 988 | } else if (is_key(key, len, "")) { 989 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_MINUS, modifier); 990 | } else if (is_key(key, len, "")) { 991 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_MULT, modifier); 992 | } else if (is_key(key, len, "")) { 993 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_DIVIDE, modifier); 994 | } else if (is_key(key, len, "")) { 995 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_EQUAL, modifier); 996 | } else if (is_key(key, len, "")) { 997 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_PERIOD, modifier); 998 | } else if (is_key(key, len, "")) { 999 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_COMMA, modifier); 1000 | } else if (is_key(key, len, "")) { 1001 | vterm_keyboard_key(term->vt, VTERM_KEY_KP_ENTER, modifier); 1002 | } else if (is_key(key, len, "j") && (modifier == VTERM_MOD_CTRL)) { 1003 | vterm_keyboard_unichar(term->vt, '\n', 0); 1004 | } else if (is_key(key, len, "SPC")) { 1005 | vterm_keyboard_unichar(term->vt, ' ', modifier); 1006 | } else if (len <= 4) { 1007 | uint32_t codepoint; 1008 | if (utf8_to_codepoint(key, len, &codepoint)) { 1009 | vterm_keyboard_unichar(term->vt, codepoint, modifier); 1010 | } 1011 | } 1012 | } 1013 | 1014 | void term_finalize(void *object) { 1015 | Term *term = (Term *)object; 1016 | for (int i = 0; i < term->sb_current; i++) { 1017 | if (term->sb_buffer[i]->info != NULL) { 1018 | free_lineinfo(term->sb_buffer[i]->info); 1019 | term->sb_buffer[i]->info = NULL; 1020 | } 1021 | free(term->sb_buffer[i]); 1022 | } 1023 | if (term->title) { 1024 | free(term->title); 1025 | term->title = NULL; 1026 | } 1027 | 1028 | if (term->directory) { 1029 | free(term->directory); 1030 | term->directory = NULL; 1031 | } 1032 | 1033 | while (term->elisp_code_first) { 1034 | ElispCodeListNode *node = term->elisp_code_first; 1035 | term->elisp_code_first = node->next; 1036 | free(node->code); 1037 | free(node); 1038 | } 1039 | term->elisp_code_p_insert = &term->elisp_code_first; 1040 | 1041 | if (term->cmd_buffer) { 1042 | free(term->cmd_buffer); 1043 | term->cmd_buffer = NULL; 1044 | } 1045 | if (term->selection_data) { 1046 | free(term->selection_data); 1047 | term->selection_data = NULL; 1048 | } 1049 | 1050 | for (int i = 0; i < term->lines_len; i++) { 1051 | if (term->lines[i] != NULL) { 1052 | free_lineinfo(term->lines[i]); 1053 | term->lines[i] = NULL; 1054 | } 1055 | } 1056 | 1057 | if (term->pty_fd > 0) { 1058 | close(term->pty_fd); 1059 | } 1060 | 1061 | free(term->sb_buffer); 1062 | free(term->lines); 1063 | vterm_free(term->vt); 1064 | free(term); 1065 | } 1066 | 1067 | static int handle_osc_cmd_51(Term *term, char subCmd, char *buffer) { 1068 | if (subCmd == 'A') { 1069 | /* "51;A" sets the current directory */ 1070 | /* "51;A" has also the role of identifying the end of the prompt */ 1071 | if (term->directory != NULL) { 1072 | free(term->directory); 1073 | term->directory = NULL; 1074 | } 1075 | term->directory = malloc(strlen(buffer) + 1); 1076 | strcpy(term->directory, buffer); 1077 | term->directory_changed = true; 1078 | 1079 | for (int i = term->cursor.row; i < term->lines_len; i++) { 1080 | if (term->lines[i] == NULL) { 1081 | term->lines[i] = alloc_lineinfo(); 1082 | } 1083 | 1084 | if (term->lines[i]->directory != NULL) { 1085 | free(term->lines[i]->directory); 1086 | } 1087 | term->lines[i]->directory = malloc(strlen(buffer) + 1); 1088 | strcpy(term->lines[i]->directory, buffer); 1089 | if (i == term->cursor.row) { 1090 | term->lines[i]->prompt_col = term->cursor.col; 1091 | } else { 1092 | term->lines[i]->prompt_col = -1; 1093 | } 1094 | } 1095 | return 1; 1096 | } else if (subCmd == 'E') { 1097 | /* "51;E" executes elisp code */ 1098 | /* The elisp code is executed in term_redraw */ 1099 | ElispCodeListNode *node = malloc(sizeof(ElispCodeListNode)); 1100 | node->code_len = strlen(buffer); 1101 | node->code = malloc(node->code_len + 1); 1102 | strcpy(node->code, buffer); 1103 | node->next = NULL; 1104 | 1105 | *(term->elisp_code_p_insert) = node; 1106 | term->elisp_code_p_insert = &(node->next); 1107 | return 1; 1108 | } 1109 | return 0; 1110 | } 1111 | 1112 | static int handle_osc_cmd(Term *term, int cmd, char *buffer) { 1113 | if (cmd == 51) { 1114 | char subCmd = '0'; 1115 | if (strlen(buffer) == 0) { 1116 | return 0; 1117 | } 1118 | subCmd = buffer[0]; 1119 | /* ++ skip the subcmd char */ 1120 | return handle_osc_cmd_51(term, subCmd, ++buffer); 1121 | } 1122 | return 0; 1123 | } 1124 | /* maybe we should drop support of libvterm < v0.2 */ 1125 | /* VTermStringFragmentNotExists was introduced when libvterm is not released */ 1126 | #ifdef VTermStringFragmentNotExists 1127 | static int osc_callback(const char *command, size_t cmdlen, void *user) { 1128 | Term *term = (Term *)user; 1129 | char buffer[cmdlen + 1]; 1130 | buffer[cmdlen] = '\0'; 1131 | memcpy(buffer, command, cmdlen); 1132 | 1133 | if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' && buffer[2] == ';' && 1134 | buffer[3] == 'A') { 1135 | return handle_osc_cmd_51(term, 'A', &buffer[4]); 1136 | } else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' && 1137 | buffer[2] == ';' && buffer[3] == 'E') { 1138 | return handle_osc_cmd_51(term, 'E', &buffer[4]); 1139 | } 1140 | return 0; 1141 | } 1142 | static VTermParserCallbacks parser_callbacks = { 1143 | .text = NULL, 1144 | .control = NULL, 1145 | .escape = NULL, 1146 | .csi = NULL, 1147 | .osc = &osc_callback, 1148 | .dcs = NULL, 1149 | }; 1150 | #else 1151 | 1152 | static int osc_callback(int cmd, VTermStringFragment frag, void *user) { 1153 | /* osc_callback (OSC = Operating System Command) */ 1154 | 1155 | /* We interpret escape codes that start with "51;" */ 1156 | /* "51;A" sets the current directory */ 1157 | /* "51;A" has also the role of identifying the end of the prompt */ 1158 | /* "51;E" executes elisp code */ 1159 | /* The elisp code is executed in term_redraw */ 1160 | Term *term = (Term *)user; 1161 | 1162 | if (frag.initial) { 1163 | /* drop old fragment,because this is a initial fragment */ 1164 | if (term->cmd_buffer) { 1165 | free(term->cmd_buffer); 1166 | term->cmd_buffer = NULL; 1167 | } 1168 | } 1169 | 1170 | if (!frag.initial && !frag.final && frag.len == 0) { 1171 | return 0; 1172 | } 1173 | 1174 | term->cmd_buffer = concat(term->cmd_buffer, frag.str, frag.len, true); 1175 | if (frag.final) { 1176 | handle_osc_cmd(term, cmd, term->cmd_buffer); 1177 | free(term->cmd_buffer); 1178 | term->cmd_buffer = NULL; 1179 | } 1180 | return 0; 1181 | } 1182 | static VTermStateFallbacks parser_callbacks = { 1183 | .control = NULL, 1184 | .csi = NULL, 1185 | .osc = &osc_callback, 1186 | .dcs = NULL, 1187 | }; 1188 | #ifndef VTermSelectionMaskNotExists 1189 | static int set_selection(VTermSelectionMask mask, VTermStringFragment frag, 1190 | void *user) { 1191 | Term *term = (Term *)user; 1192 | 1193 | if (frag.initial) { 1194 | term->selection_mask = mask; 1195 | if (term->selection_data) { 1196 | free(term->selection_data); 1197 | } 1198 | term->selection_data = NULL; 1199 | } 1200 | 1201 | if (frag.len) { 1202 | term->selection_data = 1203 | concat(term->selection_data, frag.str, frag.len, true); 1204 | } 1205 | return 1; 1206 | } 1207 | /* OSC 52 ; Pc ; Pd BEL */ 1208 | /* Manipulate Selection Data */ 1209 | /* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html */ 1210 | /* test by printf "\033]52;c;$(printf "%s" "blabla" | base64)\a" */ 1211 | /* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */ 1212 | /* for clipboard, primary, secondary, select, or cut buffers 0 through 7 */ 1213 | /* respectively */ 1214 | static VTermSelectionCallbacks selection_callbacks = { 1215 | .set = &set_selection, 1216 | .query = NULL, 1217 | }; 1218 | #endif /* VTermSelectionMaskNotExists */ 1219 | 1220 | #endif 1221 | 1222 | emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[], 1223 | void *data) { 1224 | Term *term = malloc(sizeof(Term)); 1225 | 1226 | int rows = env->extract_integer(env, args[0]); 1227 | int cols = env->extract_integer(env, args[1]); 1228 | int sb_size = env->extract_integer(env, args[2]); 1229 | int disable_bold_font = env->is_not_nil(env, args[3]); 1230 | int disable_underline = env->is_not_nil(env, args[4]); 1231 | int disable_inverse_video = env->is_not_nil(env, args[5]); 1232 | int ignore_blink_cursor = env->is_not_nil(env, args[6]); 1233 | int set_bold_hightbright = env->is_not_nil(env, args[7]); 1234 | 1235 | term->vt = vterm_new(rows, cols); 1236 | vterm_set_utf8(term->vt, 1); 1237 | 1238 | term->vts = vterm_obtain_screen(term->vt); 1239 | 1240 | VTermState *state = vterm_obtain_state(term->vt); 1241 | vterm_state_set_unrecognised_fallbacks(state, &parser_callbacks, term); 1242 | 1243 | #ifndef VTermSelectionMaskNotExists 1244 | vterm_state_set_selection_callbacks(state, &selection_callbacks, term, 1245 | term->selection_buf, SELECTION_BUF_LEN); 1246 | #endif 1247 | vterm_state_set_bold_highbright(state, set_bold_hightbright); 1248 | 1249 | vterm_screen_reset(term->vts, 1); 1250 | vterm_screen_set_callbacks(term->vts, &vterm_screen_callbacks, term); 1251 | vterm_screen_set_damage_merge(term->vts, VTERM_DAMAGE_SCROLL); 1252 | vterm_screen_enable_altscreen(term->vts, true); 1253 | term->sb_size = MIN(SB_MAX, sb_size); 1254 | term->sb_current = 0; 1255 | term->sb_pending = 0; 1256 | term->sb_clear_pending = false; 1257 | term->sb_pending_by_height_decr = 0; 1258 | term->sb_buffer = malloc(sizeof(ScrollbackLine *) * term->sb_size); 1259 | term->invalid_start = 0; 1260 | term->invalid_end = rows; 1261 | term->is_invalidated = false; 1262 | term->width = cols; 1263 | term->height = rows; 1264 | term->height_resize = 0; 1265 | term->disable_bold_font = disable_bold_font; 1266 | term->disable_underline = disable_underline; 1267 | term->disable_inverse_video = disable_inverse_video; 1268 | term->ignore_blink_cursor = ignore_blink_cursor; 1269 | emacs_value newline = env->make_string(env, "\n", 1); 1270 | for (int i = 0; i < term->height; i++) { 1271 | insert(env, newline); 1272 | } 1273 | term->linenum = term->height; 1274 | term->linenum_added = 0; 1275 | term->resizing = false; 1276 | 1277 | term->pty_fd = -1; 1278 | 1279 | term->title = NULL; 1280 | term->title_changed = false; 1281 | 1282 | term->cursor.row = 0; 1283 | term->cursor.col = 0; 1284 | term->cursor.cursor_type = -1; 1285 | term->cursor.cursor_visible = true; 1286 | term->cursor.cursor_type_changed = false; 1287 | term->cursor.cursor_blink = false; 1288 | term->cursor.cursor_blink_changed = false; 1289 | term->directory = NULL; 1290 | term->directory_changed = false; 1291 | term->elisp_code_first = NULL; 1292 | term->elisp_code_p_insert = &term->elisp_code_first; 1293 | term->selection_data = NULL; 1294 | term->selection_mask = 0; 1295 | 1296 | term->cmd_buffer = NULL; 1297 | 1298 | term->lines = malloc(sizeof(LineInfo *) * rows); 1299 | term->lines_len = rows; 1300 | for (int i = 0; i < rows; i++) { 1301 | term->lines[i] = NULL; 1302 | } 1303 | 1304 | return env->make_user_ptr(env, term_finalize, term); 1305 | } 1306 | 1307 | emacs_value Fvterm_update(emacs_env *env, ptrdiff_t nargs, emacs_value args[], 1308 | void *data) { 1309 | Term *term = env->get_user_ptr(env, args[0]); 1310 | 1311 | // Process keys 1312 | if (nargs > 1) { 1313 | ptrdiff_t len = string_bytes(env, args[1]); 1314 | unsigned char key[len]; 1315 | env->copy_string_contents(env, args[1], (char *)key, &len); 1316 | VTermModifier modifier = VTERM_MOD_NONE; 1317 | if (nargs > 2 && env->is_not_nil(env, args[2])) 1318 | modifier = modifier | VTERM_MOD_SHIFT; 1319 | if (nargs > 3 && env->is_not_nil(env, args[3])) 1320 | modifier = modifier | VTERM_MOD_ALT; 1321 | if (nargs > 4 && env->is_not_nil(env, args[4])) 1322 | modifier = modifier | VTERM_MOD_CTRL; 1323 | 1324 | // Ignore the final zero byte 1325 | term_process_key(term, env, key, len - 1, modifier); 1326 | } 1327 | 1328 | // Flush output 1329 | term_flush_output(term, env); 1330 | if (term->is_invalidated) { 1331 | vterm_invalidate(env); 1332 | } 1333 | 1334 | return env->make_integer(env, 0); 1335 | } 1336 | 1337 | emacs_value Fvterm_redraw(emacs_env *env, ptrdiff_t nargs, emacs_value args[], 1338 | void *data) { 1339 | Term *term = env->get_user_ptr(env, args[0]); 1340 | term_redraw(term, env); 1341 | return env->make_integer(env, 0); 1342 | } 1343 | 1344 | emacs_value Fvterm_write_input(emacs_env *env, ptrdiff_t nargs, 1345 | emacs_value args[], void *data) { 1346 | Term *term = env->get_user_ptr(env, args[0]); 1347 | ptrdiff_t len = string_bytes(env, args[1]); 1348 | 1349 | if (len > 0) { 1350 | char bytes[len]; 1351 | env->copy_string_contents(env, args[1], bytes, &len); 1352 | 1353 | vterm_input_write(term->vt, bytes, len); 1354 | vterm_screen_flush_damage(term->vts); 1355 | } 1356 | 1357 | return env->make_integer(env, 0); 1358 | } 1359 | 1360 | emacs_value Fvterm_set_size(emacs_env *env, ptrdiff_t nargs, emacs_value args[], 1361 | void *data) { 1362 | Term *term = env->get_user_ptr(env, args[0]); 1363 | int rows = env->extract_integer(env, args[1]); 1364 | int cols = env->extract_integer(env, args[2]); 1365 | 1366 | if (cols != term->width || rows != term->height) { 1367 | term->height_resize = rows - term->height; 1368 | if (rows > term->height) { 1369 | if (rows - term->height > term->sb_current) { 1370 | term->linenum_added = rows - term->height - term->sb_current; 1371 | } 1372 | } 1373 | term->resizing = true; 1374 | vterm_set_size(term->vt, rows, cols); 1375 | vterm_screen_flush_damage(term->vts); 1376 | 1377 | term_redraw(term, env); 1378 | } 1379 | 1380 | return Qnil; 1381 | } 1382 | 1383 | emacs_value Fvterm_set_pty_name(emacs_env *env, ptrdiff_t nargs, 1384 | emacs_value args[], void *data) { 1385 | Term *term = env->get_user_ptr(env, args[0]); 1386 | 1387 | if (nargs > 1) { 1388 | ptrdiff_t len = string_bytes(env, args[1]); 1389 | char filename[len]; 1390 | 1391 | env->copy_string_contents(env, args[1], filename, &len); 1392 | 1393 | term->pty_fd = open(filename, O_RDONLY); 1394 | } 1395 | return Qnil; 1396 | } 1397 | emacs_value Fvterm_get_pwd(emacs_env *env, ptrdiff_t nargs, emacs_value args[], 1398 | void *data) { 1399 | Term *term = env->get_user_ptr(env, args[0]); 1400 | int linenum = env->extract_integer(env, args[1]); 1401 | int row = linenr_to_row(term, linenum); 1402 | char *dir = get_row_directory(term, row); 1403 | 1404 | return dir ? env->make_string(env, dir, strlen(dir)) : Qnil; 1405 | } 1406 | 1407 | emacs_value Fvterm_get_icrnl(emacs_env *env, ptrdiff_t nargs, 1408 | emacs_value args[], void *data) { 1409 | Term *term = env->get_user_ptr(env, args[0]); 1410 | 1411 | if (term->pty_fd > 0) { 1412 | struct termios keys; 1413 | tcgetattr(term->pty_fd, &keys); 1414 | 1415 | if (keys.c_iflag & ICRNL) 1416 | return Qt; 1417 | else 1418 | return Qnil; 1419 | } 1420 | return Qnil; 1421 | } 1422 | 1423 | emacs_value Fvterm_reset_cursor_point(emacs_env *env, ptrdiff_t nargs, 1424 | emacs_value args[], void *data) { 1425 | Term *term = env->get_user_ptr(env, args[0]); 1426 | int line = row_to_linenr(term, term->cursor.row); 1427 | goto_line(env, line); 1428 | goto_col(term, env, term->cursor.row, term->cursor.col); 1429 | return point(env); 1430 | } 1431 | 1432 | int emacs_module_init(struct emacs_runtime *ert) { 1433 | emacs_env *env = ert->get_environment(ert); 1434 | 1435 | // Symbols; 1436 | Qt = env->make_global_ref(env, env->intern(env, "t")); 1437 | Qnil = env->make_global_ref(env, env->intern(env, "nil")); 1438 | Qnormal = env->make_global_ref(env, env->intern(env, "normal")); 1439 | Qbold = env->make_global_ref(env, env->intern(env, "bold")); 1440 | Qitalic = env->make_global_ref(env, env->intern(env, "italic")); 1441 | Qforeground = env->make_global_ref(env, env->intern(env, ":foreground")); 1442 | Qbackground = env->make_global_ref(env, env->intern(env, ":background")); 1443 | Qweight = env->make_global_ref(env, env->intern(env, ":weight")); 1444 | Qunderline = env->make_global_ref(env, env->intern(env, ":underline")); 1445 | Qslant = env->make_global_ref(env, env->intern(env, ":slant")); 1446 | Qreverse = env->make_global_ref(env, env->intern(env, ":inverse-video")); 1447 | Qstrike = env->make_global_ref(env, env->intern(env, ":strike-through")); 1448 | Qextend = env->make_global_ref(env, env->intern(env, ":extend")); 1449 | Qemacs_major_version = 1450 | env->make_global_ref(env, env->intern(env, "emacs-major-version")); 1451 | Qvterm_line_wrap = 1452 | env->make_global_ref(env, env->intern(env, "vterm-line-wrap")); 1453 | Qrear_nonsticky = 1454 | env->make_global_ref(env, env->intern(env, "rear-nonsticky")); 1455 | Qvterm_prompt = env->make_global_ref(env, env->intern(env, "vterm-prompt")); 1456 | 1457 | Qface = env->make_global_ref(env, env->intern(env, "font-lock-face")); 1458 | Qbox = env->make_global_ref(env, env->intern(env, "box")); 1459 | Qbar = env->make_global_ref(env, env->intern(env, "bar")); 1460 | Qhbar = env->make_global_ref(env, env->intern(env, "hbar")); 1461 | Qcursor_type = env->make_global_ref(env, env->intern(env, "cursor-type")); 1462 | 1463 | // Functions 1464 | Fapply = env->make_global_ref(env, env->intern(env, "apply")); 1465 | Fblink_cursor_mode = 1466 | env->make_global_ref(env, env->intern(env, "blink-cursor-mode")); 1467 | Fsymbol_value = env->make_global_ref(env, env->intern(env, "symbol-value")); 1468 | Flength = env->make_global_ref(env, env->intern(env, "length")); 1469 | Flist = env->make_global_ref(env, env->intern(env, "list")); 1470 | Fnth = env->make_global_ref(env, env->intern(env, "nth")); 1471 | Ferase_buffer = env->make_global_ref(env, env->intern(env, "erase-buffer")); 1472 | Finsert = env->make_global_ref(env, env->intern(env, "vterm--insert")); 1473 | Fgoto_char = env->make_global_ref(env, env->intern(env, "goto-char")); 1474 | Fput_text_property = 1475 | env->make_global_ref(env, env->intern(env, "put-text-property")); 1476 | Fadd_text_properties = 1477 | env->make_global_ref(env, env->intern(env, "add-text-properties")); 1478 | Fset = env->make_global_ref(env, env->intern(env, "set")); 1479 | Fvterm_flush_output = 1480 | env->make_global_ref(env, env->intern(env, "vterm--flush-output")); 1481 | Fforward_line = env->make_global_ref(env, env->intern(env, "forward-line")); 1482 | Fgoto_line = env->make_global_ref(env, env->intern(env, "vterm--goto-line")); 1483 | Fdelete_lines = 1484 | env->make_global_ref(env, env->intern(env, "vterm--delete-lines")); 1485 | Frecenter = env->make_global_ref(env, env->intern(env, "recenter")); 1486 | Fset_window_point = 1487 | env->make_global_ref(env, env->intern(env, "set-window-point")); 1488 | Fwindow_body_height = 1489 | env->make_global_ref(env, env->intern(env, "window-body-height")); 1490 | 1491 | Fpoint = env->make_global_ref(env, env->intern(env, "point")); 1492 | Fforward_char = env->make_global_ref(env, env->intern(env, "forward-char")); 1493 | Fget_buffer_window_list = 1494 | env->make_global_ref(env, env->intern(env, "get-buffer-window-list")); 1495 | Fselected_window = 1496 | env->make_global_ref(env, env->intern(env, "selected-window")); 1497 | 1498 | Fvterm_set_title = 1499 | env->make_global_ref(env, env->intern(env, "vterm--set-title")); 1500 | Fvterm_set_directory = 1501 | env->make_global_ref(env, env->intern(env, "vterm--set-directory")); 1502 | Fvterm_invalidate = 1503 | env->make_global_ref(env, env->intern(env, "vterm--invalidate")); 1504 | Feq = env->make_global_ref(env, env->intern(env, "eq")); 1505 | Fvterm_get_color = 1506 | env->make_global_ref(env, env->intern(env, "vterm--get-color")); 1507 | Fvterm_eval = env->make_global_ref(env, env->intern(env, "vterm--eval")); 1508 | Fvterm_set_selection = 1509 | env->make_global_ref(env, env->intern(env, "vterm--set-selection")); 1510 | 1511 | // Exported functions 1512 | emacs_value fun; 1513 | fun = 1514 | env->make_function(env, 4, 8, Fvterm_new, "Allocate a new vterm.", NULL); 1515 | bind_function(env, "vterm--new", fun); 1516 | 1517 | fun = env->make_function(env, 1, 5, Fvterm_update, 1518 | "Process io and update the screen.", NULL); 1519 | bind_function(env, "vterm--update", fun); 1520 | 1521 | fun = 1522 | env->make_function(env, 1, 1, Fvterm_redraw, "Redraw the screen.", NULL); 1523 | bind_function(env, "vterm--redraw", fun); 1524 | 1525 | fun = env->make_function(env, 2, 2, Fvterm_write_input, 1526 | "Write input to vterm.", NULL); 1527 | bind_function(env, "vterm--write-input", fun); 1528 | 1529 | fun = env->make_function(env, 3, 3, Fvterm_set_size, 1530 | "Set the size of the terminal.", NULL); 1531 | bind_function(env, "vterm--set-size", fun); 1532 | 1533 | fun = env->make_function(env, 2, 2, Fvterm_set_pty_name, 1534 | "Set the name of the pty.", NULL); 1535 | bind_function(env, "vterm--set-pty-name", fun); 1536 | fun = env->make_function(env, 2, 2, Fvterm_get_pwd, 1537 | "Get the working directory of at line n.", NULL); 1538 | bind_function(env, "vterm--get-pwd-raw", fun); 1539 | fun = env->make_function(env, 1, 1, Fvterm_reset_cursor_point, 1540 | "Reset cursor position.", NULL); 1541 | bind_function(env, "vterm--reset-point", fun); 1542 | 1543 | fun = env->make_function(env, 1, 1, Fvterm_get_icrnl, 1544 | "Get the icrnl state of the pty", NULL); 1545 | bind_function(env, "vterm--get-icrnl", fun); 1546 | 1547 | provide(env, "vterm-module"); 1548 | 1549 | return 0; 1550 | } 1551 | -------------------------------------------------------------------------------- /vterm-module.h: -------------------------------------------------------------------------------- 1 | #ifndef VTERM_MODULE_H 2 | #define VTERM_MODULE_H 3 | 4 | #include "emacs-module.h" 5 | #include 6 | #include 7 | #include 8 | 9 | // https://gcc.gnu.org/wiki/Visibility 10 | #if defined _WIN32 || defined __CYGWIN__ 11 | #ifdef __GNUC__ 12 | #define VTERM_EXPORT __attribute__((dllexport)) 13 | #else 14 | #define VTERM_EXPORT __declspec(dllexport) 15 | #endif 16 | #else 17 | #if __GNUC__ >= 4 18 | #define VTERM_EXPORT __attribute__((visibility("default"))) 19 | #else 20 | #define VTERM_EXPORT 21 | #endif 22 | #endif 23 | 24 | VTERM_EXPORT int plugin_is_GPL_compatible; 25 | 26 | #ifndef SB_MAX 27 | #define SB_MAX 100000 // Maximum 'scrollback' value. 28 | #endif 29 | 30 | #ifndef MIN 31 | #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) 32 | #endif 33 | #ifndef MAX 34 | #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) 35 | #endif 36 | 37 | typedef struct LineInfo { 38 | char *directory; /* working directory */ 39 | 40 | int prompt_col; /* end column of the prompt, if the current line contains the 41 | * prompt */ 42 | } LineInfo; 43 | 44 | typedef struct ScrollbackLine { 45 | size_t cols; 46 | LineInfo *info; 47 | VTermScreenCell cells[]; 48 | } ScrollbackLine; 49 | 50 | typedef struct ElispCodeListNode { 51 | char *code; 52 | size_t code_len; 53 | struct ElispCodeListNode *next; 54 | } ElispCodeListNode; 55 | 56 | /* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */ 57 | /* clipboard, primary, secondary, select, or cut buffers 0 through 7 */ 58 | #define SELECTION_BUF_LEN 4096 59 | 60 | typedef struct Cursor { 61 | int row, col; 62 | int cursor_type; 63 | bool cursor_visible; 64 | bool cursor_blink; 65 | bool cursor_type_changed; 66 | bool cursor_blink_changed; 67 | } Cursor; 68 | 69 | typedef struct Term { 70 | VTerm *vt; 71 | VTermScreen *vts; 72 | // buffer used to: 73 | // - convert VTermScreen cell arrays into utf8 strings 74 | // - receive data from libvterm as a result of key presses. 75 | ScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm 76 | size_t sb_current; // number of rows pushed to sb_buffer 77 | size_t sb_size; // sb_buffer size 78 | // "virtual index" that points to the first sb_buffer row that we need to 79 | // push to the terminal buffer when refreshing the scrollback. When negative, 80 | // it actually points to entries that are no longer in sb_buffer (because the 81 | // window height has increased) and must be deleted from the terminal buffer 82 | int sb_pending; 83 | int sb_pending_by_height_decr; 84 | bool sb_clear_pending; 85 | long linenum; 86 | long linenum_added; 87 | 88 | int invalid_start, invalid_end; // invalid rows in libvterm screen 89 | bool is_invalidated; 90 | 91 | Cursor cursor; 92 | char *title; 93 | bool title_changed; 94 | 95 | char *directory; 96 | bool directory_changed; 97 | 98 | // Single-linked list of elisp_code. 99 | // Newer commands are added at the tail. 100 | ElispCodeListNode *elisp_code_first; 101 | ElispCodeListNode **elisp_code_p_insert; // pointer to the position where new 102 | // node should be inserted 103 | 104 | /* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */ 105 | /* clipboard, primary, secondary, select, or cut buffers 0 through 7 */ 106 | int selection_mask; /* see VTermSelectionMask in vterm.h */ 107 | char *selection_data; 108 | char selection_buf[SELECTION_BUF_LEN]; 109 | 110 | /* the size of dirs almost = window height, value = directory of that line */ 111 | LineInfo **lines; 112 | int lines_len; 113 | 114 | int width, height; 115 | int height_resize; 116 | bool resizing; 117 | bool disable_bold_font; 118 | bool disable_underline; 119 | bool disable_inverse_video; 120 | bool ignore_blink_cursor; 121 | 122 | char *cmd_buffer; 123 | 124 | int pty_fd; 125 | } Term; 126 | 127 | static bool compare_cells(VTermScreenCell *a, VTermScreenCell *b); 128 | static bool is_key(unsigned char *key, size_t len, char *key_description); 129 | static emacs_value render_text(emacs_env *env, Term *term, char *string, 130 | int len, VTermScreenCell *cell); 131 | static emacs_value render_fake_newline(emacs_env *env, Term *term); 132 | static emacs_value render_prompt(emacs_env *env, emacs_value text); 133 | static emacs_value cell_rgb_color(emacs_env *env, Term *term, 134 | VTermScreenCell *cell, bool is_foreground); 135 | 136 | static int term_settermprop(VTermProp prop, VTermValue *val, void *user_data); 137 | 138 | static void term_redraw(Term *term, emacs_env *env); 139 | static void term_flush_output(Term *term, emacs_env *env); 140 | static void term_process_key(Term *term, emacs_env *env, unsigned char *key, 141 | size_t len, VTermModifier modifier); 142 | static void invalidate_terminal(Term *term, int start_row, int end_row); 143 | 144 | void term_finalize(void *object); 145 | 146 | emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[], 147 | void *data); 148 | emacs_value Fvterm_update(emacs_env *env, ptrdiff_t nargs, emacs_value args[], 149 | void *data); 150 | emacs_value Fvterm_redraw(emacs_env *env, ptrdiff_t nargs, emacs_value args[], 151 | void *data); 152 | emacs_value Fvterm_write_input(emacs_env *env, ptrdiff_t nargs, 153 | emacs_value args[], void *data); 154 | emacs_value Fvterm_set_size(emacs_env *env, ptrdiff_t nargs, emacs_value args[], 155 | void *data); 156 | emacs_value Fvterm_set_pty_name(emacs_env *env, ptrdiff_t nargs, 157 | emacs_value args[], void *data); 158 | emacs_value Fvterm_get_icrnl(emacs_env *env, ptrdiff_t nargs, 159 | emacs_value args[], void *data); 160 | 161 | emacs_value Fvterm_get_pwd(emacs_env *env, ptrdiff_t nargs, emacs_value args[], 162 | void *data); 163 | 164 | emacs_value Fvterm_get_prompt_point(emacs_env *env, ptrdiff_t nargs, 165 | emacs_value args[], void *data); 166 | emacs_value Fvterm_reset_cursor_point(emacs_env *env, ptrdiff_t nargs, 167 | emacs_value args[], void *data); 168 | 169 | VTERM_EXPORT int emacs_module_init(struct emacs_runtime *ert); 170 | 171 | #endif /* VTERM_MODULE_H */ 172 | --------------------------------------------------------------------------------