├── .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 | [](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 |
--------------------------------------------------------------------------------