diff --git a/configure.ac b/configure.ac index db9de10c03c..724b2d6014c 100644 --- a/configure.ac +++ b/configure.ac @@ -3772,6 +3772,17 @@ fi AC_SUBST(LIBXML2_LIBS) AC_SUBST(LIBXML2_CFLAGS) +HAVE_LIBVTERM=no +AC_DEFINE(HAVE_LIBVTERM, 1, [do I need this]) +LIBVTERM_LIBS="-lvterm" + +AC_SUBST(LIBVTERM_LIBS) +AC_SUBST(LIBVTERM_CFLAGS) + + + + + BLESSMAIL_TARGET= LIBS_MAIL= if test ! "$with_mailutils"; then diff --git a/lisp/vterm.el b/lisp/vterm.el new file mode 100644 index 00000000000..a48900e21b4 --- /dev/null +++ b/lisp/vterm.el @@ -0,0 +1,234 @@ +(require 'subr-x) +(require 'cl-lib) +(require 'color) + +(defface vterm + '((t :inherit default)) + "Default face to use in Term mode." + :group 'vterm) + +(defface vterm-color-black + '((t :foreground "black" :background "black")) + "Face used to render black color code." + :group 'vterm) + +(defface vterm-color-red + '((t :foreground "red3" :background "red3")) + "Face used to render red color code." + :group 'vterm) + +(defface vterm-color-green + '((t :foreground "green3" :background "green3")) + "Face used to render green color code." + :group 'vterm) + +(defface vterm-color-yellow + '((t :foreground "yellow3" :background "yellow3")) + "Face used to render yellow color code." + :group 'vterm) + +(defface vterm-color-blue + '((t :foreground "blue2" :background "blue2")) + "Face used to render blue color code." + :group 'vterm) + +(defface vterm-color-magenta + '((t :foreground "magenta3" :background "magenta3")) + "Face used to render magenta color code." + :group 'vterm) + +(defface vterm-color-cyan + '((t :foreground "cyan3" :background "cyan3")) + "Face used to render cyan color code." + :group 'vterm) + +(defface vterm-color-white + '((t :foreground "white" :background "white")) + "Face used to render white color code." + :group 'vterm) + +(defcustom vterm-shell (getenv "SHELL") + "The shell that gets run in the vterm." + :type 'string + :group 'vterm) + +(defcustom vterm-display-method 'switch-to-buffer + "Default display method." + :type '(choice (const :tag "Display buffer." 'switch-to-buffer) + (const :tag "Pop to buffer." 'pop-to-buffer)) + :group 'vterm) + +(defcustom vterm-max-scrollback 1000 + "Maximum 'scrollback' value." + :type 'number + :group 'vterm) + +(defcustom vterm-keymap-exceptions '("C-c" "C-x" "C-u" "C-g" "C-h" "M-x" "M-o" "C-v" "M-v" "C-y") + "Exceptions for vterm-keymap. + + If you use a keybinding with a prefix-key, add that prefix-key to + this list. Note that after doing so that prefix-key cannot be sent + to the terminal anymore." + :type '(repeat string) + :group 'vterm) + +(defcustom vterm-set-title-hook nil + "Shell set title hook. + + those functions are called one by one, with 1 arguments. + `vterm-set-title-hook' should be a symbol, a hook variable. + The value of HOOK may be nil, a function, or a list of functions. + for example + (defun vterm--rename-buffer-as-title (title) + (rename-buffer (format \"vterm %s\" title))) + (add-hook 'vterm-set-title-hook 'vterm--rename-buffer-as-title) + + see http://tldp.org/HOWTO/Xterm-Title-4.html about how to set terminal title + for different shell. " + :type 'hook + :group 'vterm) + +(defvar vterm--term nil + "Pointer to Term.") +(make-variable-buffer-local 'vterm--term) + +(defvar vterm-buffer-name "*vterm*" + "Buffer name for vterm buffers.") + +(defvar vterm-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [tab] #'vterm--self-insert) + (define-key map [backspace] #'vterm--self-insert) + (define-key map [M-backspace] #'vterm--self-insert) + (define-key map [return] #'vterm--self-insert) + (define-key map [left] #'vterm--self-insert) + (define-key map [right] #'vterm--self-insert) + (define-key map [up] #'vterm--self-insert) + (define-key map [down] #'vterm--self-insert) + (define-key map [home] #'vterm--self-insert) + (define-key map [end] #'vterm--self-insert) + (define-key map [escape] #'vterm--self-insert) + (define-key map [remap self-insert-command] #'vterm--self-insert) + (define-key map [remap yank] #'vterm-yank) + (define-key map (kbd "C-c C-y") #'vterm--self-insert) + (define-key map (kbd "C-c C-c") #'vterm-send-ctrl-c) + map) + "Keymap for `vterm-mode'.") + +;; Function keys and most of C- and M- bindings +(mapcar (lambda (key) + (define-key vterm-mode-map (kbd key) #'vterm--self-insert)) + (append (cl-loop for number from 1 to 12 + collect (format "" number)) + (cl-loop for prefix in '("C-" "M-") + append (cl-loop for char from ?a to ?z + for key = (format "%s%c" prefix char) + unless (member key vterm-keymap-exceptions) + collect key)))) + +(define-derived-mode vterm-mode fundamental-mode "VTerm" + "Major mode for vterm buffer." + (buffer-disable-undo) + (setq vterm--term (vterm-new (window-body-height) + (window-body-width) + (vterm-make-process) + ;; vterm-max-scrollback + )) + (setq buffer-read-only t) + (setq-local scroll-conservatively 101) + (setq-local scroll-margin 0) + + (add-hook 'window-size-change-functions #'vterm-resize-window t t)) + +(defun vterm-make-process () + (let ((process-environment (append '("TERM=xterm") process-environment))) + (setq vterm--process + (make-process + :name "vterm" + :buffer (current-buffer) + :command `("/bin/sh" "-c" + ,(format "stty -nl sane iutf8 rows %d columns %d >/dev/null && exec %s" + (window-body-height) + (window-body-width) + vterm-shell)) + :coding 'no-conversion + :connection-type 'pty + :filter #'vterm-filter + :sentinel #'vterm-sentinel)))) + +(defun vterm-filter (process output) + "I/O Event. Feeds PROCESS's OUTPUT to the virtual terminal. + +Then triggers a redraw from the module." + (let ((inhibit-redisplay t) + (inhibit-read-only t)) + (with-current-buffer (process-buffer process) + (vterm-write-input vterm--term output) + (vterm-update vterm--term)))) + +(defun vterm-sentinel (process string) + (let ((buf (process-buffer proc))) + (when (buffer-live-p buf) + (kill-buffer buf)))) + +(defun vterm-resize-window (frame) + (let (bufs) + (dolist (win (window-list frame)) + (let ((buf (window-buffer win))) + (with-current-buffer buf + (when (and (eq major-mode 'vterm-mode) + (not (member buf bufs))) + (with-selected-window win + (push buf bufs) + (let ((height (window-body-height)) + (width (window-body-width)) + (inhibit-read-only t)) + (set-process-window-size (get-buffer-process buf) height width) + (vterm-set-size vterm--term height width))))))))) + +(defun vterm--self-insert () + "Sends invoking key to libvterm." + (interactive) + (let* ((modifiers (event-modifiers last-input-event)) + (shift (memq 'shift modifiers)) + (meta (memq 'meta modifiers)) + (ctrl (memq 'control modifiers))) + (when-let ((key (key-description (vector (event-basic-type last-input-event))))) + (vterm-send-key key shift meta ctrl)))) + +(defun vterm-send-key (key &optional shift meta ctrl) + "Sends KEY to libvterm with optional modifiers SHIFT, META and CTRL." + (let ((inhibit-redisplay t) + (inhibit-read-only t)) + (when (and shift (not meta) (not ctrl)) + (setq key (upcase key))) + (vterm-update vterm--term key shift meta ctrl))) + +(defun vterm--face-color-hex (face attr) + "Return the color of the FACE's ATTR as a hex string." + (apply #'color-rgb-to-hex (append (color-name-to-rgb (face-attribute face attr nil 'default)) '(2)))) + +(defun vterm-yank () + "Implementation of `yank' (paste) in vterm." + (interactive) + (vterm-send-string (current-kill 0))) + +(defun vterm-send-string (string) + "Send the string STRING to vterm." + (when vterm--term + (dolist (char (string-to-list string)) + (vterm-update vterm--term (char-to-string char) nil nil nil)))) + +;;;###autoload +(defun vterm (&optional arg) + "Display vterminal. If called with prefix arg open new terminal." + (interactive "P") + (let ((buffer (if arg + (generate-new-buffer vterm-buffer-name) + (get-buffer-create vterm-buffer-name)))) + (when (or arg (not (get-buffer-process buffer))) + (with-current-buffer buffer + (vterm-mode))) + (funcall vterm-display-method buffer))) + +(provide 'vterm) diff --git a/rust_src/build.rs b/rust_src/build.rs index 1021bbcd9cc..d1473f3c989 100644 --- a/rust_src/build.rs +++ b/rust_src/build.rs @@ -731,7 +731,8 @@ fn run_bindgen() { .rustified_enum("output_method") .rustified_enum("pvec_type") .rustified_enum("symbol_redirect") - .rustified_enum("syntaxcode"); + .rustified_enum("syntaxcode") + .rustified_enum("VTermProp"); if cfg!(target_os = "windows") { builder = builder diff --git a/rust_src/src/editfns.rs b/rust_src/src/editfns.rs index 6115dacf318..8ecfc9f9ea8 100644 --- a/rust_src/src/editfns.rs +++ b/rust_src/src/editfns.rs @@ -184,16 +184,20 @@ pub fn goto_char(position: LispObject) -> LispObject { if position.is_marker() { set_point_from_marker(position); } else if let Some(num) = position.as_fixnum() { - let mut cur_buf = ThreadState::current_buffer(); - let pos = clip_to_bounds(cur_buf.begv, num, cur_buf.zv); - let bytepos = unsafe { buf_charpos_to_bytepos(cur_buf.as_mut(), pos) }; - unsafe { set_point_both(pos, bytepos) }; + goto_pos(num); } else { wrong_type!(Qinteger_or_marker_p, position) }; position } +pub fn goto_pos(pos: EmacsInt) { + let mut cur_buf = ThreadState::current_buffer(); + let p = clip_to_bounds(cur_buf.begv, pos, cur_buf.zv); + let bytepos = unsafe { buf_charpos_to_bytepos(cur_buf.as_mut(), p) }; + unsafe { set_point_both(p, bytepos) }; +} + /// Return the byte position for character position POSITION. /// If POSITION is out of range, the value is nil. #[lisp_fn] diff --git a/rust_src/src/lib.rs b/rust_src/src/lib.rs index 8e67752d999..a58117584e2 100644 --- a/rust_src/src/lib.rs +++ b/rust_src/src/lib.rs @@ -119,6 +119,7 @@ mod threads; mod time; mod util; mod vectors; +mod vterm; mod window_configuration; mod windows; mod xml; diff --git a/rust_src/src/remacs_sys.rs b/rust_src/src/remacs_sys.rs index ff2511455c4..a05ee0e761b 100755 --- a/rust_src/src/remacs_sys.rs +++ b/rust_src/src/remacs_sys.rs @@ -102,7 +102,7 @@ extern "C" { ) -> Lisp_Object; pub static minibuf_prompt: LispObject; pub fn add_process_read_fd(fd: libc::c_int); - pub fn allocate_misc(t: Lisp_Misc_Type) -> LispObject; + // pub fn allocate_misc(t: Lisp_Misc_Type) -> LispObject; #[cfg(windows)] pub fn file_attributes_c(filename: LispObject, id_format: LispObject) -> LispObject; pub fn getloadaverage(loadavg: *mut libc::c_double, nelem: libc::c_int) -> libc::c_int; diff --git a/rust_src/src/vterm.rs b/rust_src/src/vterm.rs new file mode 100644 index 00000000000..60992e9ed4f --- /dev/null +++ b/rust_src/src/vterm.rs @@ -0,0 +1,668 @@ +//! libvterm facilities + +use remacs_macros::lisp_fn; + +use libc::{c_char, c_int, c_uchar, c_void, size_t}; + +use std::{cmp, mem}; + +use crate::{ + cmds::{forward_char, forward_line}, + data::Fset, + + editfns::{goto_pos, line_beginning_position, line_end_position}, + lisp::ExternalPtr, + lisp::{defsubr, LispObject}, + multibyte::LispStringRef, + obarray::intern, + remacs_sys::{ + allocate_vterm, color_to_rgb_string, get_col_offset, is_key, mysave_value, refresh_lines, + rgb_string_to_color, row_to_linenr, term_process_key, utf8_to_codepoint, vterm_output_read, + vterm_screen_callbacks, vterm_screen_set_callbacks, VtermScrollbackLine, + }, + + remacs_sys::{ + buf_charpos_to_bytepos, code_convert_string_norecord, del_range, make_string, send_process, + vterminal, EmacsInt, Fput_text_property, Lisp_Misc_Type, Qbold, Qcursor_type, Qface, + Qitalic, Qnil, Qnormal, Qt, Qutf_8, Qvtermp, STRING_BYTES, + }, + + // vterm + remacs_sys::{ + vterm_get_size, vterm_input_write, vterm_keyboard_key, vterm_keyboard_unichar, vterm_new, + vterm_obtain_screen, vterm_obtain_state, vterm_output_get_buffer_current, + vterm_screen_enable_altscreen, vterm_screen_flush_damage, vterm_screen_reset, + vterm_screen_set_damage_merge, vterm_set_size, vterm_set_utf8, vterm_state_get_cursorpos, + vterm_state_set_default_colors, vterm_state_set_palette_color, VTermColor, VTermDamageSize, + VTermKey, VTermModifier, VTermPos, VTermProp, VTermRect, VTermScreenCell, VTermState, + VTermValue, + }, + threads::ThreadState, + vectors::length, +}; + +pub const MaximumScrollback: usize = 1000; + +pub type LispVterminalRef = ExternalPtr; + +impl LispVterminalRef { + pub fn as_lisp_obj(self) -> LispObject { + unsafe { mem::transmute(self.as_ptr()) } + } + + pub fn get_size(self) -> (c_int, c_int) { + let mut height: i32 = 0; + let mut width: i32 = 0; + unsafe { vterm_get_size((*self).vt, &mut height, &mut width) }; + (height, width) + } + + pub fn set_size(self, rows: EmacsInt, cols: EmacsInt) { + unsafe { + vterm_set_size((*self).vt, rows as c_int, cols as c_int); + } + } +} + +// TODO: update when vterminal is a pseudovector +impl LispObject { + pub fn is_vterminal(self) -> bool { + self.as_misc().map_or(false, |m| { + m.get_type() == Lisp_Misc_Type::Lisp_Misc_Save_Value + }) + } + + pub fn as_vterminal(self) -> Option { + if self.is_vterminal() { + unsafe { Some(mem::transmute(mysave_value(self))) } + } else { + None + } + } + + pub fn as_vterminal_or_error(self) -> LispVterminalRef { + self.as_vterminal() + .unwrap_or_else(|| wrong_type!(Qvtermp, self)) + } +} + +impl From for LispVterminalRef { + fn from(o: LispObject) -> Self { + o.as_vterminal_or_error() + } +} + +impl From for LispObject { + fn from(v: LispVterminalRef) -> Self { + v.as_lisp_obj() + } +} + +impl From for Option { + fn from(o: LispObject) -> Self { + o.as_vterminal() + } +} + +#[lisp_fn(name = "vterm-new")] +pub fn vterm_new_lisp(rows: EmacsInt, cols: EmacsInt, process: LispObject) -> LispObject { + unsafe { + let val = allocate_vterm(); + + let mut term = val.as_vterminal().unwrap(); + + (*term).vt = vterm_new(rows as c_int, cols as c_int); + vterm_set_utf8((*term).vt, 1); + term_setup_colors(term); + (*term).vts = vterm_obtain_screen((*term).vt); + vterm_screen_reset((*term).vts, 1); + + vterm_screen_set_callbacks( + (*term).vts, + &mut vterm_screen_callbacks, + term.as_mut() as *mut libc::c_void, + ); + + vterm_screen_set_damage_merge((*term).vts, VTermDamageSize::VTERM_DAMAGE_SCROLL); + + vterm_screen_enable_altscreen((*term).vts, 1); + + (*term).sb_size = MaximumScrollback; + (*term).sb_current = 0; + (*term).sb_pending = 0; + + let s = mem::size_of::() * MaximumScrollback; + (*term).sb_buffer = libc::malloc(s as libc::size_t) as *mut *mut VtermScrollbackLine; + + (*term).invalid_start = 0; + (*term).invalid_end = rows as c_int; + + (*term).cursor.visible = true; + + (*term).process = process; + + val + } +} + +#[no_mangle] +pub unsafe extern "C" fn vterminal_refresh_screen(mut term: LispVterminalRef) { + if (*term).invalid_end >= (*term).invalid_start { + let (height, width) = term.get_size(); + + // vterminal height may have decreased before `invalid_end` reflects it. + let line_start = row_to_linenr(term.as_mut() as *mut vterminal, (*term).invalid_start); + + goto_pos(ThreadState::current_buffer().begv as EmacsInt); + forward_line(Some(line_start as EmacsInt)); + + vterminal_delete_lines(line_start, (*term).invalid_end - (*term).invalid_start); + refresh_lines( + term.as_mut() as *mut vterminal, + (*term).invalid_start, + (*term).invalid_end, + width, + ); + } + (*term).invalid_start = std::i32::MAX; + (*term).invalid_end = -1; +} + +pub unsafe extern "C" fn vterminal_adjust_topline(mut term: LispVterminalRef, added: i64) { + let (height, width) = term.get_size(); + + let buffer_lnum = line_beginning_position(None) as c_int; + + let state: *mut VTermState = vterm_obtain_state((*term).vt); + let mut pos: VTermPos = std::mem::zeroed(); + vterm_state_get_cursorpos(state, &mut pos); + + let cursor_lnum = row_to_linenr(term.as_mut() as *mut vterminal, pos.row); + + goto_pos(ThreadState::current_buffer().begv as EmacsInt); + forward_line(Some(cmp::min(cursor_lnum, buffer_lnum) as EmacsInt - 1)); + + let offset = get_col_offset(term.as_mut() as *mut vterminal, pos.row, pos.col); + forward_char(LispObject::from((pos.col - offset as c_int) as EmacsInt)); +} + +/// Refresh the scrollback of an invalidated terminal. +#[no_mangle] +pub unsafe extern "C" fn vterminal_refresh_scrollback(mut term: LispVterminalRef) { + let (height, width) = term.get_size(); + + let mut buffer_lnum: c_int; + + if (*term).sb_pending > 0 { + buffer_lnum = ThreadState::current_buffer().zv as c_int; + let del_cnt = buffer_lnum - height - (*term).sb_size as c_int + (*term).sb_pending; + + if del_cnt > 0 { + buffer_lnum = ThreadState::current_buffer().zv as c_int; + } + + let buf_index = buffer_lnum - height + 1; + goto_pos(ThreadState::current_buffer().begv as EmacsInt); + forward_line(Some(buf_index as EmacsInt)); + + refresh_lines( + term.as_mut() as *mut vterminal, + -(*term).sb_pending, + 0, + width, + ); + (*term).sb_pending = 0; + } + + let max_line_count = (*term).sb_current as c_int + height; + let buffer_lnum = line_beginning_position(None) as c_int; + + // Remove extra lines at the bottom + if buffer_lnum > max_line_count { + vterminal_delete_lines(max_line_count + 1, buffer_lnum - max_line_count + 1); + } +} + +#[no_mangle] +pub unsafe extern "C" fn vterminal_redraw(mut vterm: LispVterminalRef) { + if vterm.is_invalidated { + if (*vterm).cursor.visible { + Fset(Qcursor_type, Qt); + } else { + Fset(Qcursor_type, Qnil); + } + + let bufline_before = line_beginning_position(None); + vterminal_refresh_scrollback(vterm); + vterminal_refresh_screen(vterm); + + let line_added = line_beginning_position(None) - bufline_before; + vterminal_adjust_topline(vterm, line_added); + } + vterm.is_invalidated = false; +} + +#[lisp_fn(min = "1", name = "vterm-update")] +pub fn vterminal_update( + mut vterm: LispVterminalRef, + string: LispObject, + key: LispObject, + shift: bool, + meta: bool, + ctrl: bool, +) -> LispObject { + unsafe { + if string.is_not_nil() { + let mut utf8 = code_convert_string_norecord(string, Qutf_8, true).as_string_or_error(); + let mut len = STRING_BYTES(utf8.as_mut()) as usize; + + let mut v: Vec = Vec::with_capacity(len as usize); + + let key = libc::memcpy( + v.as_mut_ptr() as *mut c_void, + utf8.data_ptr() as *mut c_void, + len as libc::size_t, + ) as *const c_char; + + let mut modifier = VTermModifier::VTERM_MOD_NONE; + if shift { + modifier = modifier | VTermModifier::VTERM_MOD_SHIFT; + } + + if meta { + modifier = modifier | VTermModifier::VTERM_MOD_ALT; + } + + if ctrl { + modifier = modifier | VTermModifier::VTERM_MOD_CTRL; + } + + let is_key = |key: *const c_char, val: *const c_char, len: size_t| { + libc::memcmp(key as *mut c_void, val as *mut c_void, len) == 0 + }; + + if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_ENTER, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_TAB, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_BACKSPACE, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_ESCAPE, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_UP, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_DOWN, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_LEFT, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_RIGHT, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_INS, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_DEL, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_HOME, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_END, modifier); + } else if is_key(key, "".as_ptr() as *const c_char, len) { + vterm_keyboard_key((*vterm).vt, VTermKey::VTERM_KEY_PAGEUP, modifier); + } else if is_key(key, "SPC".as_ptr() as *const c_char, len) { + vterm_keyboard_unichar((*vterm).vt, ' ' as u32, modifier); + } else if len <= 4 { + let mut codepoint: libc::uint32_t = std::mem::zeroed(); + if utf8_to_codepoint(key as *const c_uchar, len, &mut codepoint) { + vterm_keyboard_unichar((*vterm).vt, codepoint, modifier); + } + } + } + + vterminal_flush_output(vterm); + vterminal_redraw(vterm); + LispObject::from(0) + } +} + +#[no_mangle] +pub unsafe extern "C" fn term_setup_colors(vterm: LispVterminalRef) { + let mut fg: VTermColor = std::mem::zeroed(); + let mut bg: VTermColor = std::mem::zeroed(); + let state: *mut VTermState = vterm_obtain_state((*vterm).vt); + + fg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm")), + LispObject::from(intern(":foreground")) + )); + bg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm")), + LispObject::from(intern(":background")) + )); + vterm_state_set_default_colors(state, &mut fg, &mut bg); + + fg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-black")), + LispObject::from(intern(":foreground")) + )); + vterm_state_set_palette_color(state, 0, &mut fg); + bg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-black")), + LispObject::from(intern(":background")) + )); + vterm_state_set_palette_color(state, 8, &mut bg); + + fg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-red")), + LispObject::from(intern(":foreground")) + )); + vterm_state_set_palette_color(state, 1, &mut fg); + bg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-red")), + LispObject::from(intern(":background")) + )); + vterm_state_set_palette_color(state, 9, &mut bg); + + fg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-green")), + LispObject::from(intern(":foreground")) + )); + vterm_state_set_palette_color(state, 2, &mut fg); + bg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-green")), + LispObject::from(intern(":background")) + )); + vterm_state_set_palette_color(state, 10, &mut bg); + + fg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-yellow")), + LispObject::from(intern(":foreground")) + )); + vterm_state_set_palette_color(state, 3, &mut fg); + bg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-yellow")), + LispObject::from(intern(":background")) + )); + vterm_state_set_palette_color(state, 11, &mut bg); + + fg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-blue")), + LispObject::from(intern(":foreground")) + )); + vterm_state_set_palette_color(state, 4, &mut fg); + bg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-blue")), + LispObject::from(intern(":background")) + )); + vterm_state_set_palette_color(state, 12, &mut bg); + + fg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-magenta")), + LispObject::from(intern(":foreground")) + )); + vterm_state_set_palette_color(state, 5, &mut fg); + bg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-magenta")), + LispObject::from(intern(":background")) + )); + vterm_state_set_palette_color(state, 13, &mut bg); + + fg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-cyan")), + LispObject::from(intern(":foreground")) + )); + vterm_state_set_palette_color(state, 6, &mut fg); + bg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-cyan")), + LispObject::from(intern(":background")) + )); + vterm_state_set_palette_color(state, 14, &mut bg); + + fg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-white")), + LispObject::from(intern(":foreground")) + )); + vterm_state_set_palette_color(state, 7, &mut fg); + bg = rgb_string_to_color(call!( + LispObject::from(intern("vterm--face-color-hex")), + LispObject::from(intern("vterm-color-white")), + LispObject::from(intern(":background")) + )); + vterm_state_set_palette_color(state, 15, &mut bg); +} + +#[no_mangle] +pub unsafe extern "C" fn vterminal_render_text( + buffer: *mut c_char, + len: c_int, + cell: *mut VTermScreenCell, +) -> LispObject { + let text = if len == 0 { + make_string("".as_ptr() as *mut c_char, len as isize) + } else { + make_string(buffer, len as isize) + }; + + let start = LispObject::from(0); + let end = LispObject::from(length(text) as EmacsInt); + let properties = list!( + LispObject::from(intern(":foreground")), + color_to_rgb_string((*cell).fg), + LispObject::from(intern(":background")), + color_to_rgb_string((*cell).bg), + LispObject::from(intern(":weight")), + if (*cell).attrs.bold() > 0 { + Qbold + } else { + Qnormal + }, + LispObject::from(intern(":underline")), + if (*cell).attrs.underline() > 0 { + Qt + } else { + Qnil + }, + LispObject::from(intern(":slant")), + if (*cell).attrs.italic() > 0 { + Qitalic + } else { + Qnormal + }, + LispObject::from(intern(":inverse-video")), + if (*cell).attrs.reverse() > 0 { + Qt + } else { + Qnil + }, + LispObject::from(intern(":strike-through")), + if (*cell).attrs.strike() > 0 { Qt } else { Qnil } + ); + + Fput_text_property(start, end, Qface, properties, text); + + text +} + +#[no_mangle] +pub unsafe extern "C" fn vterminal_flush_output(term: LispVterminalRef) { + let len = vterm_output_get_buffer_current((*term).vt); + if len > 0 { + let mut buffer: Vec = Vec::with_capacity(len); + let len = vterm_output_read((*term).vt, buffer.as_mut_ptr() as *mut c_char, len); + + let lisp_string = make_string(buffer.as_mut_ptr() as *mut c_char, len as isize); + + send_process( + (*term).process, + buffer.as_mut_ptr() as *mut c_char, + len as isize, + lisp_string, + ); + } +} + +#[lisp_fn(name = "vterm-write-input")] +pub fn vterminal_write_input(mut vterm: LispVterminalRef, mut input: LispStringRef) -> EmacsInt { + unsafe { + vterm_input_write( + (*vterm).vt, + input.sdata_ptr(), + STRING_BYTES(input.as_mut()) as usize + 1, + ); + vterm_screen_flush_damage((*vterm).vts); + } + + 0 as EmacsInt +} + +#[lisp_fn(name = "vterm-set-size")] +pub fn vterminal_set_size_lisp( + mut vterm: LispVterminalRef, + rows: EmacsInt, + cols: EmacsInt, +) -> LispObject { + unsafe { + let (height, width) = vterm.get_size(); + + if cols as c_int != width || rows as c_int != height { + vterm.set_size(rows, cols); + vterm_screen_flush_damage((*vterm).vts); + vterminal_redraw(vterm); + } + } + Qnil +} + +#[no_mangle] +pub extern "C" fn vterminal_delete_lines(linenum: c_int, count: c_int) { + let mut cur_buf = ThreadState::current_buffer(); + + goto_pos(cur_buf.begv as EmacsInt); + forward_line(Some(linenum as EmacsInt - 1)); + + unsafe { + del_range( + cur_buf.pt, + line_end_position(Some(count as EmacsInt)) as isize, + ); + } + + let pos = cur_buf.pt; + let pos_byte = unsafe { buf_charpos_to_bytepos(cur_buf.as_mut(), pos) }; + + if cur_buf.fetch_char(pos_byte) == '\n' as i32 { + unsafe { + del_range(pos, pos + 1); + } + } +} + +// vterm_screen_callbacks + +#[no_mangle] +pub unsafe extern "C" fn term_settermprop( + prop: VTermProp, + val: *mut VTermValue, + user_data: *mut c_void, +) -> c_int { + let term = user_data as *mut vterminal; + + match prop { + VTermProp::VTERM_PROP_ALTSCREEN => vterminal_invalidate_terminal(term, 0, 0), + VTermProp::VTERM_PROP_CURSORVISIBLE => { + vterminal_invalidate_terminal(term, (*term).cursor.row, (*term).cursor.row + 1); + (*term).cursor.visible = if (*val).boolean != 0 { true } else { false }; + } + _ => return 0, + } + + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn vterminal_invalidate_terminal( + term: *mut vterminal, + start_row: c_int, + end_row: c_int, +) { + if !(start_row == -1 || end_row == -1) { + (*term).invalid_start = cmp::min((*term).invalid_start, start_row); + (*term).invalid_end = cmp::max((*term).invalid_end, end_row); + } + (*term).is_invalidated = true; +} + +#[no_mangle] +pub unsafe extern "C" fn vterminal_damage(rect: VTermRect, data: *mut c_void) -> c_int { + vterminal_invalidate_terminal(data as *mut vterminal, rect.start_row, rect.end_row); + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn vterminal_moverect( + dest: VTermRect, + src: VTermRect, + data: *mut c_void, +) -> c_int { + vterminal_invalidate_terminal( + data as *mut vterminal, + cmp::min(dest.start_row, src.start_row), + cmp::max(dest.end_row, src.end_row), + ); + + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn vterminal_movecursor( + new: VTermPos, + old: VTermPos, + visible: c_int, + data: *mut libc::c_void, +) -> c_int { + let term: *mut vterminal = data as *mut vterminal; + (*term).cursor.row = new.row; + (*term).cursor.col = new.col; + vterminal_invalidate_terminal(term, old.row, old.row + 1); + vterminal_invalidate_terminal(term, new.row, new.row + 1); + + return 1; +} + +#[no_mangle] +pub unsafe extern "C" fn vterminal_resize( + rows: c_int, + cols: c_int, + data: *mut libc::c_void, +) -> c_int { + // can not use invalidate_terminal here + // when the window heigh decreased, + // the value of term->invalid_end can't bigger than window height + let term = data as *mut vterminal; + (*term).invalid_start = 0; + (*term).invalid_end = rows; + vterminal_invalidate_terminal(term, -1, -1); + 1 +} + +#[no_mangle] +pub extern "C" fn rust_syms_of_vterm() { + def_lisp_sym!(Qvtermp, "vtermp"); +} + +include!(concat!(env!("OUT_DIR"), "/vterm_exports.rs")); diff --git a/src/Makefile.in b/src/Makefile.in index 2f3fbfd3112..f0692f16979 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -228,6 +228,9 @@ IMAGEMAGICK_CFLAGS= @IMAGEMAGICK_CFLAGS@ LIBXML2_LIBS = @LIBXML2_LIBS@ LIBXML2_CFLAGS = @LIBXML2_CFLAGS@ +LIBVTERM_LIBS = @LIBVTERM_LIBS@ +LIBVTERM_CFLAGS = @LIBVTERM_CFLAGS@ + GETADDRINFO_A_LIBS = @GETADDRINFO_A_LIBS@ LCMS2_LIBS = @LCMS2_LIBS@ @@ -369,7 +372,7 @@ EMACS_CFLAGS=-Demacs $(MYCPPFLAGS) -I. -I$(srcdir) \ -I$(lib) -I$(top_srcdir)/lib \ $(C_SWITCH_MACHINE) $(C_SWITCH_SYSTEM) $(C_SWITCH_X_SITE) \ $(GNUSTEP_CFLAGS) $(CFLAGS_SOUND) $(RSVG_CFLAGS) $(IMAGEMAGICK_CFLAGS) \ - $(PNG_CFLAGS) $(LIBXML2_CFLAGS) $(DBUS_CFLAGS) \ + $(PNG_CFLAGS) $(LIBXML2_CFLAGS) $(LIBVTERM_CFLAGS) $(DBUS_CFLAGS) \ $(XRANDR_CFLAGS) $(XINERAMA_CFLAGS) $(XFIXES_CFLAGS) $(XDBE_CFLAGS) \ $(WEBKIT_CFLAGS) $(LCMS2_CFLAGS) \ $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \ @@ -392,7 +395,7 @@ ALL_OBJC_CFLAGS = $(EMACS_CFLAGS) \ ## be dumped as pure by dump-emacs. base_obj = dispnew.o frame.o scroll.o xdisp.o menu.o $(XMENU_OBJ) window.o \ charset.o coding.o category.o ccl.o character.o chartab.o bidi.o \ - $(CM_OBJ) term.o terminal.o xfaces.o $(XOBJ) $(GTK_OBJ) $(DBUS_OBJ) \ + $(CM_OBJ) vterm.o term.o terminal.o xfaces.o $(XOBJ) $(GTK_OBJ) $(DBUS_OBJ) \ emacs.o keyboard.o macros.o keymap.o sysdep.o \ buffer.o filelock.o insdel.o \ minibuf.o fileio.o dired.o \ @@ -501,7 +504,7 @@ LIBES = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(LIBX_BASE) $(LIBIMAGE) \ $(LIB_EACCESS) $(LIB_FDATASYNC) $(LIB_TIMER_TIME) $(DBUS_LIBS) \ $(LIB_EXECINFO) $(XRANDR_LIBS) $(XINERAMA_LIBS) $(XFIXES_LIBS) \ $(XDBE_LIBS) \ - $(LIBXML2_LIBS) $(LIBGPM) $(LIBS_SYSTEM) $(CAIRO_LIBS) \ + $(LIBXML2_LIBS) $(LIBVTERM_LIBS) $(LIBGPM) $(LIBS_SYSTEM) $(CAIRO_LIBS) \ $(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \ $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \ $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) $(GETADDRINFO_A_LIBS) $(LCMS2_LIBS) \ diff --git a/src/keymap.c b/src/keymap.c index 982cccea710..0ca4421b25c 100644 --- a/src/keymap.c +++ b/src/keymap.c @@ -94,11 +94,11 @@ static Lisp_Object get_keyelt (Lisp_Object, bool); void map_keymap_item (map_keymap_function_t, Lisp_Object, Lisp_Object, Lisp_Object, void *); void map_keymap_char_table_item (Lisp_Object, Lisp_Object, Lisp_Object); -static void -CHECK_VECTOR_OR_CHAR_TABLE (Lisp_Object x) -{ - CHECK_TYPE (VECTORP (x) || CHAR_TABLE_P (x), Qvector_or_char_table_p, x); -} +/* static void */ +/* CHECK_VECTOR_OR_CHAR_TABLE (Lisp_Object x) */ +/* { */ +/* CHECK_TYPE (VECTORP (x) || CHAR_TABLE_P (x), Qvector_or_char_table_p, x); */ +/* } */ /* This function is used for installing the standard key bindings diff --git a/src/lisp.h b/src/lisp.h index 3939bb50a1d..705868397c4 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -34,6 +34,8 @@ along with GNU Emacs. If not, see . */ #include #include +#include + INLINE_HEADER_BEGIN /* Define a TYPE constant ID as an externally visible name. Use like this: @@ -454,6 +456,20 @@ typedef EMACS_INT Lisp_Word; #endif + +typedef struct VtermScrollbackLine { + size_t cols; + VTermScreenCell cells[]; +} VtermScrollbackLine; + +typedef struct VtermCursor { + int row, col; + bool blinking; + bool visible; +} VtermCursor; + + + enum Lisp_Type { /* Symbol. XSYMBOL (object) points to a struct Lisp_Symbol. */ @@ -4898,6 +4914,31 @@ maybe_gc (void) Fgarbage_collect (); } +typedef struct vterminal { + Lisp_Object process; + + VTerm *vt; + VTermScreen *vts; + // buffer used to: + // - convert VTermScreen cell arrays into utf8 strings + // - receive data from libvterm as a result of key presses. + VtermScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm + size_t sb_current; // number of rows pushed to sb_buffer + size_t sb_size; // sb_buffer size + // "virtual index" that points to the first sb_buffer row that we need to + // push to the terminal buffer when refreshing the scrollback. When negative, + // it actually points to entries that are no longer in sb_buffer (because the + // window height has increased) and must be deleted from the terminal buffer + int sb_pending; + + int invalid_start, invalid_end; // invalid rows in libvterm screen + bool is_invalidated; + + VtermCursor cursor; + char *title; + bool is_title_changed; +} vterminal; + Lisp_Object funcall_lambda (Lisp_Object, ptrdiff_t, Lisp_Object *); bool backtrace_debug_on_exit (union specbinding *pdl); @@ -4910,6 +4951,49 @@ extern bool internal_equal_cons (Lisp_Object, Lisp_Object, enum equal_kind, int, extern bool internal_equal_misc (Lisp_Object, Lisp_Object, enum equal_kind, int, Lisp_Object); extern bool internal_equal_string (Lisp_Object, Lisp_Object, enum equal_kind, int, Lisp_Object); +extern Lisp_Object allocate_misc (enum Lisp_Misc_Type); + +extern Lisp_Object allocate_vterm (void); + +extern VTermScreenCallbacks vterm_screen_callbacks; + +extern int row_to_linenr(vterminal *term, int row); + +extern Lisp_Object refresh_lines (vterminal *term, int start_row, int end_row, int end_col); + +extern void vterm_sb_buffer_size (vterminal *term); + +extern unsigned char * vterm_process_input (unsigned char *key, VTermModifier modifier, ptrdiff_t len); + +extern size_t get_col_offset(vterminal *term, int row, int end_col); + +extern void term_process_key(vterminal *term, unsigned char *key, size_t len, + VTermModifier modifier); + +extern Lisp_Object vterminal_render_text (char *buffer, int len, VTermScreenCell *cell); + +extern int vterminal_movecursor(VTermPos new, VTermPos old, int visible, + void *data); + +extern int vterminal_resize(int rows, int cols, void *term); + +extern int vterminal_damage(VTermRect rect, void *data); + +extern int vterminal_moverect(VTermRect dest, VTermRect src, void *data); + +extern VTermColor rgb_string_to_color(Lisp_Object string); + +extern Lisp_Object color_to_rgb_string(VTermColor color); + +extern vterminal *mysave_value (Lisp_Object val); + +extern int term_settermprop(VTermProp prop, VTermValue *val, void *user_data); + +extern bool utf8_to_codepoint(const unsigned char buffer[4], const size_t len, + uint32_t *codepoint); + +extern bool is_key(unsigned char *key, size_t len, const char *key_description); + INLINE_HEADER_END #endif /* EMACS_LISP_H */ diff --git a/src/vterm.c b/src/vterm.c new file mode 100644 index 00000000000..b20f994546e --- /dev/null +++ b/src/vterm.c @@ -0,0 +1,403 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "vterm.h" + +#include "lisp.h" +#include "coding.h" + + +static void fetch_cell(vterminal *, int , int , VTermScreenCell *); +static bool compare_cells(VTermScreenCell *, VTermScreenCell *); + + +static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) { + vterminal *term = (vterminal *)data; + + if (!term->sb_size) { + return 0; + } + + // copy vterm cells into sb_buffer + size_t c = (size_t)cols; + VtermScrollbackLine *sbrow = NULL; + if (term->sb_current == term->sb_size) { + if (term->sb_buffer[term->sb_current - 1]->cols == c) { + // Recycle old row if it's the right size + sbrow = term->sb_buffer[term->sb_current - 1]; + } else { + free(term->sb_buffer[term->sb_current - 1]); + } + + // Make room at the start by shifting to the right. + memmove(term->sb_buffer + 1, term->sb_buffer, + sizeof(term->sb_buffer[0]) * (term->sb_current - 1)); + + } else if (term->sb_current > 0) { + // Make room at the start by shifting to the right. + memmove(term->sb_buffer + 1, term->sb_buffer, + sizeof(term->sb_buffer[0]) * term->sb_current); + } + + if (!sbrow) { + sbrow = malloc(sizeof(VtermScrollbackLine) + c * sizeof(sbrow->cells[0])); + sbrow->cols = c; + } + + // New row is added at the start of the storage buffer. + term->sb_buffer[0] = sbrow; + if (term->sb_current < term->sb_size) { + term->sb_current++; + } + + if (term->sb_pending < (int)term->sb_size) { + term->sb_pending++; + } + + memcpy(sbrow->cells, cells, sizeof(cells[0]) * c); + + return 1; +} + +/// Scrollback pop handler (from pangoterm). +/// +/// @param cols +/// @param cells VTerm state to update. +/// @param data Term +static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) { + vterminal *term = (vterminal *)data; + + if (!term->sb_current) { + return 0; + } + + if (term->sb_pending) { + term->sb_pending--; + } + + VtermScrollbackLine *sbrow = term->sb_buffer[0]; + term->sb_current--; + // Forget the "popped" row by shifting the rest onto it. + memmove(term->sb_buffer, term->sb_buffer + 1, + sizeof(term->sb_buffer[0]) * (term->sb_current)); + + size_t cols_to_copy = (size_t)cols; + if (cols_to_copy > sbrow->cols) { + cols_to_copy = sbrow->cols; + } + + // copy to vterm state + memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy); + size_t col; + for (col = cols_to_copy; col < (size_t)cols; col++) { + cells[col].chars[0] = 0; + cells[col].width = 1; + } + + free(sbrow); + + return 1; +} + +static size_t +codepoint_to_utf8_c(const uint32_t codepoint, unsigned char buffer[4]) { + if (codepoint <= 0x7F) { + buffer[0] = codepoint; + return 1; + } + if (codepoint >= 0x80 && codepoint <= 0x07FF) { + buffer[0] = 0xC0 | (codepoint >> 6); + buffer[1] = 0x80 | (codepoint & 0x3F); + return 2; + } + if (codepoint >= 0x0800 && codepoint <= 0xFFFF) { + buffer[0] = 0xE0 | (codepoint >> 12); + buffer[1] = 0x80 | ((codepoint >> 6) & 0x3F); + buffer[2] = 0x80 | (codepoint & 0x3F); + return 3; + } + + if (codepoint >= 0x10000 && codepoint <= 0x10FFFF) { + buffer[0] = 0xF0 | (codepoint >> 18); + buffer[1] = 0x80 | ((codepoint >> 12) & 0x3F); + buffer[2] = 0x80 | ((codepoint >> 6) & 0x3F); + buffer[3] = 0x80 | (codepoint & 0x3F); + return 4; + } + return 0; +} + +bool +utf8_to_codepoint(const unsigned char buffer[4], const size_t len, + uint32_t *codepoint) { + *codepoint = 0; + if (len == 1 && buffer[0] <= 0x7F) { + *codepoint = buffer[0]; + return true; + } + if (len == 2 && (buffer[0] >= 0xC0 && buffer[0] <= 0xDF) && + (buffer[1] >= 0x80 && buffer[1] <= 0xBF)) { + *codepoint = buffer[0] & 0x1F; + *codepoint = *codepoint << 6; + *codepoint = *codepoint | (buffer[1] & 0x3F); + return true; + } + if (len == 3 && (buffer[0] >= 0xE0 && buffer[0] <= 0xEF) && + (buffer[1] >= 0x80 && buffer[1] <= 0xBF) && + (buffer[2] >= 0x80 && buffer[2] <= 0xBF)) { + *codepoint = buffer[0] & 0xF; + *codepoint = *codepoint << 6; + *codepoint = *codepoint | (buffer[1] & 0x3F); + *codepoint = *codepoint << 6; + *codepoint = *codepoint | (buffer[2] & 0x3F); + return true; + } + if (len == 4 && (buffer[0] >= 0xF0 && buffer[0] <= 0xF7) && + (buffer[1] >= 0x80 && buffer[1] <= 0xBF) && + (buffer[2] >= 0x80 && buffer[2] <= 0xBF) && + (buffer[3] >= 0x80 && buffer[3] <= 0xBF)) { + *codepoint = buffer[0] & 7; + *codepoint = *codepoint << 6; + *codepoint = *codepoint | (buffer[1] & 0x3F); + *codepoint = *codepoint << 6; + *codepoint = *codepoint | (buffer[2] & 0x3F); + *codepoint = *codepoint << 6; + *codepoint = *codepoint | (buffer[3] & 0x3F); + return true; + } + + return false; +} + +int row_to_linenr(vterminal *term, int row) { + return row != INT_MAX ? row + (int)term->sb_current + 1 : INT_MAX; +} + +static void +fetch_cell(vterminal *term, int row, int col, VTermScreenCell *cell) { + if (row < 0) { + VtermScrollbackLine *sbrow = term->sb_buffer[-row - 1]; + if ((size_t)col < sbrow->cols) { + *cell = sbrow->cells[col]; + } else { + // fill the pointer with an empty cell + VTermColor fg, bg; + VTermState *state = vterm_obtain_state(term->vt); + vterm_state_get_default_colors(state, &fg, &bg); + + *cell = (VTermScreenCell){.chars = {0}, .width = 1, .bg = bg}; + } + } else { + vterm_screen_get_cell(term->vts, (VTermPos){.row = row, .col = col}, cell); + } +} + +static bool +is_eol(vterminal *term, int end_col, int row, int col) { + /* This cell is EOL if this and every cell to the right is black */ + if (row >= 0) { + VTermPos pos = {.row = row, .col = col}; + return vterm_screen_is_eol(term->vts, pos); + } + + VtermScrollbackLine *sbrow = term->sb_buffer[-row - 1]; + int c; + for (c = col; c < end_col && c < sbrow->cols;) { + if (sbrow->cells[c].chars[0]) { + return 0; + } + c += sbrow->cells[c].width; + } + return 1; +} + +size_t get_col_offset(vterminal *term, int row, int end_col) { + int col = 0; + size_t offset = 0; + unsigned char buf[4]; + + while (col < end_col) { + VTermScreenCell cell; + fetch_cell(term, row, col, &cell); + if (cell.chars[0]) { + if (cell.width > 1) { + offset += cell.width - 1; + } + } + col += cell.width; + } + return offset; +} + +static bool compare_cells(VTermScreenCell *a, VTermScreenCell *b) { + bool equal = true; + equal = equal && (a->fg.red == b->fg.red); + equal = equal && (a->fg.green == b->fg.green); + equal = equal && (a->fg.blue == b->fg.blue); + equal = equal && (a->bg.red == b->bg.red); + equal = equal && (a->bg.green == b->bg.green); + equal = equal && (a->bg.blue == b->bg.blue); + equal = equal && (a->attrs.bold == b->attrs.bold); + equal = equal && (a->attrs.underline == b->attrs.underline); + equal = equal && (a->attrs.italic == b->attrs.italic); + equal = equal && (a->attrs.reverse == b->attrs.reverse); + equal = equal && (a->attrs.strike == b->attrs.strike); + return equal; +} + +vterminal * +mysave_value (Lisp_Object val) { + struct Lisp_Save_Value *p = XSAVE_VALUE (val); + return p->data[0].pointer; +} + +Lisp_Object +allocate_vterm () { + vterminal *term = malloc(sizeof(vterminal)); + Lisp_Object val = allocate_misc (Lisp_Misc_Save_Value); + + struct Lisp_Save_Value *p = XSAVE_VALUE (val); + + p->save_type = SAVE_POINTER; + p->data[0].pointer = term; + + return val; +} + +Lisp_Object +refresh_lines (vterminal *term, int start_row, int end_row, int end_col) { + + int i, j; + + char buffer[((end_row - start_row + 1) * end_col) * 4]; + int length = 0; + VTermScreenCell cell; + VTermScreenCell lastCell; + fetch_cell(term, start_row, 0, &lastCell); + + int offset = 0; + for (i = start_row; i < end_row; i++) { + for (j = 0; j < end_col; j++) { + fetch_cell(term, i, j, &cell); + + if (!compare_cells(&cell, &lastCell)) { + Lisp_Object text = vterminal_render_text(buffer, length, &lastCell); + call1(intern ("insert"), text); + length = 0; + } + + lastCell = cell; + if (cell.chars[0] == 0) { + if (is_eol(term, end_col, i, j)) { + /* This cell is EOL if this and every cell to the right is black */ + break; + } + buffer[length] = ' '; + length++; + } else { + unsigned char bytes[4]; + size_t count = codepoint_to_utf8_c(cell.chars[0], bytes); + for (int k = 0; k < count; k++) { + buffer[length] = bytes[k]; + length++; + } + } + + if (cell.width > 1) { + int w = cell.width - 1; + offset += w; + j = j + w; + } + } + + buffer[length] = '\n'; + length++; + } + Lisp_Object text = vterminal_render_text(buffer, length, &lastCell); + call1(intern ("insert"), text); + + return text; +} + +VTermScreenCallbacks vterm_screen_callbacks = { + .damage = vterminal_damage, + .moverect = vterminal_moverect, + .movecursor = vterminal_movecursor, + .settermprop = term_settermprop, + .resize = vterminal_resize, + .sb_pushline = term_sb_push, + .sb_popline = term_sb_pop, + +}; + +bool +vterm_module_copy_string_contents (Lisp_Object lisp_str, char *buffer, ptrdiff_t *length) +{ + CHECK_STRING (lisp_str); + + Lisp_Object lisp_str_utf8 = ENCODE_UTF_8 (lisp_str); + ptrdiff_t raw_size = SBYTES (lisp_str_utf8); + ptrdiff_t required_buf_size = raw_size + 1; + + if (buffer == NULL) + { + *length = required_buf_size; + return true; + } + + if (*length < required_buf_size) + { + *length = required_buf_size; + xsignal0 (Qargs_out_of_range); + } + + *length = required_buf_size; + memcpy (buffer, SDATA (lisp_str_utf8), raw_size + 1); + + return true; +} + +void byte_to_hex(uint8_t byte, char *hex) { snprintf(hex, 3, "%.2X", byte); } + +Lisp_Object +color_to_rgb_string(VTermColor color) { + char buffer[8]; + buffer[0] = '#'; + buffer[7] = '\0'; + byte_to_hex(color.red, buffer + 1); + byte_to_hex(color.green, buffer + 3); + byte_to_hex(color.blue, buffer + 5); + + return make_string (buffer, 7); +} + +uint8_t hex_to_byte(char *hex) { return strtoul(hex, NULL, 16); } + +VTermColor +rgb_string_to_color(Lisp_Object string) { + VTermColor color; + ptrdiff_t len = 8; + char buffer[len]; + char hex[3]; + + vterm_module_copy_string_contents (string, buffer, &len); + + hex[0] = buffer[1]; + hex[1] = buffer[2]; + color.red = hex_to_byte(hex); + hex[0] = buffer[3]; + hex[1] = buffer[4]; + color.green = hex_to_byte(hex); + hex[0] = buffer[5]; + hex[1] = buffer[6]; + color.blue = hex_to_byte(hex); + + return color; +}; +