vterm.el (62810B)
1 ;;; vterm.el --- Fully-featured terminal emulator -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2017-2020 by Lukas Fürmetz & Contributors 4 ;; 5 ;; Author: Lukas Fürmetz <fuermetz@mailbox.org> 6 ;; Version: 0.0.1 7 ;; URL: https://github.com/akermu/emacs-libvterm 8 ;; Keywords: terminals 9 ;; Package-Requires: ((emacs "25.1")) 10 11 12 ;; This file is not part of GNU Emacs. 13 14 ;; This file is free software; you can redistribute it and/or modify 15 ;; it under the terms of the GNU General Public License as published by 16 ;; the Free Software Foundation; either version 2, or (at your option) 17 ;; any later version. 18 19 ;; This file is distributed in the hope that it will be useful, 20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 ;; GNU General Public License for more details. 23 24 ;; You should have received a copy of the GNU General Public License 25 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. 26 27 28 ;;; Commentary: 29 ;; 30 ;; Emacs-libvterm (vterm) is fully-fledged terminal emulator based on an 31 ;; external library (libvterm) loaded as a dynamic module. As a result of using 32 ;; compiled code (instead of elisp), emacs-libvterm is fully capable, fast, and 33 ;; it can seamlessly handle large outputs. 34 35 ;;; Installation 36 37 ;; Emacs-libvterm requires support for loading modules. You can check if your 38 ;; Emacs supports modules by inspecting the variable module-file-suffix. If it 39 ;; nil, than, you need to recompile Emacs or obtain a copy of Emacs with this 40 ;; option enabled. 41 42 ;; Emacs-libvterm requires CMake and libvterm. If libvterm is not available, 43 ;; emacs-libvterm will downloaded and compiled. In this case, libtool is 44 ;; needed. 45 46 ;; The reccomended way to install emacs-libvterm is from MELPA. 47 48 ;;; Usage 49 50 ;; To open a terminal, simply use the command M-x vterm. 51 52 ;;; Tips and tricks 53 54 ;; Adding some shell-side configuration enables a large set of additional 55 ;; features, including, directory tracking, prompt recognition, message passing. 56 57 ;;; Code: 58 59 (require 'term/xterm) 60 61 (unless module-file-suffix 62 (error "VTerm needs module support. Please compile Emacs with 63 the --with-modules option!")) 64 65 ;;; Compilation of the module 66 67 (defcustom vterm-module-cmake-args "" 68 "Arguments given to CMake to compile vterm-module. 69 70 Currently, vterm defines the following flags (in addition to the 71 ones already available in CMake): 72 73 `USE_SYSTEM_LIBVTERM'. Set it to `Off' to use the vendored version of 74 libvterm instead of the one installed on your system. 75 76 This string is given verbatim to CMake, so it has to have the 77 correct syntax. An example of meaningful value for this variable 78 is `-DUSE_SYSTEM_LIBVTERM=Off'." 79 :type 'string 80 :group 'vterm) 81 82 (defcustom vterm-always-compile-module nil 83 "If not nil, if `vterm-module' is not found, compile it without asking. 84 85 When `vterm-always-compile-module' is nil, vterm will ask for 86 confirmation before compiling." 87 :type 'boolean 88 :group 'vterm) 89 90 (defvar vterm-install-buffer-name " *Install vterm* " 91 "Name of the buffer used for compiling vterm-module.") 92 93 (defun vterm-module--cmake-is-available () 94 "Return t if cmake is available. 95 CMake is needed to build vterm, here we check that we can find 96 the executable." 97 98 (unless (executable-find "cmake") 99 (error "Vterm needs CMake to be compiled. Please, install CMake")) 100 t) 101 102 ;;;###autoload 103 (defun vterm-module-compile () 104 "Compile vterm-module." 105 (interactive) 106 (when (vterm-module--cmake-is-available) 107 (let* ((vterm-directory 108 (shell-quote-argument 109 ;; NOTE: This is a workaround to fix an issue with how the Emacs 110 ;; feature/native-comp branch changes the result of 111 ;; `(locate-library "vterm")'. See emacs-devel thread 112 ;; https://lists.gnu.org/archive/html/emacs-devel/2020-07/msg00306.html 113 ;; for a discussion. 114 (file-name-directory (locate-library "vterm.el" t)))) 115 (make-commands 116 (concat 117 "cd " vterm-directory "; \ 118 mkdir -p build; \ 119 cd build; \ 120 cmake -G 'Unix Makefiles' " 121 vterm-module-cmake-args 122 " ..; \ 123 make; \ 124 cd -")) 125 (buffer (get-buffer-create vterm-install-buffer-name))) 126 (pop-to-buffer buffer) 127 (compilation-mode) 128 (if (zerop (let ((inhibit-read-only t)) 129 (call-process "sh" nil buffer t "-c" make-commands))) 130 (message "Compilation of `emacs-libvterm' module succeeded") 131 (error "Compilation of `emacs-libvterm' module failed!"))))) 132 133 ;; If the vterm-module is not compiled yet, compile it 134 (unless (require 'vterm-module nil t) 135 (if (or vterm-always-compile-module 136 (y-or-n-p "Vterm needs `vterm-module' to work. Compile it now? ")) 137 (progn 138 (vterm-module-compile) 139 (require 'vterm-module)) 140 (error "Vterm will not work until `vterm-module' is compiled!"))) 141 142 ;;; Dependencies 143 144 ;; Generate this list with: 145 ;; awk -F\" '/bind_function*/ {print "(declare-function", $2, "\"vterm-module\")"}' vterm-module.c 146 (declare-function vterm--new "vterm-module") 147 (declare-function vterm--update "vterm-module") 148 (declare-function vterm--redraw "vterm-module") 149 (declare-function vterm--write-input "vterm-module") 150 (declare-function vterm--set-size "vterm-module") 151 (declare-function vterm--set-pty-name "vterm-module") 152 (declare-function vterm--get-pwd-raw "vterm-module") 153 (declare-function vterm--reset-point "vterm-module") 154 (declare-function vterm--get-icrnl "vterm-module") 155 156 (require 'subr-x) 157 (require 'find-func) 158 (require 'cl-lib) 159 (require 'term) 160 (require 'color) 161 (require 'compile) 162 (require 'face-remap) 163 (require 'tramp) 164 (require 'bookmark) 165 166 ;;; Options 167 168 (defcustom vterm-shell shell-file-name 169 "The shell that gets run in the vterm." 170 :type 'string 171 :group 'vterm) 172 173 (defcustom vterm-tramp-shells '(("docker" "/bin/sh")) 174 "The shell that gets run in the vterm for tramp. 175 176 `vterm-tramp-shells' has to be a list of pairs of the format: 177 \(TRAMP-METHOD SHELL)" 178 :type '(alist :key-type string :value-type string) 179 :group 'vterm) 180 181 (defcustom vterm-buffer-name "*vterm*" 182 "The basename used for vterm buffers. 183 This is the default name used when running `vterm' or 184 `vterm-other-window'. 185 186 With a numeric prefix argument to `vterm', the buffer name will 187 be the value of this variable followed by the number. For 188 example, with the numeric prefix argument 2, the buffer would be 189 named \"*vterm*<2>\"." 190 :type 'string 191 :group 'vterm) 192 193 (defcustom vterm-max-scrollback 1000 194 "Maximum 'scrollback' value. 195 196 The maximum allowed is 100000. This value can modified by 197 changing the SB_MAX variable in vterm-module.h and recompiling 198 the module." 199 :type 'number 200 :group 'vterm) 201 202 (defcustom vterm-min-window-width 80 203 "Minimum window width." 204 :type 'number 205 :group 'vterm) 206 207 (defcustom vterm-kill-buffer-on-exit t 208 "If not nil vterm buffers are killed when the attached process is terminated. 209 210 If `vterm-kill-buffer-on-exit' is set to t, when the process 211 associated to a vterm buffer quits, the buffer is killed. When 212 nil, the buffer will still be available as if it were in 213 `fundamental-mode'." 214 :type 'boolean 215 :group 'vterm) 216 217 (define-obsolete-variable-alias 'vterm-clear-scrollback 218 'vterm-clear-scrollback-when-clearing "0.0.1") 219 220 (define-obsolete-variable-alias 'vterm-use-vterm-prompt 221 'vterm-use-vterm-prompt-detection-method "0.0.1") 222 223 (defcustom vterm-clear-scrollback-when-clearing nil 224 "If not nil `vterm-clear' clears both screen and scrollback. 225 226 The scrollback is everything that is not current visible on 227 screen in vterm buffers. 228 229 If `vterm-clear-scrollback-when-clearing' is nil, `vterm-clear' 230 clears only the screen, so the scrollback is accessible moving 231 the point up." 232 :type 'number 233 :group 'vterm) 234 235 (defcustom vterm-keymap-exceptions 236 '("C-c" "C-x" "C-u" "C-g" "C-h" "C-l" "M-x" "M-o" "C-y" "M-y") 237 "Exceptions for `vterm-keymap'. 238 239 If you use a keybinding with a prefix-key, add that prefix-key to 240 this list. Note that after doing so that prefix-key cannot be sent 241 to the terminal anymore. 242 243 The mapping is done by the macro `vterm-define-key', and the 244 function `vterm--exclude-keys' removes the keybindings defined in 245 `vterm-keymap-exceptions'." 246 :type '(repeat string) 247 :set (lambda (sym val) 248 (set sym val) 249 (when (and (fboundp 'vterm--exclude-keys) 250 (boundp 'vterm-mode-map)) 251 (vterm--exclude-keys vterm-mode-map val))) 252 :group 'vterm) 253 254 (defcustom vterm-exit-functions nil 255 "List of functions called when a vterm process exits. 256 257 Each function is called with two arguments: the vterm buffer of 258 the process if any, and a string describing the event passed from 259 the sentinel. 260 261 This hook applies only to new vterms, created after setting this 262 value with `add-hook'. 263 264 Note that this hook will not work if another package like 265 `shell-pop' sets its own sentinel to the `vterm' process." 266 :type 'hook 267 :group 'vterm) 268 269 (make-obsolete-variable 'vterm-set-title-functions 270 "This variable was substituted by `vterm-buffer-name-string'." 271 "0.0.1") 272 273 (defcustom vterm-buffer-name-string nil 274 "Format string for the title of vterm buffers. 275 276 If `vterm-buffer-name-string' is nil, vterm will not set the 277 title of its buffers. If not nil, `vterm-buffer-name-string' has 278 to be a format control string (see `format') containing one 279 instance of %s which will be substituted with the string TITLE. 280 The argument TITLE is provided by the shell. This requires shell 281 side configuration. 282 283 For example, if `vterm-buffer-name-string' is set to \"vterm %s\", 284 and the shell properly configured to set TITLE=$(pwd), than vterm 285 buffers will be named \"vterm\" followed by the current path. 286 287 See URL http://tldp.org/HOWTO/Xterm-Title-4.html for additional 288 information on the how to configure the shell." 289 :type 'string 290 :group 'vterm) 291 292 (defcustom vterm-term-environment-variable "xterm-256color" 293 "TERM value for terminal." 294 :type 'string 295 :group 'vterm) 296 297 (defcustom vterm-environment nil 298 "List of extra environment variables to the vterm shell processes only. 299 300 demo: '(\"env1=v1\" \"env2=v2\")" 301 :type '(repeat string) 302 :group 'vterm) 303 304 305 (defcustom vterm-enable-manipulate-selection-data-by-osc52 nil 306 "Support OSC 52 MANIPULATE SELECTION DATA. 307 308 Support copy text to emacs kill ring and system clipboard by using OSC 52. 309 For example: send base64 encoded 'foo' to kill ring: echo -en '\e]52;c;Zm9v\a', 310 tmux can share its copy buffer to terminals by supporting osc52(like iterm2 311 xterm) you can enable this feature for tmux by : 312 set -g set-clipboard on #osc 52 copy paste share with iterm 313 set -ga terminal-overrides ',xterm*:XT:Ms=\E]52;%p1%s;%p2%s\007' 314 set -ga terminal-overrides ',screen*:XT:Ms=\E]52;%p1%s;%p2%s\007' 315 316 The clipboard querying/clearing functionality offered by OSC 52 is not 317 implemented here,And for security reason, this feature is disabled 318 by default." 319 :type 'boolean 320 :group 'vterm) 321 322 ;; TODO: Improve doc string, it should not point to the readme but it should 323 ;; be self-contained. 324 (defcustom vterm-eval-cmds '(("find-file" find-file) 325 ("message" message) 326 ("vterm-clear-scrollback" vterm-clear-scrollback)) 327 "Whitelisted Emacs functions that can be executed from vterm. 328 329 You can execute Emacs functions directly from vterm buffers. To do this, 330 you have to escape the name of the function and its arguments with \e]51;E. 331 332 See Message passing in README. 333 334 The function you want to execute has to be in `vterm-eval-cmds'. 335 336 `vterm-eval-cmds' has to be a list of pairs of the format: 337 \(NAME-OF-COMMAND-IN-SHELL EMACS-FUNCTION) 338 339 The need for an explicit map is to avoid arbitrary code execution." 340 :type '(alist :key-type string) 341 :group 'vterm) 342 343 (defcustom vterm-disable-underline nil 344 "When not-nil, underline text properties are ignored. 345 346 This means that vterm will render underlined text as if it was not 347 underlined." 348 :type 'boolean 349 :group 'vterm) 350 351 (defcustom vterm-disable-inverse-video nil 352 "When not-nil, inverse video text properties are ignored. 353 354 This means that vterm will render reversed video text as if it was not 355 such." 356 :type 'boolean 357 :group 'vterm) 358 359 (define-obsolete-variable-alias 'vterm-disable-bold-font 360 'vterm-disable-bold "0.0.1") 361 362 (defcustom vterm-disable-bold-font nil 363 "When not-nil, bold text properties are ignored. 364 365 This means that vterm will render bold with the default face weight." 366 :type 'boolean 367 :group 'vterm) 368 369 (defcustom vterm-set-bold-hightbright nil 370 "When not-nil, using hightbright colors for bolded text, see #549." 371 :type 'boolean 372 :group 'vterm) 373 374 (defcustom vterm-ignore-blink-cursor t 375 "When t,vterm will ignore request from application to turn on/off cursor blink. 376 377 If nil, cursor in any window may begin to blink or not blink because 378 `blink-cursor-mode`is a global minor mode in Emacs, 379 you can use `M-x blink-cursor-mode` to toggle." 380 :type 'boolean 381 :group 'vterm) 382 383 (defcustom vterm-copy-exclude-prompt t 384 "When not-nil, the prompt is not included by `vterm-copy-mode-done'." 385 :type 'boolean 386 :group 'vterm) 387 388 (defcustom vterm-use-vterm-prompt-detection-method t 389 "When not-nil, the prompt is detected through the shell. 390 391 Vterm needs to know where the shell prompt is to enable all the 392 available features. There are two supported ways to do this. 393 First, the shell can inform vterm on the location of the prompt. 394 This requires shell-side configuration: the escape code 51;A is 395 used to set the current directory and prompt location. This 396 detection method is the most-reliable. To use it, you have 397 to change your shell prompt to print 51;A. 398 399 The second method is using a regular expression. This method does 400 not require any shell-side configuration. See 401 `term-prompt-regexp', for more information." 402 :type 'boolean 403 :group 'vterm) 404 405 (defcustom vterm-bookmark-check-dir t 406 "When set to non-nil, also restore directory when restoring a vterm bookmark." 407 :type 'boolean 408 :group 'vterm) 409 410 ;;; Faces 411 412 (defface vterm-color-black 413 `((t :inherit term-color-black)) 414 "Face used to render black color code. 415 The foreground color is used as ANSI color 0 and the background 416 color is used as ANSI color 8." 417 :group 'vterm) 418 419 (defface vterm-color-red 420 `((t :inherit term-color-red)) 421 "Face used to render red color code. 422 The foreground color is used as ANSI color 1 and the background 423 color is used as ANSI color 9." 424 :group 'vterm) 425 426 (defface vterm-color-green 427 `((t :inherit term-color-green)) 428 "Face used to render green color code. 429 The foreground color is used as ANSI color 2 and the background 430 color is used as ANSI color 10." 431 :group 'vterm) 432 433 (defface vterm-color-yellow 434 `((t :inherit term-color-yellow)) 435 "Face used to render yellow color code. 436 The foreground color is used as ANSI color 3 and the background 437 color is used as ANSI color 11." 438 :group 'vterm) 439 440 (defface vterm-color-blue 441 `((t :inherit term-color-blue)) 442 "Face used to render blue color code. 443 The foreground color is used as ANSI color 4 and the background 444 color is used as ANSI color 12." 445 :group 'vterm) 446 447 (defface vterm-color-magenta 448 `((t :inherit term-color-magenta)) 449 "Face used to render magenta color code. 450 The foreground color is used as ansi color 5 and the background 451 color is used as ansi color 13." 452 :group 'vterm) 453 454 (defface vterm-color-cyan 455 `((t :inherit term-color-cyan)) 456 "Face used to render cyan color code. 457 The foreground color is used as ansi color 6 and the background 458 color is used as ansi color 14." 459 :group 'vterm) 460 461 (defface vterm-color-white 462 `((t :inherit term-color-white)) 463 "Face used to render white color code. 464 The foreground color is used as ansi color 7 and the background 465 color is used as ansi color 15." 466 :group 'vterm) 467 468 (defface vterm-color-underline 469 `((t :inherit default)) 470 "Face used to render cells with underline attribute. 471 Only foreground is used." 472 :group 'vterm) 473 474 (defface vterm-color-inverse-video 475 `((t :inherit default)) 476 "Face used to render cells with inverse video attribute. 477 Only background is used." 478 :group 'vterm) 479 480 ;;; Variables 481 482 (defvar vterm-color-palette 483 [vterm-color-black 484 vterm-color-red 485 vterm-color-green 486 vterm-color-yellow 487 vterm-color-blue 488 vterm-color-magenta 489 vterm-color-cyan 490 vterm-color-white] 491 "Color palette for the foreground and background.") 492 493 (defvar-local vterm--term nil 494 "Pointer to Term.") 495 496 (defvar-local vterm--process nil 497 "Shell process of current term.") 498 499 (defvar-local vterm--redraw-timer nil) 500 (defvar-local vterm--redraw-immididately nil) 501 (defvar-local vterm--linenum-remapping nil) 502 (defvar-local vterm--prompt-tracking-enabled-p nil) 503 (defvar-local vterm--insert-function (symbol-function #'insert)) 504 (defvar-local vterm--delete-char-function (symbol-function #'delete-char)) 505 (defvar-local vterm--delete-region-function (symbol-function #'delete-region)) 506 (defvar-local vterm--undecoded-bytes nil) 507 508 (defvar vterm-timer-delay 0.1 509 "Delay for refreshing the buffer after receiving updates from libvterm. 510 511 A larger delary improves performance when receiving large bursts 512 of data. If nil, never delay. The units are seconds.") 513 514 ;;; Keybindings 515 516 ;; We have many functions defined by vterm-define-key. Later, we will bind some 517 ;; of the functions. If the following is not evaluated during compilation, the compiler 518 ;; will complain that some functions are not defined (eg, vterm-send-C-c) 519 (eval-and-compile 520 (defmacro vterm-define-key (key) 521 "Define a command that sends KEY with modifiers C and M to vterm." 522 (declare (indent defun) 523 (doc-string 3)) 524 `(defun ,(intern (format "vterm-send-%s" key))() 525 ,(format "Sends %s to the libvterm." key) 526 (interactive) 527 (vterm-send-key ,(char-to-string (get-byte (1- (length key)) key)) 528 ,(let ((case-fold-search nil)) 529 (or (string-match-p "[A-Z]$" key) 530 (string-match-p "S-" key))) 531 ,(string-match-p "M-" key) 532 ,(string-match-p "C-" key)))) 533 534 (mapc (lambda (key) 535 (eval `(vterm-define-key ,key))) 536 (cl-loop for prefix in '("M-") 537 append (cl-loop for char from ?A to ?Z 538 for key = (format "%s%c" prefix char) 539 collect key))) 540 (mapc (lambda (key) 541 (eval `(vterm-define-key ,key))) 542 (cl-loop for prefix in '("C-" "M-" "C-S-") 543 append (cl-loop for char from ?a to ?z 544 for key = (format "%s%c" prefix char) 545 collect key)))) 546 547 ;; Function keys and most of C- and M- bindings 548 (defun vterm--exclude-keys (map exceptions) 549 "Remove EXCEPTIONS from the keys bound by `vterm-define-keys'. 550 551 Exceptions are defined by `vterm-keymap-exceptions'." 552 (mapc (lambda (key) 553 (define-key map (kbd key) nil)) 554 exceptions) 555 (mapc (lambda (key) 556 (define-key map (kbd key) #'vterm--self-insert)) 557 (cl-loop for number from 1 to 12 558 for key = (format "<f%i>" number) 559 unless (member key exceptions) 560 collect key)) 561 (mapc (lambda (key) 562 (define-key map (kbd key) 563 (intern (format "vterm-send-%s" key)))) 564 (cl-loop for prefix in '("M-") 565 append (cl-loop for char from ?A to ?Z 566 for key = (format "%s%c" prefix char) 567 unless (member key exceptions) 568 collect key))) 569 (mapc (lambda (key) 570 (define-key map (kbd key) 571 (intern (format "vterm-send-%s" key)))) 572 (cl-loop for prefix in '("C-" "M-" "C-S-" ) 573 append (cl-loop for char from ?a to ?z 574 for key = (format "%s%c" prefix char) 575 unless (member key exceptions) 576 collect key))) 577 (mapc (lambda (key) 578 (define-key map (kbd key) 'ignore)) 579 (cl-loop for prefix in '("C-M-" "C-M-S-") 580 append (cl-loop for char from ?a to ?z 581 for key = (format "%s%c" prefix char) 582 unless (member key exceptions) 583 collect key)))) 584 585 (defun vterm-xterm-paste (event) 586 "Handle xterm paste EVENT in vterm." 587 (interactive "e") 588 (with-temp-buffer 589 (xterm-paste event) 590 (kill-new (buffer-string))) 591 (vterm-yank)) 592 593 (defvar vterm-mode-map 594 (let ((map (make-sparse-keymap))) 595 (vterm--exclude-keys map vterm-keymap-exceptions) 596 (define-key map (kbd "C-]") #'vterm--self-insert) 597 (define-key map (kbd "M-<") #'vterm--self-insert) 598 (define-key map (kbd "M->") #'vterm--self-insert) 599 (define-key map [tab] #'vterm-send-tab) 600 (define-key map (kbd "TAB") #'vterm-send-tab) 601 (define-key map [backtab] #'vterm--self-insert) 602 (define-key map [backspace] #'vterm-send-backspace) 603 (define-key map (kbd "DEL") #'vterm-send-backspace) 604 (define-key map [delete] #'vterm-send-delete) 605 (define-key map [M-backspace] #'vterm-send-meta-backspace) 606 (define-key map (kbd "M-DEL") #'vterm-send-meta-backspace) 607 (define-key map [C-backspace] #'vterm-send-meta-backspace) 608 (define-key map [return] #'vterm-send-return) 609 (define-key map (kbd "RET") #'vterm-send-return) 610 (define-key map [C-left] #'vterm-send-M-b) 611 (define-key map [M-left] #'vterm-send-M-b) 612 (define-key map [C-right] #'vterm-send-M-f) 613 (define-key map [M-right] #'vterm-send-M-f) 614 (define-key map [C-up] #'vterm-send-up) 615 (define-key map [C-down] #'vterm-send-down) 616 (define-key map [left] #'vterm-send-left) 617 (define-key map [right] #'vterm-send-right) 618 (define-key map [up] #'vterm-send-up) 619 (define-key map [down] #'vterm-send-down) 620 (define-key map [prior] #'vterm-send-prior) 621 (define-key map [S-prior] #'scroll-down-command) 622 (define-key map [next] #'vterm-send-next) 623 (define-key map [S-next] #'scroll-up-command) 624 (define-key map [home] #'vterm--self-insert) 625 (define-key map [end] #'vterm--self-insert) 626 (define-key map [C-home] #'vterm--self-insert) 627 (define-key map [C-end] #'vterm--self-insert) 628 (define-key map [escape] #'vterm--self-insert) 629 (define-key map [remap yank] #'vterm-yank) 630 (define-key map [remap xterm-paste] #'vterm-xterm-paste) 631 (define-key map [remap yank-pop] #'vterm-yank-pop) 632 (define-key map [remap mouse-yank-primary] #'vterm-yank-primary) 633 (define-key map (kbd "C-SPC") #'vterm--self-insert) 634 (define-key map (kbd "S-SPC") #'vterm-send-space) 635 (define-key map (kbd "C-_") #'vterm--self-insert) 636 (define-key map (kbd "C-/") #'vterm-undo) 637 (define-key map (kbd "M-.") #'vterm-send-meta-dot) 638 (define-key map (kbd "M-,") #'vterm-send-meta-comma) 639 (define-key map (kbd "C-c C-y") #'vterm--self-insert) 640 (define-key map (kbd "C-c C-c") #'vterm-send-C-c) 641 (define-key map (kbd "C-c C-l") #'vterm-clear-scrollback) 642 (define-key map (kbd "C-l") #'vterm-clear) 643 (define-key map (kbd "C-\\") #'vterm-send-ctrl-slash) 644 (define-key map (kbd "C-c C-g") #'vterm-send-C-g) 645 (define-key map (kbd "C-c C-u") #'vterm-send-C-u) 646 (define-key map [remap self-insert-command] #'vterm--self-insert) 647 (define-key map (kbd "C-c C-r") #'vterm-reset-cursor-point) 648 (define-key map (kbd "C-c C-n") #'vterm-next-prompt) 649 (define-key map (kbd "C-c C-p") #'vterm-previous-prompt) 650 (define-key map (kbd "C-c C-t") #'vterm-copy-mode) 651 map)) 652 653 (defvar vterm-copy-mode-map 654 (let ((map (make-sparse-keymap))) 655 (define-key map (kbd "C-c C-t") #'vterm-copy-mode) 656 (define-key map [return] #'vterm-copy-mode-done) 657 (define-key map (kbd "RET") #'vterm-copy-mode-done) 658 (define-key map (kbd "C-c C-r") #'vterm-reset-cursor-point) 659 (define-key map (kbd "C-a") #'vterm-beginning-of-line) 660 (define-key map (kbd "C-e") #'vterm-end-of-line) 661 (define-key map (kbd "C-c C-n") #'vterm-next-prompt) 662 (define-key map (kbd "C-c C-p") #'vterm-previous-prompt) 663 map)) 664 665 666 ;;; Mode 667 668 (define-derived-mode vterm-mode fundamental-mode "VTerm" 669 "Major mode for vterm buffer." 670 (buffer-disable-undo) 671 (and (boundp 'display-line-numbers) 672 (let ((font-height (expt text-scale-mode-step text-scale-mode-amount))) 673 (setq vterm--linenum-remapping 674 (face-remap-add-relative 'line-number :height font-height)))) 675 (let ((process-environment (append vterm-environment 676 `(,(concat "TERM=" 677 vterm-term-environment-variable) 678 ,(concat "EMACS_VTERM_PATH=" 679 (file-name-directory (find-library-name "vterm"))) 680 "INSIDE_EMACS=vterm" 681 "LINES" 682 "COLUMNS") 683 process-environment)) 684 ;; TODO: Figure out why inhibit is needed for curses to render correctly. 685 (inhibit-eol-conversion nil) 686 (coding-system-for-read 'binary) 687 (process-adaptive-read-buffering nil) 688 (width (max (- (window-body-width) (vterm--get-margin-width)) 689 vterm-min-window-width))) 690 (setq vterm--term (vterm--new (window-body-height) 691 width vterm-max-scrollback 692 vterm-disable-bold-font 693 vterm-disable-underline 694 vterm-disable-inverse-video 695 vterm-ignore-blink-cursor 696 vterm-set-bold-hightbright)) 697 (setq buffer-read-only t) 698 (setq-local scroll-conservatively 101) 699 (setq-local scroll-margin 0) 700 (setq-local hscroll-margin 0) 701 (setq-local hscroll-step 1) 702 (setq-local truncate-lines t) 703 704 705 ;; Disable all automatic fontification 706 (setq-local font-lock-defaults '(nil t)) 707 708 (add-function :filter-return 709 (local 'filter-buffer-substring-function) 710 #'vterm--filter-buffer-substring) 711 (setq vterm--process 712 (make-process 713 :name "vterm" 714 :buffer (current-buffer) 715 :command 716 `("/bin/sh" "-c" 717 ,(format 718 "stty -nl sane %s erase ^? rows %d columns %d >/dev/null && exec %s" 719 ;; Some stty implementations (i.e. that of *BSD) do not 720 ;; support the iutf8 option. to handle that, we run some 721 ;; heuristics to work out if the system supports that 722 ;; option and set the arg string accordingly. This is a 723 ;; gross hack but FreeBSD doesn't seem to want to fix it. 724 ;; 725 ;; See: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=220009 726 (if (eq system-type 'berkeley-unix) "" "iutf8") 727 (window-body-height) 728 width (vterm--get-shell))) 729 ;; :coding 'no-conversion 730 :connection-type 'pty 731 :file-handler t 732 :filter #'vterm--filter 733 ;; The sentinel is needed if there are exit functions or if 734 ;; vterm-kill-buffer-on-exit is set to t. In this latter case, 735 ;; vterm--sentinel will kill the buffer 736 :sentinel (when (or vterm-exit-functions 737 vterm-kill-buffer-on-exit) 738 #'vterm--sentinel)))) 739 740 ;; Change major-mode is not allowed 741 ;; Vterm interfaces with an underlying process. Changing the major 742 ;; mode can break this, leading to segmentation faults. 743 (add-hook 'change-major-mode-hook 744 (lambda () (interactive) 745 (user-error "You cannot change major mode in vterm buffers")) nil t) 746 747 (vterm--set-pty-name vterm--term (process-tty-name vterm--process)) 748 (process-put vterm--process 'adjust-window-size-function 749 #'vterm--window-adjust-process-window-size) 750 ;; Support to compilation-shell-minor-mode 751 ;; Is this necessary? See vterm--compilation-setup 752 (setq next-error-function 'vterm-next-error-function) 753 (setq-local bookmark-make-record-function 'vterm--bookmark-make-record)) 754 755 (defun vterm--get-shell () 756 "Get the shell that gets run in the vterm." 757 (if (ignore-errors (file-remote-p default-directory)) 758 (with-parsed-tramp-file-name default-directory nil 759 (or (cadr (assoc method vterm-tramp-shells)) 760 vterm-shell)) 761 vterm-shell)) 762 763 (defun vterm--bookmark-make-record () 764 "Create a vterm bookmark. 765 766 Notes down the current directory and buffer name." 767 `(nil 768 (handler . vterm--bookmark-handler) 769 (thisdir . ,default-directory) 770 (buf-name . ,(buffer-name)) 771 (defaults . nil))) 772 773 774 ;;;###autoload 775 (defun vterm--bookmark-handler (bmk) 776 "Handler to restore a vterm bookmark BMK. 777 778 If a vterm buffer of the same name does not exist, the function will create a 779 new vterm buffer of the name. It also checks the current directory and sets 780 it to the bookmarked directory if needed." 781 (let* ((thisdir (bookmark-prop-get bmk 'thisdir)) 782 (buf-name (bookmark-prop-get bmk 'buf-name)) 783 (buf (get-buffer buf-name)) 784 (thismode (and buf (with-current-buffer buf major-mode)))) 785 ;; create if no such vterm buffer exists 786 (when (or (not buf) (not (eq thismode 'vterm-mode))) 787 (setq buf (generate-new-buffer buf-name)) 788 (with-current-buffer buf 789 (when vterm-bookmark-check-dir 790 (setq default-directory thisdir)) 791 (vterm-mode))) 792 ;; check the current directory 793 (with-current-buffer buf 794 (when (and vterm-bookmark-check-dir 795 (not (string-equal default-directory thisdir))) 796 (when vterm-copy-mode 797 (vterm-copy-mode-done nil)) 798 (vterm-insert (concat "cd " thisdir)) 799 (vterm-send-return))) 800 ;; set to this vterm buf 801 (set-buffer buf))) 802 803 (defun vterm--compilation-setup () 804 "Function to enable the option `compilation-shell-minor-mode' for vterm. 805 `'compilation-shell-minor-mode' would change the value of local 806 variable `next-error-function', so we should call this function in 807 `compilation-shell-minor-mode-hook'." 808 (when (eq major-mode 'vterm-mode) 809 (setq next-error-function 'vterm-next-error-function))) 810 811 (add-hook 'compilation-shell-minor-mode-hook #'vterm--compilation-setup) 812 813 ;;;###autoload 814 (defun vterm-next-error-function (n &optional reset) 815 "Advance to the next error message and visit the file where the error was. 816 This is the value of `next-error-function' in Compilation 817 buffers. Prefix arg N says how many error messages to move 818 forwards (or backwards, if negative). 819 820 Optional argument RESET clears all the errors." 821 (interactive "p") 822 (let* ((pt (point)) 823 (default-directory default-directory) 824 (pwd (vterm--get-pwd))) 825 (when pwd 826 (setq default-directory pwd)) 827 (goto-char pt) 828 (compilation-next-error-function n reset))) 829 830 ;;; Copy Mode 831 832 (define-minor-mode vterm-copy-mode 833 "Toggle `vterm-copy-mode'. 834 835 When `vterm-copy-mode' is enabled, the terminal will not display 836 additional output received from the underlying process and will 837 behave similarly to buffer in `fundamental-mode'. This mode is 838 typically used to copy text from vterm buffers. 839 840 A conventient way to exit `vterm-copy-mode' is with 841 `vterm-copy-mode-done', which copies the selected text and exit 842 `vterm-copy-mode'." 843 :group 'vterm 844 :lighter " VTermCopy" 845 :keymap vterm-copy-mode-map 846 (if (equal major-mode 'vterm-mode) 847 (if vterm-copy-mode 848 (progn ;enable vterm-copy-mode 849 (use-local-map nil) 850 (vterm-send-stop)) 851 (vterm-reset-cursor-point) 852 (use-local-map vterm-mode-map) 853 (vterm-send-start)) 854 (user-error "You cannot enable vterm-copy-mode outside vterm buffers"))) 855 856 (defun vterm-copy-mode-done (arg) 857 "Save the active region or line to the kill ring and exit `vterm-copy-mode'. 858 859 If a region is defined then that region is killed, with no region then 860 current line is killed from start to end. 861 862 The option `vterm-copy-exclude-prompt' controls if the prompt 863 should be included in a line copy. Using the universal prefix ARG 864 will invert `vterm-copy-exclude-prompt' for that call." 865 (interactive "P") 866 (unless vterm-copy-mode 867 (user-error "This command is effective only in vterm-copy-mode")) 868 (unless (use-region-p) 869 (goto-char (vterm--get-beginning-of-line)) 870 ;; Are we excluding the prompt? 871 (if (or (and vterm-copy-exclude-prompt (not arg)) 872 (and (not vterm-copy-exclude-prompt) arg)) 873 (goto-char (max (or (vterm--get-prompt-point) 0) 874 (vterm--get-beginning-of-line)))) 875 (set-mark (point)) 876 (goto-char (vterm--get-end-of-line))) 877 (kill-ring-save (region-beginning) (region-end)) 878 (vterm-copy-mode -1)) 879 880 ;;; Commands 881 882 (defun vterm--self-insert () 883 "Send invoking key to libvterm." 884 (interactive) 885 (when vterm--term 886 (let* ((modifiers (event-modifiers last-command-event)) 887 (shift (memq 'shift modifiers)) 888 (meta (memq 'meta modifiers)) 889 (ctrl (memq 'control modifiers)) 890 (raw-key (event-basic-type last-command-event)) 891 (ev-keys)) 892 (if input-method-function 893 (let ((inhibit-read-only t)) 894 (setq ev-keys (funcall input-method-function raw-key)) 895 (when (listp ev-keys) 896 (dolist (k ev-keys) 897 (when-let ((key (key-description (vector k)))) 898 (vterm-send-key key shift meta ctrl))))) 899 (when-let ((key (key-description (vector raw-key)))) 900 (vterm-send-key key shift meta ctrl)))))) 901 902 (defun vterm-send-key (key &optional shift meta ctrl accept-proc-output) 903 "Send KEY to libvterm with optional modifiers SHIFT, META and CTRL." 904 (deactivate-mark) 905 (when vterm--term 906 (let ((inhibit-redisplay t) 907 (inhibit-read-only t)) 908 (when (and (not (symbolp last-command-event)) shift (not meta) (not ctrl)) 909 (setq key (upcase key))) 910 (vterm--update vterm--term key shift meta ctrl) 911 (setq vterm--redraw-immididately t) 912 (when accept-proc-output 913 (accept-process-output vterm--process vterm-timer-delay nil t))))) 914 915 (defun vterm-send (key) 916 "Send KEY to libvterm. KEY can be anything `kbd' understands." 917 (let* ((event (listify-key-sequence (kbd key))) 918 (modifiers (event-modifiers event)) 919 (base (event-basic-type event))) 920 (vterm-send-key (char-to-string base) 921 (memq 'shift modifiers) 922 (memq 'meta modifiers) 923 (memq 'control modifiers)))) 924 925 (defun vterm-send-start () 926 "Output from the system is started when the system receives START." 927 (interactive) 928 (vterm-send-key "<start>")) 929 930 (defun vterm-send-stop () 931 "Output from the system is stopped when the system receives STOP." 932 (interactive) 933 (vterm-send-key "<stop>")) 934 935 (defun vterm-send-return () 936 "Send `C-m' to the libvterm." 937 (interactive) 938 (deactivate-mark) 939 (when vterm--term 940 (if (vterm--get-icrnl vterm--term) 941 (process-send-string vterm--process "\C-j") 942 (process-send-string vterm--process "\C-m")))) 943 944 (defun vterm-send-tab () 945 "Send `<tab>' to the libvterm." 946 (interactive) 947 (vterm-send-key "<tab>")) 948 949 (defun vterm-send-space () 950 "Send `<space>' to the libvterm." 951 (interactive) 952 (vterm-send-key " ")) 953 954 (defun vterm-send-backspace () 955 "Send `<backspace>' to the libvterm." 956 (interactive) 957 (vterm-send-key "<backspace>")) 958 959 (defun vterm-send-delete () 960 "Send `<delete>' to the libvterm." 961 (interactive) 962 (vterm-send-key "<delete>")) 963 964 (defun vterm-send-meta-backspace () 965 "Send `M-<backspace>' to the libvterm." 966 (interactive) 967 (vterm-send-key "<backspace>" nil t)) 968 969 (defun vterm-send-up () 970 "Send `<up>' to the libvterm." 971 (interactive) 972 (vterm-send-key "<up>")) 973 974 (defun vterm-send-down () 975 "Send `<down>' to the libvterm." 976 (interactive) 977 (vterm-send-key "<down>")) 978 979 (defun vterm-send-left () 980 "Send `<left>' to the libvterm." 981 (interactive) 982 (vterm-send-key "<left>")) 983 984 (defun vterm-send-right () 985 "Send `<right>' to the libvterm." 986 (interactive) 987 (vterm-send-key "<right>")) 988 989 (defun vterm-send-prior () 990 "Send `<prior>' to the libvterm." 991 (interactive) 992 (vterm-send-key "<prior>")) 993 994 (defun vterm-send-next () 995 "Send `<next>' to the libvterm." 996 (interactive) 997 (vterm-send-key "<next>")) 998 999 (defun vterm-send-meta-dot () 1000 "Send `M-.' to the libvterm." 1001 (interactive) 1002 (vterm-send-key "." nil t)) 1003 1004 (defun vterm-send-meta-comma () 1005 "Send `M-,' to the libvterm." 1006 (interactive) 1007 (vterm-send-key "," nil t)) 1008 1009 (defun vterm-send-ctrl-slash () 1010 "Send `C-\' to the libvterm." 1011 (interactive) 1012 (vterm-send-key "\\" nil nil t)) 1013 1014 (defun vterm-send-escape () 1015 "Send `<escape>' to the libvterm." 1016 (interactive) 1017 (vterm-send-key "<escape>")) 1018 1019 (defun vterm-clear-scrollback () 1020 "Send `<clear-scrollback>' to the libvterm." 1021 (interactive) 1022 (vterm-send-key "<clear_scrollback>")) 1023 1024 (defun vterm-clear (&optional arg) 1025 "Send `<clear>' to the libvterm. 1026 1027 `vterm-clear-scrollback' determines whether 1028 `vterm-clear' should also clear the scrollback or not. 1029 1030 This behavior can be altered by calling `vterm-clear' with a 1031 prefix argument ARG or with \\[universal-argument]." 1032 (interactive "P") 1033 (if (or 1034 (and vterm-clear-scrollback-when-clearing (not arg)) 1035 (and arg (not vterm-clear-scrollback-when-clearing))) 1036 (vterm-clear-scrollback)) 1037 (vterm-send-C-l)) 1038 1039 (defun vterm-undo () 1040 "Send `C-_' to the libvterm." 1041 (interactive) 1042 (vterm-send-key "_" nil nil t)) 1043 1044 (defun vterm-yank (&optional arg) 1045 "Yank (paste) text in vterm. 1046 1047 Argument ARG is passed to `yank'." 1048 (interactive "P") 1049 (deactivate-mark) 1050 (vterm-goto-char (point)) 1051 (let ((inhibit-read-only t)) 1052 (cl-letf (((symbol-function 'insert-for-yank) #'vterm-insert)) 1053 (yank arg)))) 1054 1055 (defun vterm-yank-primary () 1056 "Yank text from the primary selection in vterm." 1057 (interactive) 1058 (vterm-goto-char (point)) 1059 (let ((inhibit-read-only t) 1060 (primary (gui-get-primary-selection))) 1061 (cl-letf (((symbol-function 'insert-for-yank) #'vterm-insert)) 1062 (insert-for-yank primary)))) 1063 1064 (defun vterm-yank-pop (&optional arg) 1065 "Replaced text just yanked with the next entry in the kill ring. 1066 1067 Argument ARG is passed to `yank'" 1068 (interactive "p") 1069 (vterm-goto-char (point)) 1070 (let ((inhibit-read-only t) 1071 (yank-undo-function #'(lambda (_start _end) (vterm-undo)))) 1072 (cl-letf (((symbol-function 'insert-for-yank) #'vterm-insert)) 1073 (yank-pop arg)))) 1074 1075 (defun vterm-send-string (string &optional paste-p) 1076 "Send the string STRING to vterm. 1077 Optional argument PASTE-P paste-p." 1078 (when vterm--term 1079 (when paste-p 1080 (vterm--update vterm--term "<start_paste>" )) 1081 (dolist (char (string-to-list string)) 1082 (vterm--update vterm--term (char-to-string char))) 1083 (when paste-p 1084 (vterm--update vterm--term "<end_paste>"))) 1085 (setq vterm--redraw-immididately t) 1086 (accept-process-output vterm--process vterm-timer-delay nil t)) 1087 1088 (defun vterm-insert (&rest contents) 1089 "Insert the arguments, either strings or characters, at point. 1090 1091 Provide similar behavior as `insert' for vterm." 1092 (when vterm--term 1093 (vterm--update vterm--term "<start_paste>") 1094 (dolist (c contents) 1095 (if (characterp c) 1096 (vterm--update vterm--term (char-to-string c)) 1097 (dolist (char (string-to-list c)) 1098 (vterm--update vterm--term (char-to-string char))))) 1099 (vterm--update vterm--term "<end_paste>") 1100 (setq vterm--redraw-immididately t) 1101 (accept-process-output vterm--process vterm-timer-delay nil t))) 1102 1103 (defun vterm-delete-region (start end) 1104 "Delete the text between START and END for vterm. " 1105 (when vterm--term 1106 (if (vterm-goto-char start) 1107 (cl-loop repeat (- end start) do 1108 (vterm-send-key "<delete>" nil nil nil t)) 1109 (let ((inhibit-read-only nil)) 1110 (vterm--delete-region start end))))) 1111 1112 (defun vterm-goto-char (pos) 1113 "Set point to POSITION for vterm. 1114 1115 The return value is `t' when point moved successfully. 1116 It will reset to original position if it can't move there." 1117 (when (and vterm--term 1118 (vterm-cursor-in-command-buffer-p) 1119 (vterm-cursor-in-command-buffer-p pos)) 1120 (let ((moved t) 1121 (origin-point (point)) 1122 pt cursor-pos succ) 1123 (vterm-reset-cursor-point) 1124 (setq cursor-pos (point)) 1125 (setq pt cursor-pos) 1126 (while (and (> pos pt) moved) 1127 (vterm-send-key "<right>" nil nil nil t) 1128 (setq moved (not (= pt (point)))) 1129 (setq pt (point))) 1130 (setq pt (point)) 1131 (setq moved t) 1132 (while (and (< pos pt) moved) 1133 (vterm-send-key "<left>" nil nil nil t) 1134 (setq moved (not (= pt (point)))) 1135 (setq pt (point))) 1136 (setq succ (= pos (point))) 1137 (unless succ 1138 (vterm-goto-char cursor-pos) 1139 (goto-char origin-point)) 1140 succ))) 1141 1142 ;;; Internal 1143 1144 (defun vterm--delete-region(start end) 1145 "A wrapper for `delete-region'." 1146 (funcall vterm--delete-region-function start end)) 1147 1148 (defun vterm--insert(&rest content) 1149 "A wrapper for `insert'." 1150 (apply vterm--insert-function content)) 1151 1152 (defun vterm--delete-char(n &optional killflag) 1153 "A wrapper for `delete-char'." 1154 (funcall vterm--delete-char-function n killflag)) 1155 1156 (defun vterm--invalidate () 1157 "The terminal buffer is invalidated, the buffer needs redrawing." 1158 (if (and (not vterm--redraw-immididately) 1159 vterm-timer-delay) 1160 (unless vterm--redraw-timer 1161 (setq vterm--redraw-timer 1162 (run-with-timer vterm-timer-delay nil 1163 #'vterm--delayed-redraw (current-buffer)))) 1164 (vterm--delayed-redraw (current-buffer)) 1165 (setq vterm--redraw-immididately nil))) 1166 1167 (defun vterm-check-proc (&optional buffer) 1168 "Check if there is a running process associated to the vterm buffer BUFFER. 1169 1170 BUFFER can be either a buffer or the name of one." 1171 (let* ((buffer (get-buffer (or buffer (current-buffer)))) 1172 (proc (get-buffer-process buffer))) 1173 (and proc 1174 (memq (process-status proc) '(run stop open listen connect)) 1175 (buffer-local-value 'vterm--term buffer)))) 1176 1177 (defun vterm--delayed-redraw (buffer) 1178 "Redraw the terminal buffer. 1179 Argument BUFFER the terminal buffer." 1180 (when (buffer-live-p buffer) 1181 (with-current-buffer buffer 1182 (let ((inhibit-redisplay t) 1183 (inhibit-read-only t) 1184 (windows (get-buffer-window-list))) 1185 (setq vterm--redraw-timer nil) 1186 (when vterm--term 1187 (vterm--redraw vterm--term) 1188 (unless (zerop (window-hscroll)) 1189 (when (cl-member (selected-window) windows :test #'eq) 1190 (set-window-hscroll (selected-window) 0)))))))) 1191 1192 (defun vterm--selection (targets data) 1193 "OSC 52 Manipulate Selection Data. 1194 Search Manipulate Selection Data in 1195 https://invisible-island.net/xterm/ctlseqs/ctlseqs.html ." 1196 (when vterm-enable-manipulate-selection-data-by-osc52 1197 (unless (or (string-equal data "?") 1198 (string-empty-p data)) 1199 (let* ((inhibit-eol-conversion t) 1200 (decoded-data (decode-coding-string 1201 (base64-decode-string data) locale-coding-system)) 1202 (select-enable-clipboard select-enable-clipboard) 1203 (select-enable-primary select-enable-primary)) 1204 ;; https://invisible-island.net/xterm/ctlseqs/ctlseqs.html 1205 ;; c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 1206 ;; clipboard, primary, secondary, select, or cut buffers 0 through 7 1207 (unless (string-empty-p targets) 1208 (setq select-enable-clipboard nil) 1209 (setq select-enable-primary nil)) 1210 (when (cl-find ?c targets) 1211 (setq select-enable-clipboard t)) 1212 (when (cl-find ?p targets) 1213 (setq select-enable-primary t)) 1214 1215 (kill-new decoded-data) 1216 (message "kill-ring is updated by vterm OSC 52(Manipulate Selection Data)"))))) 1217 1218 ;;; Entry Points 1219 1220 ;;;###autoload 1221 (defun vterm (&optional arg) 1222 "Create an interactive Vterm buffer. 1223 Start a new Vterm session, or switch to an already active 1224 session. Return the buffer selected (or created). 1225 1226 With a nonnumeric prefix arg, create a new session. 1227 1228 With a string prefix arg, create a new session with arg as buffer name. 1229 1230 With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch 1231 to the session with that number, or create it if it doesn't 1232 already exist. 1233 1234 The buffer name used for Vterm sessions is determined by the 1235 value of `vterm-buffer-name'." 1236 (interactive "P") 1237 (vterm--internal #'pop-to-buffer-same-window arg)) 1238 1239 ;;;###autoload 1240 (defun vterm-other-window (&optional arg) 1241 "Create an interactive Vterm buffer in another window. 1242 Start a new Vterm session, or switch to an already active 1243 session. Return the buffer selected (or created). 1244 1245 With a nonnumeric prefix arg, create a new session. 1246 1247 With a string prefix arg, create a new session with arg as buffer name. 1248 1249 With a numeric prefix arg (as in `C-u 42 M-x vterm RET'), switch 1250 to the session with that number, or create it if it doesn't 1251 already exist. 1252 1253 The buffer name used for Vterm sessions is determined by the 1254 value of `vterm-buffer-name'." 1255 (interactive "P") 1256 (vterm--internal #'pop-to-buffer arg)) 1257 1258 (defun vterm--internal (pop-to-buf-fun &optional arg) 1259 (cl-assert vterm-buffer-name) 1260 (let ((buf (cond ((numberp arg) 1261 (get-buffer-create (format "%s<%d>" 1262 vterm-buffer-name 1263 arg))) 1264 ((stringp arg) (generate-new-buffer arg)) 1265 (arg (generate-new-buffer vterm-buffer-name)) 1266 (t 1267 (get-buffer-create vterm-buffer-name))))) 1268 (cl-assert (and buf (buffer-live-p buf))) 1269 (funcall pop-to-buf-fun buf) 1270 (with-current-buffer buf 1271 (unless (derived-mode-p 'vterm-mode) 1272 (vterm-mode))) 1273 buf)) 1274 1275 ;;; Internal 1276 1277 (defun vterm--flush-output (output) 1278 "Send the virtual terminal's OUTPUT to the shell." 1279 (process-send-string vterm--process output)) 1280 ;; Terminal emulation 1281 ;; This is the standard process filter for term buffers. 1282 ;; It emulates (most of the features of) a VT100/ANSI-style terminal. 1283 1284 ;; References: 1285 ;; [ctlseqs]: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 1286 ;; [ECMA-48]: https://www.ecma-international.org/publications/standards/Ecma-048.htm 1287 ;; [vt100]: https://vt100.net/docs/vt100-ug/chapter3.html 1288 1289 (defconst vterm-control-seq-regexp 1290 (concat 1291 ;; A control character, 1292 "\\(?:[\r\n\000\007\t\b\016\017]\\|" 1293 ;; a C1 escape coded character (see [ECMA-48] section 5.3 "Elements 1294 ;; of the C1 set"), 1295 "\e\\(?:[DM78c]\\|" 1296 ;; another Emacs specific control sequence for term.el, 1297 "AnSiT[^\n]+\n\\|" 1298 ;; another Emacs specific control sequence for vterm.el 1299 ;; printf "\e]%s\e\\" 1300 "\\][^\e]+\e\\\\\\|" 1301 ;; or an escape sequence (section 5.4 "Control Sequences"), 1302 "\\[\\([\x30-\x3F]*\\)[\x20-\x2F]*[\x40-\x7E]\\)\\)") 1303 "Regexp matching control sequences handled by term.el.") 1304 1305 (defconst vterm-control-seq-prefix-regexp 1306 "[\032\e]") 1307 1308 (defun vterm--filter (process input) 1309 "I/O Event. Feeds PROCESS's INPUT to the virtual terminal. 1310 1311 Then triggers a redraw from the module." 1312 (let ((inhibit-redisplay t) 1313 (inhibit-eol-conversion t) 1314 (inhibit-read-only t) 1315 (buf (process-buffer process)) 1316 (i 0) 1317 (str-length (length input)) 1318 decoded-substring 1319 funny) 1320 (when (buffer-live-p buf) 1321 (with-current-buffer buf 1322 ;; borrowed from term.el 1323 ;; Handle non-control data. Decode the string before 1324 ;; counting characters, to avoid garbling of certain 1325 ;; multibyte characters (https://github.com/akermu/emacs-libvterm/issues/394). 1326 ;; same bug of term.el https://debbugs.gnu.org/cgi/bugreport.cgi?bug=1006 1327 (when vterm--undecoded-bytes 1328 (setq input (concat vterm--undecoded-bytes input)) 1329 (setq vterm--undecoded-bytes nil) 1330 (setq str-length (length input))) 1331 (while (< i str-length) 1332 (setq funny (string-match vterm-control-seq-regexp input i)) 1333 (let ((ctl-end (if funny (match-end 0) 1334 (setq funny (string-match vterm-control-seq-prefix-regexp input i)) 1335 (if funny 1336 (setq vterm--undecoded-bytes 1337 (substring input funny)) 1338 (setq funny str-length)) 1339 ;; The control sequence ends somewhere 1340 ;; past the end of this string. 1341 (1+ str-length)))) 1342 (when (> funny i) 1343 ;; Handle non-control data. Decode the string before 1344 ;; counting characters, to avoid garbling of certain 1345 ;; multibyte characters (emacs bug#1006). 1346 (setq decoded-substring 1347 (decode-coding-string 1348 (substring input i funny) 1349 locale-coding-system t)) 1350 ;; Check for multibyte characters that ends 1351 ;; before end of string, and save it for 1352 ;; next time. 1353 (when (= funny str-length) 1354 (let ((partial 0) 1355 (count (length decoded-substring))) 1356 (while (and (< partial count) 1357 (eq (char-charset (aref decoded-substring 1358 (- count 1 partial))) 1359 'eight-bit)) 1360 (cl-incf partial)) 1361 (when (> count partial 0) 1362 (setq vterm--undecoded-bytes 1363 (substring decoded-substring (- partial))) 1364 (setq decoded-substring 1365 (substring decoded-substring 0 (- partial))) 1366 (cl-decf str-length partial) 1367 (cl-decf funny partial)))) 1368 (ignore-errors (vterm--write-input vterm--term decoded-substring)) 1369 (setq i funny)) 1370 (when (<= ctl-end str-length) 1371 (ignore-errors (vterm--write-input vterm--term (substring input i ctl-end)))) 1372 (setq i ctl-end))) 1373 (vterm--update vterm--term))))) 1374 1375 (defun vterm--sentinel (process event) 1376 "Sentinel of vterm PROCESS. 1377 Argument EVENT process event." 1378 (let ((buf (process-buffer process))) 1379 (run-hook-with-args 'vterm-exit-functions 1380 (if (buffer-live-p buf) buf nil) 1381 event) 1382 (if (and vterm-kill-buffer-on-exit (buffer-live-p buf)) 1383 (kill-buffer buf)))) 1384 1385 (defun vterm--text-scale-mode (&optional _argv) 1386 "Fix `line-number' height for scaled text." 1387 (and text-scale-mode 1388 (equal major-mode 'vterm-mode) 1389 (boundp 'display-line-numbers) 1390 (let ((height (expt text-scale-mode-step 1391 text-scale-mode-amount))) 1392 (when vterm--linenum-remapping 1393 (face-remap-remove-relative vterm--linenum-remapping)) 1394 (setq vterm--linenum-remapping 1395 (face-remap-add-relative 'line-number :height height)))) 1396 (window--adjust-process-windows)) 1397 1398 (advice-add #'text-scale-mode :after #'vterm--text-scale-mode) 1399 1400 (defun vterm--window-adjust-process-window-size (process windows) 1401 "Adjust width of window WINDOWS associated to process PROCESS. 1402 1403 `vterm-min-window-width' determines the minimum width allowed." 1404 ;; We want `vterm-copy-mode' to resemble a fundamental buffer as much as 1405 ;; possible. Hence, we must not call this function when the minor mode is 1406 ;; enabled, otherwise the buffer would be redrawn, messing around with the 1407 ;; position of the point. 1408 (unless vterm-copy-mode 1409 (let* ((size (funcall window-adjust-process-window-size-function 1410 process windows)) 1411 (width (car size)) 1412 (height (cdr size)) 1413 (inhibit-read-only t)) 1414 (setq width (- width (vterm--get-margin-width))) 1415 (setq width (max width vterm-min-window-width)) 1416 (when (and (processp process) 1417 (process-live-p process) 1418 (> width 0) 1419 (> height 0)) 1420 (vterm--set-size vterm--term height width) 1421 (cons width height))))) 1422 1423 (defun vterm--get-margin-width () 1424 "Get margin width of vterm buffer when `display-line-numbers-mode' is enabled." 1425 (let ((width 0) 1426 (max-line-num (+ (frame-height) vterm-max-scrollback))) 1427 (when (bound-and-true-p display-line-numbers) 1428 (setq width (+ width 4 1429 (string-width (number-to-string max-line-num))))) 1430 width)) 1431 1432 (defun vterm--delete-lines (line-num count &optional delete-whole-line) 1433 "Delete COUNT lines from LINE-NUM. 1434 If LINE-NUM is negative backward-line from end of buffer. 1435 If option DELETE-WHOLE-LINE is non-nil, then this command kills 1436 the whole line including its terminating newline" 1437 (save-excursion 1438 (when (vterm--goto-line line-num) 1439 (vterm--delete-region (point) (point-at-eol count)) 1440 (when (and delete-whole-line 1441 (looking-at "\n")) 1442 (vterm--delete-char 1))))) 1443 1444 (defun vterm--goto-line (n) 1445 "Go to line N and return true on success. 1446 If N is negative backward-line from end of buffer." 1447 (cond 1448 ((> n 0) 1449 (goto-char (point-min)) 1450 (eq 0 (forward-line (1- n)))) 1451 (t 1452 (goto-char (point-max)) 1453 (eq 0 (forward-line n))))) 1454 1455 (defun vterm--set-title (title) 1456 "Use TITLE to set the buffer name according to `vterm-buffer-name-string'." 1457 (when vterm-buffer-name-string 1458 (rename-buffer (format vterm-buffer-name-string title) t))) 1459 1460 (defun vterm--set-directory (path) 1461 "Set `default-directory' to PATH." 1462 (let ((dir (vterm--get-directory path))) 1463 (when dir (setq default-directory dir)))) 1464 1465 (defun vterm--get-directory (path) 1466 "Get normalized directory to PATH." 1467 (when path 1468 (let (directory) 1469 (if (string-match "^\\(.*?\\)@\\(.*?\\):\\(.*?\\)$" path) 1470 (progn 1471 (let ((user (match-string 1 path)) 1472 (host (match-string 2 path)) 1473 (dir (match-string 3 path))) 1474 (if (and (string-equal user user-login-name) 1475 (string-equal host (system-name))) 1476 (progn 1477 (when (file-directory-p dir) 1478 (setq directory (file-name-as-directory dir)))) 1479 (setq directory (file-name-as-directory (concat "/-:" path)))))) 1480 (when (file-directory-p path) 1481 (setq directory (file-name-as-directory path)))) 1482 directory))) 1483 1484 (defun vterm--get-pwd (&optional linenum) 1485 "Get working directory at LINENUM." 1486 (when vterm--term 1487 (let ((raw-pwd (vterm--get-pwd-raw 1488 vterm--term 1489 (or linenum (line-number-at-pos))))) 1490 (when raw-pwd 1491 (vterm--get-directory raw-pwd))))) 1492 1493 (defun vterm--get-color (index) 1494 "Get color by index from `vterm-color-palette'. 1495 Argument INDEX index of the terminal color. 1496 Special values for INDEX are: 1497 -11 foreground for cells with underline attribute, foreground of 1498 the `vterm-color-underline' face is used in this case. 1499 -12 background for cells with inverse video attribute, background 1500 of the `vterm-color-inverse-video' face is used in this case." 1501 (cond 1502 ((and (>= index 0) (< index 8)) 1503 (face-foreground 1504 (elt vterm-color-palette index) 1505 nil 'default)) 1506 ((and (>= index 8) (< index 16)) 1507 (face-background 1508 (elt vterm-color-palette (% index 8)) 1509 nil 'default)) 1510 ((= index -11) 1511 (face-foreground 'vterm-color-underline nil 'default)) 1512 ((= index -12) 1513 (face-background 'vterm-color-inverse-video nil 'default)) 1514 (t 1515 nil))) 1516 1517 (defun vterm--eval (str) 1518 "Check if string STR is `vterm-eval-cmds' and execute command. 1519 1520 All passed in arguments are strings and forwarded as string to 1521 the called functions." 1522 (let* ((parts (split-string-and-unquote str)) 1523 (command (car parts)) 1524 (args (cdr parts)) 1525 (f (assoc command vterm-eval-cmds))) 1526 (if f 1527 (apply (cadr f) args) 1528 (message "Failed to find command: %s" command)))) 1529 1530 ;; TODO: Improve doc string, it should not point to the readme but it should 1531 ;; be self-contained. 1532 (defun vterm--prompt-tracking-enabled-p () 1533 "Return t if tracking the prompt is enabled. 1534 1535 Prompt tracking need shell side configurations. 1536 1537 For zsh user, this is done by PROMPT=$PROMPT'%{$(vterm_prompt_end)%}'. 1538 1539 The shell send semantic information about where the prompt ends via properly 1540 escaped sequences to Emacs. 1541 1542 More information see `Shell-side configuration' and `Directory tracking' 1543 in README." 1544 (or vterm--prompt-tracking-enabled-p 1545 (save-excursion 1546 (setq vterm--prompt-tracking-enabled-p 1547 (next-single-property-change (point-min) 'vterm-prompt))))) 1548 1549 (defun vterm-next-prompt (n) 1550 "Move to end of Nth next prompt in the buffer." 1551 (interactive "p") 1552 (if (and vterm-use-vterm-prompt-detection-method 1553 (vterm--prompt-tracking-enabled-p)) 1554 (let ((pt (point)) 1555 (promp-pt (vterm--get-prompt-point))) 1556 (when promp-pt (goto-char promp-pt)) 1557 (cl-loop repeat (or n 1) do 1558 (setq pt (next-single-property-change (point-at-bol 2) 'vterm-prompt)) 1559 (when pt (goto-char pt)))) 1560 (term-next-prompt n))) 1561 1562 (defun vterm-previous-prompt (n) 1563 "Move to end of Nth previous prompt in the buffer." 1564 (interactive "p") 1565 (if (and vterm-use-vterm-prompt-detection-method 1566 (vterm--prompt-tracking-enabled-p)) 1567 (let ((pt (point)) 1568 (prompt-pt (vterm--get-prompt-point))) 1569 (when prompt-pt 1570 (goto-char prompt-pt) 1571 (when (> pt (point)) 1572 (setq n (1- (or n 1)))) 1573 (cl-loop repeat n do 1574 (setq pt (previous-single-property-change (1- (point)) 'vterm-prompt)) 1575 (when pt (goto-char (1- pt)))))) 1576 (term-previous-prompt n))) 1577 1578 (defun vterm--get-beginning-of-line () 1579 "Find the start of the line, bypassing line wraps." 1580 (save-excursion 1581 (beginning-of-line) 1582 (while (and (not (bobp)) 1583 (get-text-property (1- (point)) 'vterm-line-wrap)) 1584 (forward-char -1) 1585 (beginning-of-line)) 1586 (point))) 1587 1588 (defun vterm--get-end-of-line () 1589 "Find the start of the line, bypassing line wraps." 1590 (save-excursion 1591 (end-of-line) 1592 (while (get-text-property (point) 'vterm-line-wrap) 1593 (forward-char) 1594 (end-of-line)) 1595 (point))) 1596 1597 ;; TODO: Improve doc string, it should not point to the readme but it should 1598 ;; be self-contained. 1599 (defun vterm--get-prompt-point () 1600 "Get the position of the end of current prompt. 1601 More information see `vterm--prompt-tracking-enabled-p' and 1602 `Directory tracking and Prompt tracking'in README." 1603 (let ((end-point (vterm--get-end-of-line)) 1604 prompt-point) 1605 (save-excursion 1606 (if (and vterm-use-vterm-prompt-detection-method 1607 (vterm--prompt-tracking-enabled-p)) 1608 (if (get-text-property end-point 'vterm-prompt) 1609 end-point 1610 (setq prompt-point (previous-single-property-change end-point 'vterm-prompt)) 1611 (when prompt-point (setq prompt-point (1- prompt-point)))) 1612 (goto-char end-point) 1613 (if (search-backward-regexp term-prompt-regexp nil t) 1614 (goto-char (match-end 0)) 1615 (vterm--get-beginning-of-line)))))) 1616 1617 (defun vterm--at-prompt-p () 1618 "Return t if the cursor position is at shell prompt." 1619 (= (point) (or (vterm--get-prompt-point) 0))) 1620 1621 (defun vterm-cursor-in-command-buffer-p (&optional pt) 1622 "Check whether cursor in command buffer area." 1623 (save-excursion 1624 (vterm-reset-cursor-point) 1625 (let ((promp-pt (vterm--get-prompt-point)) 1626 eol) 1627 (when promp-pt 1628 (goto-char promp-pt) 1629 (setq eol (vterm--get-end-of-line)) 1630 (<= promp-pt (or pt (vterm--get-cursor-point)) eol))))) 1631 1632 (defun vterm-beginning-of-line () 1633 "Move point to the beginning of the line. 1634 1635 Move the point to the first character after the shell prompt on this line. 1636 If the point is already there, move to the beginning of the line. 1637 Effectively toggle between the two positions." 1638 (interactive) 1639 (if (vterm--at-prompt-p) 1640 (goto-char (vterm--get-beginning-of-line)) 1641 (goto-char (max (or (vterm--get-prompt-point) 0) 1642 (vterm--get-beginning-of-line))))) 1643 1644 (defun vterm-end-of-line () 1645 "Move point to the end of the line, bypassing line wraps." 1646 (interactive) 1647 (goto-char (vterm--get-end-of-line))) 1648 1649 (defun vterm-reset-cursor-point () 1650 "Make sure the cursor at the right position." 1651 (interactive) 1652 (when vterm--term 1653 (let ((inhibit-read-only t)) 1654 (vterm--reset-point vterm--term)))) 1655 1656 (defun vterm--get-cursor-point () 1657 "Get term cursor position." 1658 (when vterm--term 1659 (save-excursion 1660 (vterm-reset-cursor-point)))) 1661 1662 (defun vterm--remove-fake-newlines () 1663 "Filter out injected newlines were injected when rendering the terminal. 1664 1665 These newlines were tagged with 'vterm-line-wrap property so we 1666 can find them and remove them." 1667 (goto-char (point-min)) 1668 (let (fake-newline) 1669 (while (setq fake-newline (next-single-property-change (point) 1670 'vterm-line-wrap)) 1671 (goto-char fake-newline) 1672 (cl-assert (eq ?\n (char-after))) 1673 (let ((inhibit-read-only t)) 1674 (vterm--delete-char 1))))) 1675 1676 1677 (defun vterm--filter-buffer-substring (content) 1678 "Filter string CONTENT of fake/injected newlines." 1679 (with-temp-buffer 1680 (vterm--insert content) 1681 (vterm--remove-fake-newlines) 1682 (buffer-string))) 1683 1684 1685 (provide 'vterm) 1686 ;; Local Variables: 1687 ;; indent-tabs-mode: nil 1688 ;; End: 1689 ;;; vterm.el ends here