patch 8.0.0693: no terminal emulator support

Problem:    No terminal emulator support.  Cannot properly run commands in the
            GUI.  Cannot run a job interactively with an ssh connection.
Solution:   Very early implementation of the :terminal command.  Includes
            libvterm converted to ANSI C.  Many parts still missing.
This commit is contained in:
Bram Moolenaar
2017-07-07 11:54:15 +02:00
parent da5116da45
commit e4f25e4a8d
87 changed files with 11693 additions and 13 deletions

View File

@ -85,6 +85,7 @@ SRC_ALL = \
src/syntax.c \
src/tag.c \
src/term.c \
src/terminal.c \
src/term.h \
src/termlib.c \
src/ui.c \
@ -187,6 +188,7 @@ SRC_ALL = \
src/proto/syntax.pro \
src/proto/tag.pro \
src/proto/term.pro \
src/proto/terminal.pro \
src/proto/termlib.pro \
src/proto/ui.pro \
src/proto/undo.pro \
@ -194,6 +196,76 @@ SRC_ALL = \
src/proto/version.pro \
src/proto/winclip.pro \
src/proto/window.pro \
src/libvterm/.bzrignore \
src/libvterm/.gitignore \
src/libvterm/LICENSE \
src/libvterm/Makefile \
src/libvterm/README \
src/libvterm/tbl2inc_c.pl \
src/libvterm/vterm.pc.in \
src/libvterm/bin/unterm.c \
src/libvterm/bin/vterm-ctrl.c \
src/libvterm/bin/vterm-dump.c \
src/libvterm/doc/URLs \
src/libvterm/doc/seqs.txt \
src/libvterm/include/vterm.h \
src/libvterm/include/vterm_keycodes.h \
src/libvterm/src/encoding.c \
src/libvterm/src/encoding/DECdrawing.inc \
src/libvterm/src/encoding/DECdrawing.tbl \
src/libvterm/src/encoding/uk.inc \
src/libvterm/src/encoding/uk.tbl \
src/libvterm/src/keyboard.c \
src/libvterm/src/mouse.c \
src/libvterm/src/parser.c \
src/libvterm/src/pen.c \
src/libvterm/src/rect.h \
src/libvterm/src/screen.c \
src/libvterm/src/state.c \
src/libvterm/src/unicode.c \
src/libvterm/src/utf8.h \
src/libvterm/src/vterm.c \
src/libvterm/src/vterm_internal.h \
src/libvterm/t/02parser.test \
src/libvterm/t/03encoding_utf8.test \
src/libvterm/t/10state_putglyph.test \
src/libvterm/t/11state_movecursor.test \
src/libvterm/t/12state_scroll.test \
src/libvterm/t/13state_edit.test \
src/libvterm/t/14state_encoding.test \
src/libvterm/t/15state_mode.test \
src/libvterm/t/16state_resize.test \
src/libvterm/t/17state_mouse.test \
src/libvterm/t/18state_termprops.test \
src/libvterm/t/20state_wrapping.test \
src/libvterm/t/21state_tabstops.test \
src/libvterm/t/22state_save.test \
src/libvterm/t/25state_input.test \
src/libvterm/t/26state_query.test \
src/libvterm/t/27state_reset.test \
src/libvterm/t/28state_dbl_wh.test \
src/libvterm/t/29state_fallback.test \
src/libvterm/t/30pen.test \
src/libvterm/t/40screen_ascii.test \
src/libvterm/t/41screen_unicode.test \
src/libvterm/t/42screen_damage.test \
src/libvterm/t/43screen_resize.test \
src/libvterm/t/44screen_pen.test \
src/libvterm/t/45screen_protect.test \
src/libvterm/t/46screen_extent.test \
src/libvterm/t/47screen_dbl_wh.test \
src/libvterm/t/48screen_termprops.test \
src/libvterm/t/90vttest_01-movement-1.test \
src/libvterm/t/90vttest_01-movement-2.test \
src/libvterm/t/90vttest_01-movement-3.test \
src/libvterm/t/90vttest_01-movement-4.test \
src/libvterm/t/90vttest_02-screen-1.test \
src/libvterm/t/90vttest_02-screen-2.test \
src/libvterm/t/90vttest_02-screen-3.test \
src/libvterm/t/90vttest_02-screen-4.test \
src/libvterm/t/92lp1640917.test \
src/libvterm/t/harness.c \
src/libvterm/t/run-test.pl \
# source files for Unix only

View File

@ -101,6 +101,7 @@ DOCS = \
tabpage.txt \
tagsrch.txt \
term.txt \
terminal.txt \
tips.txt \
todo.txt \
uganda.txt \
@ -236,6 +237,7 @@ HTMLS = \
tabpage.html \
tagsrch.html \
term.html \
terminal.html \
tips.html \
todo.html \
uganda.html \

130
runtime/doc/terminal.txt Normal file
View File

@ -0,0 +1,130 @@
*terminal.txt* For Vim version 8.0. Last change: 2017 Jul 04
VIM REFERENCE MANUAL by Bram Moolenaar
Terminal window support *terminal*
WARNING: THIS IS ONLY PARTLY IMPLEMENTED, ANYTHING CAN STILL CHANGE
1. Basic use |terminal-use|
2. Remote testing |terminal-testing|
3. Debugging |terminal-debug|
{Vi does not have any of these commands}
==============================================================================
1. Basic use *terminal-use*
This feature is for running a terminal emulator in a Vim window. A job can be
started connected to the terminal emulator. For example, to run a shell: >
:term bash
Or to run a debugger: >
:term gdb vim
The job runs asynchronously from Vim, the window will be updated to show
output from the job, also while editing in any other window.
When the keyboard focus is in the terminal window, typed keys will be send to
the job. This uses a pty when possible.
Navigate between windows with CTRL-W commands (and mouse).
E.g. CTRL-W CTRL-W moves focus to the next window.
Option 'termkey'
Specify key for Vim command in terminal window. local to window.
Default is CTRL-W.
Option 'termsize'
Specify terminal size. Local to window.
When empty the terminal gets the size from the window.
When set (e.g., "24x80") the terminal size is fixed. If the window is smaller
only the top-left part is displayed. (TODO: scrolling?)
Syntax ~
*:ter* *:terminal*
:terminal[!] [command] Open a new terminal window.
If [command] is provided run it as a job and connect
the input and output to the terminal.
If [command] is not given the 'shell' option is used.
A new buffer will be created, using [command] or
'shell' as the name. If a buffer by this name already
exists a number is added in parenthesis.
E.g. if "gdb" exists the second terminal buffer will
use "gdb (1)".
The window can be closed, in which case the buffer
becomes hidden. The command will not be stopped. The
`:buffer` command can be used to turn the current
window into a terminal window, using the existing
buffer. If there are unsaved changes this fails, use
! to force, as usual.
Resizing ~
The size of the terminal can be in one of three modes:
1. The 'termsize' option is empty: The terminal size follows the window size.
The minimal size is 2 screen lines with 10 cells.
2. The 'termsize' option is "rows*cols", where "rows" is the minimal number of
screen rows and "cols" is the minial number of cells.
3. The 'termsize' option is "rowsXcols" (where the x is upper or lower case).
The terminal size is fixed to the specified number of screen lines and
cells. If the window is bigger there will be unused empty space.
If the window is smaller than the terminal size, only part of the terminal can
be seen (the lower-left part).
The |term_getsize()| function can be used to get the current size of the
terminal. |term_setsize()| can be used only when in the first or second mode,
not when 'termsize' is "rowsXcols".
==============================================================================
2. Remote testing *terminal-testing*
Most Vim tests execute a script inside Vim. For some tests this does not
work, running the test interferes with the code being tested. To avoid this
Vim is executed in a terminal window. The test sends keystrokes to it and
inspects the resulting screen state.
Functions ~
term_sendkeys() send keystrokes to a terminal
term_wait() wait for screen to be updated
term_scrape() inspect terminal screen
==============================================================================
3. Debugging *terminal-debug*
The Terminal debugging plugin can be used to debug a program with gdb and view
the source code in a Vim window. For example: >
:TermDebug vim
This opens three windows:
- A terminal window in which "gdb vim" is executed. Here you can directly
interact with gdb.
- A terminal window for the executed program. When "run" is used in gdb the
program I/O will happen in this window, so that it does not interfere with
controlling gdb.
- A normal Vim window used to show the source code. When gdb jumps to a
source file location this window will display the code, if possible. Values
of variables can be inspected, breakpoints set and cleared, etc.
This uses two terminal windows. To open the gdb window: >
:term gdb [arguments]
To open the terminal to run the tested program |term_open()| is used.
TODO
vim:tw=78:ts=8:ft=help:norl:

View File

@ -482,6 +482,11 @@ CClink = $(CC)
# Uncomment this when you do not want inter process communication.
#CONF_OPT_CHANNEL = --disable-channel
# TERMINAL - Terminal emulator support, :terminal command. Requires the
# channel feature.
# Uncomment this when you want terminal emulator support.
#CONF_OPT_TERMINAL = --enable-terminal
# MULTIBYTE - To edit multi-byte characters.
# Uncomment this when you want to edit a multibyte language.
# It's automatically enabled with normal features, GTK or IME support.
@ -598,6 +603,9 @@ CClink = $(CC)
# Use this with GCC to check for mistakes, unused arguments, etc.
#CFLAGS = -g -Wall -Wextra -Wshadow -Wmissing-prototypes -Wunreachable-code -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1
# Add -Wpedantic to find // comments and other C99 constructs.
# Better disable Perl and Python to avoid a lot of warnings.
#CFLAGS = -g -Wall -Wextra -Wshadow -Wmissing-prototypes -Wpedantic -Wunreachable-code -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1
#CFLAGS = -g -O2 -Wall -Wextra -Wmissing-prototypes -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 -DU_DEBUG
#PYTHON_CFLAGS_EXTRA = -Wno-missing-field-initializers
#MZSCHEME_CFLAGS_EXTRA = -Wno-unreachable-code -Wno-unused-parameter
@ -1371,6 +1379,13 @@ ALL_GUI_PRO = gui.pro gui_gtk.pro gui_motif.pro gui_xmdlg.pro gui_athena.pro gu
# }}}
TERM_DEPS = \
libvterm/include/vterm.h \
libvterm/include/vterm_keycodes.h \
libvterm/src/rect.h \
libvterm/src/utf8.h \
libvterm/src/vterm_internal.h
### Command to create dependencies based on #include "..."
### prototype headers are ignored due to -DPROTO, system
### headers #include <...> are ignored if we use the -MM option, as
@ -1560,6 +1575,7 @@ BASIC_SRC = \
syntax.c \
tag.c \
term.c \
terminal.c \
ui.c \
undo.c \
userfunc.c \
@ -1569,6 +1585,7 @@ BASIC_SRC = \
SRC = $(BASIC_SRC) \
$(GUI_SRC) \
$(TERM_SRC) \
$(HANGULIN_SRC) \
$(LUA_SRC) \
$(MZSCHEME_SRC) \
@ -1610,7 +1627,7 @@ ALL_SRC = $(BASIC_SRC) $(ALL_GUI_SRC) $(UNITTEST_SRC) $(EXTRA_SRC)
LINT_SRC = $(BASIC_SRC) $(GUI_SRC) $(HANGULIN_SRC) \
$(PYTHON_SRC) $(PYTHON3_SRC) $(TCL_SRC) \
$(WORKSHOP_SRC) $(WSDEBUG_SRC) \
$(NETBEANS_SRC) $(CHANNEL_SRC)
$(NETBEANS_SRC) $(CHANNEL_SRC) $(TERM_SRC)
#LINT_SRC = $(SRC)
#LINT_SRC = $(ALL_SRC)
#LINT_SRC = $(BASIC_SRC)
@ -1665,12 +1682,14 @@ OBJ_COMMON = \
objects/syntax.o \
objects/tag.o \
objects/term.o \
objects/terminal.o \
objects/ui.o \
objects/undo.o \
objects/userfunc.o \
objects/version.o \
objects/window.o \
$(GUI_OBJ) \
$(TERM_OBJ) \
$(LUA_OBJ) \
$(MZSCHEME_OBJ) \
$(PERL_OBJ) \
@ -1795,6 +1814,7 @@ PRO_AUTO = \
syntax.pro \
tag.pro \
term.pro \
terminal.pro \
termlib.pro \
ui.pro \
undo.pro \
@ -1848,7 +1868,7 @@ config auto/config.mk: auto/configure config.mk.in config.h.in
$(CONF_OPT_OUTPUT) $(CONF_OPT_GPM) $(CONF_OPT_WORKSHOP) \
$(CONF_OPT_FEAT) $(CONF_TERM_LIB) \
$(CONF_OPT_COMPBY) $(CONF_OPT_ACL) $(CONF_OPT_NETBEANS) \
$(CONF_OPT_CHANNEL) \
$(CONF_OPT_CHANNEL) $(CONF_OPT_TERMINAL) \
$(CONF_ARGS) $(CONF_OPT_MZSCHEME) $(CONF_OPT_PLTHOME) \
$(CONF_OPT_LUA) $(CONF_OPT_LUA_PREFIX) \
$(CONF_OPT_SYSMOUSE); \
@ -3228,6 +3248,9 @@ objects/tag.o: tag.c
objects/term.o: term.c
$(CCC) -o $@ term.c
objects/terminal.o: terminal.c $(TERM_DEPS)
$(CCC) -o $@ terminal.c
objects/ui.o: ui.c
$(CCC) -o $@ ui.c
@ -3255,6 +3278,34 @@ objects/channel.o: channel.c
Makefile:
@echo The name of the makefile MUST be "Makefile" (with capital M)!!!!
CCCTERM = $(CCC) -Ilibvterm/include -DINLINE=""
objects/term_encoding.o: libvterm/src/encoding.c $(TERM_DEPS)
$(CCCTERM) -o $@ libvterm/src/encoding.c
objects/term_keyboard.o: libvterm/src/keyboard.c $(TERM_DEPS)
$(CCCTERM) -o $@ libvterm/src/keyboard.c
objects/term_mouse.o: libvterm/src/mouse.c $(TERM_DEPS)
$(CCCTERM) -o $@ libvterm/src/mouse.c
objects/term_parser.o: libvterm/src/parser.c $(TERM_DEPS)
$(CCCTERM) -o $@ libvterm/src/parser.c
objects/term_pen.o: libvterm/src/pen.c $(TERM_DEPS)
$(CCCTERM) -o $@ libvterm/src/pen.c
objects/term_screen.o: libvterm/src/screen.c $(TERM_DEPS)
$(CCCTERM) -o $@ libvterm/src/screen.c
objects/term_state.o: libvterm/src/state.c $(TERM_DEPS)
$(CCCTERM) -o $@ libvterm/src/state.c
objects/term_unicode.o: libvterm/src/unicode.c $(TERM_DEPS)
$(CCCTERM) -o $@ libvterm/src/unicode.c
objects/term_vterm.o: libvterm/src/vterm.c $(TERM_DEPS)
$(CCCTERM) -o $@ libvterm/src/vterm.c
###############################################################################
### MacOS X installation
###
@ -3399,7 +3450,7 @@ objects/ex_cmds2.o: ex_cmds2.c vim.h auto/config.h feature.h os_unix.h \
objects/ex_docmd.o: ex_docmd.c vim.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h structs.h \
regexp.h gui.h gui_beval.h proto/gui_beval.pro alloc.h ex_cmds.h spell.h \
proto.h globals.h farsi.h arabic.h
proto.h globals.h farsi.h arabic.h ex_cmdidxs.h
objects/ex_eval.o: ex_eval.c vim.h auto/config.h feature.h os_unix.h auto/osdef.h \
ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \
gui_beval.h proto/gui_beval.pro alloc.h ex_cmds.h spell.h proto.h \

33
src/auto/configure vendored
View File

@ -655,6 +655,8 @@ X_PRE_LIBS
X_CFLAGS
XMKMF
xmkmfpath
TERM_OBJ
TERM_SRC
CHANNEL_OBJ
CHANNEL_SRC
NETBEANS_OBJ
@ -814,6 +816,7 @@ enable_cscope
enable_workshop
enable_netbeans
enable_channel
enable_terminal
enable_multibyte
enable_hangulinput
enable_xim
@ -1491,6 +1494,7 @@ Optional Features:
--enable-workshop Include Sun Visual Workshop support.
--disable-netbeans Disable NetBeans integration support.
--disable-channel Disable process communication support.
--enable-terminal Disable terminal emulation support.
--enable-multibyte Include multibyte editing support.
--enable-hangulinput Include Hangul input support.
--enable-xim Include XIM input support.
@ -7464,6 +7468,35 @@ if test "$enable_channel" = "yes"; then
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking --enable-terminal argument" >&5
$as_echo_n "checking --enable-terminal argument... " >&6; }
# Check whether --enable-terminal was given.
if test "${enable_terminal+set}" = set; then :
enableval=$enable_terminal; enable_terminal="yes"
fi
if test "$enable_terminal" = "yes"; then
if test "x$features" = "xtiny" -o "x$features" = "xsmall"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: cannot use terminal emulator with tiny or small features" >&5
$as_echo "cannot use terminal emulator with tiny or small features" >&6; }
enable_terminal="no"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
if test "$enable_terminal" = "yes"; then
$as_echo "#define FEAT_TERMINAL 1" >>confdefs.h
TERM_SRC="libvterm/src/encoding.c libvterm/src/keyboard.c libvterm/src/mouse.c libvterm/src/parser.c libvterm/src/pen.c libvterm/src/screen.c libvterm/src/state.c libvterm/src/unicode.c libvterm/src/vterm.c"
TERM_OBJ="objects/term_encoding.o objects/term_keyboard.o objects/term_mouse.o objects/term_parser.o objects/term_pen.o objects/term_screen.o objects/term_state.o objects/term_unicode.o objects/term_vterm.o"
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking --enable-multibyte argument" >&5
$as_echo_n "checking --enable-multibyte argument... " >&6; }
# Check whether --enable-multibyte was given.

View File

@ -431,6 +431,9 @@
/* Define if you want to include process communication. */
#undef FEAT_JOB_CHANNEL
/* Define if you want to include terminal emulator support. */
#undef FEAT_TERMINAL
/* Define default global runtime path */
#undef RUNTIME_GLOBAL

View File

@ -91,6 +91,8 @@ NETBEANS_SRC = @NETBEANS_SRC@
NETBEANS_OBJ = @NETBEANS_OBJ@
CHANNEL_SRC = @CHANNEL_SRC@
CHANNEL_OBJ = @CHANNEL_OBJ@
TERM_SRC = @TERM_SRC@
TERM_OBJ = @TERM_OBJ@
RUBY = @vi_cv_path_ruby@
RUBY_SRC = @RUBY_SRC@

View File

@ -2028,6 +2028,28 @@ if test "$enable_channel" = "yes"; then
AC_SUBST(CHANNEL_OBJ)
fi
AC_MSG_CHECKING(--enable-terminal argument)
AC_ARG_ENABLE(terminal,
[ --enable-terminal Disable terminal emulation support.],
[enable_terminal="yes"], )
if test "$enable_terminal" = "yes"; then
if test "x$features" = "xtiny" -o "x$features" = "xsmall"; then
AC_MSG_RESULT([cannot use terminal emulator with tiny or small features])
enable_terminal="no"
else
AC_MSG_RESULT(yes)
fi
else
AC_MSG_RESULT(no)
fi
if test "$enable_terminal" = "yes"; then
AC_DEFINE(FEAT_TERMINAL)
TERM_SRC="libvterm/src/encoding.c libvterm/src/keyboard.c libvterm/src/mouse.c libvterm/src/parser.c libvterm/src/pen.c libvterm/src/screen.c libvterm/src/state.c libvterm/src/unicode.c libvterm/src/vterm.c"
AC_SUBST(TERM_SRC)
TERM_OBJ="objects/term_encoding.o objects/term_keyboard.o objects/term_mouse.o objects/term_parser.o objects/term_pen.o objects/term_screen.o objects/term_state.o objects/term_unicode.o objects/term_vterm.o"
AC_SUBST(TERM_OBJ)
fi
AC_MSG_CHECKING(--enable-multibyte argument)
AC_ARG_ENABLE(multibyte,
[ --enable-multibyte Include multibyte editing support.], ,

View File

@ -5870,6 +5870,9 @@ f_has(typval_T *argvars, typval_T *rettv)
#ifdef FEAT_TERMGUICOLORS
"termguicolors",
#endif
#ifdef FEAT_TERMINAL
"terminal",
#endif
#ifdef TERMINFO
"terminfo",
#endif

View File

@ -25,12 +25,12 @@ static const unsigned short cmdidxs1[26] =
/* r */ 351,
/* s */ 370,
/* t */ 437,
/* u */ 472,
/* v */ 483,
/* w */ 501,
/* x */ 516,
/* y */ 525,
/* z */ 526
/* u */ 473,
/* v */ 484,
/* w */ 502,
/* x */ 517,
/* y */ 526,
/* z */ 527
};
/*
@ -60,7 +60,7 @@ static const unsigned char cmdidxs2[26][26] =
/* q */ { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* r */ { 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 18, 0, 0, 0, 0 },
/* s */ { 2, 6, 15, 0, 18, 22, 0, 24, 25, 0, 0, 28, 30, 34, 38, 40, 0, 48, 0, 49, 0, 61, 62, 0, 63, 0 },
/* t */ { 2, 0, 19, 0, 22, 23, 0, 24, 0, 25, 0, 26, 27, 28, 29, 30, 0, 31, 33, 0, 34, 0, 0, 0, 0, 0 },
/* t */ { 2, 0, 19, 0, 22, 24, 0, 25, 0, 26, 0, 27, 28, 29, 30, 31, 0, 32, 34, 0, 35, 0, 0, 0, 0, 0 },
/* u */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* v */ { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 9, 12, 0, 0, 0, 0, 15, 0, 16, 0, 0, 0, 0, 0 },
/* w */ { 2, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 0, 0, 8, 0, 9, 10, 0, 12, 0, 13, 14, 0, 0, 0, 0 },
@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][26] =
/* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
static const int command_count = 539;
static const int command_count = 540;

View File

@ -488,6 +488,9 @@ static void ex_folddo(exarg_T *eap);
#ifndef FEAT_PROFILE
# define ex_profile ex_ni
#endif
#ifndef FEAT_TERMINAL
# define ex_terminal ex_ni
#endif
/*
* Declare cmdnames[].

View File

@ -1267,6 +1267,13 @@
# undef FEAT_JOB_CHANNEL
#endif
/*
* +terminal ":terminal" command. Runs a terminal in a window.
*/
#if !defined(FEAT_JOB_CHANNEL) && defined(FEAT_TERMINAL)
# undef FEAT_TERMINAL
#endif
/*
* +signs Allow signs to be displayed to the left of text lines.
* Adds the ":sign" command.

13
src/libvterm/.bzrignore Normal file
View File

@ -0,0 +1,13 @@
.libs
*.lo
*.la
bin/*
!bin/*.c
pangoterm
t/test
t/suites.h
t/externs.h
t/harness
src/encoding/*.inc

18
src/libvterm/.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
*~
*.swp
tags
src/*.o
src/*.lo
src/encoding/*.inc
libvterm.la
bin/unterm
bin/vterm-ctrl
bin/vterm-dump
t/harness
t/harness.lo
t/harness.o
.libs/

23
src/libvterm/LICENSE Normal file
View File

@ -0,0 +1,23 @@
The MIT License
Copyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

145
src/libvterm/Makefile Normal file
View File

@ -0,0 +1,145 @@
ifeq ($(shell uname),Darwin)
LIBTOOL ?= glibtool
else
LIBTOOL ?= libtool
endif
ifneq ($(VERBOSE),1)
LIBTOOL +=--quiet
endif
# override CFLAGS +=-Wall -Iinclude -std=c99 -DINLINE="static inline" -DUSE_INLINE
override CFLAGS +=-Wall -Iinclude -std=c90 -Wpedantic -DINLINE=""
ifeq ($(shell uname),SunOS)
override CFLAGS +=-D__EXTENSIONS__ -D_XPG6 -D__XOPEN_OR_POSIX
endif
ifeq ($(DEBUG),1)
override CFLAGS +=-ggdb -DDEBUG
endif
ifeq ($(PROFILE),1)
override CFLAGS +=-pg
override LDFLAGS+=-pg
endif
CFILES=$(sort $(wildcard src/*.c))
HFILES=$(sort $(wildcard include/*.h))
OBJECTS=$(CFILES:.c=.lo)
LIBRARY=libvterm.la
BINFILES_SRC=$(sort $(wildcard bin/*.c))
BINFILES=$(BINFILES_SRC:.c=)
TBLFILES=$(sort $(wildcard src/encoding/*.tbl))
INCFILES=$(TBLFILES:.tbl=.inc)
HFILES_INT=$(sort $(wildcard src/*.h)) $(HFILES)
VERSION_MAJOR=0
VERSION_MINOR=0
VERSION_CURRENT=0
VERSION_REVISION=0
VERSION_AGE=0
VERSION=0
PREFIX=/usr/local
BINDIR=$(PREFIX)/bin
LIBDIR=$(PREFIX)/lib
INCDIR=$(PREFIX)/include
MANDIR=$(PREFIX)/share/man
MAN3DIR=$(MANDIR)/man3
all: $(LIBRARY) $(BINFILES)
$(LIBRARY): $(OBJECTS)
@echo LINK $@
@$(LIBTOOL) --mode=link --tag=CC $(CC) -rpath $(LIBDIR) -version-info $(VERSION_CURRENT):$(VERSION_REVISION):$(VERSION_AGE) -o $@ $^ $(LDFLAGS)
src/%.lo: src/%.c $(HFILES_INT)
@echo CC $<
@$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $<
src/encoding/%.inc: src/encoding/%.tbl
@echo TBL $<
@perl -CSD tbl2inc_c.pl $< >$@
src/encoding.lo: $(INCFILES)
bin/%: bin/%.c $(LIBRARY)
@echo CC $<
@$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $< -lvterm $(LDFLAGS)
t/harness.lo: t/harness.c $(HFILES)
@echo CC $<
@$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $<
t/harness: t/harness.lo $(LIBRARY)
@echo LINK $@
@$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
.PHONY: test
test: $(LIBRARY) t/harness
for T in `ls t/[0-9]*.test`; do echo "** $$T **"; perl t/run-test.pl $$T $(if $(VALGRIND),--valgrind) || exit 1; done
.PHONY: clean
clean:
$(LIBTOOL) --mode=clean rm -f $(OBJECTS) $(INCFILES)
$(LIBTOOL) --mode=clean rm -f t/harness.lo t/harness
$(LIBTOOL) --mode=clean rm -f $(LIBRARY) $(BINFILES)
.PHONY: install
install: install-inc install-lib install-bin
install-inc:
install -d $(DESTDIR)$(INCDIR)
install -m644 $(HFILES) $(DESTDIR)$(INCDIR)
install -d $(DESTDIR)$(LIBDIR)/pkgconfig
sed -e "s,@PREFIX@,$(PREFIX)," -e "s,@LIBDIR@,$(LIBDIR)," -e "s,@VERSION@,$(VERSION)," <vterm.pc.in >$(DESTDIR)$(LIBDIR)/pkgconfig/vterm.pc
install-lib: $(LIBRARY)
install -d $(DESTDIR)$(LIBDIR)
$(LIBTOOL) --mode=install install $(LIBRARY) $(DESTDIR)$(LIBDIR)/$(LIBRARY)
$(LIBTOOL) --mode=finish $(DESTDIR)$(LIBDIR)
install-bin: $(BINFILES)
install -d $(DESTDIR)$(BINDIR)
$(LIBTOOL) --mode=install install $(BINFILES) $(DESTDIR)$(BINDIR)/
# DIST CUT
VERSION=$(VERSION_MAJOR).$(VERSION_MINOR)
DISTDIR=libvterm-$(VERSION)
distdir: $(INCFILES)
mkdir __distdir
cp LICENSE __distdir
mkdir __distdir/src
cp src/*.c src/*.h __distdir/src
mkdir __distdir/src/encoding
cp src/encoding/*.inc __distdir/src/encoding
mkdir __distdir/include
cp include/*.h __distdir/include
mkdir __distdir/bin
cp bin/*.c __distdir/bin
mkdir __distdir/t
cp t/*.test t/harness.c t/run-test.pl __distdir/t
sed "s,@VERSION@,$(VERSION)," <vterm.pc.in >__distdir/vterm.pc.in
sed "/^# DIST CUT/Q" <Makefile >__distdir/Makefile
mv __distdir $(DISTDIR)
TARBALL=$(DISTDIR).tar.gz
dist: distdir
tar -czf $(TARBALL) $(DISTDIR)
rm -rf $(DISTDIR)
dist+bzr:
$(MAKE) dist VERSION=$(VERSION)+bzr`bzr revno`
distdir+bzr:
$(MAKE) distdir VERSION=$(VERSION)+bzr`bzr revno`

13
src/libvterm/README Normal file
View File

@ -0,0 +1,13 @@
This is a MODIFIED version of libvterm.
The original can be found:
On the original site (tar archive and Bazaar repository):
http://www.leonerd.org.uk/code/libvterm/
Cloned on Github:
https://github.com/neovim/libvterm
Modifications:
- Add a .gitignore file.
- Convert from C99 to C90.
- Other changes to support embedding in Vim.
-

287
src/libvterm/bin/unterm.c Normal file
View File

@ -0,0 +1,287 @@
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <unistd.h>
#include "vterm.h"
#define DEFINE_INLINES
#include "../src/utf8.h" /* fill_utf8 */
#define streq(a,b) (!strcmp(a,b))
static VTerm *vt;
static VTermScreen *vts;
static int cols;
static int rows;
static enum {
FORMAT_PLAIN,
FORMAT_SGR
} format = FORMAT_PLAIN;
static int col2index(VTermColor target)
{
int index;
for(index = 0; index < 256; index++) {
VTermColor col;
vterm_state_get_palette_color(NULL, index, &col);
if(col.red == target.red && col.green == target.green && col.blue == target.blue)
return index;
}
return -1;
}
static void dump_cell(const VTermScreenCell *cell, const VTermScreenCell *prevcell)
{
switch(format) {
case FORMAT_PLAIN:
break;
case FORMAT_SGR:
{
/* If all 7 attributes change, that means 7 SGRs max */
/* Each colour could consume up to 3 */
int sgr[7 + 2*3]; int sgri = 0;
if(!prevcell->attrs.bold && cell->attrs.bold)
sgr[sgri++] = 1;
if(prevcell->attrs.bold && !cell->attrs.bold)
sgr[sgri++] = 22;
if(!prevcell->attrs.underline && cell->attrs.underline)
sgr[sgri++] = 4;
if(prevcell->attrs.underline && !cell->attrs.underline)
sgr[sgri++] = 24;
if(!prevcell->attrs.italic && cell->attrs.italic)
sgr[sgri++] = 3;
if(prevcell->attrs.italic && !cell->attrs.italic)
sgr[sgri++] = 23;
if(!prevcell->attrs.blink && cell->attrs.blink)
sgr[sgri++] = 5;
if(prevcell->attrs.blink && !cell->attrs.blink)
sgr[sgri++] = 25;
if(!prevcell->attrs.reverse && cell->attrs.reverse)
sgr[sgri++] = 7;
if(prevcell->attrs.reverse && !cell->attrs.reverse)
sgr[sgri++] = 27;
if(!prevcell->attrs.strike && cell->attrs.strike)
sgr[sgri++] = 9;
if(prevcell->attrs.strike && !cell->attrs.strike)
sgr[sgri++] = 29;
if(!prevcell->attrs.font && cell->attrs.font)
sgr[sgri++] = 10 + cell->attrs.font;
if(prevcell->attrs.font && !cell->attrs.font)
sgr[sgri++] = 10;
if(prevcell->fg.red != cell->fg.red ||
prevcell->fg.green != cell->fg.green ||
prevcell->fg.blue != cell->fg.blue) {
int index = col2index(cell->fg);
if(index == -1)
sgr[sgri++] = 39;
else if(index < 8)
sgr[sgri++] = 30 + index;
else if(index < 16)
sgr[sgri++] = 90 + (index - 8);
else {
sgr[sgri++] = 38;
sgr[sgri++] = 5 | (1<<31);
sgr[sgri++] = index | (1<<31);
}
}
if(prevcell->bg.red != cell->bg.red ||
prevcell->bg.green != cell->bg.green ||
prevcell->bg.blue != cell->bg.blue) {
int index = col2index(cell->bg);
if(index == -1)
sgr[sgri++] = 49;
else if(index < 8)
sgr[sgri++] = 40 + index;
else if(index < 16)
sgr[sgri++] = 100 + (index - 8);
else {
sgr[sgri++] = 48;
sgr[sgri++] = 5 | (1<<31);
sgr[sgri++] = index | (1<<31);
}
}
if(!sgri)
break;
printf("\x1b[");
{
int i;
for(i = 0; i < sgri; i++)
printf(!i ? "%d" :
sgr[i] & (1<<31) ? ":%d" :
";%d",
sgr[i] & ~(1<<31));
}
printf("m");
}
break;
}
{
int i;
for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
char bytes[6];
bytes[fill_utf8(cell->chars[i], bytes)] = 0;
printf("%s", bytes);
}
}
}
static void dump_eol(const VTermScreenCell *prevcell)
{
switch(format) {
case FORMAT_PLAIN:
break;
case FORMAT_SGR:
if(prevcell->attrs.bold || prevcell->attrs.underline || prevcell->attrs.italic ||
prevcell->attrs.blink || prevcell->attrs.reverse || prevcell->attrs.strike ||
prevcell->attrs.font)
printf("\x1b[m");
break;
}
printf("\n");
}
void dump_row(int row)
{
VTermPos pos;
VTermScreenCell prevcell;
pos.row = row;
pos.col = 0;
memset(&prevcell, 0, sizeof(prevcell));
vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);
while(pos.col < cols) {
VTermScreenCell cell;
vterm_screen_get_cell(vts, pos, &cell);
dump_cell(&cell, &prevcell);
pos.col += cell.width;
prevcell = cell;
}
dump_eol(&prevcell);
}
static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
{
VTermScreenCell prevcell;
int col;
memset(&prevcell, 0, sizeof(prevcell));
vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);
for(col = 0; col < cols; col++) {
dump_cell(cells + col, &prevcell);
prevcell = cells[col];
}
dump_eol(&prevcell);
return 1;
}
static int screen_resize(int new_rows, int new_cols, void *user)
{
rows = new_rows;
cols = new_cols;
return 1;
}
static VTermScreenCallbacks cb_screen = {
NULL, /* damage */
NULL, /* moverect */
NULL, /* movecursor */
NULL, /* settermprop */
NULL, /* bell */
&screen_resize, /* resize */
&screen_sb_pushline, /* sb_pushline */
NULL, /* popline */
};
int main(int argc, char *argv[])
{
int opt;
const char *file;
int fd;
int len;
char buffer[1024];
int row;
rows = 25;
cols = 80;
while((opt = getopt(argc, argv, "f:l:c:")) != -1) {
switch(opt) {
case 'f':
if(streq(optarg, "plain"))
format = FORMAT_PLAIN;
else if(streq(optarg, "sgr"))
format = FORMAT_SGR;
else {
fprintf(stderr, "Unrecognised format '%s'\n", optarg);
exit(1);
}
break;
case 'l':
rows = atoi(optarg);
if(!rows)
rows = 25;
break;
case 'c':
cols = atoi(optarg);
if(!cols)
cols = 80;
break;
}
}
file = argv[optind++];
fd = open(file, O_RDONLY);
if(fd == -1) {
fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno));
exit(1);
}
vt = vterm_new(rows, cols);
vterm_set_utf8(vt, true);
vts = vterm_obtain_screen(vt);
vterm_screen_set_callbacks(vts, &cb_screen, NULL);
vterm_screen_reset(vts, 1);
while((len = read(fd, buffer, sizeof(buffer))) > 0) {
vterm_input_write(vt, buffer, len);
}
for(row = 0; row < rows; row++) {
dump_row(row);
}
close(fd);
vterm_free(vt);
return 0;
}

View File

@ -0,0 +1,362 @@
#define _XOPEN_SOURCE 500 /* strdup */
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define streq(a,b) (strcmp(a,b)==0)
#include <termios.h>
static char *getvalue(int *argip, int argc, char *argv[])
{
if(*argip >= argc) {
fprintf(stderr, "Expected an option value\n");
exit(1);
}
return argv[(*argip)++];
}
static int getchoice(int *argip, int argc, char *argv[], const char *options[])
{
const char *arg = getvalue(argip, argc, argv);
int value = -1;
while(options[++value])
if(streq(arg, options[value]))
return value;
fprintf(stderr, "Unrecognised option value %s\n", arg);
exit(1);
}
typedef enum {
OFF,
ON,
QUERY
} BoolQuery;
static BoolQuery getboolq(int *argip, int argc, char *argv[])
{
const char *choices[] = {"off", "on", "query", NULL};
return getchoice(argip, argc, argv, choices);
}
static char *helptext[] = {
"reset",
"s8c1t [off|on]",
"keypad [app|num]",
"screen [off|on|query]",
"cursor [off|on|query]",
"curblink [off|on|query]",
"curshape [block|under|bar|query]",
"mouse [off|click|clickdrag|motion]",
"altscreen [off|on|query]",
"bracketpaste [off|on|query]",
"icontitle [STR]",
"icon [STR]",
"title [STR]",
NULL
};
static bool seticanon(bool icanon, bool echo)
{
struct termios termios;
tcgetattr(0, &termios);
bool ret = (termios.c_lflag & ICANON);
if(icanon) termios.c_lflag |= ICANON;
else termios.c_lflag &= ~ICANON;
if(echo) termios.c_lflag |= ECHO;
else termios.c_lflag &= ~ECHO;
tcsetattr(0, TCSANOW, &termios);
return ret;
}
static void await_c1(int c1)
{
int c;
/* await CSI - 8bit or 2byte 7bit form */
bool in_esc = false;
while((c = getchar())) {
if(c == c1)
break;
if(in_esc && c == (char)(c1 - 0x40))
break;
if(!in_esc && c == 0x1b)
in_esc = true;
else
in_esc = false;
}
}
static char *read_csi()
{
unsigned char csi[32];
int i = 0;
await_c1(0x9B); /* CSI */
/* TODO: This really should be a more robust CSI parser
*/
for(; i < sizeof(csi)-1; i++) {
int c = csi[i] = getchar();
if(c >= 0x40 && c <= 0x7e)
break;
}
csi[++i] = 0;
/* TODO: returns longer than 32? */
return strdup((char *)csi);
}
static char *read_dcs()
{
unsigned char dcs[32];
bool in_esc = false;
int i;
await_c1(0x90);
for(i = 0; i < sizeof(dcs)-1; ) {
char c = getchar();
if(c == 0x9c) /* ST */
break;
if(in_esc && c == 0x5c)
break;
if(!in_esc && c == 0x1b)
in_esc = true;
else {
dcs[i++] = c;
in_esc = false;
}
}
dcs[++i] = 0;
return strdup((char *)dcs);
}
static void usage(int exitcode)
{
char **p;
fprintf(stderr, "Control a libvterm-based terminal\n"
"\n"
"Options:\n");
for(p = helptext; *p; p++)
fprintf(stderr, " %s\n", *p);
exit(exitcode);
}
static bool query_dec_mode(int mode)
{
char *s = NULL;
printf("\x1b[?%d$p", mode);
do {
int reply_mode, reply_value;
char reply_cmd;
if(s)
free(s);
s = read_csi();
/* expect "?" mode ";" value "$y" */
/* If the sscanf format string ends in a literal, we can't tell from
* its return value if it matches. Hence we'll %c the cmd and check it
* explicitly
*/
if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3)
continue;
if(reply_cmd != 'y')
continue;
if(reply_mode != mode)
continue;
free(s);
if(reply_value == 1 || reply_value == 3)
return true;
if(reply_value == 2 || reply_value == 4)
return false;
printf("Unrecognised reply to DECRQM: %d\n", reply_value);
return false;
} while(1);
}
static void do_dec_mode(int mode, BoolQuery val, const char *name)
{
switch(val) {
case OFF:
case ON:
printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l');
break;
case QUERY:
if(query_dec_mode(mode))
printf("%s on\n", name);
else
printf("%s off\n", name);
break;
}
}
static int query_rqss_numeric(char *cmd)
{
char *s = NULL;
printf("\x1bP$q%s\x1b\\", cmd);
do {
int num;
if(s)
free(s);
s = read_dcs();
if(!s)
return -1;
if(strlen(s) < strlen(cmd))
return -1;
if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) {
printf("No match\n");
continue;
}
if(s[0] != '1' || s[1] != '$' || s[2] != 'r')
return -1;
if(sscanf(s + 3, "%d", &num) != 1)
return -1;
return num;
} while(1);
}
bool wasicanon;
void restoreicanon(void)
{
seticanon(wasicanon, true);
}
int main(int argc, char *argv[])
{
int argi = 1;
if(argc == 1)
usage(0);
wasicanon = seticanon(false, false);
atexit(restoreicanon);
while(argi < argc) {
const char *arg = argv[argi++];
if(streq(arg, "reset")) {
printf("\x1b" "c");
}
else if(streq(arg, "s8c1t")) {
const char *choices[] = {"off", "on", NULL};
switch(getchoice(&argi, argc, argv, choices)) {
case 0:
printf("\x1b F"); break;
case 1:
printf("\x1b G"); break;
}
}
else if(streq(arg, "keypad")) {
const char *choices[] = {"app", "num", NULL};
switch(getchoice(&argi, argc, argv, choices)) {
case 0:
printf("\x1b="); break;
case 1:
printf("\x1b>"); break;
}
}
else if(streq(arg, "screen")) {
do_dec_mode(5, getboolq(&argi, argc, argv), "screen");
}
else if(streq(arg, "cursor")) {
do_dec_mode(25, getboolq(&argi, argc, argv), "cursor");
}
else if(streq(arg, "curblink")) {
do_dec_mode(12, getboolq(&argi, argc, argv), "curblink");
}
else if(streq(arg, "curshape")) {
/* TODO: This ought to query the current value of DECSCUSR because it */
/* may need blinking on or off */
const char *choices[] = {"block", "under", "bar", "query", NULL};
int shape = getchoice(&argi, argc, argv, choices);
switch(shape) {
case 3: /* query */
shape = query_rqss_numeric(" q");
switch(shape) {
case 1: case 2:
printf("curshape block\n");
break;
case 3: case 4:
printf("curshape under\n");
break;
case 5: case 6:
printf("curshape bar\n");
break;
}
break;
case 0:
case 1:
case 2:
printf("\x1b[%d q", 1 + (shape * 2));
break;
}
}
else if(streq(arg, "mouse")) {
const char *choices[] = {"off", "click", "clickdrag", "motion", NULL};
switch(getchoice(&argi, argc, argv, choices)) {
case 0:
printf("\x1b[?1000l"); break;
case 1:
printf("\x1b[?1000h"); break;
case 2:
printf("\x1b[?1002h"); break;
case 3:
printf("\x1b[?1003h"); break;
}
}
else if(streq(arg, "altscreen")) {
do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen");
}
else if(streq(arg, "bracketpaste")) {
do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste");
}
else if(streq(arg, "icontitle")) {
printf("\x1b]0;%s\a", getvalue(&argi, argc, argv));
}
else if(streq(arg, "icon")) {
printf("\x1b]1;%s\a", getvalue(&argi, argc, argv));
}
else if(streq(arg, "title")) {
printf("\x1b]2;%s\a", getvalue(&argi, argc, argv));
}
else {
fprintf(stderr, "Unrecognised command %s\n", arg);
exit(1);
}
}
return 0;
}

View File

@ -0,0 +1,231 @@
/* Require getopt(3) */
#define _XOPEN_SOURCE
#include <stdio.h>
#include <string.h>
#define streq(a,b) (strcmp(a,b)==0)
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "vterm.h"
static const char *special_begin = "{";
static const char *special_end = "}";
static int parser_text(const char bytes[], size_t len, void *user)
{
unsigned char *b = (unsigned char *)bytes;
int i;
for(i = 0; i < len; /* none */) {
if(b[i] < 0x20) /* C0 */
break;
else if(b[i] < 0x80) /* ASCII */
i++;
else if(b[i] < 0xa0) /* C1 */
break;
else if(b[i] < 0xc0) /* UTF-8 continuation */
break;
else if(b[i] < 0xe0) { /* UTF-8 2-byte */
/* 2-byte UTF-8 */
if(len < i+2) break;
i += 2;
}
else if(b[i] < 0xf0) { /* UTF-8 3-byte */
if(len < i+3) break;
i += 3;
}
else if(b[i] < 0xf8) { /* UTF-8 4-byte */
if(len < i+4) break;
i += 4;
}
else /* otherwise invalid */
break;
}
printf("%.*s", i, b);
return i;
}
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
static const char *name_c0[] = {
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "LS0", "LS1",
"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US",
};
static const char *name_c1[] = {
NULL, NULL, "BPH", "NBH", NULL, "NEL", "SSA", "ESA", "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3",
"DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA", "SOS", NULL, "SCI", "CSI", "ST", "OSC", "PM", "APC",
};
static int parser_control(unsigned char control, void *user)
{
if(control < 0x20)
printf("%s%s%s", special_begin, name_c0[control], special_end);
else if(control >= 0x80 && control < 0xa0 && name_c1[control - 0x80])
printf("%s%s%s", special_begin, name_c1[control - 0x80], special_end);
else
printf("%sCONTROL 0x%02x%s", special_begin, control, special_end);
if(control == 0x0a)
printf("\n");
return 1;
}
static int parser_escape(const char bytes[], size_t len, void *user)
{
if(bytes[0] >= 0x20 && bytes[0] < 0x30) {
if(len < 2)
return -1;
len = 2;
}
else {
len = 1;
}
printf("%sESC %.*s%s", special_begin, (int)len, bytes, special_end);
return len;
}
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
static const char *name_csi_plain[] = {
"ICH", "CUU", "CUD", "CUF", "CUB", "CNL", "CPL", "CHA", "CUP", "CHT", "ED", "EL", "IL", "DL", "EF", "EA",
"DCH", "SSE", "CPR", "SU", "SD", "NP", "PP", "CTC", "ECH", "CVT", "CBT", "SRS", "PTX", "SDS", "SIMD",NULL,
"HPA", "HPR", "REP", "DA", "VPA", "VPR", "HVP", "TBC", "SM", "MC", "HPB", "VPB", "RM", "SGR", "DSR", "DAQ",
};
/*0 4 8 B */
static const int newline_csi_plain[] = {
0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
};
static int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
{
const char *name = NULL;
if(!leader && !intermed && command < 0x70)
name = name_csi_plain[command - 0x40];
else if(leader && streq(leader, "?") && !intermed) {
/* DEC */
switch(command) {
case 'h': name = "DECSM"; break;
case 'l': name = "DECRM"; break;
}
if(name)
leader = NULL;
}
if(!leader && !intermed && command < 0x70 && newline_csi_plain[command - 0x40])
printf("\n");
if(name)
printf("%s%s", special_begin, name);
else
printf("%sCSI", special_begin);
if(leader && leader[0])
printf(" %s", leader);
{
int i;
for(i = 0; i < argcount; i++) {
printf(i ? "," : " ");
}
if(args[i] == CSI_ARG_MISSING)
printf("*");
else {
while(CSI_ARG_HAS_MORE(args[i]))
printf("%ld+", CSI_ARG(args[i++]));
printf("%ld", CSI_ARG(args[i]));
}
}
if(intermed && intermed[0])
printf(" %s", intermed);
if(name)
printf("%s", special_end);
else
printf(" %c%s", command, special_end);
return 1;
}
static int parser_osc(const char *command, size_t cmdlen, void *user)
{
printf("%sOSC %.*s%s", special_begin, (int)cmdlen, command, special_end);
return 1;
}
static int parser_dcs(const char *command, size_t cmdlen, void *user)
{
printf("%sDCS %.*s%s", special_begin, (int)cmdlen, command, special_end);
return 1;
}
static VTermParserCallbacks parser_cbs = {
&parser_text, /* text */
&parser_control, /* control */
&parser_escape, /* escape */
&parser_csi, /* csi */
&parser_osc, /* osc */
&parser_dcs, /* dcs */
NULL /* resize */
};
int main(int argc, char *argv[])
{
int use_colour = isatty(1);
const char *file;
int fd;
VTerm *vt;
int len;
char buffer[1024];
int opt;
while((opt = getopt(argc, argv, "c")) != -1) {
switch(opt) {
case 'c': use_colour = 1; break;
}
}
file = argv[optind++];
if(!file || streq(file, "-"))
fd = 0; /* stdin */
else {
fd = open(file, O_RDONLY);
if(fd == -1) {
fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno));
exit(1);
}
}
if(use_colour) {
special_begin = "\x1b[7m{";
special_end = "}\x1b[m";
}
/* Size matters not for the parser */
vt = vterm_new(25, 80);
vterm_set_utf8(vt, 1);
vterm_parser_set_callbacks(vt, &parser_cbs, NULL);
while((len = read(fd, buffer, sizeof(buffer))) > 0) {
vterm_input_write(vt, buffer, len);
}
printf("\n");
close(fd);
vterm_free(vt);
return 0;
}

11
src/libvterm/doc/URLs Normal file
View File

@ -0,0 +1,11 @@
ECMA-48:
http://www.ecma-international.org/publications/standards/Ecma-048.htm
Xterm Control Sequences:
http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
Digital VT100 User Guide:
http://vt100.net/docs/vt100-ug/
Digital VT220 Programmer Reference Manual
http://vt100.net/docs/vt220-rm/

226
src/libvterm/doc/seqs.txt Normal file
View File

@ -0,0 +1,226 @@
Sequences documented in parens are implicit ones from parser.c, which move
between states.
1 = VT100
2 = VT220
3 = VT320
C0 controls
123 0x00 = NUL
123 0x07 = BEL
123 0x08 = BS
123 0x09 = HT
123 0x0A = LF
123 0x0B = VT
123 0x0C = FF
123 0x0D = CR
123 0x0E = LS1
123 0x0F = LS0
(0x18 = CAN)
(0x1A = SUB)
(0x1B = ESC)
123 0x7f = DEL (ignored)
C1 controls
123 0x84 = IND
123 0x85 = NEL
123 0x88 = HTS
123 0x8D = RI
23 0x8e = SS2
23 0x8f = SS3
(0x90 = DCS)
(0x9B = CSI)
(0x9C = ST)
(0x9D = OSC)
Escape sequences
- excluding sequences that are C1 aliases
123 ESC () = SCS, select character set (G0, G1)
23 ESC *+ = SCS, select character set (G2, G3)
123 ESC 7 = DECSC - save cursor
123 ESC 8 = DECRC - restore cursor
123 ESC # 3 = DECDHL, double-height line (top half)
123 ESC # 4 = DECDHL, double-height line (bottom half)
123 ESC # 5 = DECSWL, single-width single-height line
123 ESC # 6 = DECDWL, double-width single-height line
123 ESC # 8 = DECALN
123 ESC < = Ignored (used by VT100 to exit VT52 mode)
123 ESC = = DECKPAM, keypad application mode
123 ESC > = DECKPNM, keypad numeric mode
23 ESC Sp F = S7C1T
23 ESC Sp G = S8C1T
(ESC P = DCS)
(ESC [ = CSI)
(ESC \ = ST)
(ESC ] = OSC)
123 ESC c = RIS, reset initial state
3 ESC n = LS2
3 ESC o = LS3
3 ESC ~ = LS1R
3 ESC } = LS2R
3 ESC | = LS3R
DCSes
3 DCS $ q ST = DECRQSS
3 m = Request SGR
Sp q = Request DECSCUSR
3 " q = Request DECSCA
3 r = Request DECSTBM
s = Request DECSLRM
CSIs
23 CSI @ = ICH
123 CSI A = CUU
123 CSI B = CUD
123 CSI C = CUF
123 CSI D = CUB
CSI E = CNL
CSI F = CPL
CSI G = CHA
123 CSI H = CUP
CSI I = CHT
123 CSI J = ED
23 CSI ? J = DECSED, selective erase in display
123 CSI K = EL
23 CSI ? K = DECSEL, selective erase in line
23 CSI L = IL
23 CSI M = DL
23 CSI P = DCH
CSI S = SU
CSI T = SD
23 CSI X = ECH
CSI Z = CBT
CSI ` = HPA
CSI a = HPR
123 CSI c = DA, device attributes
123 0 = DA
23 CSI > c = DECSDA
23 0 = SDA
CSI d = VPA
CSI e = VPR
123 CSI f = HVP
123 CSI g = TBC
123 CSI h = SM, Set mode
123 CSI ? h = DECSM, DEC set mode
CSI j = HPB
CSI k = VPB
123 CSI l = RM, Reset mode
123 CSI ? l = DECRM, DEC reset mode
123 CSI m = SGR, Set Graphic Rendition
123 CSI n = DSR, Device Status Report
23 5 = operating status
23 6 = CPR = cursor position
23 CSI ? n = DECDSR; behaves as DSR but uses CSI ? instead of CSI to respond
23 CSI ! p = DECSTR, soft terminal reset
3 CSI ? $ p = DECRQM, request mode
CSI Sp q = DECSCUSR (odd numbers blink, even numbers solid)
1 or 2 = block
3 or 4 = underline
5 or 6 = I-beam to left
23 CSI " q = DECSCA, select character attributes
123 CSI r = DECSTBM
CSI s = DECSLRM
CSI ' } = DECIC
CSI ' ~ = DECDC
OSCs
OSC 0; = Set icon name and title
OSC 1; = Set icon name
OSC 2; = Set title
Standard modes
23 SM 4 = IRM
123 SM 20 = NLM, linefeed/newline
DEC modes
123 DECSM 1 = DECCKM, cursor keys
123 DECSM 5 = DECSCNM, screen
123 DECSM 6 = DECOM, origin
123 DECSM 7 = DECAWM, autowrap
DECSM 12 = Cursor blink
23 DECSM 25 = DECTCEM, text cursor enable
DECSM 69 = DECVSSM, vertical screen split
DECSM 1000 = Mouse click/release tracking
DECSM 1002 = Mouse click/release/drag tracking
DECSM 1003 = Mouse all movements tracking
DECSM 1005 = Mouse protocol extended (UTF-8) - not recommended
DECSM 1006 = Mouse protocol SGR
DECSM 1015 = Mouse protocol rxvt
DECSM 1047 = Altscreen
DECSM 1048 = Save cursor
DECSM 1049 = 1047 + 1048
DECSM 2004 = Bracketed paste
Graphic Renditions
123 SGR 0 = Reset
123 SGR 1 = Bold on
SGR 3 = Italic on
123 SGR 4 = Underline single
123 SGR 5 = Blink on
123 SGR 7 = Reverse on
SGR 9 = Strikethrough on
SGR 10-19 = Select font
SGR 21 = Underline double
23 SGR 22 = Bold off
SGR 23 = Italic off
23 SGR 24 = Underline off
23 SGR 25 = Blink off
23 SGR 27 = Reverse off
SGR 29 = Strikethrough off
SGR 30-37 = Foreground ANSI
SGR 38 = Foreground alternative palette
SGR 39 = Foreground default
SGR 40-47 = Background ANSI
SGR 48 = Background alternative palette
SGR 49 = Background default
SGR 90-97 = Foreground ANSI high-intensity
SGR 100-107 = Background ANSI high-intensity
The state storage used by ESC 7 and DECSM 1048/1049 is shared.
Unimplemented sequences:
The following sequences are not recognised by libvterm.
123 0x05 = ENQ
3 0x11 = DC1 (XON)
3 0x13 = DC3 (XOFF)
12 ESC Z = DECID, identify terminal
DCS $ q = [DECRQSS]
3 " p = Request DECSCL
3 $ } = Request DECSASD
3 $ ~ = Request DECSSDT
23 DCS { = DECDLD, down-line-loadable character set
23 DCS | = DECUDK, user-defined key
23 CSI i = DEC printer control
23 CSI " p = DECSCL, set compatibility level
1 CSI q = DECLL, load LEDs
3 CSI $ u = DECRQTSR, request terminal state report
3 1 = terminal state report
3 CSI & u = DECRQUPSS, request user-preferred supplemental set
3 CSI $ w = DECRQPSR, request presentation state report
3 1 = cursor information report
3 2 = tab stop report
1 CSI x = DECREQTPARM, request terminal parameters
123 CSI y = DECTST, invoke confidence test
3 CSI $ } = DECSASD, select active status display
3 CSI $ ~ = DECSSDT, select status line type
23 SM 2 = KAM, keyboard action
123 SM 12 = SRM, send/receive
123 DECSM 2 = DECANM, ANSI/VT52
123 DECSM 3 = DECCOLM, 132 column
123 DECSM 4 = DECSCLM, scrolling
123 DECSM 8 = DECARM, auto-repeat
12 DECSM 9 = DECINLM, interlace
23 DECSM 18 = DECPFF, print form feed
23 DECSM 19 = DECPEX, print extent
23 DECSM 42 = DECNRCM, national/multinational character

View File

@ -0,0 +1,370 @@
/*
* NOTE: This is a MODIFIED version of libvterm, see the README file.
*/
#ifndef __VTERM_H__
#define __VTERM_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include "vterm_keycodes.h"
typedef struct VTerm VTerm;
typedef struct VTermState VTermState;
typedef struct VTermScreen VTermScreen;
/* Specifies a screen point. */
typedef struct {
int row;
int col;
} VTermPos;
/*
* Some small utility functions; we can just keep these static here.
*/
/*
* Order points by on-screen flow order:
* Return < 0 if "a" is before "b"
* Return 0 if "a" and "b" are equal
* Return > 0 if "a" is after "b".
*/
int vterm_pos_cmp(VTermPos a, VTermPos b);
#if defined(DEFINE_INLINES) || USE_INLINE
INLINE int vterm_pos_cmp(VTermPos a, VTermPos b)
{
return (a.row == b.row) ? a.col - b.col : a.row - b.row;
}
#endif
/* Specifies a rectangular screen area. */
typedef struct {
int start_row;
int end_row;
int start_col;
int end_col;
} VTermRect;
/* Return true if the rect "r" contains the point "p". */
int vterm_rect_contains(VTermRect r, VTermPos p);
#if defined(DEFINE_INLINES) || USE_INLINE
INLINE int vterm_rect_contains(VTermRect r, VTermPos p)
{
return p.row >= r.start_row && p.row < r.end_row &&
p.col >= r.start_col && p.col < r.end_col;
}
#endif
/* Move "rect" "row_delta" down and "col_delta" right.
* Does not check boundaries. */
void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta);
#if defined(DEFINE_INLINES) || USE_INLINE
INLINE void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta)
{
rect->start_row += row_delta; rect->end_row += row_delta;
rect->start_col += col_delta; rect->end_col += col_delta;
}
#endif
typedef struct {
uint8_t red, green, blue;
} VTermColor;
typedef enum {
/* VTERM_VALUETYPE_NONE = 0 */
VTERM_VALUETYPE_BOOL = 1,
VTERM_VALUETYPE_INT,
VTERM_VALUETYPE_STRING,
VTERM_VALUETYPE_COLOR
} VTermValueType;
typedef union {
int boolean;
int number;
char *string;
VTermColor color;
} VTermValue;
typedef enum {
/* VTERM_ATTR_NONE = 0 */
VTERM_ATTR_BOLD = 1, /* bool: 1, 22 */
VTERM_ATTR_UNDERLINE, /* number: 4, 21, 24 */
VTERM_ATTR_ITALIC, /* bool: 3, 23 */
VTERM_ATTR_BLINK, /* bool: 5, 25 */
VTERM_ATTR_REVERSE, /* bool: 7, 27 */
VTERM_ATTR_STRIKE, /* bool: 9, 29 */
VTERM_ATTR_FONT, /* number: 10-19 */
VTERM_ATTR_FOREGROUND, /* color: 30-39 90-97 */
VTERM_ATTR_BACKGROUND /* color: 40-49 100-107 */
} VTermAttr;
typedef enum {
/* VTERM_PROP_NONE = 0 */
VTERM_PROP_CURSORVISIBLE = 1, /* bool */
VTERM_PROP_CURSORBLINK, /* bool */
VTERM_PROP_ALTSCREEN, /* bool */
VTERM_PROP_TITLE, /* string */
VTERM_PROP_ICONNAME, /* string */
VTERM_PROP_REVERSE, /* bool */
VTERM_PROP_CURSORSHAPE, /* number */
VTERM_PROP_MOUSE /* number */
} VTermProp;
enum {
VTERM_PROP_CURSORSHAPE_BLOCK = 1,
VTERM_PROP_CURSORSHAPE_UNDERLINE,
VTERM_PROP_CURSORSHAPE_BAR_LEFT
};
enum {
VTERM_PROP_MOUSE_NONE = 0,
VTERM_PROP_MOUSE_CLICK,
VTERM_PROP_MOUSE_DRAG,
VTERM_PROP_MOUSE_MOVE
};
typedef struct {
const uint32_t *chars;
int width;
unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */
unsigned int dwl:1; /* DECDWL or DECDHL double-width line */
unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */
} VTermGlyphInfo;
typedef struct {
unsigned int doublewidth:1; /* DECDWL or DECDHL line */
unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */
} VTermLineInfo;
typedef struct {
/* libvterm relies on the allocated memory to be zeroed out before it is
* returned by the allocator. */
void *(*malloc)(size_t size, void *allocdata);
void (*free)(void *ptr, void *allocdata);
} VTermAllocatorFunctions;
/* Allocate and initialize a new terminal with default allocators. */
VTerm *vterm_new(int rows, int cols);
/* Allocate and initialize a new terminal with specified allocators. */
VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata);
/* Free and cleanup a terminal and all its data. */
void vterm_free(VTerm* vt);
void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp);
void vterm_set_size(VTerm *vt, int rows, int cols);
int vterm_get_utf8(const VTerm *vt);
void vterm_set_utf8(VTerm *vt, int is_utf8);
size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len);
size_t vterm_output_get_buffer_size(const VTerm *vt);
size_t vterm_output_get_buffer_current(const VTerm *vt);
size_t vterm_output_get_buffer_remaining(const VTerm *vt);
size_t vterm_output_read(VTerm *vt, char *buffer, size_t len);
void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod);
void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod);
void vterm_keyboard_start_paste(VTerm *vt);
void vterm_keyboard_end_paste(VTerm *vt);
void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod);
void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod);
/* ------------
* Parser layer
* ------------ */
/* Flag to indicate non-final subparameters in a single CSI parameter.
* Consider
* CSI 1;2:3:4;5a
* 1 4 and 5 are final.
* 2 and 3 are non-final and will have this bit set
*
* Don't confuse this with the final byte of the CSI escape; 'a' in this case.
*/
#define CSI_ARG_FLAG_MORE (1<<31)
#define CSI_ARG_MASK (~(1<<31))
#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE)
#define CSI_ARG(a) ((a) & CSI_ARG_MASK)
/* Can't use -1 to indicate a missing argument; use this instead */
#define CSI_ARG_MISSING ((1UL<<31)-1)
#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING)
#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a))
#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a))
typedef struct {
int (*text)(const char *bytes, size_t len, void *user);
int (*control)(unsigned char control, void *user);
int (*escape)(const char *bytes, size_t len, void *user);
int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
int (*osc)(const char *command, size_t cmdlen, void *user);
int (*dcs)(const char *command, size_t cmdlen, void *user);
int (*resize)(int rows, int cols, void *user);
} VTermParserCallbacks;
void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user);
void *vterm_parser_get_cbdata(VTerm *vt);
/* -----------
* State layer
* ----------- */
typedef struct {
int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user);
int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user);
int (*moverect)(VTermRect dest, VTermRect src, void *user);
int (*erase)(VTermRect rect, int selective, void *user);
int (*initpen)(void *user);
int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user);
int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
int (*bell)(void *user);
int (*resize)(int rows, int cols, VTermPos *delta, void *user);
int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);
} VTermStateCallbacks;
VTermState *vterm_obtain_state(VTerm *vt);
void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user);
void *vterm_state_get_cbdata(VTermState *state);
/* Only invokes control, csi, osc, dcs */
void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user);
void *vterm_state_get_unrecognised_fbdata(VTermState *state);
void vterm_state_reset(VTermState *state, int hard);
void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos);
void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg);
void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col);
void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg);
void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col);
void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright);
int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val);
int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val);
const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row);
/* ------------
* Screen layer
* ------------ */
typedef struct {
unsigned int bold : 1;
unsigned int underline : 2;
unsigned int italic : 1;
unsigned int blink : 1;
unsigned int reverse : 1;
unsigned int strike : 1;
unsigned int font : 4; /* 0 to 9 */
unsigned int dwl : 1; /* On a DECDWL or DECDHL line */
unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */
} VTermScreenCellAttrs;
typedef struct {
#define VTERM_MAX_CHARS_PER_CELL 6
uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
char width;
VTermScreenCellAttrs attrs;
VTermColor fg, bg;
} VTermScreenCell;
/* All fields are optional, NULL when not used. */
typedef struct {
int (*damage)(VTermRect rect, void *user);
int (*moverect)(VTermRect dest, VTermRect src, void *user);
int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
int (*bell)(void *user);
int (*resize)(int rows, int cols, void *user);
int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user);
int (*sb_popline)(int cols, VTermScreenCell *cells, void *user);
} VTermScreenCallbacks;
VTermScreen *vterm_obtain_screen(VTerm *vt);
/*
* Install screen callbacks. These are invoked when the screen contents is
* changed. "user" is passed into to the callback.
*/
void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user);
void *vterm_screen_get_cbdata(VTermScreen *screen);
/* Only invokes control, csi, osc, dcs */
void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user);
void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen);
void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen);
typedef enum {
VTERM_DAMAGE_CELL, /* every cell */
VTERM_DAMAGE_ROW, /* entire rows */
VTERM_DAMAGE_SCREEN, /* entire screen */
VTERM_DAMAGE_SCROLL /* entire screen + scrollrect */
} VTermDamageSize;
void vterm_screen_flush_damage(VTermScreen *screen);
void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size);
void vterm_screen_reset(VTermScreen *screen, int hard);
/* Neither of these functions NUL-terminate the buffer */
size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect);
size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect);
typedef enum {
VTERM_ATTR_BOLD_MASK = 1 << 0,
VTERM_ATTR_UNDERLINE_MASK = 1 << 1,
VTERM_ATTR_ITALIC_MASK = 1 << 2,
VTERM_ATTR_BLINK_MASK = 1 << 3,
VTERM_ATTR_REVERSE_MASK = 1 << 4,
VTERM_ATTR_STRIKE_MASK = 1 << 5,
VTERM_ATTR_FONT_MASK = 1 << 6,
VTERM_ATTR_FOREGROUND_MASK = 1 << 7,
VTERM_ATTR_BACKGROUND_MASK = 1 << 8
} VTermAttrMask;
int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs);
int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell);
int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos);
/* ---------
* Utilities
* --------- */
VTermValueType vterm_get_attr_type(VTermAttr attr);
VTermValueType vterm_get_prop_type(VTermProp prop);
void vterm_scroll_rect(VTermRect rect,
int downward,
int rightward,
int (*moverect)(VTermRect src, VTermRect dest, void *user),
int (*eraserect)(VTermRect rect, int selective, void *user),
void *user);
void vterm_copy_cells(VTermRect dest,
VTermRect src,
void (*copycell)(VTermPos dest, VTermPos src, void *user),
void *user);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,58 @@
#ifndef __VTERM_INPUT_H__
#define __VTERM_INPUT_H__
typedef enum {
VTERM_MOD_NONE = 0x00,
VTERM_MOD_SHIFT = 0x01,
VTERM_MOD_ALT = 0x02,
VTERM_MOD_CTRL = 0x04
} VTermModifier;
typedef enum {
VTERM_KEY_NONE,
VTERM_KEY_ENTER,
VTERM_KEY_TAB,
VTERM_KEY_BACKSPACE,
VTERM_KEY_ESCAPE,
VTERM_KEY_UP,
VTERM_KEY_DOWN,
VTERM_KEY_LEFT,
VTERM_KEY_RIGHT,
VTERM_KEY_INS,
VTERM_KEY_DEL,
VTERM_KEY_HOME,
VTERM_KEY_END,
VTERM_KEY_PAGEUP,
VTERM_KEY_PAGEDOWN,
VTERM_KEY_FUNCTION_0 = 256,
VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255,
VTERM_KEY_KP_0,
VTERM_KEY_KP_1,
VTERM_KEY_KP_2,
VTERM_KEY_KP_3,
VTERM_KEY_KP_4,
VTERM_KEY_KP_5,
VTERM_KEY_KP_6,
VTERM_KEY_KP_7,
VTERM_KEY_KP_8,
VTERM_KEY_KP_9,
VTERM_KEY_KP_MULT,
VTERM_KEY_KP_PLUS,
VTERM_KEY_KP_COMMA,
VTERM_KEY_KP_MINUS,
VTERM_KEY_KP_PERIOD,
VTERM_KEY_KP_DIVIDE,
VTERM_KEY_KP_ENTER,
VTERM_KEY_KP_EQUAL,
VTERM_KEY_MAX /* Must be last */
} VTermKey;
#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n))
#endif

232
src/libvterm/src/encoding.c Normal file
View File

@ -0,0 +1,232 @@
#include "vterm_internal.h"
#define UNICODE_INVALID 0xFFFD
#if defined(DEBUG) && DEBUG > 1
# define DEBUG_PRINT_UTF8
#endif
struct UTF8DecoderData {
/* number of bytes remaining in this codepoint */
int bytes_remaining;
/* number of bytes total in this codepoint once it's finished
(for detecting overlongs) */
int bytes_total;
int this_cp;
};
static void init_utf8(VTermEncoding *enc UNUSED, void *data_)
{
struct UTF8DecoderData *data = data_;
data->bytes_remaining = 0;
data->bytes_total = 0;
}
static void decode_utf8(VTermEncoding *enc UNUSED, void *data_,
uint32_t cp[], int *cpi, int cplen,
const char bytes[], size_t *pos, size_t bytelen)
{
struct UTF8DecoderData *data = data_;
#ifdef DEBUG_PRINT_UTF8
printf("BEGIN UTF-8\n");
#endif
for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
unsigned char c = bytes[*pos];
#ifdef DEBUG_PRINT_UTF8
printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining);
#endif
if(c < 0x20) /* C0 */
return;
else if(c >= 0x20 && c < 0x7f) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
cp[(*cpi)++] = c;
#ifdef DEBUG_PRINT_UTF8
printf(" UTF-8 char: U+%04x\n", c);
#endif
data->bytes_remaining = 0;
}
else if(c == 0x7f) /* DEL */
return;
else if(c >= 0x80 && c < 0xc0) {
if(!data->bytes_remaining) {
cp[(*cpi)++] = UNICODE_INVALID;
continue;
}
data->this_cp <<= 6;
data->this_cp |= c & 0x3f;
data->bytes_remaining--;
if(!data->bytes_remaining) {
#ifdef DEBUG_PRINT_UTF8
printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total);
#endif
/* Check for overlong sequences */
switch(data->bytes_total) {
case 2:
if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID;
break;
case 3:
if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID;
break;
case 4:
if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID;
break;
case 5:
if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID;
break;
case 6:
if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID;
break;
}
/* Now look for plain invalid ones */
if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) ||
data->this_cp == 0xFFFE ||
data->this_cp == 0xFFFF)
data->this_cp = UNICODE_INVALID;
#ifdef DEBUG_PRINT_UTF8
printf(" char: U+%04x\n", data->this_cp);
#endif
cp[(*cpi)++] = data->this_cp;
}
}
else if(c >= 0xc0 && c < 0xe0) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
data->this_cp = c & 0x1f;
data->bytes_total = 2;
data->bytes_remaining = 1;
}
else if(c >= 0xe0 && c < 0xf0) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
data->this_cp = c & 0x0f;
data->bytes_total = 3;
data->bytes_remaining = 2;
}
else if(c >= 0xf0 && c < 0xf8) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
data->this_cp = c & 0x07;
data->bytes_total = 4;
data->bytes_remaining = 3;
}
else if(c >= 0xf8 && c < 0xfc) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
data->this_cp = c & 0x03;
data->bytes_total = 5;
data->bytes_remaining = 4;
}
else if(c >= 0xfc && c < 0xfe) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
data->this_cp = c & 0x01;
data->bytes_total = 6;
data->bytes_remaining = 5;
}
else {
cp[(*cpi)++] = UNICODE_INVALID;
}
}
}
static VTermEncoding encoding_utf8 = {
&init_utf8, /* init */
&decode_utf8 /* decode */
};
static void decode_usascii(VTermEncoding *enc UNUSED, void *data UNUSED,
uint32_t cp[], int *cpi, int cplen,
const char bytes[], size_t *pos, size_t bytelen)
{
int is_gr = bytes[*pos] & 0x80;
for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
unsigned char c = bytes[*pos] ^ is_gr;
if(c < 0x20 || c == 0x7f || c >= 0x80)
return;
cp[(*cpi)++] = c;
}
}
static VTermEncoding encoding_usascii = {
NULL, /* init */
&decode_usascii /* decode */
};
struct StaticTableEncoding {
const VTermEncoding enc;
const uint32_t chars[128];
};
static void decode_table(VTermEncoding *enc, void *data UNUSED,
uint32_t cp[], int *cpi, int cplen,
const char bytes[], size_t *pos, size_t bytelen)
{
struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc;
int is_gr = bytes[*pos] & 0x80;
for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
unsigned char c = bytes[*pos] ^ is_gr;
if(c < 0x20 || c == 0x7f || c >= 0x80)
return;
if(table->chars[c])
cp[(*cpi)++] = table->chars[c];
else
cp[(*cpi)++] = c;
}
}
#include "encoding/DECdrawing.inc"
#include "encoding/uk.inc"
static struct {
VTermEncodingType type;
char designation;
VTermEncoding *enc;
}
encodings[] = {
{ ENC_UTF8, 'u', &encoding_utf8 },
{ ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing },
{ ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk },
{ ENC_SINGLE_94, 'B', &encoding_usascii },
{ 0 },
};
/* This ought to be INTERNAL but isn't because it's used by unit testing */
VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)
{
int i;
for(i = 0; encodings[i].designation; i++)
if(encodings[i].type == type && encodings[i].designation == designation)
return encodings[i].enc;
return NULL;
}

View File

@ -0,0 +1,31 @@
6/0 = U+25C6 # BLACK DIAMOND
6/1 = U+2592 # MEDIUM SHADE (checkerboard)
6/2 = U+2409 # SYMBOL FOR HORIZONTAL TAB
6/3 = U+240C # SYMBOL FOR FORM FEED
6/4 = U+240D # SYMBOL FOR CARRIAGE RETURN
6/5 = U+240A # SYMBOL FOR LINE FEED
6/6 = U+00B0 # DEGREE SIGN
6/7 = U+00B1 # PLUS-MINUS SIGN (plus or minus)
6/8 = U+2424 # SYMBOL FOR NEW LINE
6/9 = U+240B # SYMBOL FOR VERTICAL TAB
6/10 = U+2518 # BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner)
6/11 = U+2510 # BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner)
6/12 = U+250C # BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner)
6/13 = U+2514 # BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner)
6/14 = U+253C # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines)
6/15 = U+23BA # HORIZONTAL SCAN LINE-1
7/0 = U+23BB # HORIZONTAL SCAN LINE-3
7/1 = U+2500 # BOX DRAWINGS LIGHT HORIZONTAL
7/2 = U+23BC # HORIZONTAL SCAN LINE-7
7/3 = U+23BD # HORIZONTAL SCAN LINE-9
7/4 = U+251C # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
7/5 = U+2524 # BOX DRAWINGS LIGHT VERTICAL AND LEFT
7/6 = U+2534 # BOX DRAWINGS LIGHT UP AND HORIZONTAL
7/7 = U+252C # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
7/8 = U+2502 # BOX DRAWINGS LIGHT VERTICAL
7/9 = U+2A7D # LESS-THAN OR SLANTED EQUAL-TO
7/10 = U+2A7E # GREATER-THAN OR SLANTED EQUAL-TO
7/11 = U+03C0 # GREEK SMALL LETTER PI
7/12 = U+2260 # NOT EQUAL TO
7/13 = U+00A3 # POUND SIGN
7/14 = U+00B7 # MIDDLE DOT

View File

@ -0,0 +1 @@
2/3 = "£"

228
src/libvterm/src/keyboard.c Normal file
View File

@ -0,0 +1,228 @@
#include "vterm_internal.h"
#include <stdio.h>
#include "utf8.h"
void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
{
int needs_CSIu;
/* The shift modifier is never important for Unicode characters
* apart from Space
*/
if(c != ' ')
mod &= ~VTERM_MOD_SHIFT;
if(mod == 0) {
/* Normal text - ignore just shift */
char str[6];
int seqlen = fill_utf8(c, str);
vterm_push_output_bytes(vt, str, seqlen);
return;
}
switch(c) {
/* Special Ctrl- letters that can't be represented elsewise */
case 'i': case 'j': case 'm': case '[':
needs_CSIu = 1;
break;
/* Ctrl-\ ] ^ _ don't need CSUu */
case '\\': case ']': case '^': case '_':
needs_CSIu = 0;
break;
/* Shift-space needs CSIu */
case ' ':
needs_CSIu = !!(mod & VTERM_MOD_SHIFT);
break;
/* All other characters needs CSIu except for letters a-z */
default:
needs_CSIu = (c < 'a' || c > 'z');
}
/* ALT we can just prefix with ESC; anything else requires CSI u */
if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) {
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1);
return;
}
if(mod & VTERM_MOD_CTRL)
c &= 0x1f;
vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c);
}
typedef struct {
enum {
KEYCODE_NONE,
KEYCODE_LITERAL,
KEYCODE_TAB,
KEYCODE_ENTER,
KEYCODE_SS3,
KEYCODE_CSI,
KEYCODE_CSI_CURSOR,
KEYCODE_CSINUM,
KEYCODE_KEYPAD
} type;
char literal;
int csinum;
} keycodes_s;
static keycodes_s keycodes[] = {
{ KEYCODE_NONE, 0, 0 }, /* NONE */
{ KEYCODE_ENTER, '\r', 0 }, /* ENTER */
{ KEYCODE_TAB, '\t', 0 }, /* TAB */
{ KEYCODE_LITERAL, '\x7f', 0 }, /* BACKSPACE == ASCII DEL */
{ KEYCODE_LITERAL, '\x1b', 0 }, /* ESCAPE */
{ KEYCODE_CSI_CURSOR, 'A', 0 }, /* UP */
{ KEYCODE_CSI_CURSOR, 'B', 0 }, /* DOWN */
{ KEYCODE_CSI_CURSOR, 'D', 0 }, /* LEFT */
{ KEYCODE_CSI_CURSOR, 'C', 0 }, /* RIGHT */
{ KEYCODE_CSINUM, '~', 2 }, /* INS */
{ KEYCODE_CSINUM, '~', 3 }, /* DEL */
{ KEYCODE_CSI_CURSOR, 'H', 0 }, /* HOME */
{ KEYCODE_CSI_CURSOR, 'F', 0 }, /* END */
{ KEYCODE_CSINUM, '~', 5 }, /* PAGEUP */
{ KEYCODE_CSINUM, '~', 6 }, /* PAGEDOWN */
};
static keycodes_s keycodes_fn[] = {
{ KEYCODE_NONE, 0, 0 }, /* F0 - shouldn't happen */
{ KEYCODE_CSI_CURSOR, 'P', 0 }, /* F1 */
{ KEYCODE_CSI_CURSOR, 'Q', 0 }, /* F2 */
{ KEYCODE_CSI_CURSOR, 'R', 0 }, /* F3 */
{ KEYCODE_CSI_CURSOR, 'S', 0 }, /* F4 */
{ KEYCODE_CSINUM, '~', 15 }, /* F5 */
{ KEYCODE_CSINUM, '~', 17 }, /* F6 */
{ KEYCODE_CSINUM, '~', 18 }, /* F7 */
{ KEYCODE_CSINUM, '~', 19 }, /* F8 */
{ KEYCODE_CSINUM, '~', 20 }, /* F9 */
{ KEYCODE_CSINUM, '~', 21 }, /* F10 */
{ KEYCODE_CSINUM, '~', 23 }, /* F11 */
{ KEYCODE_CSINUM, '~', 24 }, /* F12 */
};
static keycodes_s keycodes_kp[] = {
{ KEYCODE_KEYPAD, '0', 'p' }, /* KP_0 */
{ KEYCODE_KEYPAD, '1', 'q' }, /* KP_1 */
{ KEYCODE_KEYPAD, '2', 'r' }, /* KP_2 */
{ KEYCODE_KEYPAD, '3', 's' }, /* KP_3 */
{ KEYCODE_KEYPAD, '4', 't' }, /* KP_4 */
{ KEYCODE_KEYPAD, '5', 'u' }, /* KP_5 */
{ KEYCODE_KEYPAD, '6', 'v' }, /* KP_6 */
{ KEYCODE_KEYPAD, '7', 'w' }, /* KP_7 */
{ KEYCODE_KEYPAD, '8', 'x' }, /* KP_8 */
{ KEYCODE_KEYPAD, '9', 'y' }, /* KP_9 */
{ KEYCODE_KEYPAD, '*', 'j' }, /* KP_MULT */
{ KEYCODE_KEYPAD, '+', 'k' }, /* KP_PLUS */
{ KEYCODE_KEYPAD, ',', 'l' }, /* KP_COMMA */
{ KEYCODE_KEYPAD, '-', 'm' }, /* KP_MINUS */
{ KEYCODE_KEYPAD, '.', 'n' }, /* KP_PERIOD */
{ KEYCODE_KEYPAD, '/', 'o' }, /* KP_DIVIDE */
{ KEYCODE_KEYPAD, '\n', 'M' }, /* KP_ENTER */
{ KEYCODE_KEYPAD, '=', 'X' }, /* KP_EQUAL */
};
void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
{
keycodes_s k;
if(key == VTERM_KEY_NONE)
return;
if(key < VTERM_KEY_FUNCTION_0) {
if(key >= sizeof(keycodes)/sizeof(keycodes[0]))
return;
k = keycodes[key];
}
else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) {
if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0]))
return;
k = keycodes_fn[key - VTERM_KEY_FUNCTION_0];
}
else if(key >= VTERM_KEY_KP_0) {
if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0]))
return;
k = keycodes_kp[key - VTERM_KEY_KP_0];
}
switch(k.type) {
case KEYCODE_NONE:
break;
case KEYCODE_TAB:
/* Shift-Tab is CSI Z but plain Tab is 0x09 */
if(mod == VTERM_MOD_SHIFT)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
else if(mod & VTERM_MOD_SHIFT)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1);
else
goto case_LITERAL;
break;
case KEYCODE_ENTER:
/* Enter is CRLF in newline mode, but just LF in linefeed */
if(vt->state->mode.newline)
vterm_push_output_sprintf(vt, "\r\n");
else
goto case_LITERAL;
break;
case KEYCODE_LITERAL: case_LITERAL:
if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL))
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1);
else
vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal);
break;
case KEYCODE_SS3: case_SS3:
if(mod == 0)
vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal);
else
goto case_CSI;
break;
case KEYCODE_CSI: case_CSI:
if(mod == 0)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal);
else
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal);
break;
case KEYCODE_CSINUM:
if(mod == 0)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal);
else
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal);
break;
case KEYCODE_CSI_CURSOR:
if(vt->state->mode.cursor)
goto case_SS3;
else
goto case_CSI;
case KEYCODE_KEYPAD:
if(vt->state->mode.keypad) {
k.literal = k.csinum;
goto case_SS3;
}
else
goto case_LITERAL;
}
}
void vterm_keyboard_start_paste(VTerm *vt)
{
if(vt->state->mode.bracketpaste)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~");
}
void vterm_keyboard_end_paste(VTerm *vt)
{
if(vt->state->mode.bracketpaste)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~");
}

96
src/libvterm/src/mouse.c Normal file
View File

@ -0,0 +1,96 @@
#include "vterm_internal.h"
#include "utf8.h"
static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row)
{
modifiers <<= 2;
switch(state->mouse_protocol) {
case MOUSE_X10:
if(col + 0x21 > 0xff)
col = 0xff - 0x21;
if(row + 0x21 > 0xff)
row = 0xff - 0x21;
if(!pressed)
code = 3;
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c",
(code | modifiers) + 0x20, col + 0x21, row + 0x21);
break;
case MOUSE_UTF8:
{
char utf8[18]; size_t len = 0;
if(!pressed)
code = 3;
len += fill_utf8((code | modifiers) + 0x20, utf8 + len);
len += fill_utf8(col + 0x21, utf8 + len);
len += fill_utf8(row + 0x21, utf8 + len);
utf8[len] = 0;
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8);
}
break;
case MOUSE_SGR:
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c",
code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');
break;
case MOUSE_RXVT:
if(!pressed)
code = 3;
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM",
code | modifiers, col + 1, row + 1);
break;
}
}
void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod)
{
VTermState *state = vt->state;
if(col == state->mouse_col && row == state->mouse_row)
return;
state->mouse_col = col;
state->mouse_row = row;
if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) ||
(state->mouse_flags & MOUSE_WANT_MOVE)) {
int button = state->mouse_buttons & 0x01 ? 1 :
state->mouse_buttons & 0x02 ? 2 :
state->mouse_buttons & 0x04 ? 3 : 4;
output_mouse(state, button-1 + 0x20, 1, mod, col, row);
}
}
void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod)
{
VTermState *state = vt->state;
int old_buttons = state->mouse_buttons;
if(button > 0 && button <= 3) {
if(pressed)
state->mouse_buttons |= (1 << (button-1));
else
state->mouse_buttons &= ~(1 << (button-1));
}
/* Most of the time we don't get button releases from 4/5 */
if(state->mouse_buttons == old_buttons && button < 4)
return;
if(button < 4) {
output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row);
}
else if(button < 6) {
output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row);
}
}

346
src/libvterm/src/parser.c Normal file
View File

@ -0,0 +1,346 @@
#include "vterm_internal.h"
#include <stdio.h>
#include <string.h>
#define CSI_ARGS_MAX 16
#define CSI_LEADER_MAX 16
#define CSI_INTERMED_MAX 16
static void do_control(VTerm *vt, unsigned char control)
{
if(vt->parser_callbacks && vt->parser_callbacks->control)
if((*vt->parser_callbacks->control)(control, vt->cbdata))
return;
DEBUG_LOG1("libvterm: Unhandled control 0x%02x\n", control);
}
static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char command)
{
int i = 0;
int leaderlen = 0;
char leader[CSI_LEADER_MAX];
int argcount = 1; /* Always at least 1 arg */
long csi_args[CSI_ARGS_MAX];
int argi;
int intermedlen = 0;
char intermed[CSI_INTERMED_MAX];
/* Extract leader bytes 0x3c to 0x3f */
for( ; i < (int)arglen; i++) {
if(args[i] < 0x3c || args[i] > 0x3f)
break;
if(leaderlen < CSI_LEADER_MAX-1)
leader[leaderlen++] = args[i];
}
leader[leaderlen] = 0;
for( ; i < (int)arglen; i++)
if(args[i] == 0x3b || args[i] == 0x3a) /* ; or : */
argcount++;
/* TODO: Consider if these buffers should live in the VTerm struct itself */
if(argcount > CSI_ARGS_MAX)
argcount = CSI_ARGS_MAX;
for(argi = 0; argi < argcount; argi++)
csi_args[argi] = CSI_ARG_MISSING;
argi = 0;
for(i = leaderlen; i < (int)arglen && argi < argcount; i++) {
switch(args[i]) {
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34:
case 0x35: case 0x36: case 0x37: case 0x38: case 0x39:
if(csi_args[argi] == CSI_ARG_MISSING)
csi_args[argi] = 0;
csi_args[argi] *= 10;
csi_args[argi] += args[i] - '0';
break;
case 0x3a:
csi_args[argi] |= CSI_ARG_FLAG_MORE;
/* FALLTHROUGH */
case 0x3b:
argi++;
break;
default:
goto done_leader;
}
}
done_leader: ;
for( ; i < (int)arglen; i++) {
if((args[i] & 0xf0) != 0x20)
break;
if(intermedlen < CSI_INTERMED_MAX-1)
intermed[intermedlen++] = args[i];
}
intermed[intermedlen] = 0;
if(i < (int)arglen) {
DEBUG_LOG2("libvterm: TODO unhandled CSI bytes \"%.*s\"\n", (int)(arglen - i), args + i);
}
#if 0
printf("Parsed CSI args %.*s as:\n", arglen, args);
printf(" leader: %s\n", leader);
for(argi = 0; argi < argcount; argi++) {
printf(" %lu", CSI_ARG(csi_args[argi]));
if(!CSI_ARG_HAS_MORE(csi_args[argi]))
printf("\n");
printf(" intermed: %s\n", intermed);
}
#endif
if(vt->parser_callbacks && vt->parser_callbacks->csi)
if((*vt->parser_callbacks->csi)(leaderlen ? leader : NULL, csi_args, argcount, intermedlen ? intermed : NULL, command, vt->cbdata))
return;
DEBUG_LOG3("libvterm: Unhandled CSI %.*s %c\n", (int)arglen, args, command);
}
static void append_strbuffer(VTerm *vt, const char *str, size_t len)
{
if(len > vt->strbuffer_len - vt->strbuffer_cur) {
len = vt->strbuffer_len - vt->strbuffer_cur;
DEBUG_LOG1("Truncating strbuffer preserve to %zd bytes\n", len);
}
if(len > 0) {
strncpy(vt->strbuffer + vt->strbuffer_cur, str, len);
vt->strbuffer_cur += len;
}
}
static size_t do_string(VTerm *vt, const char *str_frag, size_t len)
{
size_t eaten;
if(vt->strbuffer_cur) {
if(str_frag)
append_strbuffer(vt, str_frag, len);
str_frag = vt->strbuffer;
len = vt->strbuffer_cur;
}
else if(!str_frag) {
DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n");
len = 0;
}
vt->strbuffer_cur = 0;
switch(vt->parser_state) {
case NORMAL:
if(vt->parser_callbacks && vt->parser_callbacks->text)
if((eaten = (*vt->parser_callbacks->text)(str_frag, len, vt->cbdata)))
return eaten;
DEBUG_LOG1("libvterm: Unhandled text (%zu chars)\n", len);
return 0;
case ESC:
if(len == 1 && str_frag[0] >= 0x40 && str_frag[0] < 0x60) {
/* C1 emulations using 7bit clean */
/* ESC 0x40 == 0x80 */
do_control(vt, str_frag[0] + 0x40);
return 0;
}
if(vt->parser_callbacks && vt->parser_callbacks->escape)
if((*vt->parser_callbacks->escape)(str_frag, len, vt->cbdata))
return 0;
DEBUG_LOG1("libvterm: Unhandled escape ESC 0x%02x\n", str_frag[len-1]);
return 0;
case CSI:
do_string_csi(vt, str_frag, len - 1, str_frag[len - 1]);
return 0;
case OSC:
if(vt->parser_callbacks && vt->parser_callbacks->osc)
if((*vt->parser_callbacks->osc)(str_frag, len, vt->cbdata))
return 0;
DEBUG_LOG2("libvterm: Unhandled OSC %.*s\n", (int)len, str_frag);
return 0;
case DCS:
if(vt->parser_callbacks && vt->parser_callbacks->dcs)
if((*vt->parser_callbacks->dcs)(str_frag, len, vt->cbdata))
return 0;
DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)len, str_frag);
return 0;
case ESC_IN_OSC:
case ESC_IN_DCS:
DEBUG_LOG("libvterm: ARGH! Should never do_string() in ESC_IN_{OSC,DCS}\n");
return 0;
}
return 0;
}
size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
{
size_t pos = 0;
const char *string_start;
switch(vt->parser_state) {
case NORMAL:
string_start = NULL;
break;
case ESC:
case ESC_IN_OSC:
case ESC_IN_DCS:
case CSI:
case OSC:
case DCS:
string_start = bytes;
break;
}
#define ENTER_STRING_STATE(st) do { vt->parser_state = st; string_start = bytes + pos + 1; } while(0)
#define ENTER_NORMAL_STATE() do { vt->parser_state = NORMAL; string_start = NULL; } while(0)
for( ; pos < len; pos++) {
unsigned char c = bytes[pos];
if(c == 0x00 || c == 0x7f) { /* NUL, DEL */
if(vt->parser_state != NORMAL) {
append_strbuffer(vt, string_start, bytes + pos - string_start);
string_start = bytes + pos + 1;
}
continue;
}
if(c == 0x18 || c == 0x1a) { /* CAN, SUB */
ENTER_NORMAL_STATE();
continue;
}
else if(c == 0x1b) { /* ESC */
if(vt->parser_state == OSC)
vt->parser_state = ESC_IN_OSC;
else if(vt->parser_state == DCS)
vt->parser_state = ESC_IN_DCS;
else
ENTER_STRING_STATE(ESC);
continue;
}
else if(c == 0x07 && /* BEL, can stand for ST in OSC or DCS state */
(vt->parser_state == OSC || vt->parser_state == DCS)) {
/* fallthrough */
}
else if(c < 0x20) { /* other C0 */
if(vt->parser_state != NORMAL)
append_strbuffer(vt, string_start, bytes + pos - string_start);
do_control(vt, c);
if(vt->parser_state != NORMAL)
string_start = bytes + pos + 1;
continue;
}
/* else fallthrough */
switch(vt->parser_state) {
case ESC_IN_OSC:
case ESC_IN_DCS:
if(c == 0x5c) { /* ST */
switch(vt->parser_state) {
case ESC_IN_OSC: vt->parser_state = OSC; break;
case ESC_IN_DCS: vt->parser_state = DCS; break;
default: break;
}
do_string(vt, string_start, bytes + pos - string_start - 1);
ENTER_NORMAL_STATE();
break;
}
vt->parser_state = ESC;
string_start = bytes + pos;
/* else fallthrough */
case ESC:
switch(c) {
case 0x50: /* DCS */
ENTER_STRING_STATE(DCS);
break;
case 0x5b: /* CSI */
ENTER_STRING_STATE(CSI);
break;
case 0x5d: /* OSC */
ENTER_STRING_STATE(OSC);
break;
default:
if(c >= 0x30 && c < 0x7f) {
/* +1 to pos because we want to include this command byte as well */
do_string(vt, string_start, bytes + pos - string_start + 1);
ENTER_NORMAL_STATE();
}
else if(c >= 0x20 && c < 0x30) {
/* intermediate byte */
}
else {
DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c);
}
}
break;
case CSI:
if(c >= 0x40 && c <= 0x7f) {
/* +1 to pos because we want to include this command byte as well */
do_string(vt, string_start, bytes + pos - string_start + 1);
ENTER_NORMAL_STATE();
}
break;
case OSC:
case DCS:
if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
do_string(vt, string_start, bytes + pos - string_start);
ENTER_NORMAL_STATE();
}
break;
case NORMAL:
if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
switch(c) {
case 0x90: /* DCS */
ENTER_STRING_STATE(DCS);
break;
case 0x9b: /* CSI */
ENTER_STRING_STATE(CSI);
break;
case 0x9d: /* OSC */
ENTER_STRING_STATE(OSC);
break;
default:
do_control(vt, c);
break;
}
}
else {
size_t text_eaten = do_string(vt, bytes + pos, len - pos);
if(text_eaten == 0) {
string_start = bytes + pos;
goto pause;
}
pos += (text_eaten - 1); /* we'll ++ it again in a moment */
}
break;
}
}
pause:
if(string_start && string_start < len + bytes) {
size_t remaining = len - (string_start - bytes);
append_strbuffer(vt, string_start, remaining);
}
return len;
}

504
src/libvterm/src/pen.c Normal file
View File

@ -0,0 +1,504 @@
#include "vterm_internal.h"
#include <stdio.h>
static const VTermColor ansi_colors[] = {
/* R G B */
{ 0, 0, 0 }, /* black */
{ 224, 0, 0 }, /* red */
{ 0, 224, 0 }, /* green */
{ 224, 224, 0 }, /* yellow */
{ 0, 0, 224 }, /* blue */
{ 224, 0, 224 }, /* magenta */
{ 0, 224, 224 }, /* cyan */
{ 224, 224, 224 }, /* white == light grey */
/* high intensity */
{ 128, 128, 128 }, /* black */
{ 255, 64, 64 }, /* red */
{ 64, 255, 64 }, /* green */
{ 255, 255, 64 }, /* yellow */
{ 64, 64, 255 }, /* blue */
{ 255, 64, 255 }, /* magenta */
{ 64, 255, 255 }, /* cyan */
{ 255, 255, 255 }, /* white for real */
};
static int ramp6[] = {
0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF,
};
static int ramp24[] = {
0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79,
0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF,
};
static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col)
{
if(index >= 0 && index < 16) {
*col = state->colors[index];
return true;
}
return false;
}
static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col)
{
if(index >= 0 && index < 16) {
/* Normal 8 colours or high intensity - parse as palette 0 */
return lookup_colour_ansi(state, index, col);
}
else if(index >= 16 && index < 232) {
/* 216-colour cube */
index -= 16;
col->blue = ramp6[index % 6];
col->green = ramp6[index/6 % 6];
col->red = ramp6[index/6/6 % 6];
return true;
}
else if(index >= 232 && index < 256) {
/* 24 greyscales */
index -= 232;
col->blue = ramp24[index];
col->green = ramp24[index];
col->red = ramp24[index];
return true;
}
return false;
}
static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col, int *index)
{
switch(palette) {
case 2: /* RGB mode - 3 args contain colour values directly */
if(argcount < 3)
return argcount;
col->red = CSI_ARG(args[0]);
col->green = CSI_ARG(args[1]);
col->blue = CSI_ARG(args[2]);
return 3;
case 5: /* XTerm 256-colour mode */
if(index)
*index = CSI_ARG_OR(args[0], -1);
lookup_colour_palette(state, argcount ? CSI_ARG_OR(args[0], -1) : -1, col);
return argcount ? 1 : 0;
default:
DEBUG_LOG1("Unrecognised colour palette %d\n", palette);
return 0;
}
}
/* Some conveniences */
static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type UNUSED, VTermValue *val)
{
#ifdef DEBUG
if(type != vterm_get_attr_type(attr)) {
DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n",
attr, vterm_get_attr_type(attr), type);
return;
}
#endif
if(state->callbacks && state->callbacks->setpenattr)
(*state->callbacks->setpenattr)(attr, val, state->cbdata);
}
static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean)
{
VTermValue val;
val.boolean = boolean;
setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val);
}
static void setpenattr_int(VTermState *state, VTermAttr attr, int number)
{
VTermValue val;
val.number = number;
setpenattr(state, attr, VTERM_VALUETYPE_INT, &val);
}
static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color)
{
VTermValue val;
val.color = color;
setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val);
}
static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col)
{
VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg;
lookup_colour_ansi(state, col, colp);
setpenattr_col(state, attr, *colp);
}
INTERNAL void vterm_state_newpen(VTermState *state)
{
int col;
/* 90% grey so that pure white is brighter */
state->default_fg.red = state->default_fg.green = state->default_fg.blue = 240;
state->default_bg.red = state->default_bg.green = state->default_bg.blue = 0;
for(col = 0; col < 16; col++)
state->colors[col] = ansi_colors[col];
}
INTERNAL void vterm_state_resetpen(VTermState *state)
{
state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
state->pen.underline = 0; setpenattr_int( state, VTERM_ATTR_UNDERLINE, 0);
state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
state->pen.font = 0; setpenattr_int( state, VTERM_ATTR_FONT, 0);
state->fg_index = -1;
state->bg_index = -1;
state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg);
state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg);
}
INTERNAL void vterm_state_savepen(VTermState *state, int save)
{
if(save) {
state->saved.pen = state->pen;
}
else {
state->pen = state->saved.pen;
setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold);
setpenattr_int( state, VTERM_ATTR_UNDERLINE, state->pen.underline);
setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic);
setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink);
setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse);
setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike);
setpenattr_int( state, VTERM_ATTR_FONT, state->pen.font);
setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg);
setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg);
}
}
void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg)
{
*default_fg = state->default_fg;
*default_bg = state->default_bg;
}
void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col)
{
lookup_colour_palette(state, index, col);
}
void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg)
{
state->default_fg = *default_fg;
state->default_bg = *default_bg;
}
void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col)
{
if(index >= 0 && index < 16)
state->colors[index] = *col;
}
void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright)
{
state->bold_is_highbright = bold_is_highbright;
}
INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount)
{
/* SGR - ECMA-48 8.3.117 */
int argi = 0;
int value;
while(argi < argcount) {
/* This logic is easier to do 'done' backwards; set it true, and make it
false again in the 'default' case */
int done = 1;
long arg;
switch(arg = CSI_ARG(args[argi])) {
case CSI_ARG_MISSING:
case 0: /* Reset */
vterm_state_resetpen(state);
break;
case 1: /* Bold on */
state->pen.bold = 1;
setpenattr_bool(state, VTERM_ATTR_BOLD, 1);
if(state->fg_index > -1 && state->fg_index < 8 && state->bold_is_highbright)
set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, state->fg_index + (state->pen.bold ? 8 : 0));
break;
case 3: /* Italic on */
state->pen.italic = 1;
setpenattr_bool(state, VTERM_ATTR_ITALIC, 1);
break;
case 4: /* Underline single */
state->pen.underline = 1;
setpenattr_int(state, VTERM_ATTR_UNDERLINE, 1);
break;
case 5: /* Blink */
state->pen.blink = 1;
setpenattr_bool(state, VTERM_ATTR_BLINK, 1);
break;
case 7: /* Reverse on */
state->pen.reverse = 1;
setpenattr_bool(state, VTERM_ATTR_REVERSE, 1);
break;
case 9: /* Strikethrough on */
state->pen.strike = 1;
setpenattr_bool(state, VTERM_ATTR_STRIKE, 1);
break;
case 10: case 11: case 12: case 13: case 14:
case 15: case 16: case 17: case 18: case 19: /* Select font */
state->pen.font = CSI_ARG(args[argi]) - 10;
setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);
break;
case 21: /* Underline double */
state->pen.underline = 2;
setpenattr_int(state, VTERM_ATTR_UNDERLINE, 2);
break;
case 22: /* Bold off */
state->pen.bold = 0;
setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
break;
case 23: /* Italic and Gothic (currently unsupported) off */
state->pen.italic = 0;
setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
break;
case 24: /* Underline off */
state->pen.underline = 0;
setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0);
break;
case 25: /* Blink off */
state->pen.blink = 0;
setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
break;
case 27: /* Reverse off */
state->pen.reverse = 0;
setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
break;
case 29: /* Strikethrough off */
state->pen.strike = 0;
setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
break;
case 30: case 31: case 32: case 33:
case 34: case 35: case 36: case 37: /* Foreground colour palette */
value = CSI_ARG(args[argi]) - 30;
state->fg_index = value;
if(state->pen.bold && state->bold_is_highbright)
value += 8;
set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
break;
case 38: /* Foreground colour alternative palette */
state->fg_index = -1;
if(argcount - argi < 1)
return;
argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg, &state->fg_index);
setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
break;
case 39: /* Foreground colour default */
state->fg_index = -1;
state->pen.fg = state->default_fg;
setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
break;
case 40: case 41: case 42: case 43:
case 44: case 45: case 46: case 47: /* Background colour palette */
value = CSI_ARG(args[argi]) - 40;
state->bg_index = value;
set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
break;
case 48: /* Background colour alternative palette */
state->bg_index = -1;
if(argcount - argi < 1)
return;
argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg, &state->bg_index);
setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
break;
case 49: /* Default background */
state->bg_index = -1;
state->pen.bg = state->default_bg;
setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
break;
case 90: case 91: case 92: case 93:
case 94: case 95: case 96: case 97: /* Foreground colour high-intensity palette */
value = CSI_ARG(args[argi]) - 90 + 8;
state->fg_index = value;
set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
break;
case 100: case 101: case 102: case 103:
case 104: case 105: case 106: case 107: /* Background colour high-intensity palette */
value = CSI_ARG(args[argi]) - 100 + 8;
state->bg_index = value;
set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
break;
default:
done = 0;
break;
}
if(!done)
{
DEBUG_LOG1("libvterm: Unhandled CSI SGR %lu\n", arg);
}
while(CSI_ARG_HAS_MORE(args[argi++]));
}
}
INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount UNUSED)
{
int argi = 0;
if(state->pen.bold)
args[argi++] = 1;
if(state->pen.italic)
args[argi++] = 3;
if(state->pen.underline == 1)
args[argi++] = 4;
if(state->pen.blink)
args[argi++] = 5;
if(state->pen.reverse)
args[argi++] = 7;
if(state->pen.strike)
args[argi++] = 9;
if(state->pen.font)
args[argi++] = 10 + state->pen.font;
if(state->pen.underline == 2)
args[argi++] = 21;
if(state->fg_index >= 0 && state->fg_index < 8)
args[argi++] = 30 + state->fg_index;
else if(state->fg_index >= 8 && state->fg_index < 16)
args[argi++] = 90 + state->fg_index - 8;
else if(state->fg_index >= 16 && state->fg_index < 256) {
args[argi++] = CSI_ARG_FLAG_MORE|38;
args[argi++] = CSI_ARG_FLAG_MORE|5;
args[argi++] = state->fg_index;
}
else if(state->fg_index == -1) {
/* Send palette 2 if the actual FG colour is not default */
if(state->pen.fg.red != state->default_fg.red ||
state->pen.fg.green != state->default_fg.green ||
state->pen.fg.blue != state->default_fg.blue ) {
args[argi++] = CSI_ARG_FLAG_MORE|38;
args[argi++] = CSI_ARG_FLAG_MORE|2;
args[argi++] = CSI_ARG_FLAG_MORE | state->pen.fg.red;
args[argi++] = CSI_ARG_FLAG_MORE | state->pen.fg.green;
args[argi++] = state->pen.fg.blue;
}
}
if(state->bg_index >= 0 && state->bg_index < 8)
args[argi++] = 40 + state->bg_index;
else if(state->bg_index >= 8 && state->bg_index < 16)
args[argi++] = 100 + state->bg_index - 8;
else if(state->bg_index >= 16 && state->bg_index < 256) {
args[argi++] = CSI_ARG_FLAG_MORE|48;
args[argi++] = CSI_ARG_FLAG_MORE|5;
args[argi++] = state->bg_index;
}
else if(state->bg_index == -1) {
/* Send palette 2 if the actual BG colour is not default */
if(state->pen.bg.red != state->default_bg.red ||
state->pen.bg.green != state->default_bg.green ||
state->pen.bg.blue != state->default_bg.blue ) {
args[argi++] = CSI_ARG_FLAG_MORE|48;
args[argi++] = CSI_ARG_FLAG_MORE|2;
args[argi++] = CSI_ARG_FLAG_MORE | state->pen.bg.red;
args[argi++] = CSI_ARG_FLAG_MORE | state->pen.bg.green;
args[argi++] = state->pen.bg.blue;
}
}
return argi;
}
int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val)
{
switch(attr) {
case VTERM_ATTR_BOLD:
val->boolean = state->pen.bold;
return 1;
case VTERM_ATTR_UNDERLINE:
val->number = state->pen.underline;
return 1;
case VTERM_ATTR_ITALIC:
val->boolean = state->pen.italic;
return 1;
case VTERM_ATTR_BLINK:
val->boolean = state->pen.blink;
return 1;
case VTERM_ATTR_REVERSE:
val->boolean = state->pen.reverse;
return 1;
case VTERM_ATTR_STRIKE:
val->boolean = state->pen.strike;
return 1;
case VTERM_ATTR_FONT:
val->number = state->pen.font;
return 1;
case VTERM_ATTR_FOREGROUND:
val->color = state->pen.fg;
return 1;
case VTERM_ATTR_BACKGROUND:
val->color = state->pen.bg;
return 1;
}
return 0;
}

56
src/libvterm/src/rect.h Normal file
View File

@ -0,0 +1,56 @@
/*
* Some utility functions on VTermRect structures
*/
#define STRFrect "(%d,%d-%d,%d)"
#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col
/* Expand dst to contain src as well */
static void rect_expand(VTermRect *dst, VTermRect *src)
{
if(dst->start_row > src->start_row) dst->start_row = src->start_row;
if(dst->start_col > src->start_col) dst->start_col = src->start_col;
if(dst->end_row < src->end_row) dst->end_row = src->end_row;
if(dst->end_col < src->end_col) dst->end_col = src->end_col;
}
/* Clip the dst to ensure it does not step outside of bounds */
static void rect_clip(VTermRect *dst, VTermRect *bounds)
{
if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row;
if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col;
if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row;
if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col;
/* Ensure it doesn't end up negatively-sized */
if(dst->end_row < dst->start_row) dst->end_row = dst->start_row;
if(dst->end_col < dst->start_col) dst->end_col = dst->start_col;
}
/* True if the two rectangles are equal */
static int rect_equal(VTermRect *a, VTermRect *b)
{
return (a->start_row == b->start_row) &&
(a->start_col == b->start_col) &&
(a->end_row == b->end_row) &&
(a->end_col == b->end_col);
}
/* True if small is contained entirely within big */
static int rect_contains(VTermRect *big, VTermRect *small)
{
if(small->start_row < big->start_row) return 0;
if(small->start_col < big->start_col) return 0;
if(small->end_row > big->end_row) return 0;
if(small->end_col > big->end_col) return 0;
return 1;
}
/* True if the rectangles overlap at all */
static int rect_intersects(VTermRect *a, VTermRect *b)
{
if(a->start_row > b->end_row || b->start_row > a->end_row)
return 0;
if(a->start_col > b->end_col || b->start_col > a->end_col)
return 0;
return 1;
}

937
src/libvterm/src/screen.c Normal file
View File

@ -0,0 +1,937 @@
#include "vterm_internal.h"
#include <stdio.h>
#include <string.h>
#include "rect.h"
#include "utf8.h"
#define UNICODE_SPACE 0x20
#define UNICODE_LINEFEED 0x0a
/* State of the pen at some moment in time, also used in a cell */
typedef struct
{
/* After the bitfield */
VTermColor fg, bg;
unsigned int bold : 1;
unsigned int underline : 2;
unsigned int italic : 1;
unsigned int blink : 1;
unsigned int reverse : 1;
unsigned int strike : 1;
unsigned int font : 4; /* 0 to 9 */
/* Extra state storage that isn't strictly pen-related */
unsigned int protected_cell : 1;
unsigned int dwl : 1; /* on a DECDWL or DECDHL line */
unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */
} ScreenPen;
/* Internal representation of a screen cell */
typedef struct
{
uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
ScreenPen pen;
} ScreenCell;
static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell);
struct VTermScreen
{
VTerm *vt;
VTermState *state;
const VTermScreenCallbacks *callbacks;
void *cbdata;
VTermDamageSize damage_merge;
/* start_row == -1 => no damage */
VTermRect damaged;
VTermRect pending_scrollrect;
int pending_scroll_downward, pending_scroll_rightward;
int rows;
int cols;
int global_reverse;
/* Primary and Altscreen. buffers[1] is lazily allocated as needed */
ScreenCell *buffers[2];
/* buffer will == buffers[0] or buffers[1], depending on altscreen */
ScreenCell *buffer;
/* buffer for a single screen row used in scrollback storage callbacks */
VTermScreenCell *sb_buffer;
ScreenPen pen;
};
static ScreenCell *getcell(const VTermScreen *screen, int row, int col)
{
if(row < 0 || row >= screen->rows)
return NULL;
if(col < 0 || col >= screen->cols)
return NULL;
return screen->buffer + (screen->cols * row) + col;
}
static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols)
{
ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);
int row, col;
for(row = 0; row < new_rows; row++) {
for(col = 0; col < new_cols; col++) {
ScreenCell *new_cell = new_buffer + row*new_cols + col;
if(buffer && row < screen->rows && col < screen->cols)
*new_cell = buffer[row * screen->cols + col];
else {
new_cell->chars[0] = 0;
new_cell->pen = screen->pen;
}
}
}
if(buffer)
vterm_allocator_free(screen->vt, buffer);
return new_buffer;
}
static void damagerect(VTermScreen *screen, VTermRect rect)
{
VTermRect emit;
switch(screen->damage_merge) {
case VTERM_DAMAGE_CELL:
/* Always emit damage event */
emit = rect;
break;
case VTERM_DAMAGE_ROW:
/* Emit damage longer than one row. Try to merge with existing damage in
* the same row */
if(rect.end_row > rect.start_row + 1) {
/* Bigger than 1 line - flush existing, emit this */
vterm_screen_flush_damage(screen);
emit = rect;
}
else if(screen->damaged.start_row == -1) {
/* None stored yet */
screen->damaged = rect;
return;
}
else if(rect.start_row == screen->damaged.start_row) {
/* Merge with the stored line */
if(screen->damaged.start_col > rect.start_col)
screen->damaged.start_col = rect.start_col;
if(screen->damaged.end_col < rect.end_col)
screen->damaged.end_col = rect.end_col;
return;
}
else {
/* Emit the currently stored line, store a new one */
emit = screen->damaged;
screen->damaged = rect;
}
break;
case VTERM_DAMAGE_SCREEN:
case VTERM_DAMAGE_SCROLL:
/* Never emit damage event */
if(screen->damaged.start_row == -1)
screen->damaged = rect;
else {
rect_expand(&screen->damaged, &rect);
}
return;
default:
DEBUG_LOG1("TODO: Maybe merge damage for level %d\n", screen->damage_merge);
return;
}
if(screen->callbacks && screen->callbacks->damage)
(*screen->callbacks->damage)(emit, screen->cbdata);
}
static void damagescreen(VTermScreen *screen)
{
VTermRect rect = {0,0,0,0};
rect.end_row = screen->rows;
rect.end_col = screen->cols;
damagerect(screen, rect);
}
static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
{
int i;
int col;
VTermRect rect;
VTermScreen *screen = user;
ScreenCell *cell = getcell(screen, pos.row, pos.col);
if(!cell)
return 0;
for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
cell->chars[i] = info->chars[i];
cell->pen = screen->pen;
}
if(i < VTERM_MAX_CHARS_PER_CELL)
cell->chars[i] = 0;
for(col = 1; col < info->width; col++)
getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1;
rect.start_row = pos.row;
rect.end_row = pos.row+1;
rect.start_col = pos.col;
rect.end_col = pos.col+info->width;
cell->pen.protected_cell = info->protected_cell;
cell->pen.dwl = info->dwl;
cell->pen.dhl = info->dhl;
damagerect(screen, rect);
return 1;
}
static int moverect_internal(VTermRect dest, VTermRect src, void *user)
{
VTermScreen *screen = user;
if(screen->callbacks && screen->callbacks->sb_pushline &&
dest.start_row == 0 && dest.start_col == 0 && /* starts top-left corner */
dest.end_col == screen->cols && /* full width */
screen->buffer == screen->buffers[0]) { /* not altscreen */
VTermPos pos;
for(pos.row = 0; pos.row < src.start_row; pos.row++) {
for(pos.col = 0; pos.col < screen->cols; pos.col++)
vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
(screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
}
}
{
int cols = src.end_col - src.start_col;
int downward = src.start_row - dest.start_row;
int init_row, test_row, inc_row;
int row;
if(downward < 0) {
init_row = dest.end_row - 1;
test_row = dest.start_row - 1;
inc_row = -1;
}
else {
init_row = dest.start_row;
test_row = dest.end_row;
inc_row = +1;
}
for(row = init_row; row != test_row; row += inc_row)
memmove(getcell(screen, row, dest.start_col),
getcell(screen, row + downward, src.start_col),
cols * sizeof(ScreenCell));
}
return 1;
}
static int moverect_user(VTermRect dest, VTermRect src, void *user)
{
VTermScreen *screen = user;
if(screen->callbacks && screen->callbacks->moverect) {
if(screen->damage_merge != VTERM_DAMAGE_SCROLL)
/* Avoid an infinite loop */
vterm_screen_flush_damage(screen);
if((*screen->callbacks->moverect)(dest, src, screen->cbdata))
return 1;
}
damagerect(screen, dest);
return 1;
}
static int erase_internal(VTermRect rect, int selective, void *user)
{
VTermScreen *screen = user;
int row, col;
for(row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) {
const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row);
for(col = rect.start_col; col < rect.end_col; col++) {
ScreenCell *cell = getcell(screen, row, col);
if(selective && cell->pen.protected_cell)
continue;
cell->chars[0] = 0;
cell->pen = screen->pen;
cell->pen.dwl = info->doublewidth;
cell->pen.dhl = info->doubleheight;
}
}
return 1;
}
static int erase_user(VTermRect rect, int selective UNUSED, void *user)
{
VTermScreen *screen = user;
damagerect(screen, rect);
return 1;
}
static int erase(VTermRect rect, int selective, void *user)
{
erase_internal(rect, selective, user);
return erase_user(rect, 0, user);
}
static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
{
VTermScreen *screen = user;
if(screen->damage_merge != VTERM_DAMAGE_SCROLL) {
vterm_scroll_rect(rect, downward, rightward,
moverect_internal, erase_internal, screen);
vterm_screen_flush_damage(screen);
vterm_scroll_rect(rect, downward, rightward,
moverect_user, erase_user, screen);
return 1;
}
if(screen->damaged.start_row != -1 &&
!rect_intersects(&rect, &screen->damaged)) {
vterm_screen_flush_damage(screen);
}
if(screen->pending_scrollrect.start_row == -1) {
screen->pending_scrollrect = rect;
screen->pending_scroll_downward = downward;
screen->pending_scroll_rightward = rightward;
}
else if(rect_equal(&screen->pending_scrollrect, &rect) &&
((screen->pending_scroll_downward == 0 && downward == 0) ||
(screen->pending_scroll_rightward == 0 && rightward == 0))) {
screen->pending_scroll_downward += downward;
screen->pending_scroll_rightward += rightward;
}
else {
vterm_screen_flush_damage(screen);
screen->pending_scrollrect = rect;
screen->pending_scroll_downward = downward;
screen->pending_scroll_rightward = rightward;
}
vterm_scroll_rect(rect, downward, rightward,
moverect_internal, erase_internal, screen);
if(screen->damaged.start_row == -1)
return 1;
if(rect_contains(&rect, &screen->damaged)) {
/* Scroll region entirely contains the damage; just move it */
vterm_rect_move(&screen->damaged, -downward, -rightward);
rect_clip(&screen->damaged, &rect);
}
/* There are a number of possible cases here, but lets restrict this to only
* the common case where we might actually gain some performance by
* optimising it. Namely, a vertical scroll that neatly cuts the damage
* region in half.
*/
else if(rect.start_col <= screen->damaged.start_col &&
rect.end_col >= screen->damaged.end_col &&
rightward == 0) {
if(screen->damaged.start_row >= rect.start_row &&
screen->damaged.start_row < rect.end_row) {
screen->damaged.start_row -= downward;
if(screen->damaged.start_row < rect.start_row)
screen->damaged.start_row = rect.start_row;
if(screen->damaged.start_row > rect.end_row)
screen->damaged.start_row = rect.end_row;
}
if(screen->damaged.end_row >= rect.start_row &&
screen->damaged.end_row < rect.end_row) {
screen->damaged.end_row -= downward;
if(screen->damaged.end_row < rect.start_row)
screen->damaged.end_row = rect.start_row;
if(screen->damaged.end_row > rect.end_row)
screen->damaged.end_row = rect.end_row;
}
}
else {
DEBUG_LOG2("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n",
ARGSrect(screen->damaged), ARGSrect(rect));
}
return 1;
}
static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
{
VTermScreen *screen = user;
if(screen->callbacks && screen->callbacks->movecursor)
return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata);
return 0;
}
static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
{
VTermScreen *screen = user;
switch(attr) {
case VTERM_ATTR_BOLD:
screen->pen.bold = val->boolean;
return 1;
case VTERM_ATTR_UNDERLINE:
screen->pen.underline = val->number;
return 1;
case VTERM_ATTR_ITALIC:
screen->pen.italic = val->boolean;
return 1;
case VTERM_ATTR_BLINK:
screen->pen.blink = val->boolean;
return 1;
case VTERM_ATTR_REVERSE:
screen->pen.reverse = val->boolean;
return 1;
case VTERM_ATTR_STRIKE:
screen->pen.strike = val->boolean;
return 1;
case VTERM_ATTR_FONT:
screen->pen.font = val->number;
return 1;
case VTERM_ATTR_FOREGROUND:
screen->pen.fg = val->color;
return 1;
case VTERM_ATTR_BACKGROUND:
screen->pen.bg = val->color;
return 1;
}
return 0;
}
static int settermprop(VTermProp prop, VTermValue *val, void *user)
{
VTermScreen *screen = user;
switch(prop) {
case VTERM_PROP_ALTSCREEN:
if(val->boolean && !screen->buffers[1])
return 0;
screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0];
/* only send a damage event on disable; because during enable there's an
* erase that sends a damage anyway
*/
if(!val->boolean)
damagescreen(screen);
break;
case VTERM_PROP_REVERSE:
screen->global_reverse = val->boolean;
damagescreen(screen);
break;
default:
; /* ignore */
}
if(screen->callbacks && screen->callbacks->settermprop)
return (*screen->callbacks->settermprop)(prop, val, screen->cbdata);
return 1;
}
static int bell(void *user)
{
VTermScreen *screen = user;
if(screen->callbacks && screen->callbacks->bell)
return (*screen->callbacks->bell)(screen->cbdata);
return 0;
}
static int resize(int new_rows, int new_cols, VTermPos *delta, void *user)
{
VTermScreen *screen = user;
int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]);
int old_rows = screen->rows;
int old_cols = screen->cols;
int first_blank_row;
if(!is_altscreen && new_rows < old_rows) {
/* Fewer rows - determine if we're going to scroll at all, and if so, push
those lines to scrollback */
VTermPos pos = { 0, 0 };
VTermPos cursor = screen->state->pos;
/* Find the first blank row after the cursor. */
for(pos.row = old_rows - 1; pos.row >= new_rows; pos.row--)
if(!vterm_screen_is_eol(screen, pos) || cursor.row == pos.row)
break;
first_blank_row = pos.row + 1;
if(first_blank_row > new_rows) {
VTermRect rect = {0,0,0,0};
rect.end_row = old_rows;
rect.end_col = old_cols;
scrollrect(rect, first_blank_row - new_rows, 0, user);
vterm_screen_flush_damage(screen);
delta->row -= first_blank_row - new_rows;
}
}
screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols);
if(screen->buffers[1])
screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols);
screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0];
screen->rows = new_rows;
screen->cols = new_cols;
if(screen->sb_buffer)
vterm_allocator_free(screen->vt, screen->sb_buffer);
screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
if(new_cols > old_cols) {
VTermRect rect;
rect.start_row = 0;
rect.end_row = old_rows;
rect.start_col = old_cols;
rect.end_col = new_cols;
damagerect(screen, rect);
}
if(new_rows > old_rows) {
if(!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) {
int rows = new_rows - old_rows;
while(rows) {
VTermRect rect = {0,0,0,0};
VTermPos pos = { 0, 0 };
if(!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata)))
break;
rect.end_row = screen->rows;
rect.end_col = screen->cols;
scrollrect(rect, -1, 0, user);
for(pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width)
vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col);
rect.end_row = 1;
damagerect(screen, rect);
vterm_screen_flush_damage(screen);
rows--;
delta->row++;
}
}
{
VTermRect rect;
rect.start_row = old_rows;
rect.end_row = new_rows;
rect.start_col = 0;
rect.end_col = new_cols;
damagerect(screen, rect);
}
}
if(screen->callbacks && screen->callbacks->resize)
return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata);
return 1;
}
static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)
{
VTermScreen *screen = user;
int col;
VTermRect rect;
if(newinfo->doublewidth != oldinfo->doublewidth ||
newinfo->doubleheight != oldinfo->doubleheight) {
for(col = 0; col < screen->cols; col++) {
ScreenCell *cell = getcell(screen, row, col);
cell->pen.dwl = newinfo->doublewidth;
cell->pen.dhl = newinfo->doubleheight;
}
rect.start_row = row;
rect.end_row = row + 1;
rect.start_col = 0;
rect.end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols;
damagerect(screen, rect);
if(newinfo->doublewidth) {
rect.start_col = screen->cols / 2;
rect.end_col = screen->cols;
erase_internal(rect, 0, user);
}
}
return 1;
}
static VTermStateCallbacks state_cbs = {
&putglyph, /* putglyph */
&movecursor, /* movecursor */
&scrollrect, /* scrollrect */
NULL, /* moverect */
&erase, /* erase */
NULL, /* initpen */
&setpenattr, /* setpenattr */
&settermprop, /* settermprop */
&bell, /* bell */
&resize, /* resize */
&setlineinfo /* setlineinfo */
};
static VTermScreen *screen_new(VTerm *vt)
{
VTermState *state = vterm_obtain_state(vt);
VTermScreen *screen;
int rows, cols;
if(!state)
return NULL;
screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
vterm_get_size(vt, &rows, &cols);
screen->vt = vt;
screen->state = state;
screen->damage_merge = VTERM_DAMAGE_CELL;
screen->damaged.start_row = -1;
screen->pending_scrollrect.start_row = -1;
screen->rows = rows;
screen->cols = cols;
screen->callbacks = NULL;
screen->cbdata = NULL;
screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols);
screen->buffer = screen->buffers[0];
screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols);
vterm_state_set_callbacks(screen->state, &state_cbs, screen);
return screen;
}
INTERNAL void vterm_screen_free(VTermScreen *screen)
{
vterm_allocator_free(screen->vt, screen->buffers[0]);
if(screen->buffers[1])
vterm_allocator_free(screen->vt, screen->buffers[1]);
vterm_allocator_free(screen->vt, screen->sb_buffer);
vterm_allocator_free(screen->vt, screen);
}
void vterm_screen_reset(VTermScreen *screen, int hard)
{
screen->damaged.start_row = -1;
screen->pending_scrollrect.start_row = -1;
vterm_state_reset(screen->state, hard);
vterm_screen_flush_damage(screen);
}
static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect)
{
size_t outpos = 0;
int padding = 0;
int row, col;
#define PUT(c) \
if(utf8) { \
size_t thislen = utf8_seqlen(c); \
if(buffer && outpos + thislen <= len) \
outpos += fill_utf8((c), (char *)buffer + outpos); \
else \
outpos += thislen; \
} \
else { \
if(buffer && outpos + 1 <= len) \
((uint32_t*)buffer)[outpos++] = (c); \
else \
outpos++; \
}
for(row = rect.start_row; row < rect.end_row; row++) {
for(col = rect.start_col; col < rect.end_col; col++) {
ScreenCell *cell = getcell(screen, row, col);
int i;
if(cell->chars[0] == 0)
/* Erased cell, might need a space */
padding++;
else if(cell->chars[0] == (uint32_t)-1)
/* Gap behind a double-width char, do nothing */
;
else {
while(padding) {
PUT(UNICODE_SPACE);
padding--;
}
for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
PUT(cell->chars[i]);
}
}
}
if(row < rect.end_row - 1) {
PUT(UNICODE_LINEFEED);
padding = 0;
}
}
return outpos;
}
size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect)
{
return _get_chars(screen, 0, chars, len, rect);
}
size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect)
{
return _get_chars(screen, 1, str, len, rect);
}
/* Copy internal to external representation of a screen cell */
int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell)
{
ScreenCell *intcell = getcell(screen, pos.row, pos.col);
int i;
if(!intcell)
return 0;
for(i = 0; ; i++) {
cell->chars[i] = intcell->chars[i];
if(!intcell->chars[i])
break;
}
cell->attrs.bold = intcell->pen.bold;
cell->attrs.underline = intcell->pen.underline;
cell->attrs.italic = intcell->pen.italic;
cell->attrs.blink = intcell->pen.blink;
cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse;
cell->attrs.strike = intcell->pen.strike;
cell->attrs.font = intcell->pen.font;
cell->attrs.dwl = intcell->pen.dwl;
cell->attrs.dhl = intcell->pen.dhl;
cell->fg = intcell->pen.fg;
cell->bg = intcell->pen.bg;
if(pos.col < (screen->cols - 1) &&
getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1)
cell->width = 2;
else
cell->width = 1;
return 1;
}
/* Copy external to internal representation of a screen cell */
/* static because it's only used internally for sb_popline during resize */
static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell)
{
ScreenCell *intcell = getcell(screen, pos.row, pos.col);
int i;
if(!intcell)
return 0;
for(i = 0; ; i++) {
intcell->chars[i] = cell->chars[i];
if(!cell->chars[i])
break;
}
intcell->pen.bold = cell->attrs.bold;
intcell->pen.underline = cell->attrs.underline;
intcell->pen.italic = cell->attrs.italic;
intcell->pen.blink = cell->attrs.blink;
intcell->pen.reverse = cell->attrs.reverse ^ screen->global_reverse;
intcell->pen.strike = cell->attrs.strike;
intcell->pen.font = cell->attrs.font;
intcell->pen.fg = cell->fg;
intcell->pen.bg = cell->bg;
if(cell->width == 2)
getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-1;
return 1;
}
int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)
{
/* This cell is EOL if this and every cell to the right is black */
for(; pos.col < screen->cols; pos.col++) {
ScreenCell *cell = getcell(screen, pos.row, pos.col);
if(cell->chars[0] != 0)
return 0;
}
return 1;
}
VTermScreen *vterm_obtain_screen(VTerm *vt)
{
VTermScreen *screen;
if(vt->screen)
return vt->screen;
screen = screen_new(vt);
vt->screen = screen;
return screen;
}
void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
{
if(!screen->buffers[1] && altscreen) {
int rows, cols;
vterm_get_size(screen->vt, &rows, &cols);
screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols);
}
}
void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user)
{
screen->callbacks = callbacks;
screen->cbdata = user;
}
void *vterm_screen_get_cbdata(VTermScreen *screen)
{
return screen->cbdata;
}
void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user)
{
vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user);
}
void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen)
{
return vterm_state_get_unrecognised_fbdata(screen->state);
}
void vterm_screen_flush_damage(VTermScreen *screen)
{
if(screen->pending_scrollrect.start_row != -1) {
vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward,
moverect_user, erase_user, screen);
screen->pending_scrollrect.start_row = -1;
}
if(screen->damaged.start_row != -1) {
if(screen->callbacks && screen->callbacks->damage)
(*screen->callbacks->damage)(screen->damaged, screen->cbdata);
screen->damaged.start_row = -1;
}
}
void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size)
{
vterm_screen_flush_damage(screen);
screen->damage_merge = size;
}
static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
{
if((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold))
return 1;
if((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline))
return 1;
if((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic))
return 1;
if((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink))
return 1;
if((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse))
return 1;
if((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike))
return 1;
if((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font))
return 1;
if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_equal(a->pen.fg, b->pen.fg))
return 1;
if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_equal(a->pen.bg, b->pen.bg))
return 1;
return 0;
}
int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs)
{
int col;
ScreenCell *target = getcell(screen, pos.row, pos.col);
/* TODO: bounds check */
extent->start_row = pos.row;
extent->end_row = pos.row + 1;
if(extent->start_col < 0)
extent->start_col = 0;
if(extent->end_col < 0)
extent->end_col = screen->cols;
for(col = pos.col - 1; col >= extent->start_col; col--)
if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
break;
extent->start_col = col + 1;
for(col = pos.col + 1; col < extent->end_col; col++)
if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
break;
extent->end_col = col - 1;
return 1;
}

1851
src/libvterm/src/state.c Normal file

File diff suppressed because it is too large Load Diff

331
src/libvterm/src/unicode.c Normal file
View File

@ -0,0 +1,331 @@
#include "vterm_internal.h"
/* ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
* With modifications:
* made functions static
* moved 'combining' table to file scope, so other functions can see it
* ###################################################################
*/
/*
* This is an implementation of wcwidth() and wcswidth() (defined in
* IEEE Std 1002.1-2001) for Unicode.
*
* http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
* http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
*
* In fixed-width output devices, Latin characters all occupy a single
* "cell" position of equal width, whereas ideographic CJK characters
* occupy two such cells. Interoperability between terminal-line
* applications and (teletype-style) character terminals using the
* UTF-8 encoding requires agreement on which character should advance
* the cursor by how many cell positions. No established formal
* standards exist at present on which Unicode character shall occupy
* how many cell positions on character terminals. These routines are
* a first attempt of defining such behavior based on simple rules
* applied to data provided by the Unicode Consortium.
*
* For some graphical characters, the Unicode standard explicitly
* defines a character-cell width via the definition of the East Asian
* FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
* In all these cases, there is no ambiguity about which width a
* terminal shall use. For characters in the East Asian Ambiguous (A)
* class, the width choice depends purely on a preference of backward
* compatibility with either historic CJK or Western practice.
* Choosing single-width for these characters is easy to justify as
* the appropriate long-term solution, as the CJK practice of
* displaying these characters as double-width comes from historic
* implementation simplicity (8-bit encoded characters were displayed
* single-width and 16-bit ones double-width, even for Greek,
* Cyrillic, etc.) and not any typographic considerations.
*
* Much less clear is the choice of width for the Not East Asian
* (Neutral) class. Existing practice does not dictate a width for any
* of these characters. It would nevertheless make sense
* typographically to allocate two character cells to characters such
* as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
* represented adequately with a single-width glyph. The following
* routines at present merely assign a single-cell width to all
* neutral characters, in the interest of simplicity. This is not
* entirely satisfactory and should be reconsidered before
* establishing a formal standard in this area. At the moment, the
* decision which Not East Asian (Neutral) characters should be
* represented by double-width glyphs cannot yet be answered by
* applying a simple rule from the Unicode database content. Setting
* up a proper standard for the behavior of UTF-8 character terminals
* will require a careful analysis not only of each Unicode character,
* but also of each presentation form, something the author of these
* routines has avoided to do so far.
*
* http://www.unicode.org/unicode/reports/tr11/
*
* Markus Kuhn -- 2007-05-26 (Unicode 5.0)
*
* Permission to use, copy, modify, and distribute this software
* for any purpose and without fee is hereby granted. The author
* disclaims all warranties with regard to this software.
*
* Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
*/
struct interval {
int first;
int last;
};
/* sorted list of non-overlapping intervals of non-spacing characters */
/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
static const struct interval combining[] = {
{ 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
{ 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
{ 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
{ 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
{ 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
{ 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
{ 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
{ 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
{ 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
{ 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
{ 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
{ 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
{ 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
{ 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
{ 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
{ 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
{ 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
{ 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
{ 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
{ 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
{ 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
{ 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
{ 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
{ 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
{ 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
{ 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
{ 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
{ 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
{ 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
{ 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
{ 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
{ 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
{ 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
{ 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
{ 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
{ 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
{ 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
{ 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
{ 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
{ 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
{ 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
{ 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
{ 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
{ 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
{ 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
{ 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
{ 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
{ 0xE0100, 0xE01EF }
};
/* auxiliary function for binary search in interval table */
static int bisearch(uint32_t ucs, const struct interval *table, int max) {
int min = 0;
int mid;
if ((int)ucs < table[0].first || (int)ucs > table[max].last)
return 0;
while (max >= min) {
mid = (min + max) / 2;
if ((int)ucs > table[mid].last)
min = mid + 1;
else if ((int)ucs < table[mid].first)
max = mid - 1;
else
return 1;
}
return 0;
}
/* The following two functions define the column width of an ISO 10646
* character as follows:
*
* - The null character (U+0000) has a column width of 0.
*
* - Other C0/C1 control characters and DEL will lead to a return
* value of -1.
*
* - Non-spacing and enclosing combining characters (general
* category code Mn or Me in the Unicode database) have a
* column width of 0.
*
* - SOFT HYPHEN (U+00AD) has a column width of 1.
*
* - Other format characters (general category code Cf in the Unicode
* database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
*
* - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
* have a column width of 0.
*
* - Spacing characters in the East Asian Wide (W) or East Asian
* Full-width (F) category as defined in Unicode Technical
* Report #11 have a column width of 2.
*
* - All remaining characters (including all printable
* ISO 8859-1 and WGL4 characters, Unicode control characters,
* etc.) have a column width of 1.
*
* This implementation assumes that uint32_t characters are encoded
* in ISO 10646.
*/
static int mk_wcwidth(uint32_t ucs)
{
/* test for 8-bit control characters */
if (ucs == 0)
return 0;
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
return -1;
/* binary search in table of non-spacing characters */
if (bisearch(ucs, combining,
sizeof(combining) / sizeof(struct interval) - 1))
return 0;
/* if we arrive here, ucs is not a combining or C0/C1 control character */
return 1 +
(ucs >= 0x1100 &&
(ucs <= 0x115f || /* Hangul Jamo init. consonants */
ucs == 0x2329 || ucs == 0x232a ||
(ucs >= 0x2e80 && ucs <= 0xa4cf &&
ucs != 0x303f) || /* CJK ... Yi */
(ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
(ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
(ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
(ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
(ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
(ucs >= 0xffe0 && ucs <= 0xffe6) ||
(ucs >= 0x20000 && ucs <= 0x2fffd) ||
(ucs >= 0x30000 && ucs <= 0x3fffd)));
}
#if 0 /* unused */
static int mk_wcswidth(const uint32_t *pwcs, size_t n)
{
int w, width = 0;
for (;*pwcs && n-- > 0; pwcs++)
if ((w = mk_wcwidth(*pwcs)) < 0)
return -1;
else
width += w;
return width;
}
/*
* The following functions are the same as mk_wcwidth() and
* mk_wcswidth(), except that spacing characters in the East Asian
* Ambiguous (A) category as defined in Unicode Technical Report #11
* have a column width of 2. This variant might be useful for users of
* CJK legacy encodings who want to migrate to UCS without changing
* the traditional terminal character-width behaviour. It is not
* otherwise recommended for general use.
*/
static int mk_wcwidth_cjk(uint32_t ucs)
{
/* sorted list of non-overlapping intervals of East Asian Ambiguous
* characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
static const struct interval ambiguous[] = {
{ 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
{ 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
{ 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
{ 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
{ 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
{ 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
{ 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
{ 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
{ 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
{ 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
{ 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
{ 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
{ 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
{ 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
{ 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
{ 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
{ 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
{ 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
{ 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
{ 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
{ 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
{ 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
{ 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
{ 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
{ 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
{ 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
{ 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
{ 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
{ 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
{ 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
{ 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
{ 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
{ 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
{ 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
{ 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
{ 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
{ 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
{ 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
{ 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
{ 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
{ 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },
{ 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },
{ 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },
{ 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },
{ 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },
{ 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },
{ 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },
{ 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
{ 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
{ 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
{ 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },
{ 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
};
/* binary search in table of non-spacing characters */
if (bisearch(ucs, ambiguous,
sizeof(ambiguous) / sizeof(struct interval) - 1))
return 2;
return mk_wcwidth(ucs);
}
static int mk_wcswidth_cjk(const uint32_t *pwcs, size_t n)
{
int w, width = 0;
for (;*pwcs && n-- > 0; pwcs++)
if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
return -1;
else
width += w;
return width;
}
#endif
/* ################################
* ### The rest added by Paul Evans */
INTERNAL int vterm_unicode_width(uint32_t codepoint)
{
return mk_wcwidth(codepoint);
}
INTERNAL int vterm_unicode_is_combining(uint32_t codepoint)
{
return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1);
}

47
src/libvterm/src/utf8.h Normal file
View File

@ -0,0 +1,47 @@
/* The following functions copied and adapted from libtermkey
*
* http://www.leonerd.org.uk/code/libtermkey/
*/
unsigned int utf8_seqlen(long codepoint);
#if defined(DEFINE_INLINES) || USE_INLINE
INLINE unsigned int utf8_seqlen(long codepoint)
{
if(codepoint < 0x0000080) return 1;
if(codepoint < 0x0000800) return 2;
if(codepoint < 0x0010000) return 3;
if(codepoint < 0x0200000) return 4;
if(codepoint < 0x4000000) return 5;
return 6;
}
#endif
/* Does NOT NUL-terminate the buffer */
int fill_utf8(long codepoint, char *str);
#if defined(DEFINE_INLINES) || USE_INLINE
INLINE int fill_utf8(long codepoint, char *str)
{
int nbytes = utf8_seqlen(codepoint);
/* This is easier done backwards */
int b = nbytes;
while(b > 1) {
b--;
str[b] = 0x80 | (codepoint & 0x3f);
codepoint >>= 6;
}
switch(nbytes) {
case 1: str[0] = (codepoint & 0x7f); break;
case 2: str[0] = 0xc0 | (codepoint & 0x1f); break;
case 3: str[0] = 0xe0 | (codepoint & 0x0f); break;
case 4: str[0] = 0xf0 | (codepoint & 0x07); break;
case 5: str[0] = 0xf8 | (codepoint & 0x03); break;
case 6: str[0] = 0xfc | (codepoint & 0x01); break;
}
return nbytes;
}
#endif
/* end copy */

385
src/libvterm/src/vterm.c Normal file
View File

@ -0,0 +1,385 @@
#define DEFINE_INLINES
#include "vterm_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "utf8.h"
/*****************
* API functions *
*****************/
static void *default_malloc(size_t size, void *allocdata UNUSED)
{
void *ptr = malloc(size);
if(ptr)
memset(ptr, 0, size);
return ptr;
}
static void default_free(void *ptr, void *allocdata UNUSED)
{
free(ptr);
}
static VTermAllocatorFunctions default_allocator = {
&default_malloc, /* malloc */
&default_free /* free */
};
VTerm *vterm_new(int rows, int cols)
{
return vterm_new_with_allocator(rows, cols, &default_allocator, NULL);
}
VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata)
{
/* Need to bootstrap using the allocator function directly */
VTerm *vt = (*funcs->malloc)(sizeof(VTerm), allocdata);
vt->allocator = funcs;
vt->allocdata = allocdata;
vt->rows = rows;
vt->cols = cols;
vt->parser_state = NORMAL;
vt->parser_callbacks = NULL;
vt->cbdata = NULL;
vt->strbuffer_len = 64;
vt->strbuffer_cur = 0;
vt->strbuffer = vterm_allocator_malloc(vt, vt->strbuffer_len);
vt->outbuffer_len = 64;
vt->outbuffer_cur = 0;
vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len);
return vt;
}
void vterm_free(VTerm *vt)
{
if(vt->screen)
vterm_screen_free(vt->screen);
if(vt->state)
vterm_state_free(vt->state);
vterm_allocator_free(vt, vt->strbuffer);
vterm_allocator_free(vt, vt->outbuffer);
vterm_allocator_free(vt, vt);
}
INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size)
{
return (*vt->allocator->malloc)(size, vt->allocdata);
}
INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr)
{
(*vt->allocator->free)(ptr, vt->allocdata);
}
void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp)
{
if(rowsp)
*rowsp = vt->rows;
if(colsp)
*colsp = vt->cols;
}
void vterm_set_size(VTerm *vt, int rows, int cols)
{
vt->rows = rows;
vt->cols = cols;
if(vt->parser_callbacks && vt->parser_callbacks->resize)
(*vt->parser_callbacks->resize)(rows, cols, vt->cbdata);
}
int vterm_get_utf8(const VTerm *vt)
{
return vt->mode.utf8;
}
void vterm_set_utf8(VTerm *vt, int is_utf8)
{
vt->mode.utf8 = is_utf8;
}
INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len)
{
if(len > vt->outbuffer_len - vt->outbuffer_cur) {
DEBUG_LOG("vterm_push_output(): buffer overflow; truncating output\n");
len = vt->outbuffer_len - vt->outbuffer_cur;
}
memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len);
vt->outbuffer_cur += len;
}
static int outbuffer_is_full(VTerm *vt)
{
return vt->outbuffer_cur >= vt->outbuffer_len - 1;
}
INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args)
{
int written;
char buffer[1024]; /* 1Kbyte is enough for everybody, right? */
if(outbuffer_is_full(vt)) {
DEBUG_LOG("vterm_push_output(): buffer overflow; truncating output\n");
return;
}
written = vsprintf(buffer, format, args);
if(written >= (int)(vt->outbuffer_len - vt->outbuffer_cur)) {
/* output was truncated */
written = vt->outbuffer_len - vt->outbuffer_cur;
}
if (written > 0)
{
strncpy(vt->outbuffer + vt->outbuffer_cur, buffer, written + 1);
vt->outbuffer_cur += written;
}
}
INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...)
{
va_list args;
va_start(args, format);
vterm_push_output_vsprintf(vt, format, args);
va_end(args);
}
INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...)
{
size_t orig_cur = vt->outbuffer_cur;
va_list args;
if(ctrl >= 0x80 && !vt->mode.ctrl8bit)
vterm_push_output_sprintf(vt, ESC_S "%c", ctrl - 0x40);
else
vterm_push_output_sprintf(vt, "%c", ctrl);
va_start(args, fmt);
vterm_push_output_vsprintf(vt, fmt, args);
va_end(args);
if(outbuffer_is_full(vt))
vt->outbuffer_cur = orig_cur;
}
INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...)
{
size_t orig_cur = vt->outbuffer_cur;
va_list args;
if(!vt->mode.ctrl8bit)
vterm_push_output_sprintf(vt, ESC_S "%c", C1_DCS - 0x40);
else
vterm_push_output_sprintf(vt, "%c", C1_DCS);
va_start(args, fmt);
vterm_push_output_vsprintf(vt, fmt, args);
va_end(args);
vterm_push_output_sprintf_ctrl(vt, C1_ST, "");
if(outbuffer_is_full(vt))
vt->outbuffer_cur = orig_cur;
}
size_t vterm_output_get_buffer_size(const VTerm *vt)
{
return vt->outbuffer_len;
}
size_t vterm_output_get_buffer_current(const VTerm *vt)
{
return vt->outbuffer_cur;
}
size_t vterm_output_get_buffer_remaining(const VTerm *vt)
{
return vt->outbuffer_len - vt->outbuffer_cur;
}
size_t vterm_output_read(VTerm *vt, char *buffer, size_t len)
{
if(len > vt->outbuffer_cur)
len = vt->outbuffer_cur;
memcpy(buffer, vt->outbuffer, len);
if(len < vt->outbuffer_cur)
memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len);
vt->outbuffer_cur -= len;
return len;
}
void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user)
{
vt->parser_callbacks = callbacks;
vt->cbdata = user;
}
void *vterm_parser_get_cbdata(VTerm *vt)
{
return vt->cbdata;
}
VTermValueType vterm_get_attr_type(VTermAttr attr)
{
switch(attr) {
case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL;
case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT;
case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL;
case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL;
case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL;
case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL;
case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT;
case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR;
case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR;
}
return 0; /* UNREACHABLE */
}
VTermValueType vterm_get_prop_type(VTermProp prop)
{
switch(prop) {
case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL;
case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL;
case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL;
case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING;
case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING;
case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL;
case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT;
case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT;
}
return 0; /* UNREACHABLE */
}
void vterm_scroll_rect(VTermRect rect,
int downward,
int rightward,
int (*moverect)(VTermRect src, VTermRect dest, void *user),
int (*eraserect)(VTermRect rect, int selective, void *user),
void *user)
{
VTermRect src;
VTermRect dest;
if(abs(downward) >= rect.end_row - rect.start_row ||
abs(rightward) >= rect.end_col - rect.start_col) {
/* Scroll more than area; just erase the lot */
(*eraserect)(rect, 0, user);
return;
}
if(rightward >= 0) {
/* rect: [XXX................]
* src: [----------------]
* dest: [----------------]
*/
dest.start_col = rect.start_col;
dest.end_col = rect.end_col - rightward;
src.start_col = rect.start_col + rightward;
src.end_col = rect.end_col;
}
else {
/* rect: [................XXX]
* src: [----------------]
* dest: [----------------]
*/
int leftward = -rightward;
dest.start_col = rect.start_col + leftward;
dest.end_col = rect.end_col;
src.start_col = rect.start_col;
src.end_col = rect.end_col - leftward;
}
if(downward >= 0) {
dest.start_row = rect.start_row;
dest.end_row = rect.end_row - downward;
src.start_row = rect.start_row + downward;
src.end_row = rect.end_row;
}
else {
int upward = -downward;
dest.start_row = rect.start_row + upward;
dest.end_row = rect.end_row;
src.start_row = rect.start_row;
src.end_row = rect.end_row - upward;
}
if(moverect)
(*moverect)(dest, src, user);
if(downward > 0)
rect.start_row = rect.end_row - downward;
else if(downward < 0)
rect.end_row = rect.start_row - downward;
if(rightward > 0)
rect.start_col = rect.end_col - rightward;
else if(rightward < 0)
rect.end_col = rect.start_col - rightward;
(*eraserect)(rect, 0, user);
}
void vterm_copy_cells(VTermRect dest,
VTermRect src,
void (*copycell)(VTermPos dest, VTermPos src, void *user),
void *user)
{
int downward = src.start_row - dest.start_row;
int rightward = src.start_col - dest.start_col;
int init_row, test_row, init_col, test_col;
int inc_row, inc_col;
VTermPos pos;
if(downward < 0) {
init_row = dest.end_row - 1;
test_row = dest.start_row - 1;
inc_row = -1;
}
else /* downward >= 0 */ {
init_row = dest.start_row;
test_row = dest.end_row;
inc_row = +1;
}
if(rightward < 0) {
init_col = dest.end_col - 1;
test_col = dest.start_col - 1;
inc_col = -1;
}
else /* rightward >= 0 */ {
init_col = dest.start_col;
test_col = dest.end_col;
inc_col = +1;
}
for(pos.row = init_row; pos.row != test_row; pos.row += inc_row)
for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) {
VTermPos srcpos;
srcpos.row = pos.row + downward;
srcpos.col = pos.col + rightward;
(*copycell)(pos, srcpos, user);
}
}

View File

@ -0,0 +1,237 @@
#ifndef __VTERM_INTERNAL_H__
#define __VTERM_INTERNAL_H__
#include "vterm.h"
#include <stdarg.h>
#if defined(__GNUC__)
# define INTERNAL __attribute__((visibility("internal")))
# define UNUSED __attribute__((unused))
#else
# define INTERNAL
# define UNUSED
#endif
#ifdef DEBUG
# define DEBUG_LOG(s) fprintf(stderr, s)
# define DEBUG_LOG1(s, a) fprintf(stderr, s, a)
# define DEBUG_LOG2(s, a, b) fprintf(stderr, s, a, b)
# define DEBUG_LOG3(s, a, b, c) fprintf(stderr, s, a, b, c)
#else
# define DEBUG_LOG(s)
# define DEBUG_LOG1(s, a)
# define DEBUG_LOG2(s, a, b)
# define DEBUG_LOG3(s, a, b, c)
#endif
#define ESC_S "\x1b"
typedef struct VTermEncoding VTermEncoding;
typedef struct {
VTermEncoding *enc;
/* This size should be increased if required by other stateful encodings */
char data[4*sizeof(uint32_t)];
} VTermEncodingInstance;
struct VTermPen
{
VTermColor fg;
VTermColor bg;
unsigned int bold:1;
unsigned int underline:2;
unsigned int italic:1;
unsigned int blink:1;
unsigned int reverse:1;
unsigned int strike:1;
unsigned int font:4; /* To store 0-9 */
};
int vterm_color_equal(VTermColor a, VTermColor b);
#if defined(DEFINE_INLINES) || USE_INLINE
INLINE int vterm_color_equal(VTermColor a, VTermColor b)
{
return a.red == b.red && a.green == b.green && a.blue == b.blue;
}
#endif
struct VTermState
{
VTerm *vt;
const VTermStateCallbacks *callbacks;
void *cbdata;
const VTermParserCallbacks *fallbacks;
void *fbdata;
int rows;
int cols;
/* Current cursor position */
VTermPos pos;
int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */
int scrollregion_top;
int scrollregion_bottom; /* -1 means unbounded */
#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows)
int scrollregion_left;
#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0)
int scrollregion_right; /* -1 means unbounded */
#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols)
/* Bitvector of tab stops */
unsigned char *tabstops;
VTermLineInfo *lineinfo;
#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols)
#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row)
/* Mouse state */
int mouse_col, mouse_row;
int mouse_buttons;
int mouse_flags;
#define MOUSE_WANT_CLICK 0x01
#define MOUSE_WANT_DRAG 0x02
#define MOUSE_WANT_MOVE 0x04
enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol;
/* Last glyph output, for Unicode recombining purposes */
uint32_t *combine_chars;
size_t combine_chars_size; /* Number of ELEMENTS in the above */
int combine_width; /* The width of the glyph above */
VTermPos combine_pos; /* Position before movement */
struct {
unsigned int keypad:1;
unsigned int cursor:1;
unsigned int autowrap:1;
unsigned int insert:1;
unsigned int newline:1;
unsigned int cursor_visible:1;
unsigned int cursor_blink:1;
unsigned int cursor_shape:2;
unsigned int alt_screen:1;
unsigned int origin:1;
unsigned int screen:1;
unsigned int leftrightmargin:1;
unsigned int bracketpaste:1;
} mode;
VTermEncodingInstance encoding[4], encoding_utf8;
int gl_set, gr_set, gsingle_set;
struct VTermPen pen;
VTermColor default_fg;
VTermColor default_bg;
VTermColor colors[16]; /* Store the 8 ANSI and the 8 ANSI high-brights only */
int fg_index;
int bg_index;
int bold_is_highbright;
unsigned int protected_cell : 1;
/* Saved state under DEC mode 1048/1049 */
struct {
VTermPos pos;
struct VTermPen pen;
struct {
int cursor_visible:1;
int cursor_blink:1;
unsigned int cursor_shape:2;
} mode;
} saved;
};
struct VTerm
{
VTermAllocatorFunctions *allocator;
void *allocdata;
int rows;
int cols;
struct {
unsigned int utf8:1;
unsigned int ctrl8bit:1;
} mode;
enum VTermParserState {
NORMAL,
CSI,
OSC,
DCS,
ESC,
ESC_IN_OSC,
ESC_IN_DCS
} parser_state;
const VTermParserCallbacks *parser_callbacks;
void *cbdata;
/* len == malloc()ed size; cur == number of valid bytes */
char *strbuffer;
size_t strbuffer_len;
size_t strbuffer_cur;
char *outbuffer;
size_t outbuffer_len;
size_t outbuffer_cur;
VTermState *state;
VTermScreen *screen;
};
struct VTermEncoding {
void (*init) (VTermEncoding *enc, void *data);
void (*decode)(VTermEncoding *enc, void *data,
uint32_t cp[], int *cpi, int cplen,
const char bytes[], size_t *pos, size_t len);
};
typedef enum {
ENC_UTF8,
ENC_SINGLE_94
} VTermEncodingType;
void *vterm_allocator_malloc(VTerm *vt, size_t size);
void vterm_allocator_free(VTerm *vt, void *ptr);
void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len);
void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args);
void vterm_push_output_sprintf(VTerm *vt, const char *format, ...);
void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...);
void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...);
void vterm_state_free(VTermState *state);
void vterm_state_newpen(VTermState *state);
void vterm_state_resetpen(VTermState *state);
void vterm_state_setpen(VTermState *state, const long args[], int argcount);
int vterm_state_getpen(VTermState *state, long args[], int argcount);
void vterm_state_savepen(VTermState *state, int save);
enum {
C1_SS3 = 0x8f,
C1_DCS = 0x90,
C1_CSI = 0x9b,
C1_ST = 0x9c
};
void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...);
void vterm_screen_free(VTermScreen *screen);
VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation);
int vterm_unicode_width(uint32_t codepoint);
int vterm_unicode_is_combining(uint32_t codepoint);
#endif

View File

@ -0,0 +1,200 @@
INIT
UTF8 0
WANTPARSER
!Basic text
PUSH "hello"
text 0x68, 0x65, 0x6c, 0x6c, 0x6f
!C0
PUSH "\x03"
control 3
PUSH "\x1f"
control 0x1f
!C1 8bit
PUSH "\x83"
control 0x83
PUSH "\x9f"
control 0x9f
!C1 7bit
PUSH "\e\x43"
control 0x83
PUSH "\e\x5f"
control 0x9f
!High bytes
PUSH "\xa0\xcc\xfe"
text 0xa0, 0xcc, 0xfe
!Mixed
PUSH "1\n2"
text 0x31
control 10
text 0x32
!Escape
PUSH "\e="
escape "="
!Escape 2-byte
PUSH "\e(X"
escape "(X"
!Split write Escape
PUSH "\e("
PUSH "Y"
escape "(Y"
!Escape cancels Escape, starts another
PUSH "\e(\e)Z"
escape ")Z"
!CAN cancels Escape, returns to normal mode
PUSH "\e(\x{18}AB"
text 0x41, 0x42
!C0 in Escape interrupts and continues
PUSH "\e(\nX"
control 10
escape "(X"
!CSI 0 args
PUSH "\e[a"
csi 0x61 *
!CSI 1 arg
PUSH "\e[9b"
csi 0x62 9
!CSI 2 args
PUSH "\e[3;4c"
csi 0x63 3,4
!CSI 1 arg 1 sub
PUSH "\e[1:2c"
csi 0x63 1+,2
!CSI many digits
PUSH "\e[678d"
csi 0x64 678
!CSI leading zero
PUSH "\e[007e"
csi 0x65 7
!CSI qmark
PUSH "\e[?2;7f"
csi 0x66 L=3f 2,7
!CSI greater
PUSH "\e[>c"
csi 0x63 L=3e *
!CSI SP
PUSH "\e[12 q"
csi 0x71 12 I=20
!Mixed CSI
PUSH "A\e[8mB"
text 0x41
csi 0x6d 8
text 0x42
!Split write
PUSH "\e"
PUSH "[a"
csi 0x61 *
PUSH "foo\e["
text 0x66, 0x6f, 0x6f
PUSH "4b"
csi 0x62 4
PUSH "\e[12;"
PUSH "3c"
csi 0x63 12,3
!Escape cancels CSI, starts Escape
PUSH "\e[123\e9"
escape "9"
!CAN cancels CSI, returns to normal mode
PUSH "\e[12\x{18}AB"
text 0x41, 0x42
!C0 in Escape interrupts and continues
PUSH "\e[12\n;3X"
control 10
csi 0x58 12,3
!OSC BEL
PUSH "\e]1;Hello\x07"
osc "1;Hello"
!OSC ST (7bit)
PUSH "\e]1;Hello\e\\"
osc "1;Hello"
!OSC ST (8bit)
PUSH "\x{9d}1;Hello\x9c"
osc "1;Hello"
!Escape cancels OSC, starts Escape
PUSH "\e]Something\e9"
escape "9"
!CAN cancels OSC, returns to normal mode
PUSH "\e]12\x{18}AB"
text 0x41, 0x42
!C0 in OSC interrupts and continues
PUSH "\e]2;\nBye\x07"
control 10
osc "2;Bye"
!DCS BEL
PUSH "\ePHello\x07"
dcs "Hello"
!DCS ST (7bit)
PUSH "\ePHello\e\\"
dcs "Hello"
!DCS ST (8bit)
PUSH "\x{90}Hello\x9c"
dcs "Hello"
!Escape cancels DCS, starts Escape
PUSH "\ePSomething\e9"
escape "9"
!CAN cancels DCS, returns to normal mode
PUSH "\eP12\x{18}AB"
text 0x41, 0x42
!C0 in OSC interrupts and continues
PUSH "\ePBy\ne\x07"
control 10
dcs "Bye"
!NUL ignored
PUSH "\x{00}"
!NUL ignored within CSI
PUSH "\e[12\x{00}3m"
csi 0x6d 123
!DEL ignored
PUSH "\x{7f}"
!DEL ignored within CSI
PUSH "\e[12\x{7f}3m"
csi 0x6d 123
!DEL inside text"
PUSH "AB\x{7f}C"
text 0x41,0x42
text 0x43

View File

@ -0,0 +1,122 @@
INIT
WANTENCODING
!Low
ENCIN "123"
encout 0x31,0x32,0x33
# We want to prove the UTF-8 parser correctly handles all the sequences.
# Easy way to do this is to check it does low/high boundary cases, as that
# leaves only two for each sequence length
#
# These ranges are therefore:
#
# Two bytes:
# U+0080 = 000 10000000 => 00010 000000
# => 11000010 10000000 = C2 80
# U+07FF = 111 11111111 => 11111 111111
# => 11011111 10111111 = DF BF
#
# Three bytes:
# U+0800 = 00001000 00000000 => 0000 100000 000000
# => 11100000 10100000 10000000 = E0 A0 80
# U+FFFD = 11111111 11111101 => 1111 111111 111101
# => 11101111 10111111 10111101 = EF BF BD
# (We avoid U+FFFE and U+FFFF as they're invalid codepoints)
#
# Four bytes:
# U+10000 = 00001 00000000 00000000 => 000 010000 000000 000000
# => 11110000 10010000 10000000 10000000 = F0 90 80 80
# U+1FFFFF = 11111 11111111 11111111 => 111 111111 111111 111111
# => 11110111 10111111 10111111 10111111 = F7 BF BF BF
!2 byte
ENCIN "\xC2\x80\xDF\xBF"
encout 0x0080, 0x07FF
!3 byte
ENCIN "\xE0\xA0\x80\xEF\xBF\xBD"
encout 0x0800,0xFFFD
!4 byte
ENCIN "\xF0\x90\x80\x80\xF7\xBF\xBF\xBF"
encout 0x10000,0x1fffff
# Next up, we check some invalid sequences
# + Early termination (back to low bytes too soon)
# + Early restart (another sequence introduction before the previous one was finished)
!Early termination
ENCIN "\xC2!"
encout 0xfffd,0x21
ENCIN "\xE0!\xE0\xA0!"
encout 0xfffd,0x21,0xfffd,0x21
ENCIN "\xF0!\xF0\x90!\xF0\x90\x80!"
encout 0xfffd,0x21,0xfffd,0x21,0xfffd,0x21
!Early restart
ENCIN "\xC2\xC2\x90"
encout 0xfffd,0x0090
ENCIN "\xE0\xC2\x90\xE0\xA0\xC2\x90"
encout 0xfffd,0x0090,0xfffd,0x0090
ENCIN "\xF0\xC2\x90\xF0\x90\xC2\x90\xF0\x90\x80\xC2\x90"
encout 0xfffd,0x0090,0xfffd,0x0090,0xfffd,0x0090
# Test the overlong sequences by giving an overlong encoding of U+0000 and
# an encoding of the highest codepoint still too short
#
# Two bytes:
# U+0000 = C0 80
# U+007F = 000 01111111 => 00001 111111 =>
# => 11000001 10111111 => C1 BF
#
# Three bytes:
# U+0000 = E0 80 80
# U+07FF = 00000111 11111111 => 0000 011111 111111
# => 11100000 10011111 10111111 = E0 9F BF
#
# Four bytes:
# U+0000 = F0 80 80 80
# U+FFFF = 11111111 11111111 => 000 001111 111111 111111
# => 11110000 10001111 10111111 10111111 = F0 8F BF BF
!Overlong
ENCIN "\xC0\x80\xC1\xBF"
encout 0xfffd,0xfffd
ENCIN "\xE0\x80\x80\xE0\x9F\xBF"
encout 0xfffd,0xfffd
ENCIN "\xF0\x80\x80\x80\xF0\x8F\xBF\xBF"
encout 0xfffd,0xfffd
# UTF-16 surrogates U+D800 and U+DFFF
!UTF-16 Surrogates
ENCIN "\xED\xA0\x80\xED\xBF\xBF"
encout 0xfffd,0xfffd
!Split write
ENCIN "\xC2"
ENCIN "\xA0"
encout 0x000A0
ENCIN "\xE0"
ENCIN "\xA0\x80"
encout 0x00800
ENCIN "\xE0\xA0"
ENCIN "\x80"
encout 0x00800
ENCIN "\xF0"
ENCIN "\x90\x80\x80"
encout 0x10000
ENCIN "\xF0\x90"
ENCIN "\x80\x80"
encout 0x10000
ENCIN "\xF0\x90\x80"
ENCIN "\x80"
encout 0x10000

View File

@ -0,0 +1,55 @@
INIT
UTF8 1
WANTSTATE g
!Low
RESET
PUSH "ABC"
putglyph 0x41 1 0,0
putglyph 0x42 1 0,1
putglyph 0x43 1 0,2
!UTF-8 1 char
# U+00C1 = 0xC3 0x81 name: LATIN CAPITAL LETTER A WITH ACUTE
# U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE
RESET
PUSH "\xC3\x81\xC3\xA9"
putglyph 0xc1 1 0,0
putglyph 0xe9 1 0,1
!UTF-8 wide char
# U+FF10 = 0xEF 0xBC 0x90 name: FULLWIDTH DIGIT ZERO
RESET
PUSH "\xEF\xBC\x90 "
putglyph 0xff10 2 0,0
putglyph 0x20 1 0,2
!UTF-8 combining chars
# U+0301 = 0xCC 0x81 name: COMBINING ACUTE
RESET
PUSH "e\xCC\x81Z"
putglyph 0x65,0x301 1 0,0
putglyph 0x5a 1 0,1
!Combining across buffers
RESET
PUSH "e"
putglyph 0x65 1 0,0
PUSH "\xCC\x81Z"
putglyph 0x65,0x301 1 0,0
putglyph 0x5a 1 0,1
RESET
PUSH "e"
putglyph 0x65 1 0,0
PUSH "\xCC\x81"
putglyph 0x65,0x301 1 0,0
PUSH "\xCC\x82"
putglyph 0x65,0x301,0x302 1 0,0
!DECSCA protected
RESET
PUSH "A\e[1\"qB\e[2\"qC"
putglyph 0x41 1 0,0
putglyph 0x42 1 0,1 prot
putglyph 0x43 1 0,2

View File

@ -0,0 +1,224 @@
INIT
UTF8 1
WANTSTATE
!Implicit
PUSH "ABC"
?cursor = 0,3
!Backspace
PUSH "\b"
?cursor = 0,2
!Horizontal Tab
PUSH "\t"
?cursor = 0,8
!Carriage Return
PUSH "\r"
?cursor = 0,0
!Linefeed
PUSH "\n"
?cursor = 1,0
!Backspace bounded by lefthand edge
PUSH "\e[4;2H"
?cursor = 3,1
PUSH "\b"
?cursor = 3,0
PUSH "\b"
?cursor = 3,0
!Backspace cancels phantom
PUSH "\e[4;80H"
?cursor = 3,79
PUSH "X"
?cursor = 3,79
PUSH "\b"
?cursor = 3,78
!HT bounded by righthand edge
PUSH "\e[1;78H"
?cursor = 0,77
PUSH "\t"
?cursor = 0,79
PUSH "\t"
?cursor = 0,79
RESET
!Index
PUSH "ABC\eD"
?cursor = 1,3
!Reverse Index
PUSH "\eM"
?cursor = 0,3
!Newline
PUSH "\eE"
?cursor = 1,0
RESET
!Cursor Forward
PUSH "\e[B"
?cursor = 1,0
PUSH "\e[3B"
?cursor = 4,0
PUSH "\e[0B"
?cursor = 5,0
!Cursor Down
PUSH "\e[C"
?cursor = 5,1
PUSH "\e[3C"
?cursor = 5,4
PUSH "\e[0C"
?cursor = 5,5
!Cursor Up
PUSH "\e[A"
?cursor = 4,5
PUSH "\e[3A"
?cursor = 1,5
PUSH "\e[0A"
?cursor = 0,5
!Cursor Backward
PUSH "\e[D"
?cursor = 0,4
PUSH "\e[3D"
?cursor = 0,1
PUSH "\e[0D"
?cursor = 0,0
!Cursor Next Line
PUSH " "
?cursor = 0,3
PUSH "\e[E"
?cursor = 1,0
PUSH " "
?cursor = 1,3
PUSH "\e[2E"
?cursor = 3,0
PUSH "\e[0E"
?cursor = 4,0
!Cursor Previous Line
PUSH " "
?cursor = 4,3
PUSH "\e[F"
?cursor = 3,0
PUSH " "
?cursor = 3,3
PUSH "\e[2F"
?cursor = 1,0
PUSH "\e[0F"
?cursor = 0,0
!Cursor Horizonal Absolute
PUSH "\n"
?cursor = 1,0
PUSH "\e[20G"
?cursor = 1,19
PUSH "\e[G"
?cursor = 1,0
!Cursor Position
PUSH "\e[10;5H"
?cursor = 9,4
PUSH "\e[8H"
?cursor = 7,0
PUSH "\e[H"
?cursor = 0,0
!Cursor Position cancels phantom
PUSH "\e[10;78H"
?cursor = 9,77
PUSH "ABC"
?cursor = 9,79
PUSH "\e[10;80H"
PUSH "C"
?cursor = 9,79
PUSH "X"
?cursor = 10,1
RESET
!Bounds Checking
PUSH "\e[A"
?cursor = 0,0
PUSH "\e[D"
?cursor = 0,0
PUSH "\e[25;80H"
?cursor = 24,79
PUSH "\e[B"
?cursor = 24,79
PUSH "\e[C"
?cursor = 24,79
PUSH "\e[E"
?cursor = 24,0
PUSH "\e[H"
?cursor = 0,0
PUSH "\e[F"
?cursor = 0,0
PUSH "\e[999G"
?cursor = 0,79
PUSH "\e[99;99H"
?cursor = 24,79
RESET
!Horizontal Position Absolute
PUSH "\e[5`"
?cursor = 0,4
!Horizontal Position Relative
PUSH "\e[3a"
?cursor = 0,7
!Horizontal Position Backward
PUSH "\e[3j"
?cursor = 0,4
!Horizontal and Vertical Position
PUSH "\e[3;3f"
?cursor = 2,2
!Vertical Position Absolute
PUSH "\e[5d"
?cursor = 4,2
!Vertical Position Relative
PUSH "\e[2e"
?cursor = 6,2
!Vertical Position Backward
PUSH "\e[2k"
?cursor = 4,2
RESET
!Horizontal Tab
PUSH "\t"
?cursor = 0,8
PUSH " "
?cursor = 0,11
PUSH "\t"
?cursor = 0,16
PUSH " "
?cursor = 0,23
PUSH "\t"
?cursor = 0,24
PUSH " "
?cursor = 0,32
PUSH "\t"
?cursor = 0,40
!Cursor Horizontal Tab
PUSH "\e[I"
?cursor = 0,48
PUSH "\e[2I"
?cursor = 0,64
!Cursor Backward Tab
PUSH "\e[Z"
?cursor = 0,56
PUSH "\e[2Z"
?cursor = 0,40

View File

@ -0,0 +1,150 @@
INIT
UTF8 1
WANTSTATE s
!Linefeed
PUSH "\n"x24
?cursor = 24,0
PUSH "\n"
scrollrect 0..25,0..80 => +1,+0
?cursor = 24,0
RESET
!Index
PUSH "\e[25H"
PUSH "\eD"
scrollrect 0..25,0..80 => +1,+0
RESET
!Reverse Index
PUSH "\eM"
scrollrect 0..25,0..80 => -1,+0
RESET
!Linefeed in DECSTBM
PUSH "\e[1;10r"
?cursor = 0,0
PUSH "\n"x9
?cursor = 9,0
PUSH "\n"
scrollrect 0..10,0..80 => +1,+0
?cursor = 9,0
!Linefeed outside DECSTBM
PUSH "\e[20H"
?cursor = 19,0
PUSH "\n"
?cursor = 20,0
!Index in DECSTBM
PUSH "\e[10H"
PUSH "\e[9;10r"
PUSH "\eM"
?cursor = 8,0
PUSH "\eM"
scrollrect 8..10,0..80 => -1,+0
!Reverse Index in DECSTBM
PUSH "\e[25H"
?cursor = 24,0
PUSH "\n"
# no scrollrect
?cursor = 24,0
!Linefeed in DECSTBM+DECSLRM
PUSH "\e[?69h"
PUSH "\e[3;10r\e[10;40s"
PUSH "\e[10;10H\n"
scrollrect 2..10,9..40 => +1,+0
!IND/RI in DECSTBM+DECSLRM
PUSH "\eD"
scrollrect 2..10,9..40 => +1,+0
PUSH "\e[3;10H\eM"
scrollrect 2..10,9..40 => -1,+0
!DECRQSS on DECSTBM
PUSH "\eP\$qr\e\\"
output "\eP1\$r3;10r\e\\"
!DECRQSS on DECSLRM
PUSH "\eP\$qs\e\\"
output "\eP1\$r10;40s\e\\"
!Setting invalid DECSLRM with !DECVSSM is still rejected
PUSH "\e[?69l\e[;0s\e[?69h"
RESET
!Scroll Down
PUSH "\e[S"
scrollrect 0..25,0..80 => +1,+0
?cursor = 0,0
PUSH "\e[2S"
scrollrect 0..25,0..80 => +2,+0
?cursor = 0,0
PUSH "\e[100S"
scrollrect 0..25,0..80 => +25,+0
!Scroll Up
PUSH "\e[T"
scrollrect 0..25,0..80 => -1,+0
?cursor = 0,0
PUSH "\e[2T"
scrollrect 0..25,0..80 => -2,+0
?cursor = 0,0
PUSH "\e[100T"
scrollrect 0..25,0..80 => -25,+0
!SD/SU in DECSTBM
PUSH "\e[5;20r"
PUSH "\e[S"
scrollrect 4..20,0..80 => +1,+0
PUSH "\e[T"
scrollrect 4..20,0..80 => -1,+0
RESET
!SD/SU in DECSTBM+DECSLRM
PUSH "\e[?69h"
PUSH "\e[3;10r\e[10;40s"
?cursor = 0,0
PUSH "\e[3;10H"
?cursor = 2,9
PUSH "\e[S"
scrollrect 2..10,9..40 => +1,+0
PUSH "\e[?69l"
PUSH "\e[S"
scrollrect 2..10,0..80 => +1,+0
!Invalid boundaries
RESET
PUSH "\e[100;105r\eD"
PUSH "\e[5;2r\eD"
RESET
WANTSTATE -s+me
!Scroll Down move+erase emulation
PUSH "\e[S"
moverect 1..25,0..80 -> 0..24,0..80
erase 24..25,0..80
?cursor = 0,0
PUSH "\e[2S"
moverect 2..25,0..80 -> 0..23,0..80
erase 23..25,0..80
?cursor = 0,0
!Scroll Up move+erase emulation
PUSH "\e[T"
moverect 0..24,0..80 -> 1..25,0..80
erase 0..1,0..80
?cursor = 0,0
PUSH "\e[2T"
moverect 0..23,0..80 -> 2..25,0..80
erase 0..2,0..80
?cursor = 0,0

View File

@ -0,0 +1,300 @@
INIT
UTF8 1
WANTSTATE se
!ICH
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "ACD"
PUSH "\e[2D"
?cursor = 0,1
PUSH "\e[@"
scrollrect 0..1,1..80 => +0,-1
?cursor = 0,1
PUSH "B"
?cursor = 0,2
PUSH "\e[3@"
scrollrect 0..1,2..80 => +0,-3
!ICH with DECSLRM
PUSH "\e[?69h"
PUSH "\e[;50s"
PUSH "\e[20G\e[@"
scrollrect 0..1,19..50 => +0,-1
!ICH outside DECSLRM
PUSH "\e[70G\e[@"
# nothing happens
!DCH
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "ABBC"
PUSH "\e[3D"
?cursor = 0,1
PUSH "\e[P"
scrollrect 0..1,1..80 => +0,+1
?cursor = 0,1
PUSH "\e[3P"
scrollrect 0..1,1..80 => +0,+3
?cursor = 0,1
!DCH with DECSLRM
PUSH "\e[?69h"
PUSH "\e[;50s"
PUSH "\e[20G\e[P"
scrollrect 0..1,19..50 => +0,+1
!DCH outside DECSLRM
PUSH "\e[70G\e[P"
# nothing happens
!ECH
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "ABC"
PUSH "\e[2D"
?cursor = 0,1
PUSH "\e[X"
erase 0..1,1..2
?cursor = 0,1
PUSH "\e[3X"
erase 0..1,1..4
?cursor = 0,1
# ECH more columns than there are should be bounded
PUSH "\e[100X"
erase 0..1,1..80
!IL
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "A\r\nC"
?cursor = 1,1
PUSH "\e[L"
scrollrect 1..25,0..80 => -1,+0
# TODO: ECMA-48 says we should move to line home, but neither xterm nor
# xfce4-terminal do this
?cursor = 1,1
PUSH "\rB"
?cursor = 1,1
PUSH "\e[3L"
scrollrect 1..25,0..80 => -3,+0
!IL with DECSTBM
PUSH "\e[5;15r"
PUSH "\e[5H\e[L"
scrollrect 4..15,0..80 => -1,+0
!IL outside DECSTBM
PUSH "\e[20H\e[L"
# nothing happens
!IL with DECSTBM+DECSLRM
PUSH "\e[?69h"
PUSH "\e[10;50s"
PUSH "\e[5;10H\e[L"
scrollrect 4..15,9..50 => -1,+0
!DL
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "A\r\nB\r\nB\r\nC"
?cursor = 3,1
PUSH "\e[2H"
?cursor = 1,0
PUSH "\e[M"
scrollrect 1..25,0..80 => +1,+0
?cursor = 1,0
PUSH "\e[3M"
scrollrect 1..25,0..80 => +3,+0
?cursor = 1,0
!DL with DECSTBM
PUSH "\e[5;15r"
PUSH "\e[5H\e[M"
scrollrect 4..15,0..80 => +1,+0
!DL outside DECSTBM
PUSH "\e[20H\e[M"
# nothing happens
!DL with DECSTBM+DECSLRM
PUSH "\e[?69h"
PUSH "\e[10;50s"
PUSH "\e[5;10H\e[M"
scrollrect 4..15,9..50 => +1,+0
!DECIC
RESET
erase 0..25,0..80
PUSH "\e[20G\e[5'}"
scrollrect 0..25,19..80 => +0,-5
!DECIC with DECSTBM+DECSLRM
PUSH "\e[?69h"
PUSH "\e[4;20r\e[20;60s"
PUSH "\e[4;20H\e[3'}"
scrollrect 3..20,19..60 => +0,-3
!DECIC outside DECSLRM
PUSH "\e[70G\e['}"
# nothing happens
!DECDC
RESET
erase 0..25,0..80
PUSH "\e[20G\e[5'~"
scrollrect 0..25,19..80 => +0,+5
!DECDC with DECSTBM+DECSLRM
PUSH "\e[?69h"
PUSH "\e[4;20r\e[20;60s"
PUSH "\e[4;20H\e[3'~"
scrollrect 3..20,19..60 => +0,+3
!DECDC outside DECSLRM
PUSH "\e[70G\e['~"
# nothing happens
!EL 0
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "ABCDE"
PUSH "\e[3D"
?cursor = 0,2
PUSH "\e[0K"
erase 0..1,2..80
?cursor = 0,2
!EL 1
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "ABCDE"
PUSH "\e[3D"
?cursor = 0,2
PUSH "\e[1K"
erase 0..1,0..3
?cursor = 0,2
!EL 2
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "ABCDE"
PUSH "\e[3D"
?cursor = 0,2
PUSH "\e[2K"
erase 0..1,0..80
?cursor = 0,2
!SEL
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "\e[11G"
?cursor = 0,10
PUSH "\e[?0K"
erase 0..1,10..80 selective
?cursor = 0,10
PUSH "\e[?1K"
erase 0..1,0..11 selective
?cursor = 0,10
PUSH "\e[?2K"
erase 0..1,0..80 selective
?cursor = 0,10
!ED 0
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "\e[2;2H"
?cursor = 1,1
PUSH "\e[0J"
erase 1..2,1..80
erase 2..25,0..80
?cursor = 1,1
!ED 1
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "\e[2;2H"
?cursor = 1,1
PUSH "\e[1J"
erase 0..1,0..80
erase 1..2,0..2
?cursor = 1,1
!ED 2
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "\e[2;2H"
?cursor = 1,1
PUSH "\e[2J"
erase 0..25,0..80
?cursor = 1,1
!SED
RESET
erase 0..25,0..80
PUSH "\e[5;5H"
?cursor = 4,4
PUSH "\e[?0J"
erase 4..5,4..80 selective
erase 5..25,0..80 selective
?cursor = 4,4
PUSH "\e[?1J"
erase 0..4,0..80 selective
erase 4..5,0..5 selective
?cursor = 4,4
PUSH "\e[?2J"
erase 0..25,0..80 selective
?cursor = 4,4
!DECRQSS on DECSCA
PUSH "\e[2\"q"
PUSH "\eP\$q\"q\e\\"
output "\eP1\$r2\"q\e\\"
WANTSTATE -s+m
!ICH move+erase emuation
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "ACD"
PUSH "\e[2D"
?cursor = 0,1
PUSH "\e[@"
moverect 0..1,1..79 -> 0..1,2..80
erase 0..1,1..2
?cursor = 0,1
PUSH "B"
?cursor = 0,2
PUSH "\e[3@"
moverect 0..1,2..77 -> 0..1,5..80
erase 0..1,2..5
!DCH move+erase emulation
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "ABBC"
PUSH "\e[3D"
?cursor = 0,1
PUSH "\e[P"
moverect 0..1,2..80 -> 0..1,1..79
erase 0..1,79..80
?cursor = 0,1
PUSH "\e[3P"
moverect 0..1,4..80 -> 0..1,1..77
erase 0..1,77..80
?cursor = 0,1

View File

@ -0,0 +1,105 @@
INIT
WANTSTATE g
!Default
RESET
PUSH "#"
putglyph 0x23 1 0,0
!Designate G0=UK
RESET
PUSH "\e(A"
PUSH "#"
putglyph 0x00a3 1 0,0
!Designate G0=DEC drawing
RESET
PUSH "\e(0"
PUSH "a"
putglyph 0x2592 1 0,0
!Designate G1 + LS1
RESET
PUSH "\e)0"
PUSH "a"
putglyph 0x61 1 0,0
PUSH "\x0e"
PUSH "a"
putglyph 0x2592 1 0,1
!LS0
PUSH "\x0f"
PUSH "a"
putglyph 0x61 1 0,2
!Designate G2 + LS2
PUSH "\e*0"
PUSH "a"
putglyph 0x61 1 0,3
PUSH "\en"
PUSH "a"
putglyph 0x2592 1 0,4
PUSH "\x0f"
PUSH "a"
putglyph 0x61 1 0,5
!Designate G3 + LS3
PUSH "\e+0"
PUSH "a"
putglyph 0x61 1 0,6
PUSH "\eo"
PUSH "a"
putglyph 0x2592 1 0,7
PUSH "\x0f"
PUSH "a"
putglyph 0x61 1 0,8
!SS2
PUSH "a\x{8e}aa"
putglyph 0x61 1 0,9
putglyph 0x2592 1 0,10
putglyph 0x61 1 0,11
!SS3
PUSH "a\x{8f}aa"
putglyph 0x61 1 0,12
putglyph 0x2592 1 0,13
putglyph 0x61 1 0,14
!LS1R
RESET
PUSH "\e~"
PUSH "\xe1"
putglyph 0x61 1 0,0
PUSH "\e)0"
PUSH "\xe1"
putglyph 0x2592 1 0,1
!LS2R
RESET
PUSH "\e}"
PUSH "\xe1"
putglyph 0x61 1 0,0
PUSH "\e*0"
PUSH "\xe1"
putglyph 0x2592 1 0,1
!LS3R
RESET
PUSH "\e|"
PUSH "\xe1"
putglyph 0x61 1 0,0
PUSH "\e+0"
PUSH "\xe1"
putglyph 0x2592 1 0,1
UTF8 1
!Mixed US-ASCII and UTF-8
# U+0108 == 0xc4 0x88
RESET
PUSH "\e(B"
PUSH "AB\xc4\x88D"
putglyph 0x0041 1 0,0
putglyph 0x0042 1 0,1
putglyph 0x0108 1 0,2
putglyph 0x0044 1 0,3

View File

@ -0,0 +1,86 @@
INIT
UTF8 1
WANTSTATE gme
!Insert/Replace Mode
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "AC\e[DB"
putglyph 0x41 1 0,0
putglyph 0x43 1 0,1
putglyph 0x42 1 0,1
PUSH "\e[4h"
PUSH "\e[G"
PUSH "AC\e[DB"
moverect 0..1,0..79 -> 0..1,1..80
erase 0..1,0..1
putglyph 0x41 1 0,0
moverect 0..1,1..79 -> 0..1,2..80
erase 0..1,1..2
putglyph 0x43 1 0,1
moverect 0..1,1..79 -> 0..1,2..80
erase 0..1,1..2
putglyph 0x42 1 0,1
!Insert mode only happens once for UTF-8 combining
PUSH "e"
moverect 0..1,2..79 -> 0..1,3..80
erase 0..1,2..3
putglyph 0x65 1 0,2
PUSH "\xCC\x81"
putglyph 0x65,0x301 1 0,2
!Newline/Linefeed mode
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "\e[5G\n"
?cursor = 1,4
PUSH "\e[20h"
PUSH "\e[5G\n"
?cursor = 2,0
!DEC origin mode
RESET
erase 0..25,0..80
?cursor = 0,0
PUSH "\e[5;15r"
PUSH "\e[H"
?cursor = 0,0
PUSH "\e[3;3H"
?cursor = 2,2
PUSH "\e[?6h"
PUSH "\e[H"
?cursor = 4,0
PUSH "\e[3;3H"
?cursor = 6,2
!DECRQM on DECOM
PUSH "\e[?6h"
PUSH "\e[?6\$p"
output "\e[?6;1\$y"
PUSH "\e[?6l"
PUSH "\e[?6\$p"
output "\e[?6;2\$y"
!Origin mode with DECSLRM
PUSH "\e[?6h"
PUSH "\e[?69h"
PUSH "\e[20;60s"
PUSH "\e[H"
?cursor = 4,19
PUSH "\e[?69l"
!Origin mode bounds cursor to scrolling region
PUSH "\e[H"
PUSH "\e[10A"
?cursor = 4,0
PUSH "\e[20B"
?cursor = 14,0
!Origin mode without scroll region
PUSH "\e[?6l"
PUSH "\e[r\e[?6h"
?cursor = 0,0

View File

@ -0,0 +1,48 @@
INIT
WANTSTATE g
!Placement
RESET
PUSH "AB\e[79GCDE"
putglyph 0x41 1 0,0
putglyph 0x42 1 0,1
putglyph 0x43 1 0,78
putglyph 0x44 1 0,79
putglyph 0x45 1 1,0
!Resize
RESET
RESIZE 27,85
PUSH "AB\e[79GCDE"
putglyph 0x41 1 0,0
putglyph 0x42 1 0,1
putglyph 0x43 1 0,78
putglyph 0x44 1 0,79
putglyph 0x45 1 0,80
?cursor = 0,81
!Resize without reset
RESIZE 28,90
?cursor = 0,81
PUSH "FGHI"
putglyph 0x46 1 0,81
putglyph 0x47 1 0,82
putglyph 0x48 1 0,83
putglyph 0x49 1 0,84
?cursor = 0,85
!Resize shrink moves cursor
RESIZE 25,80
?cursor = 0,79
!Resize grow doesn't cancel phantom
RESET
PUSH "\e[79GAB"
putglyph 0x41 1 0,78
putglyph 0x42 1 0,79
?cursor = 0,79
RESIZE 30,100
?cursor = 0,80
PUSH "C"
putglyph 0x43 1 0,80
?cursor = 0,81

View File

@ -0,0 +1,172 @@
INIT
WANTSTATE p
!DECRQM on with mouse off
PUSH "\e[?1000\$p"
output "\e[?1000;2\$y"
PUSH "\e[?1002\$p"
output "\e[?1002;2\$y"
PUSH "\e[?1003\$p"
output "\e[?1003;2\$y"
!Mouse in simple button report mode
RESET
settermprop 1 true
settermprop 2 true
settermprop 7 1
PUSH "\e[?1000h"
settermprop 8 1
!Press 1
MOUSEMOVE 0,0 0
MOUSEBTN d 1 0
output "\e[M\x20\x21\x21"
!Release 1
MOUSEBTN u 1 0
output "\e[M\x23\x21\x21"
!Ctrl-Press 1
MOUSEBTN d 1 C
output "\e[M\x30\x21\x21"
MOUSEBTN u 1 C
output "\e[M\x33\x21\x21"
!Button 2
MOUSEBTN d 2 0
output "\e[M\x21\x21\x21"
MOUSEBTN u 2 0
output "\e[M\x23\x21\x21"
!Position
MOUSEMOVE 10,20 0
MOUSEBTN d 1 0
output "\e[M\x20\x35\x2b"
MOUSEBTN u 1 0
output "\e[M\x23\x35\x2b"
MOUSEMOVE 10,21 0
# no output
!Wheel events
MOUSEBTN d 4 0
output "\e[M\x60\x36\x2b"
MOUSEBTN d 4 0
output "\e[M\x60\x36\x2b"
MOUSEBTN d 5 0
output "\e[M\x61\x36\x2b"
!DECRQM on mouse button mode
PUSH "\e[?1000\$p"
output "\e[?1000;1\$y"
PUSH "\e[?1002\$p"
output "\e[?1002;2\$y"
PUSH "\e[?1003\$p"
output "\e[?1003;2\$y"
!Drag events
RESET
settermprop 1 true
settermprop 2 true
settermprop 7 1
PUSH "\e[?1002h"
settermprop 8 2
MOUSEMOVE 5,5 0
MOUSEBTN d 1 0
output "\e[M\x20\x26\x26"
MOUSEMOVE 5,6 0
output "\e[M\x40\x27\x26"
MOUSEMOVE 6,6 0
output "\e[M\x40\x27\x27"
MOUSEMOVE 6,6 0
# no output
MOUSEBTN u 1 0
output "\e[M\x23\x27\x27"
MOUSEMOVE 6,7
# no output
!DECRQM on mouse drag mode
PUSH "\e[?1000\$p"
output "\e[?1000;2\$y"
PUSH "\e[?1002\$p"
output "\e[?1002;1\$y"
PUSH "\e[?1003\$p"
output "\e[?1003;2\$y"
!Non-drag motion events
PUSH "\e[?1003h"
settermprop 8 3
MOUSEMOVE 6,8 0
output "\e[M\x43\x29\x27"
!DECRQM on mouse motion mode
PUSH "\e[?1000\$p"
output "\e[?1000;2\$y"
PUSH "\e[?1002\$p"
output "\e[?1002;2\$y"
PUSH "\e[?1003\$p"
output "\e[?1003;1\$y"
!Bounds checking
MOUSEMOVE 300,300 0
output "\e[M\x43\xff\xff"
MOUSEBTN d 1 0
output "\e[M\x20\xff\xff"
MOUSEBTN u 1 0
output "\e[M\x23\xff\xff"
!DECRQM on standard encoding mode
PUSH "\e[?1005\$p"
output "\e[?1005;2\$y"
PUSH "\e[?1006\$p"
output "\e[?1006;2\$y"
PUSH "\e[?1015\$p"
output "\e[?1015;2\$y"
!UTF-8 extended encoding mode
# 300 + 32 + 1 = 333 = U+014d = \xc5\x8d
PUSH "\e[?1005h"
MOUSEBTN d 1 0
output "\e[M\x20\xc5\x8d\xc5\x8d"
MOUSEBTN u 1 0
output "\e[M\x23\xc5\x8d\xc5\x8d"
!DECRQM on UTF-8 extended encoding mode
PUSH "\e[?1005\$p"
output "\e[?1005;1\$y"
PUSH "\e[?1006\$p"
output "\e[?1006;2\$y"
PUSH "\e[?1015\$p"
output "\e[?1015;2\$y"
!SGR extended encoding mode
PUSH "\e[?1006h"
MOUSEBTN d 1 0
output "\e[<0;301;301M"
MOUSEBTN u 1 0
output "\e[<0;301;301m"
!DECRQM on SGR extended encoding mode
PUSH "\e[?1005\$p"
output "\e[?1005;2\$y"
PUSH "\e[?1006\$p"
output "\e[?1006;1\$y"
PUSH "\e[?1015\$p"
output "\e[?1015;2\$y"
!rxvt extended encoding mode
PUSH "\e[?1015h"
MOUSEBTN d 1 0
output "\e[0;301;301M"
MOUSEBTN u 1 0
output "\e[3;301;301M"
!DECRQM on rxvt extended encoding mode
PUSH "\e[?1005\$p"
output "\e[?1005;2\$y"
PUSH "\e[?1006\$p"
output "\e[?1006;2\$y"
PUSH "\e[?1015\$p"
output "\e[?1015;1\$y"

View File

@ -0,0 +1,36 @@
INIT
WANTSTATE p
RESET
settermprop 1 true
settermprop 2 true
settermprop 7 1
!Cursor visibility
PUSH "\e[?25h"
settermprop 1 true
PUSH "\e[?25\$p"
output "\e[?25;1\$y"
PUSH "\e[?25l"
settermprop 1 false
PUSH "\e[?25\$p"
output "\e[?25;2\$y"
!Cursor blink
PUSH "\e[?12h"
settermprop 2 true
PUSH "\e[?12\$p"
output "\e[?12;1\$y"
PUSH "\e[?12l"
settermprop 2 false
PUSH "\e[?12\$p"
output "\e[?12;2\$y"
!Cursor shape
PUSH "\e[3 q"
settermprop 2 true
settermprop 7 2
!Title
PUSH "\e]2;Here is my title\a"
settermprop 4 "Here is my title"

View File

@ -0,0 +1,69 @@
INIT
UTF8 1
WANTSTATE gm
!79th Column
PUSH "\e[75G"
PUSH "A"x5
putglyph 0x41 1 0,74
putglyph 0x41 1 0,75
putglyph 0x41 1 0,76
putglyph 0x41 1 0,77
putglyph 0x41 1 0,78
?cursor = 0,79
!80th Column Phantom
PUSH "A"
putglyph 0x41 1 0,79
?cursor = 0,79
!Line Wraparound
PUSH "B"
putglyph 0x42 1 1,0
?cursor = 1,1
!Line Wraparound during combined write
PUSH "\e[78G"
PUSH "BBBCC"
putglyph 0x42 1 1,77
putglyph 0x42 1 1,78
putglyph 0x42 1 1,79
putglyph 0x43 1 2,0
putglyph 0x43 1 2,1
?cursor = 2,2
!DEC Auto Wrap Mode
RESET
PUSH "\e[?7l"
PUSH "\e[75G"
PUSH "D"x6
putglyph 0x44 1 0,74
putglyph 0x44 1 0,75
putglyph 0x44 1 0,76
putglyph 0x44 1 0,77
putglyph 0x44 1 0,78
putglyph 0x44 1 0,79
?cursor = 0,79
PUSH "D"
putglyph 0x44 1 0,79
?cursor = 0,79
PUSH "\e[?7h"
!80th column causes linefeed on wraparound
PUSH "\e[25;78HABC"
putglyph 0x41 1 24,77
putglyph 0x42 1 24,78
putglyph 0x43 1 24,79
?cursor = 24,79
PUSH "D"
moverect 1..25,0..80 -> 0..24,0..80
putglyph 0x44 1 24,0
!80th column phantom linefeed phantom cancelled by explicit cursor move
PUSH "\e[25;78HABC"
putglyph 0x41 1 24,77
putglyph 0x42 1 24,78
putglyph 0x43 1 24,79
?cursor = 24,79
PUSH "\e[25;1HD"
putglyph 0x44 1 24,0

View File

@ -0,0 +1,60 @@
INIT
WANTSTATE g
!Initial
RESET
PUSH "\tX"
putglyph 0x58 1 0,8
PUSH "\tX"
putglyph 0x58 1 0,16
?cursor = 0,17
!HTS
PUSH "\e[5G\eH"
PUSH "\e[G\tX"
putglyph 0x58 1 0,4
?cursor = 0,5
!TBC 0
PUSH "\e[9G\e[g"
PUSH "\e[G\tX\tX"
putglyph 0x58 1 0,4
putglyph 0x58 1 0,16
?cursor = 0,17
!TBC 3
PUSH "\e[3g\e[50G\eH\e[G"
?cursor = 0,0
PUSH "\tX"
putglyph 0x58 1 0,49
?cursor = 0,50
!Tabstops after resize
RESET
RESIZE 30,100
# Should be 100/8 = 12 tabstops
PUSH "\tX"
putglyph 0x58 1 0,8
PUSH "\tX"
putglyph 0x58 1 0,16
PUSH "\tX"
putglyph 0x58 1 0,24
PUSH "\tX"
putglyph 0x58 1 0,32
PUSH "\tX"
putglyph 0x58 1 0,40
PUSH "\tX"
putglyph 0x58 1 0,48
PUSH "\tX"
putglyph 0x58 1 0,56
PUSH "\tX"
putglyph 0x58 1 0,64
PUSH "\tX"
putglyph 0x58 1 0,72
PUSH "\tX"
putglyph 0x58 1 0,80
PUSH "\tX"
putglyph 0x58 1 0,88
PUSH "\tX"
putglyph 0x58 1 0,96
?cursor = 0,97

View File

@ -0,0 +1,64 @@
INIT
WANTSTATE p
RESET
settermprop 1 true
settermprop 2 true
settermprop 7 1
!Set up state
PUSH "\e[2;2H"
?cursor = 1,1
PUSH "\e[1m"
?pen bold = on
!Save
PUSH "\e[?1048h"
!Change state
PUSH "\e[5;5H"
?cursor = 4,4
PUSH "\e[4 q"
settermprop 2 false
settermprop 7 2
PUSH "\e[22;4m"
?pen bold = off
?pen underline = 1
!Restore
PUSH "\e[?1048l"
settermprop 1 true
settermprop 2 true
settermprop 7 1
?cursor = 1,1
?pen bold = on
?pen underline = 0
!Save/restore using DECSC/DECRC
PUSH "\e[2;2H\e7"
?cursor = 1,1
PUSH "\e[5;5H"
?cursor = 4,4
PUSH "\e8"
settermprop 1 true
settermprop 2 true
settermprop 7 1
?cursor = 1,1
!Save twice, restore twice happens on both edge transitions
PUSH "\e[2;10H\e[?1048h\e[6;10H\e[?1048h"
PUSH "\e[H"
?cursor = 0,0
PUSH "\e[?1048l"
settermprop 1 true
settermprop 2 true
settermprop 7 1
?cursor = 5,9
PUSH "\e[H"
?cursor = 0,0
PUSH "\e[?1048l"
settermprop 1 true
settermprop 2 true
settermprop 7 1
?cursor = 5,9

View File

@ -0,0 +1,132 @@
INIT
WANTSTATE
!Unmodified ASCII
INCHAR 0 41
output "A"
INCHAR 0 61
output "a"
!Ctrl modifier on ASCII letters
INCHAR C 41
output "\e[65;5u"
INCHAR C 61
output "\x01"
!Alt modifier on ASCII letters
INCHAR A 41
output "\eA"
INCHAR A 61
output "\ea"
!Ctrl-Alt modifier on ASCII letters
INCHAR CA 41
output "\e[65;7u"
INCHAR CA 61
output "\e\x01"
!Special handling of Ctrl-I
INCHAR 0 49
output "I"
INCHAR 0 69
output "i"
INCHAR C 49
output "\e[73;5u"
INCHAR C 69
output "\e[105;5u"
INCHAR A 49
output "\eI"
INCHAR A 69
output "\ei"
INCHAR CA 49
output "\e[73;7u"
INCHAR CA 69
output "\e[105;7u"
!Special handling of Space
INCHAR 0 20
output " "
INCHAR S 20
output "\e[32;2u"
INCHAR C 20
output "\0"
INCHAR SC 20
output "\e[32;6u"
INCHAR A 20
output "\e "
INCHAR SA 20
output "\e[32;4u"
INCHAR CA 20
output "\e\0"
INCHAR SCA 20
output "\e[32;8u"
!Cursor keys in reset (cursor) mode
INKEY 0 Up
output "\e[A"
INKEY S Up
output "\e[1;2A"
INKEY C Up
output "\e[1;5A"
INKEY SC Up
output "\e[1;6A"
INKEY A Up
output "\e[1;3A"
INKEY SA Up
output "\e[1;4A"
INKEY CA Up
output "\e[1;7A"
INKEY SCA Up
output "\e[1;8A"
!Cursor keys in application mode
PUSH "\e[?1h"
# Plain "Up" should be SS3 A now
INKEY 0 Up
output "\eOA"
# Modified keys should still use CSI
INKEY S Up
output "\e[1;2A"
INKEY C Up
output "\e[1;5A"
!Shift-Tab should be different
INKEY 0 Tab
output "\x09"
INKEY S Tab
output "\e[Z"
INKEY C Tab
output "\e[9;5u"
INKEY A Tab
output "\e\x09"
INKEY CA Tab
output "\e[9;7u"
!Enter in linefeed mode
INKEY 0 Enter
output "\x0d"
!Enter in newline mode
PUSH "\e[20h"
INKEY 0 Enter
output "\x0d\x0a"
!Keypad in DECKPNM
INKEY 0 KP0
output "0"
!Keypad in DECKPAM
PUSH "\e="
INKEY 0 KP0
output "\eOp"
!Bracketed paste mode off
PASTE START
PASTE END
!Bracketed paste mode on
PUSH "\e[?2004h"
PASTE START
output "\e[200~"
PASTE END
output "\e[201~"

View File

@ -0,0 +1,62 @@
INIT
WANTSTATE
!DA
RESET
PUSH "\e[c"
output "\e[?1;2c"
!DSR
RESET
PUSH "\e[5n"
output "\e[0n"
!CPR
PUSH "\e[6n"
output "\e[1;1R"
PUSH "\e[10;10H\e[6n"
output "\e[10;10R"
!DECCPR
PUSH "\e[?6n"
output "\e[?10;10R"
!DECRQSS on DECSCUSR
PUSH "\e[3 q"
PUSH "\eP\$q q\e\\"
output "\eP1\$r3 q\e\\"
!DECRQSS on SGR
PUSH "\e[1;5;7m"
PUSH "\eP\$qm\e\\"
output "\eP1\$r1;5;7m\e\\"
!DECRQSS on SGR ANSI colours
PUSH "\e[0;31;42m"
PUSH "\eP\$qm\e\\"
output "\eP1\$r31;42m\e\\"
!DECRQSS on SGR ANSI hi-bright colours
PUSH "\e[0;93;104m"
PUSH "\eP\$qm\e\\"
output "\eP1\$r93;104m\e\\"
!DECRQSS on SGR 256-palette colours
PUSH "\e[0;38:5:56;48:5:78m"
PUSH "\eP\$qm\e\\"
output "\eP1\$r38:5:56;48:5:78m\e\\"
!DECRQSS on SGR RGB8 colours
PUSH "\e[0;38:2:24:68:112;48:2:13:57:101m"
PUSH "\eP\$qm\e\\"
output "\eP1\$r38:2:24:68:112;48:2:13:57:101m\e\\"
!S8C1T on DSR
PUSH "\e G"
PUSH "\e[5n"
output "\x{9b}0n"
PUSH "\e F"
!Truncation on attempted buffer overflow
PUSH "\e[6n" x 20
output "\e[10;10R" x 7

View File

@ -0,0 +1,32 @@
INIT
WANTSTATE
RESET
!RIS homes cursor
PUSH "\e[5;5H"
?cursor = 4,4
WANTSTATE +m
PUSH "\ec"
?cursor = 0,0
WANTSTATE -m
!RIS cancels scrolling region
PUSH "\e[5;10r"
WANTSTATE +s
PUSH "\ec\e[25H\n"
scrollrect 0..25,0..80 => +1,+0
WANTSTATE -s
!RIS erases screen
PUSH "ABCDE"
WANTSTATE +e
PUSH "\ec"
erase 0..25,0..80
WANTSTATE -e
!RIS clears tabstops
PUSH "\e[5G\eH\e[G\t"
?cursor = 0,4
PUSH "\ec\t"
?cursor = 0,8

View File

@ -0,0 +1,61 @@
INIT
WANTSTATE g
!Single Width, Single Height
RESET
PUSH "\e#5"
PUSH "Hello"
putglyph 0x48 1 0,0
putglyph 0x65 1 0,1
putglyph 0x6c 1 0,2
putglyph 0x6c 1 0,3
putglyph 0x6f 1 0,4
!Double Width, Single Height
RESET
PUSH "\e#6"
PUSH "Hello"
putglyph 0x48 1 0,0 dwl
putglyph 0x65 1 0,1 dwl
putglyph 0x6c 1 0,2 dwl
putglyph 0x6c 1 0,3 dwl
putglyph 0x6f 1 0,4 dwl
?cursor = 0,5
PUSH "\e[40GAB"
putglyph 0x41 1 0,39 dwl
putglyph 0x42 1 1,0
?cursor = 1,1
!Double Height
RESET
PUSH "\e#3"
PUSH "Hello"
putglyph 0x48 1 0,0 dwl dhl-top
putglyph 0x65 1 0,1 dwl dhl-top
putglyph 0x6c 1 0,2 dwl dhl-top
putglyph 0x6c 1 0,3 dwl dhl-top
putglyph 0x6f 1 0,4 dwl dhl-top
?cursor = 0,5
PUSH "\r\n\e#4"
PUSH "Hello"
putglyph 0x48 1 1,0 dwl dhl-bottom
putglyph 0x65 1 1,1 dwl dhl-bottom
putglyph 0x6c 1 1,2 dwl dhl-bottom
putglyph 0x6c 1 1,3 dwl dhl-bottom
putglyph 0x6f 1 1,4 dwl dhl-bottom
?cursor = 1,5
!Double Width scrolling
RESET
PUSH "\e[20H\e#6ABC"
putglyph 0x41 1 19,0 dwl
putglyph 0x42 1 19,1 dwl
putglyph 0x43 1 19,2 dwl
PUSH "\e[25H\n"
PUSH "\e[19;4HDE"
putglyph 0x44 1 18,3 dwl
putglyph 0x45 1 18,4 dwl
PUSH "\e[H\eM"
PUSH "\e[20;6HFG"
putglyph 0x46 1 19,5 dwl
putglyph 0x47 1 19,6 dwl

View File

@ -0,0 +1,19 @@
INIT
WANTSTATE f
RESET
!Unrecognised control
PUSH "\x03"
control 03
!Unrecognised CSI
PUSH "\e[?15;2z"
csi 0x7a L=3f 15,2
!Unrecognised OSC
PUSH "\e]27;Something\e\\"
osc "27;Something"
!Unrecognised DCS
PUSH "\ePz123\e\\"
dcs "z123"

106
src/libvterm/t/30pen.test Normal file
View File

@ -0,0 +1,106 @@
INIT
UTF8 1
WANTSTATE
!Reset
PUSH "\e[m"
?pen bold = off
?pen underline = 0
?pen italic = off
?pen blink = off
?pen reverse = off
?pen font = 0
?pen foreground = rgb(240,240,240)
?pen background = rgb(0,0,0)
!Bold
PUSH "\e[1m"
?pen bold = on
PUSH "\e[22m"
?pen bold = off
PUSH "\e[1m\e[m"
?pen bold = off
!Underline
PUSH "\e[4m"
?pen underline = 1
PUSH "\e[21m"
?pen underline = 2
PUSH "\e[24m"
?pen underline = 0
PUSH "\e[4m\e[m"
?pen underline = 0
!Italic
PUSH "\e[3m"
?pen italic = on
PUSH "\e[23m"
?pen italic = off
PUSH "\e[3m\e[m"
?pen italic = off
!Blink
PUSH "\e[5m"
?pen blink = on
PUSH "\e[25m"
?pen blink = off
PUSH "\e[5m\e[m"
?pen blink = off
!Reverse
PUSH "\e[7m"
?pen reverse = on
PUSH "\e[27m"
?pen reverse = off
PUSH "\e[7m\e[m"
?pen reverse = off
!Font Selection
PUSH "\e[11m"
?pen font = 1
PUSH "\e[19m"
?pen font = 9
PUSH "\e[10m"
?pen font = 0
PUSH "\e[11m\e[m"
?pen font = 0
!Foreground
PUSH "\e[31m"
?pen foreground = rgb(224,0,0)
PUSH "\e[32m"
?pen foreground = rgb(0,224,0)
PUSH "\e[34m"
?pen foreground = rgb(0,0,224)
PUSH "\e[91m"
?pen foreground = rgb(255,64,64)
PUSH "\e[38:2:10:20:30m"
?pen foreground = rgb(10,20,30)
PUSH "\e[38:5:1m"
?pen foreground = rgb(224,0,0)
PUSH "\e[39m"
?pen foreground = rgb(240,240,240)
!Background
PUSH "\e[41m"
?pen background = rgb(224,0,0)
PUSH "\e[42m"
?pen background = rgb(0,224,0)
PUSH "\e[44m"
?pen background = rgb(0,0,224)
PUSH "\e[101m"
?pen background = rgb(255,64,64)
PUSH "\e[48:2:10:20:30m"
?pen background = rgb(10,20,30)
PUSH "\e[48:5:1m"
?pen background = rgb(224,0,0)
PUSH "\e[49m"
?pen background = rgb(0,0,0)
!Bold+ANSI colour == highbright
PUSH "\e[m\e[1;37m"
?pen bold = on
?pen foreground = rgb(255,255,255)
PUSH "\e[m\e[37;1m"
?pen bold = on
?pen foreground = rgb(255,255,255)

View File

@ -0,0 +1,69 @@
INIT
WANTSCREEN c
!Get
RESET
PUSH "ABC"
movecursor 0,3
?screen_chars 0,0,1,3 = 0x41,0x42,0x43
?screen_chars 0,0,1,80 = 0x41,0x42,0x43
?screen_text 0,0,1,3 = 0x41,0x42,0x43
?screen_text 0,0,1,80 = 0x41,0x42,0x43
?screen_cell 0,0 = {0x41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
?screen_cell 0,1 = {0x42} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
?screen_cell 0,2 = {0x43} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
?screen_row 0 = "ABC"
?screen_eol 0,0 = 0
?screen_eol 0,2 = 0
?screen_eol 0,3 = 1
PUSH "\e[H"
movecursor 0,0
?screen_chars 0,0,1,80 = 0x41,0x42,0x43
?screen_text 0,0,1,80 = 0x41,0x42,0x43
PUSH "E"
movecursor 0,1
?screen_chars 0,0,1,80 = 0x45,0x42,0x43
?screen_text 0,0,1,80 = 0x45,0x42,0x43
WANTSCREEN -c
!Erase
RESET
PUSH "ABCDE\e[H\e[K"
?screen_chars 0,0,1,80 =
?screen_text 0,0,1,80 =
!Copycell
RESET
PUSH "ABC\e[H\e[@"
PUSH "1"
?screen_chars 0,0,1,80 = 0x31,0x41,0x42,0x43
RESET
PUSH "ABC\e[H\e[P"
?screen_chars 0,0,1,1 = 0x42
?screen_chars 0,1,1,2 = 0x43
?screen_chars 0,0,1,80 = 0x42,0x43
!Space padding
RESET
PUSH "Hello\e[CWorld"
?screen_chars 0,0,1,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64
?screen_text 0,0,1,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64
!Linefeed padding
RESET
PUSH "Hello\r\nWorld"
?screen_chars 0,0,2,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x0a,0x57,0x6f,0x72,0x6c,0x64
?screen_text 0,0,2,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x0a,0x57,0x6f,0x72,0x6c,0x64
!Altscreen
RESET
PUSH "P"
?screen_chars 0,0,1,80 = 0x50
PUSH "\e[?1049h"
?screen_chars 0,0,1,80 =
PUSH "\e[2K\e[HA"
?screen_chars 0,0,1,80 = 0x41
PUSH "\e[?1049l"
?screen_chars 0,0,1,80 = 0x50

View File

@ -0,0 +1,47 @@
INIT
UTF8 1
WANTSCREEN
!Single width UTF-8
# U+00C1 = 0xC3 0x81 name: LATIN CAPITAL LETTER A WITH ACUTE
# U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE
RESET
PUSH "\xC3\x81\xC3\xA9"
?screen_chars 0,0,1,80 = 0xc1,0xe9
?screen_text 0,0,1,80 = 0xc3,0x81,0xc3,0xa9
?screen_cell 0,0 = {0xc1} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
!Wide char
# U+FF10 = 0xEF 0xBC 0x90 name: FULLWIDTH DIGIT ZERO
RESET
PUSH "0123\e[H"
PUSH "\xEF\xBC\x90"
?screen_chars 0,0,1,80 = 0xff10,0x32,0x33
?screen_text 0,0,1,80 = 0xef,0xbc,0x90,0x32,0x33
?screen_cell 0,0 = {0xff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
!Combining char
# U+0301 = 0xCC 0x81 name: COMBINING ACUTE
RESET
PUSH "0123\e[H"
PUSH "e\xCC\x81"
?screen_chars 0,0,1,80 = 0x65,0x301,0x31,0x32,0x33
?screen_text 0,0,1,80 = 0x65,0xcc,0x81,0x31,0x32,0x33
?screen_cell 0,0 = {0x65,0x301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
!10 combining accents should not crash
RESET
PUSH "e\xCC\x81\xCC\x82\xCC\x83\xCC\x84\xCC\x85\xCC\x86\xCC\x87\xCC\x88\xCC\x89\xCC\x8A"
?screen_cell 0,0 = {0x65,0x301,0x302,0x303,0x304,0x305} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
!40 combining accents in two split writes of 20 should not crash
RESET
PUSH "e\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81"
PUSH "\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81"
?screen_cell 0,0 = {0x65,0x301,0x301,0x301,0x301,0x301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
!Outputing CJK doublewidth in 80th column should wraparound to next line and not crash"
RESET
PUSH "\e[80G\xEF\xBC\x90"
?screen_cell 0,79 = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
?screen_cell 1,0 = {0xff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)

View File

@ -0,0 +1,155 @@
INIT
WANTSCREEN Db
!Putglyph
RESET
damage 0..25,0..80
PUSH "123"
damage 0..1,0..1 = 0<31>
damage 0..1,1..2 = 0<32>
damage 0..1,2..3 = 0<33>
!Erase
PUSH "\e[H"
PUSH "\e[3X"
damage 0..1,0..3
!Scroll damages entire line in two chunks
PUSH "\e[H\e[5@"
damage 0..1,5..80
damage 0..1,0..5
!Scroll down damages entire screen in two chunks
PUSH "\e[T"
damage 1..25,0..80
damage 0..1,0..80
!Altscreen damages entire area
PUSH "\e[?1049h"
damage 0..25,0..80
PUSH "\e[?1049l"
damage 0..25,0..80
WANTSCREEN m
!Scroll invokes moverect but not damage
PUSH "\e[5@"
moverect 0..1,0..75 -> 0..1,5..80
damage 0..1,0..5
WANTSCREEN -m
!Merge to cells
RESET
damage 0..25,0..80
DAMAGEMERGE CELL
PUSH "A"
damage 0..1,0..1 = 0<41>
PUSH "B"
damage 0..1,1..2 = 0<42>
PUSH "C"
damage 0..1,2..3 = 0<43>
!Merge entire rows
RESET
damage 0..25,0..80
DAMAGEMERGE ROW
PUSH "ABCDE\r\nEFGH"
damage 0..1,0..5 = 0<41 42 43 44 45>
DAMAGEFLUSH
damage 1..2,0..4 = 1<45 46 47 48>
PUSH "\e[3;6r\e[6H\eD"
damage 2..5,0..80
DAMAGEFLUSH
damage 5..6,0..80
!Merge entire screen
RESET
damage 0..25,0..80
DAMAGEMERGE SCREEN
PUSH "ABCDE\r\nEFGH"
DAMAGEFLUSH
damage 0..2,0..5 = 0<41 42 43 44 45> 1<45 46 47 48>
PUSH "\e[3;6r\e[6H\eD"
DAMAGEFLUSH
damage 2..6,0..80
!Merge entire screen with moverect
WANTSCREEN m
RESET
damage 0..25,0..80
DAMAGEMERGE SCREEN
PUSH "ABCDE\r\nEFGH"
PUSH "\e[3;6r\e[6H\eD"
damage 0..2,0..5 = 0<41 42 43 44 45> 1<45 46 47 48>
moverect 3..6,0..80 -> 2..5,0..80
DAMAGEFLUSH
damage 5..6,0..80
!Merge scroll
RESET
damage 0..25,0..80
DAMAGEMERGE SCROLL
PUSH "\e[H1\r\n2\r\n3"
PUSH "\e[25H\n\n\n"
sb_pushline 80 = 31
sb_pushline 80 = 32
sb_pushline 80 = 33
DAMAGEFLUSH
moverect 3..25,0..80 -> 0..22,0..80
damage 0..25,0..80
!Merge scroll with damage
PUSH "\e[25H"
PUSH "ABCDE\r\nEFGH\r\n"
sb_pushline 80 =
sb_pushline 80 =
DAMAGEFLUSH
moverect 2..25,0..80 -> 0..23,0..80
damage 22..25,0..80 = 22<41 42 43 44 45> 23<45 46 47 48>
!Merge scroll with damage past region
PUSH "\e[3;6r\e[6H1\r\n2\r\n3\r\n4\r\n5"
DAMAGEFLUSH
damage 2..6,0..80 = 2<32> 3<33> 4<34> 5<35>
!Damage entirely outside scroll region
PUSH "\e[HABC\e[3;6r\e[6H\r\n6"
damage 0..1,0..3 = 0<41 42 43>
DAMAGEFLUSH
moverect 3..6,0..80 -> 2..5,0..80
damage 5..6,0..80 = 5<36>
!Damage overlapping scroll region
PUSH "\e[H\e[2J"
DAMAGEFLUSH
damage 0..25,0..80
PUSH "\e[HABCD\r\nEFGH\r\nIJKL\e[2;5r\e[5H\r\nMNOP"
DAMAGEFLUSH
moverect 2..5,0..80 -> 1..4,0..80
damage 0..5,0..80 = 0<41 42 43 44> 1<49 4A 4B 4C>
## TODO: is this right?
!Merge scroll*2 with damage
RESET
damage 0..25,0..80
DAMAGEMERGE SCROLL
PUSH "\e[25H\r\nABCDE\b\b\b\e[2P\r\n"
sb_pushline 80 =
moverect 1..25,0..80 -> 0..24,0..80
damage 24..25,0..80 = 24<41 42 43 44 45>
moverect 24..25,4..80 -> 24..25,2..78
damage 24..25,78..80
sb_pushline 80 =
DAMAGEFLUSH
moverect 1..25,0..80 -> 0..24,0..80
damage 24..25,0..80
?screen_chars 23,0,24,5 = 0x41,0x42,0x45

View File

@ -0,0 +1,90 @@
INIT
WANTSTATE
WANTSCREEN
!Resize wider preserves cells
RESET
RESIZE 25,80
PUSH "AB\r\nCD"
?screen_chars 0,0,1,80 = 0x41,0x42
?screen_chars 1,0,2,80 = 0x43,0x44
RESIZE 25,100
?screen_chars 0,0,1,100 = 0x41,0x42
?screen_chars 1,0,2,100 = 0x43,0x44
!Resize wider allows print in new area
RESET
RESIZE 25,80
PUSH "AB\e[79GCD"
?screen_chars 0,0,1,2 = 0x41,0x42
?screen_chars 0,78,1,80 = 0x43,0x44
RESIZE 25,100
?screen_chars 0,0,1,2 = 0x41,0x42
?screen_chars 0,78,1,80 = 0x43,0x44
PUSH "E"
?screen_chars 0,78,1,81 = 0x43,0x44,0x45
!Resize shorter with blanks just truncates
RESET
RESIZE 25,80
PUSH "Top\e[10HLine 10"
?screen_chars 0,0,1,80 = 0x54,0x6f,0x70
?screen_chars 9,0,10,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31,0x30
?cursor = 9,7
RESIZE 20,80
?screen_chars 0,0,1,80 = 0x54,0x6f,0x70
?screen_chars 9,0,10,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31,0x30
?cursor = 9,7
!Resize shorter with content must scroll
RESET
RESIZE 25,80
PUSH "Top\e[25HLine 25\e[15H"
?screen_chars 0,0,1,80 = 0x54,0x6f,0x70
?screen_chars 24,0,25,80 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35
?cursor = 14,0
WANTSCREEN b
RESIZE 20,80
sb_pushline 80 = 54 6F 70
sb_pushline 80 =
sb_pushline 80 =
sb_pushline 80 =
sb_pushline 80 =
?screen_chars 0,0,1,80 =
?screen_chars 19,0,20,80 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35
?cursor = 9,0
!Resize shorter does not lose line with cursor
# See also https://github.com/neovim/libvterm/commit/1b745d29d45623aa8d22a7b9288c7b0e331c7088
RESET
WANTSCREEN -b
RESIZE 25,80
WANTSCREEN b
PUSH "\e[24HLine 24\r\nLine 25\r\n"
sb_pushline 80 =
?screen_chars 23,0,24,10 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35
?cursor = 24,0
RESIZE 24,80
sb_pushline 80 =
?screen_chars 22,0,23,10 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35
?cursor = 23,0
!Resize taller attempts to pop scrollback
RESET
WANTSCREEN -b
RESIZE 25,80
PUSH "Line 1\e[25HBottom\e[15H"
?screen_chars 0,0,1,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31
?screen_chars 24,0,25,80 = 0x42,0x6f,0x74,0x74,0x6f,0x6d
?cursor = 14,0
WANTSCREEN b
RESIZE 30,80
sb_popline 80
sb_popline 80
sb_popline 80
sb_popline 80
sb_popline 80
?screen_chars 0,0,1,80 = 0x41,0x42,0x43,0x44,0x45
?screen_chars 5,0,6,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31
?screen_chars 29,0,30,80 = 0x42,0x6f,0x74,0x74,0x6f,0x6d
?cursor = 19,0

View File

@ -0,0 +1,55 @@
INIT
WANTSCREEN
RESET
!Plain
PUSH "A"
?screen_cell 0,0 = {0x41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
!Bold
PUSH "\e[1mB"
?screen_cell 0,1 = {0x42} width=1 attrs={B} fg=rgb(240,240,240) bg=rgb(0,0,0)
!Italic
PUSH "\e[3mC"
?screen_cell 0,2 = {0x43} width=1 attrs={BI} fg=rgb(240,240,240) bg=rgb(0,0,0)
!Underline
PUSH "\e[4mD"
?screen_cell 0,3 = {0x44} width=1 attrs={BU1I} fg=rgb(240,240,240) bg=rgb(0,0,0)
!Reset
PUSH "\e[mE"
?screen_cell 0,4 = {0x45} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
!Font
PUSH "\e[11mF\e[m"
?screen_cell 0,5 = {0x46} width=1 attrs={F1} fg=rgb(240,240,240) bg=rgb(0,0,0)
!Foreground
PUSH "\e[31mG\e[m"
?screen_cell 0,6 = {0x47} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(0,0,0)
!Background
PUSH "\e[42mH\e[m"
?screen_cell 0,7 = {0x48} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,224,0)
!EL sets reverse and colours to end of line
PUSH "\e[H\e[7;33;44m\e[K"
?screen_cell 0,0 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)
?screen_cell 0,79 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)
!DECSCNM xors reverse for entire screen
PUSH "\e[?5h"
?screen_cell 0,0 = {} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)
?screen_cell 0,79 = {} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)
?screen_cell 1,0 = {} width=1 attrs={R} fg=rgb(240,240,240) bg=rgb(0,0,0)
PUSH "\e[?5\$p"
output "\e[?5;1\$y"
PUSH "\e[?5l"
?screen_cell 0,0 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)
?screen_cell 0,79 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)
?screen_cell 1,0 = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
PUSH "\e[?5\$p"
output "\e[?5;2\$y"

View File

@ -0,0 +1,16 @@
INIT
WANTSCREEN
!Selective erase
RESET
PUSH "A\e[1\"qB\e[\"qC"
?screen_chars 0,0,1,3 = 0x41,0x42,0x43
PUSH "\e[G\e[?J"
?screen_chars 0,0,1,3 = 0x20,0x42
!Non-selective erase
RESET
PUSH "A\e[1\"qB\e[\"qC"
?screen_chars 0,0,1,3 = 0x41,0x42,0x43
PUSH "\e[G\e[J"
?screen_chars 0,0,1,3 =

View File

@ -0,0 +1,11 @@
INIT
WANTSCREEN
!Bold extent
RESET
PUSH "AB\e[1mCD\e[mE"
?screen_attrs_extent 0,0 = 0,0-1,1
?screen_attrs_extent 0,1 = 0,0-1,1
?screen_attrs_extent 0,2 = 0,2-1,3
?screen_attrs_extent 0,3 = 0,2-1,3
?screen_attrs_extent 0,4 = 0,4-1,79

View File

@ -0,0 +1,32 @@
INIT
WANTSCREEN
RESET
!Single Width, Single Height
RESET
PUSH "\e#5"
PUSH "abcde"
?screen_cell 0,0 = {0x61} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
!Double Width, Single Height
RESET
PUSH "\e#6"
PUSH "abcde"
?screen_cell 0,0 = {0x61} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)
!Double Height
RESET
PUSH "\e#3"
PUSH "abcde"
PUSH "\r\n\e#4"
PUSH "abcde"
?screen_cell 0,0 = {0x61} width=1 attrs={} dwl dhl-top fg=rgb(240,240,240) bg=rgb(0,0,0)
?screen_cell 1,0 = {0x61} width=1 attrs={} dwl dhl-bottom fg=rgb(240,240,240) bg=rgb(0,0,0)
!Late change
RESET
PUSH "abcde"
?screen_cell 0,0 = {0x61} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
PUSH "\e#6"
?screen_cell 0,0 = {0x61} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)

View File

@ -0,0 +1,17 @@
INIT
WANTSCREEN p
RESET
settermprop 1 true
settermprop 2 true
settermprop 7 1
!Cursor visibility
PUSH "\e[?25h"
settermprop 1 true
PUSH "\e[?25l"
settermprop 1 false
!Title
PUSH "\e]2;Here is my title\a"
settermprop 4 "Here is my title"

View File

@ -0,0 +1,87 @@
INIT
WANTSTATE
WANTSCREEN
RESET
PUSH "\e#8"
PUSH "\e[9;10H\e[1J"
PUSH "\e[18;60H\e[0J\e[1K"
PUSH "\e[9;71H\e[0K"
$SEQ 10 16: PUSH "\e[\#;10H\e[1K\e[\#;71H\e[0K"
PUSH "\e[17;30H\e[2K"
$SEQ 1 80: PUSH "\e[24;\#f*\e[1;\#f*"
PUSH "\e[2;2H"
$REP 22: PUSH "+\e[1D\eD"
PUSH "\e[23;79H"
$REP 22: PUSH "+\e[1D\eM"
PUSH "\e[2;1H"
$SEQ 2 23: PUSH "*\e[\#;80H*\e[10D\eE"
PUSH "\e[2;10H\e[42D\e[2C"
$REP 76: PUSH "+\e[0C\e[2D\e[1C"
PUSH "\e[23;70H\e[42C\e[2D"
$REP 76: PUSH "+\e[1D\e[1C\e[0D\b"
PUSH "\e[1;1H"
PUSH "\e[10A"
PUSH "\e[1A"
PUSH "\e[0A"
PUSH "\e[24;80H"
PUSH "\e[10B"
PUSH "\e[1B"
PUSH "\e[0B"
PUSH "\e[10;12H"
$REP 58: PUSH " "
PUSH "\e[1B\e[58D"
$REP 58: PUSH " "
PUSH "\e[1B\e[58D"
$REP 58: PUSH " "
PUSH "\e[1B\e[58D"
$REP 58: PUSH " "
PUSH "\e[1B\e[58D"
$REP 58: PUSH " "
PUSH "\e[1B\e[58D"
$REP 58: PUSH " "
PUSH "\e[1B\e[58D"
PUSH "\e[5A\e[1CThe screen should be cleared, and have an unbroken bor-"
PUSH "\e[12;13Hder of *'s and +'s around the edge, and exactly in the"
PUSH "\e[13;13Hmiddle there should be a frame of E's around this text"
PUSH "\e[14;13Hwith one (1) free position around it. Push <RETURN>"
# And the result is...
!Output
?screen_row 0 = "********************************************************************************"
?screen_row 1 = "*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*"
$SEQ 2 7: ?screen_row \# = "*+ +*"
?screen_row 8 = "*+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +*"
?screen_row 9 = "*+ E E +*"
?screen_row 10 = "*+ E The screen should be cleared, and have an unbroken bor- E +*"
?screen_row 11 = "*+ E der of *'s and +'s around the edge, and exactly in the E +*"
?screen_row 12 = "*+ E middle there should be a frame of E's around this text E +*"
?screen_row 13 = "*+ E with one (1) free position around it. Push <RETURN> E +*"
?screen_row 14 = "*+ E E +*"
?screen_row 15 = "*+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +*"
$SEQ 16 21: ?screen_row \# = "*+ +*"
?screen_row 22 = "*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*"
?screen_row 23 = "********************************************************************************"
?cursor = 13,67

View File

@ -0,0 +1,40 @@
INIT
WANTSTATE
WANTSCREEN
RESET
PUSH "\e[3;21r"
PUSH "\e[?6h"
PUSH "\e[19;1HA\e[19;80Ha\x0a\e[18;80HaB\e[19;80HB\b b\x0a\e[19;80HC\b\b\t\tc\e[19;2H\bC\x0a\e[19;80H\x0a\e[18;1HD\e[18;80Hd"
PUSH "\e[19;1HE\e[19;80He\x0a\e[18;80HeF\e[19;80HF\b f\x0a\e[19;80HG\b\b\t\tg\e[19;2H\bG\x0a\e[19;80H\x0a\e[18;1HH\e[18;80Hh"
PUSH "\e[19;1HI\e[19;80Hi\x0a\e[18;80HiJ\e[19;80HJ\b j\x0a\e[19;80HK\b\b\t\tk\e[19;2H\bK\x0a\e[19;80H\x0a\e[18;1HL\e[18;80Hl"
PUSH "\e[19;1HM\e[19;80Hm\x0a\e[18;80HmN\e[19;80HN\b n\x0a\e[19;80HO\b\b\t\to\e[19;2H\bO\x0a\e[19;80H\x0a\e[18;1HP\e[18;80Hp"
PUSH "\e[19;1HQ\e[19;80Hq\x0a\e[18;80HqR\e[19;80HR\b r\x0a\e[19;80HS\b\b\t\ts\e[19;2H\bS\x0a\e[19;80H\x0a\e[18;1HT\e[18;80Ht"
PUSH "\e[19;1HU\e[19;80Hu\x0a\e[18;80HuV\e[19;80HV\b v\x0a\e[19;80HW\b\b\t\tw\e[19;2H\bW\x0a\e[19;80H\x0a\e[18;1HX\e[18;80Hx"
PUSH "\e[19;1HY\e[19;80Hy\x0a\e[18;80HyZ\e[19;80HZ\b z\x0a"
!Output
?screen_row 2 = "I i"
?screen_row 3 = "J j"
?screen_row 4 = "K k"
?screen_row 5 = "L l"
?screen_row 6 = "M m"
?screen_row 7 = "N n"
?screen_row 8 = "O o"
?screen_row 9 = "P p"
?screen_row 10 = "Q q"
?screen_row 11 = "R r"
?screen_row 12 = "S s"
?screen_row 13 = "T t"
?screen_row 14 = "U u"
?screen_row 15 = "V v"
?screen_row 16 = "W w"
?screen_row 17 = "X x"
?screen_row 18 = "Y y"
?screen_row 19 = "Z z"
?screen_row 20 = ""
?cursor = 20,79

View File

@ -0,0 +1,21 @@
# Test of cursor-control characters inside ESC sequences
INIT
WANTSTATE
WANTSCREEN
RESET
PUSH "A B C D E F G H I"
PUSH "\x0d\x0a"
PUSH "A\e[2\bCB\e[2\bCC\e[2\bCD\e[2\bCE\e[2\bCF\e[2\bCG\e[2\bCH\e[2\bCI"
PUSH "\x0d\x0a"
PUSH "A \e[\x0d2CB\e[\x0d4CC\e[\x0d6CD\e[\x0d8CE\e[\x0d10CF\e[\x0d12CG\e[\x0d14CH\e[\x0d16CI"
PUSH "\x0d\x0a"
PUSH "A \e[1\x0bAB \e[1\x0bAC \e[1\x0bAD \e[1\x0bAE \e[1\x0bAF \e[1\x0bAG \e[1\x0bAH \e[1\x0bAI \e[1\x0bA"
!Output
$SEQ 0 2: ?screen_row \# = "A B C D E F G H I"
?screen_row 3 = "A B C D E F G H I "
?cursor = 3,18

View File

@ -0,0 +1,36 @@
# Test of leading zeroes in ESC sequences
INIT
WANTSCREEN
RESET
PUSH "\e[00000000004;000000001HT"
PUSH "\e[00000000004;000000002Hh"
PUSH "\e[00000000004;000000003Hi"
PUSH "\e[00000000004;000000004Hs"
PUSH "\e[00000000004;000000005H "
PUSH "\e[00000000004;000000006Hi"
PUSH "\e[00000000004;000000007Hs"
PUSH "\e[00000000004;000000008H "
PUSH "\e[00000000004;000000009Ha"
PUSH "\e[00000000004;0000000010H "
PUSH "\e[00000000004;0000000011Hc"
PUSH "\e[00000000004;0000000012Ho"
PUSH "\e[00000000004;0000000013Hr"
PUSH "\e[00000000004;0000000014Hr"
PUSH "\e[00000000004;0000000015He"
PUSH "\e[00000000004;0000000016Hc"
PUSH "\e[00000000004;0000000017Ht"
PUSH "\e[00000000004;0000000018H "
PUSH "\e[00000000004;0000000019Hs"
PUSH "\e[00000000004;0000000020He"
PUSH "\e[00000000004;0000000021Hn"
PUSH "\e[00000000004;0000000022Ht"
PUSH "\e[00000000004;0000000023He"
PUSH "\e[00000000004;0000000024Hn"
PUSH "\e[00000000004;0000000025Hc"
PUSH "\e[00000000004;0000000026He"
!Output
?screen_row 3 = "This is a correct sentence"

View File

@ -0,0 +1,18 @@
# Test of WRAP AROUND mode setting.
INIT
WANTSCREEN
RESET
PUSH "\e[?7h"
$REP 170: PUSH "*"
PUSH "\e[?7l\e[3;1H"
$REP 177: PUSH "*"
PUSH "\e[?7h\e[5;1HOK"
!Output
$SEQ 0 2: ?screen_row \# = "********************************************************************************"
?screen_row 3 = ""
?screen_row 4 = "OK"

View File

@ -0,0 +1,29 @@
# TAB setting/resetting
INIT
WANTSTATE
WANTSCREEN
RESET
PUSH "\e[2J\e[3g"
PUSH "\e[1;1H"
$REP 26: PUSH "\e[3C\eH"
PUSH "\e[1;4H"
$REP 13: PUSH "\e[0g\e[6C"
PUSH "\e[1;7H"
PUSH "\e[1g\e[2g"
PUSH "\e[1;1H"
$REP 13: PUSH "\t*"
PUSH "\e[2;2H"
$REP 13: PUSH " *"
!Output
?screen_row 0 = " * * * * * * * * * * * * *"
?screen_row 1 = " * * * * * * * * * * * * *"
?cursor = 1,79

View File

@ -0,0 +1,16 @@
# Origin mode
INIT
WANTSCREEN
RESET
PUSH "\e[?6h"
PUSH "\e[23;24r"
PUSH "\n"
PUSH "Bottom"
PUSH "\e[1;1H"
PUSH "Above"
!Output
?screen_row 22 = "Above"
?screen_row 23 = "Bottom"

View File

@ -0,0 +1,17 @@
# Origin mode (2)
INIT
WANTSCREEN
RESET
PUSH "\e[?6l"
PUSH "\e[23;24r"
PUSH "\e[24;1H"
PUSH "Bottom"
PUSH "\e[1;1H"
PUSH "Top"
!Output
?screen_row 23 = "Bottom"
?screen_row 0 = "Top"

View File

@ -0,0 +1,13 @@
INIT
WANTSTATE
!Mouse reporting should not break by idempotent DECSM 1002
PUSH "\e[?1002h"
MOUSEMOVE 0,0 0
MOUSEBTN d 1 0
output "\e[M\x20\x21\x21"
MOUSEMOVE 1,0 0
output "\e[M\x40\x21\x22"
PUSH "\e[?1002h"
MOUSEMOVE 2,0 0
output "\e[M\x40\x21\x23"

929
src/libvterm/t/harness.c Normal file
View File

@ -0,0 +1,929 @@
#include "vterm.h"
#include "../src/vterm_internal.h" /* We pull in some internal bits too */
#include <stdio.h>
#include <string.h>
#define streq(a,b) (!strcmp(a,b))
#define strstartswith(a,b) (!strncmp(a,b,strlen(b)))
static size_t inplace_hex2bytes(char *s)
{
char *inpos = s, *outpos = s;
while(*inpos) {
unsigned int ch;
sscanf(inpos, "%2x", &ch);
*outpos = ch;
outpos += 1; inpos += 2;
}
return outpos - s;
}
static VTermModifier strpe_modifiers(char **strp)
{
VTermModifier state = 0;
while((*strp)[0]) {
switch(((*strp)++)[0]) {
case 'S': state |= VTERM_MOD_SHIFT; break;
case 'C': state |= VTERM_MOD_CTRL; break;
case 'A': state |= VTERM_MOD_ALT; break;
default: return state;
}
}
return state;
}
static VTermKey strp_key(char *str)
{
static struct {
char *name;
VTermKey key;
} keys[] = {
{ "Up", VTERM_KEY_UP },
{ "Tab", VTERM_KEY_TAB },
{ "Enter", VTERM_KEY_ENTER },
{ "KP0", VTERM_KEY_KP_0 },
{ NULL, VTERM_KEY_NONE },
};
int i;
for(i = 0; keys[i].name; i++) {
if(streq(str, keys[i].name))
return keys[i].key;
}
return VTERM_KEY_NONE;
}
static VTerm *vt;
static VTermState *state;
static VTermScreen *screen;
static VTermEncodingInstance encoding;
static int parser_text(const char bytes[], size_t len, void *user)
{
int i;
printf("text ");
for(i = 0; i < len; i++) {
unsigned char b = bytes[i];
if(b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0))
break;
printf(i ? ",%x" : "%x", b);
}
printf("\n");
return i;
}
static int parser_control(unsigned char control, void *user)
{
printf("control %02x\n", control);
return 1;
}
static int parser_escape(const char bytes[], size_t len, void *user)
{
int i;
if(bytes[0] >= 0x20 && bytes[0] < 0x30) {
if(len < 2)
return -1;
len = 2;
}
else {
len = 1;
}
printf("escape ");
for(i = 0; i < len; i++)
printf("%02x", bytes[i]);
printf("\n");
return len;
}
static int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
{
int i;
printf("csi %02x", command);
if(leader && leader[0]) {
printf(" L=");
for(i = 0; leader[i]; i++)
printf("%02x", leader[i]);
}
for(i = 0; i < argcount; i++) {
char sep = i ? ',' : ' ';
if(args[i] == CSI_ARG_MISSING)
printf("%c*", sep);
else
printf("%c%ld%s", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? "+" : "");
}
if(intermed && intermed[0]) {
printf(" I=");
for(i = 0; intermed[i]; i++)
printf("%02x", intermed[i]);
}
printf("\n");
return 1;
}
static int parser_osc(const char *command, size_t cmdlen, void *user)
{
int i;
printf("osc ");
for(i = 0; i < cmdlen; i++)
printf("%02x", command[i]);
printf("\n");
return 1;
}
static int parser_dcs(const char *command, size_t cmdlen, void *user)
{
int i;
printf("dcs ");
for(i = 0; i < cmdlen; i++)
printf("%02x", command[i]);
printf("\n");
return 1;
}
static VTermParserCallbacks parser_cbs = {
parser_text, /* text */
parser_control, /* control */
parser_escape, /* escape */
parser_csi, /* csi */
parser_osc, /* osc */
parser_dcs, /* dcs */
NULL /* resize */
};
/* These callbacks are shared by State and Screen */
static int want_movecursor = 0;
static VTermPos state_pos;
static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
{
state_pos = pos;
if(want_movecursor)
printf("movecursor %d,%d\n", pos.row, pos.col);
return 1;
}
static int want_scrollrect = 0;
static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
{
if(!want_scrollrect)
return 0;
printf("scrollrect %d..%d,%d..%d => %+d,%+d\n",
rect.start_row, rect.end_row, rect.start_col, rect.end_col,
downward, rightward);
return 1;
}
static int want_moverect = 0;
static int moverect(VTermRect dest, VTermRect src, void *user)
{
if(!want_moverect)
return 0;
printf("moverect %d..%d,%d..%d -> %d..%d,%d..%d\n",
src.start_row, src.end_row, src.start_col, src.end_col,
dest.start_row, dest.end_row, dest.start_col, dest.end_col);
return 1;
}
static int want_settermprop = 0;
static int settermprop(VTermProp prop, VTermValue *val, void *user)
{
VTermValueType type;
if(!want_settermprop)
return 1;
type = vterm_get_prop_type(prop);
switch(type) {
case VTERM_VALUETYPE_BOOL:
printf("settermprop %d %s\n", prop, val->boolean ? "true" : "false");
return 1;
case VTERM_VALUETYPE_INT:
printf("settermprop %d %d\n", prop, val->number);
return 1;
case VTERM_VALUETYPE_STRING:
printf("settermprop %d \"%s\"\n", prop, val->string);
return 1;
case VTERM_VALUETYPE_COLOR:
printf("settermprop %d rgb(%d,%d,%d)\n", prop, val->color.red, val->color.green, val->color.blue);
return 1;
}
return 0;
}
/* These callbacks are for State */
static int want_state_putglyph = 0;
static int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
{
int i;
if(!want_state_putglyph)
return 1;
printf("putglyph ");
for(i = 0; info->chars[i]; i++)
printf(i ? ",%x" : "%x", info->chars[i]);
printf(" %d %d,%d", info->width, pos.row, pos.col);
if(info->protected_cell)
printf(" prot");
if(info->dwl)
printf(" dwl");
if(info->dhl)
printf(" dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?" );
printf("\n");
return 1;
}
static int want_state_erase = 0;
static int state_erase(VTermRect rect, int selective, void *user)
{
if(!want_state_erase)
return 1;
printf("erase %d..%d,%d..%d%s\n",
rect.start_row, rect.end_row, rect.start_col, rect.end_col,
selective ? " selective" : "");
return 1;
}
static struct {
int bold;
int underline;
int italic;
int blink;
int reverse;
int strike;
int font;
VTermColor foreground;
VTermColor background;
} state_pen;
static int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
{
switch(attr) {
case VTERM_ATTR_BOLD:
state_pen.bold = val->boolean;
break;
case VTERM_ATTR_UNDERLINE:
state_pen.underline = val->number;
break;
case VTERM_ATTR_ITALIC:
state_pen.italic = val->boolean;
break;
case VTERM_ATTR_BLINK:
state_pen.blink = val->boolean;
break;
case VTERM_ATTR_REVERSE:
state_pen.reverse = val->boolean;
break;
case VTERM_ATTR_STRIKE:
state_pen.strike = val->boolean;
break;
case VTERM_ATTR_FONT:
state_pen.font = val->number;
break;
case VTERM_ATTR_FOREGROUND:
state_pen.foreground = val->color;
break;
case VTERM_ATTR_BACKGROUND:
state_pen.background = val->color;
break;
}
return 1;
}
static int state_setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)
{
return 1;
}
VTermStateCallbacks state_cbs = {
state_putglyph, /* putglyph */
movecursor, /* movecursor */
scrollrect, /* scrollrect */
moverect, /* moverect */
state_erase, /* erase */
NULL, /* initpen */
state_setpenattr, /* setpenattr */
settermprop, /* settermprop */
NULL, /* bell */
NULL, /* resize */
state_setlineinfo, /* setlineinfo */
};
static int want_screen_damage = 0;
static int want_screen_damage_cells = 0;
static int screen_damage(VTermRect rect, void *user)
{
if(!want_screen_damage)
return 1;
printf("damage %d..%d,%d..%d",
rect.start_row, rect.end_row, rect.start_col, rect.end_col);
if(want_screen_damage_cells) {
bool equals = false;
int row;
int col;
for(row = rect.start_row; row < rect.end_row; row++) {
int eol = rect.end_col;
while(eol > rect.start_col) {
VTermScreenCell cell;
VTermPos pos;
pos.row = row;
pos.col = eol-1;
vterm_screen_get_cell(screen, pos, &cell);
if(cell.chars[0])
break;
eol--;
}
if(eol == rect.start_col)
break;
if(!equals)
printf(" ="), equals = true;
printf(" %d<", row);
for(col = rect.start_col; col < eol; col++) {
VTermScreenCell cell;
VTermPos pos;
pos.row = row;
pos.col = col;
vterm_screen_get_cell(screen, pos, &cell);
printf(col == rect.start_col ? "%02X" : " %02X", cell.chars[0]);
}
printf(">");
}
}
printf("\n");
return 1;
}
static int want_screen_scrollback = 0;
static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
{
int eol;
int c;
if(!want_screen_scrollback)
return 1;
eol = cols;
while(eol && !cells[eol-1].chars[0])
eol--;
printf("sb_pushline %d =", cols);
for(c = 0; c < eol; c++)
printf(" %02X", cells[c].chars[0]);
printf("\n");
return 1;
}
static int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
{
int col;
if(!want_screen_scrollback)
return 0;
/* All lines of scrollback contain "ABCDE" */
for(col = 0; col < cols; col++) {
if(col < 5)
cells[col].chars[0] = 'A' + col;
else
cells[col].chars[0] = 0;
cells[col].width = 1;
}
printf("sb_popline %d\n", cols);
return 1;
}
VTermScreenCallbacks screen_cbs = {
screen_damage, /* damage */
moverect, /* moverect */
movecursor, /* movecursor */
settermprop, /* settermprop */
NULL, /* bell */
NULL, /* resize */
screen_sb_pushline, /* sb_pushline */
screen_sb_popline /* sb_popline */
};
int main(int argc, char **argv)
{
char line[1024] = {0};
int flag;
int err;
setvbuf(stdout, NULL, _IONBF, 0);
while(fgets(line, sizeof line, stdin)) {
char *nl;
size_t outlen;
err = 0;
if((nl = strchr(line, '\n')))
*nl = '\0';
if(streq(line, "INIT")) {
if(!vt)
vt = vterm_new(25, 80);
}
else if(streq(line, "WANTPARSER")) {
vterm_parser_set_callbacks(vt, &parser_cbs, NULL);
}
else if(strstartswith(line, "WANTSTATE") && (line[9] == '\0' || line[9] == ' ')) {
int i = 9;
int sense = 1;
if(!state) {
state = vterm_obtain_state(vt);
vterm_state_set_callbacks(state, &state_cbs, NULL);
vterm_state_set_bold_highbright(state, 1);
vterm_state_reset(state, 1);
}
while(line[i] == ' ')
i++;
for( ; line[i]; i++)
switch(line[i]) {
case '+':
sense = 1;
break;
case '-':
sense = 0;
break;
case 'g':
want_state_putglyph = sense;
break;
case 's':
want_scrollrect = sense;
break;
case 'm':
want_moverect = sense;
break;
case 'e':
want_state_erase = sense;
break;
case 'p':
want_settermprop = sense;
break;
case 'f':
vterm_state_set_unrecognised_fallbacks(state, sense ? &parser_cbs : NULL, NULL);
break;
default:
fprintf(stderr, "Unrecognised WANTSTATE flag '%c'\n", line[i]);
}
}
else if(strstartswith(line, "WANTSCREEN") && (line[10] == '\0' || line[10] == ' ')) {
int i = 10;
int sense = 1;
if(!screen)
screen = vterm_obtain_screen(vt);
vterm_screen_enable_altscreen(screen, 1);
vterm_screen_set_callbacks(screen, &screen_cbs, NULL);
while(line[i] == ' ')
i++;
for( ; line[i]; i++)
switch(line[i]) {
case '-':
sense = 0;
break;
case 'd':
want_screen_damage = sense;
break;
case 'D':
want_screen_damage = sense;
want_screen_damage_cells = sense;
break;
case 'm':
want_moverect = sense;
break;
case 'c':
want_movecursor = sense;
break;
case 'p':
want_settermprop = 1;
break;
case 'b':
want_screen_scrollback = sense;
break;
default:
fprintf(stderr, "Unrecognised WANTSCREEN flag '%c'\n", line[i]);
}
}
else if(sscanf(line, "UTF8 %d", &flag)) {
vterm_set_utf8(vt, flag);
}
else if(streq(line, "RESET")) {
if(state) {
vterm_state_reset(state, 1);
vterm_state_get_cursorpos(state, &state_pos);
}
if(screen) {
vterm_screen_reset(screen, 1);
}
}
else if(strstartswith(line, "RESIZE ")) {
int rows, cols;
char *linep = line + 7;
while(linep[0] == ' ')
linep++;
sscanf(linep, "%d, %d", &rows, &cols);
vterm_set_size(vt, rows, cols);
}
else if(strstartswith(line, "PUSH ")) {
char *bytes = line + 5;
size_t len = inplace_hex2bytes(bytes);
size_t written = vterm_input_write(vt, bytes, len);
if(written < len)
fprintf(stderr, "! short write\n");
}
else if(streq(line, "WANTENCODING")) {
/* This isn't really external API but it's hard to get this out any
* other way
*/
encoding.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
if(encoding.enc->init)
(*encoding.enc->init)(encoding.enc, encoding.data);
}
else if(strstartswith(line, "ENCIN ")) {
char *bytes = line + 6;
size_t len = inplace_hex2bytes(bytes);
uint32_t cp[1024];
int cpi = 0;
size_t pos = 0;
(*encoding.enc->decode)(encoding.enc, encoding.data,
cp, &cpi, len, bytes, &pos, len);
if(cpi > 0) {
int i;
printf("encout ");
for(i = 0; i < cpi; i++) {
printf(i ? ",%x" : "%x", cp[i]);
}
printf("\n");
}
}
else if(strstartswith(line, "INCHAR ")) {
char *linep = line + 7;
unsigned int c = 0;
VTermModifier mod;
while(linep[0] == ' ')
linep++;
mod = strpe_modifiers(&linep);
sscanf(linep, " %x", &c);
vterm_keyboard_unichar(vt, c, mod);
}
else if(strstartswith(line, "INKEY ")) {
VTermModifier mod;
VTermKey key;
char *linep = line + 6;
while(linep[0] == ' ')
linep++;
mod = strpe_modifiers(&linep);
while(linep[0] == ' ')
linep++;
key = strp_key(linep);
vterm_keyboard_key(vt, key, mod);
}
else if(strstartswith(line, "PASTE ")) {
char *linep = line + 6;
if(streq(linep, "START"))
vterm_keyboard_start_paste(vt);
else if(streq(linep, "END"))
vterm_keyboard_end_paste(vt);
else
goto abort_line;
}
else if(strstartswith(line, "MOUSEMOVE ")) {
char *linep = line + 10;
int row, col, len;
VTermModifier mod;
while(linep[0] == ' ')
linep++;
sscanf(linep, "%d,%d%n", &row, &col, &len);
linep += len;
while(linep[0] == ' ')
linep++;
mod = strpe_modifiers(&linep);
vterm_mouse_move(vt, row, col, mod);
}
else if(strstartswith(line, "MOUSEBTN ")) {
char *linep = line + 9;
char press;
int button, len;
VTermModifier mod;
while(linep[0] == ' ')
linep++;
sscanf(linep, "%c %d%n", &press, &button, &len);
linep += len;
while(linep[0] == ' ')
linep++;
mod = strpe_modifiers(&linep);
vterm_mouse_button(vt, button, (press == 'd' || press == 'D'), mod);
}
else if(strstartswith(line, "DAMAGEMERGE ")) {
char *linep = line + 12;
while(linep[0] == ' ')
linep++;
if(streq(linep, "CELL"))
vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_CELL);
else if(streq(linep, "ROW"))
vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_ROW);
else if(streq(linep, "SCREEN"))
vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_SCREEN);
else if(streq(linep, "SCROLL"))
vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_SCROLL);
}
else if(strstartswith(line, "DAMAGEFLUSH")) {
vterm_screen_flush_damage(screen);
}
else if(line[0] == '?') {
if(streq(line, "?cursor")) {
VTermPos pos;
vterm_state_get_cursorpos(state, &pos);
if(pos.row != state_pos.row)
printf("! row mismatch: state=%d,%d event=%d,%d\n",
pos.row, pos.col, state_pos.row, state_pos.col);
else if(pos.col != state_pos.col)
printf("! col mismatch: state=%d,%d event=%d,%d\n",
pos.row, pos.col, state_pos.row, state_pos.col);
else
printf("%d,%d\n", state_pos.row, state_pos.col);
}
else if(strstartswith(line, "?pen ")) {
VTermValue val;
char *linep = line + 5;
while(linep[0] == ' ')
linep++;
#define BOOLSTR(v) ((v) ? "on" : "off")
if(streq(linep, "bold")) {
vterm_state_get_penattr(state, VTERM_ATTR_BOLD, &val);
if(val.boolean != state_pen.bold)
printf("! pen bold mismatch; state=%s, event=%s\n",
BOOLSTR(val.boolean), BOOLSTR(state_pen.bold));
else
printf("%s\n", BOOLSTR(state_pen.bold));
}
else if(streq(linep, "underline")) {
vterm_state_get_penattr(state, VTERM_ATTR_UNDERLINE, &val);
if(val.boolean != state_pen.underline)
printf("! pen underline mismatch; state=%d, event=%d\n",
val.boolean, state_pen.underline);
else
printf("%d\n", state_pen.underline);
}
else if(streq(linep, "italic")) {
vterm_state_get_penattr(state, VTERM_ATTR_ITALIC, &val);
if(val.boolean != state_pen.italic)
printf("! pen italic mismatch; state=%s, event=%s\n",
BOOLSTR(val.boolean), BOOLSTR(state_pen.italic));
else
printf("%s\n", BOOLSTR(state_pen.italic));
}
else if(streq(linep, "blink")) {
vterm_state_get_penattr(state, VTERM_ATTR_BLINK, &val);
if(val.boolean != state_pen.blink)
printf("! pen blink mismatch; state=%s, event=%s\n",
BOOLSTR(val.boolean), BOOLSTR(state_pen.blink));
else
printf("%s\n", BOOLSTR(state_pen.blink));
}
else if(streq(linep, "reverse")) {
vterm_state_get_penattr(state, VTERM_ATTR_REVERSE, &val);
if(val.boolean != state_pen.reverse)
printf("! pen reverse mismatch; state=%s, event=%s\n",
BOOLSTR(val.boolean), BOOLSTR(state_pen.reverse));
else
printf("%s\n", BOOLSTR(state_pen.reverse));
}
else if(streq(linep, "font")) {
vterm_state_get_penattr(state, VTERM_ATTR_FONT, &val);
if(val.boolean != state_pen.font)
printf("! pen font mismatch; state=%d, event=%d\n",
val.boolean, state_pen.font);
else
printf("%d\n", state_pen.font);
}
else if(streq(linep, "foreground")) {
printf("rgb(%d,%d,%d)\n", state_pen.foreground.red, state_pen.foreground.green, state_pen.foreground.blue);
}
else if(streq(linep, "background")) {
printf("rgb(%d,%d,%d)\n", state_pen.background.red, state_pen.background.green, state_pen.background.blue);
}
else
printf("?\n");
}
else if(strstartswith(line, "?screen_chars ")) {
char *linep = line + 13;
VTermRect rect;
size_t len;
while(linep[0] == ' ')
linep++;
if(sscanf(linep, "%d,%d,%d,%d", &rect.start_row, &rect.start_col, &rect.end_row, &rect.end_col) < 4) {
printf("! screen_chars unrecognised input\n");
goto abort_line;
}
len = vterm_screen_get_chars(screen, NULL, 0, rect);
if(len == (size_t)-1)
printf("! screen_chars error\n");
else if(len == 0)
printf("\n");
else {
uint32_t *chars = malloc(sizeof(uint32_t) * len);
size_t i;
vterm_screen_get_chars(screen, chars, len, rect);
for(i = 0; i < len; i++) {
printf("0x%02x%s", chars[i], i < len-1 ? "," : "\n");
}
free(chars);
}
}
else if(strstartswith(line, "?screen_text ")) {
char *linep = line + 12;
VTermRect rect;
size_t len;
while(linep[0] == ' ')
linep++;
if(sscanf(linep, "%d,%d,%d,%d", &rect.start_row, &rect.start_col, &rect.end_row, &rect.end_col) < 4) {
printf("! screen_text unrecognised input\n");
goto abort_line;
}
len = vterm_screen_get_text(screen, NULL, 0, rect);
if(len == (size_t)-1)
printf("! screen_text error\n");
else if(len == 0)
printf("\n");
else {
/* Put an overwrite guard at both ends of the buffer */
unsigned char *buffer = malloc(len + 4);
unsigned char *text = buffer + 2;
text[-2] = 0x55; text[-1] = 0xAA;
text[len] = 0x55; text[len+1] = 0xAA;
vterm_screen_get_text(screen, (char *)text, len, rect);
if(text[-2] != 0x55 || text[-1] != 0xAA)
printf("! screen_get_text buffer overrun left [%02x,%02x]\n", text[-2], text[-1]);
else if(text[len] != 0x55 || text[len+1] != 0xAA)
printf("! screen_get_text buffer overrun right [%02x,%02x]\n", text[len], text[len+1]);
else
{
size_t i;
for(i = 0; i < len; i++) {
printf("0x%02x%s", text[i], i < len-1 ? "," : "\n");
}
}
free(buffer);
}
}
else if(strstartswith(line, "?screen_cell ")) {
char *linep = line + 12;
int i;
VTermPos pos;
VTermScreenCell cell;
while(linep[0] == ' ')
linep++;
if(sscanf(linep, "%d,%d\n", &pos.row, &pos.col) < 2) {
printf("! screen_cell unrecognised input\n");
goto abort_line;
}
if(!vterm_screen_get_cell(screen, pos, &cell))
goto abort_line;
printf("{");
for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell.chars[i]; i++) {
printf("%s0x%x", i ? "," : "", cell.chars[i]);
}
printf("} width=%d attrs={", cell.width);
if(cell.attrs.bold) printf("B");
if(cell.attrs.underline) printf("U%d", cell.attrs.underline);
if(cell.attrs.italic) printf("I");
if(cell.attrs.blink) printf("K");
if(cell.attrs.reverse) printf("R");
if(cell.attrs.font) printf("F%d", cell.attrs.font);
printf("} ");
if(cell.attrs.dwl) printf("dwl ");
if(cell.attrs.dhl) printf("dhl-%s ", cell.attrs.dhl == 2 ? "bottom" : "top");
printf("fg=rgb(%d,%d,%d) ", cell.fg.red, cell.fg.green, cell.fg.blue);
printf("bg=rgb(%d,%d,%d)\n", cell.bg.red, cell.bg.green, cell.bg.blue);
}
else if(strstartswith(line, "?screen_eol ")) {
VTermPos pos;
char *linep = line + 12;
while(linep[0] == ' ')
linep++;
if(sscanf(linep, "%d,%d\n", &pos.row, &pos.col) < 2) {
printf("! screen_eol unrecognised input\n");
goto abort_line;
}
printf("%d\n", vterm_screen_is_eol(screen, pos));
}
else if(strstartswith(line, "?screen_attrs_extent ")) {
VTermPos pos;
VTermRect rect;
char *linep = line + 21;
while(linep[0] == ' ')
linep++;
if(sscanf(linep, "%d,%d\n", &pos.row, &pos.col) < 2) {
printf("! screen_attrs_extent unrecognised input\n");
goto abort_line;
}
rect.start_col = 0;
rect.end_col = -1;
if(!vterm_screen_get_attrs_extent(screen, &rect, pos, ~0)) {
printf("! screen_attrs_extent failed\n");
goto abort_line;
}
printf("%d,%d-%d,%d\n", rect.start_row, rect.start_col, rect.end_row, rect.end_col);
}
else
printf("?\n");
memset(line, 0, sizeof line);
continue;
}
else
abort_line: err = 1;
outlen = vterm_output_get_buffer_current(vt);
if(outlen > 0) {
int i;
char outbuff[1024];
vterm_output_read(vt, outbuff, outlen);
printf("output ");
for(i = 0; i < outlen; i++)
printf("%x%s", (unsigned char)outbuff[i], i < outlen-1 ? "," : "\n");
}
printf(err ? "?\n" : "DONE\n");
}
vterm_free(vt);
return 0;
}

196
src/libvterm/t/run-test.pl Normal file
View File

@ -0,0 +1,196 @@
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
use IO::Handle;
use IPC::Open2 qw( open2 );
use POSIX qw( WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG );
my $VALGRIND = 0;
GetOptions(
'valgrind|v+' => \$VALGRIND,
) or exit 1;
my ( $hin, $hout, $hpid );
{
local $ENV{LD_LIBRARY_PATH} = ".libs";
my @command = "t/.libs/harness";
unshift @command, "valgrind", "--quiet", "--error-exitcode=126" if $VALGRIND;
$hpid = open2 $hout, $hin, @command or die "Cannot open2 harness - $!";
}
my $exitcode = 0;
my $command;
my @expect;
sub do_onetest
{
$hin->print( "$command\n" );
undef $command;
my $fail_printed = 0;
while( my $outline = <$hout> ) {
last if $outline eq "DONE\n" or $outline eq "?\n";
chomp $outline;
if( !@expect ) {
print "# Test failed\n" unless $fail_printed++;
print "# expected nothing more\n" .
"# Actual: $outline\n";
next;
}
my $expectation = shift @expect;
next if $expectation eq $outline;
print "# Test failed\n" unless $fail_printed++;
print "# Expected: $expectation\n" .
"# Actual: $outline\n";
}
if( @expect ) {
print "# Test failed\n" unless $fail_printed++;
print "# Expected: $_\n" .
"# didn't happen\n" for @expect;
}
$exitcode = 1 if $fail_printed;
}
sub do_line
{
my ( $line ) = @_;
if( $line =~ m/^!(.*)/ ) {
do_onetest if defined $command;
print "> $1\n";
}
# Commands have capitals
elsif( $line =~ m/^([A-Z]+)/ ) {
# Some convenience formatting
if( $line =~ m/^(PUSH|ENCIN) (.*)$/ ) {
# we're evil
my $string = eval($2);
$line = "$1 " . unpack "H*", $string;
}
do_onetest if defined $command;
$command = $line;
undef @expect;
}
# Expectations have lowercase
elsif( $line =~ m/^([a-z]+)/ ) {
# Convenience formatting
if( $line =~ m/^(text|encout) (.*)$/ ) {
$line = "$1 " . join ",", map sprintf("%x", $_), eval($2);
}
elsif( $line =~ m/^(output) (.*)$/ ) {
$line = "$1 " . join ",", map sprintf("%x", $_), unpack "C*", eval($2);
}
elsif( $line =~ m/^control (.*)$/ ) {
$line = sprintf "control %02x", eval($1);
}
elsif( $line =~ m/^csi (\S+) (.*)$/ ) {
$line = sprintf "csi %02x %s", eval($1), $2; # TODO
}
elsif( $line =~ m/^(escape|osc|dcs) (.*)$/ ) {
$line = "$1 " . join "", map sprintf("%02x", $_), unpack "C*", eval($2);
}
elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) {
$line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2";
}
elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|settermprop|setmousefunc) / ) {
# no conversion
}
else {
warn "Unrecognised test expectation '$line'\n";
}
push @expect, $line;
}
# ?screen_row assertion is emulated here
elsif( $line =~ s/^\?screen_row\s+(\d+)\s*=\s*// ) {
my $row = $1;
my $row1 = $row + 1;
my $want = eval($line);
do_onetest if defined $command;
# TODO: may not be 80
$hin->print( "\?screen_chars $row,0,$row1,80\n" );
my $response = <$hout>;
chomp $response;
$response = pack "C*", map hex, split m/,/, $response;
if( $response ne $want ) {
print "# Assert ?screen_row $row failed:\n" .
"# Expected: $want\n" .
"# Actual: $response\n";
$exitcode = 1;
}
}
# Assertions start with '?'
elsif( $line =~ s/^\?([a-z]+.*?=)\s+// ) {
do_onetest if defined $command;
my ( $assertion ) = $1 =~ m/^(.*)\s+=/;
$hin->print( "\?$assertion\n" );
my $response = <$hout>; defined $response or wait, die "Test harness failed - $?\n";
chomp $response;
if( $response ne $line ) {
print "# Assert $assertion failed:\n" .
"# Expected: $line\n" .
"# Actual: $response\n";
$exitcode = 1;
}
}
# Test controls start with '$'
elsif( $line =~ s/\$SEQ\s+(\d+)\s+(\d+):\s*// ) {
my ( $low, $high ) = ( $1, $2 );
foreach my $val ( $low .. $high ) {
( my $inner = $line ) =~ s/\\#/$val/g;
do_line( $inner );
}
}
elsif( $line =~ s/\$REP\s+(\d+):\s*// ) {
my $count = $1;
do_line( $line ) for 1 .. $count;
}
else {
die "Unrecognised TEST line $line\n";
}
}
open my $test, "<", $ARGV[0] or die "Cannot open test script $ARGV[0] - $!";
while( my $line = <$test> ) {
$line =~ s/^\s+//;
next if $line =~ m/^(?:#|$)/;
chomp $line;
do_line( $line );
}
do_onetest if defined $command;
close $hin;
close $hout;
waitpid $hpid, 0;
if( $? ) {
printf STDERR "Harness exited %d\n", WEXITSTATUS($?) if WIFEXITED($?);
printf STDERR "Harness exit signal %d\n", WTERMSIG($?) if WIFSIGNALED($?);
$exitcode = WIFEXITED($?) ? WEXITSTATUS($?) : 125;
}
exit $exitcode;

51
src/libvterm/tbl2inc_c.pl Normal file
View File

@ -0,0 +1,51 @@
#!/usr/bin/perl
use strict;
use warnings;
my ( $encname ) = $ARGV[0] =~ m{/([^/.]+).tbl}
or die "Cannot parse encoding name out of $ARGV[0]\n";
print <<"EOF";
static const struct StaticTableEncoding encoding_$encname = {
{
NULL, /* init */
&decode_table /* decode */
},
{
EOF
my $row = 0;
while( <> ) {
s/\s*#.*//; # strip comment
if ($_ =~ m{^\d+/\d+}) {
my ($up, $low) = ($_ =~ m{^(\d+)/(\d+)});
my $thisrow = $up * 16 + $low;
while ($row < $thisrow) {
print " 0x0, /* $row */\n";
++$row;
}
}
s{^(\d+)/(\d+)}{""}e; # Remove 3/1
s{ = }{""}e; # Remove " = "
s{"(.)"}{sprintf "0x%04x", ord $1}e; # Convert "A" to 0x41
s{U\+}{0x}; # Convert U+0041 to 0x0041
s{$}{, /* $row */}; # append comma and index
print " $_";
++$row;
}
while ($row < 128) {
print " 0x0, /* $row */\n";
++$row;
}
print <<"EOF";
}
};
EOF

9
src/libvterm/vterm.pc.in Normal file
View File

@ -0,0 +1,9 @@
prefix=@PREFIX@
libdir=@LIBDIR@
includedir=${prefix}/include
Name: vterm
Description: Abstract VT220/Xterm/ECMA-48 emulation library
Version: @VERSION@
Libs: -L${libdir} -lvterm
Cflags: -I${includedir}

View File

@ -257,6 +257,9 @@
# define PV_COCU OPT_WIN(WV_COCU)
# define PV_COLE OPT_WIN(WV_COLE)
#endif
#ifdef FEAT_TERMINAL
# define PV_TMS OPT_WIN(WV_TMS)
#endif
#ifdef FEAT_SIGNS
# define PV_SCL OPT_WIN(WV_SCL)
#endif
@ -2776,6 +2779,15 @@ static struct vimoption options[] =
#else
(char_u*)NULL, PV_NONE,
{(char_u *)FALSE, (char_u *)FALSE}
#endif
SCRIPTID_INIT},
{"termsize", "tms", P_STRING|P_ALLOCED|P_RWIN|P_VI_DEF,
#ifdef FEAT_TERMINAL
(char_u *)VAR_WIN, PV_TMS,
{(char_u *)"", (char_u *)NULL}
#else
(char_u *)NULL, PV_NONE,
{(char_u *)NULL, (char_u *)0L}
#endif
SCRIPTID_INIT},
{"terse", NULL, P_BOOL|P_VI_DEF,
@ -10662,8 +10674,11 @@ get_varp(struct vimoption *p)
case PV_CRBIND: return (char_u *)&(curwin->w_p_crb);
#endif
#ifdef FEAT_CONCEAL
case PV_COCU: return (char_u *)&(curwin->w_p_cocu);
case PV_COLE: return (char_u *)&(curwin->w_p_cole);
case PV_COCU: return (char_u *)&(curwin->w_p_cocu);
case PV_COLE: return (char_u *)&(curwin->w_p_cole);
#endif
#ifdef FEAT_TERMINAL
case PV_TMS: return (char_u *)&(curwin->w_p_tms);
#endif
case PV_AI: return (char_u *)&(curbuf->b_p_ai);
@ -10871,6 +10886,9 @@ copy_winopt(winopt_T *from, winopt_T *to)
to->wo_cocu = vim_strsave(from->wo_cocu);
to->wo_cole = from->wo_cole;
#endif
#ifdef FEAT_TERMINAL
to->wo_tms = vim_strsave(from->wo_tms);
#endif
#ifdef FEAT_FOLDING
to->wo_fdc = from->wo_fdc;
to->wo_fdc_save = from->wo_fdc_save;
@ -10937,6 +10955,9 @@ check_winopt(winopt_T *wop UNUSED)
#ifdef FEAT_CONCEAL
check_string_option(&wop->wo_cocu);
#endif
#ifdef FEAT_TERMINAL
check_string_option(&wop->wo_tms);
#endif
#ifdef FEAT_LINEBREAK
check_string_option(&wop->wo_briopt);
#endif
@ -10976,6 +10997,9 @@ clear_winopt(winopt_T *wop UNUSED)
#ifdef FEAT_CONCEAL
clear_string_option(&wop->wo_cocu);
#endif
#ifdef FEAT_TERMINAL
clear_string_option(&wop->wo_tms);
#endif
}
/*

View File

@ -1130,6 +1130,9 @@ enum
, WV_COCU
, WV_COLE
#endif
#ifdef FEAT_TERMINAL
, WV_TMS
#endif
#ifdef FEAT_CURSORBIND
, WV_CRBIND
#endif

View File

@ -162,6 +162,9 @@ void qsort(void *base, size_t elm_count, size_t elm_size, int (*cmp)(const void
# include "syntax.pro"
# include "tag.pro"
# include "term.pro"
# ifdef FEAT_TERMINAL
# include "terminal.pro"
# endif
# if defined(HAVE_TGETENT) && (defined(AMIGA) || defined(VMS))
# include "termlib.pro"
# endif

3
src/proto/terminal.pro Normal file
View File

@ -0,0 +1,3 @@
/* terminal.c */
void ex_terminal(exarg_T *eap);
/* vim: set ft=c : */

View File

@ -68,6 +68,7 @@ typedef struct wininfo_S wininfo_T;
typedef struct frame_S frame_T;
typedef int scid_T; /* script ID */
typedef struct file_buffer buf_T; /* forward declaration */
typedef struct terminal_S term_T;
/*
* Reference to a buffer that stores the value of buf_free_count.
@ -268,6 +269,10 @@ typedef struct
char_u *wo_scl;
# define w_p_scl w_onebuf_opt.wo_scl /* 'signcolumn' */
#endif
#ifdef FEAT_TERMINAL
char_u *wo_tms;
#define w_p_tms w_onebuf_opt.wo_tms /* 'termsize' */
#endif
#ifdef FEAT_EVAL
int wo_scriptID[WV_COUNT]; /* SIDs for window-local options */
@ -2351,6 +2356,11 @@ struct file_buffer
#endif
int b_mapped_ctrl_c; /* modes where CTRL-C is mapped */
#ifdef FEAT_TERMINAL
term_T *b_term; /* When not NULL this buffer is for a terminal
* window. */
#endif
}; /* file_buffer */

211
src/terminal.c Normal file
View File

@ -0,0 +1,211 @@
/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* Terminal window support, see ":help :terminal".
*
* For a terminal one VTerm is constructed. This uses libvterm. A copy of
* that library is in the libvterm directory.
*
* The VTerm invokes callbacks when its screen contents changes. The line
* range is stored in tl_dirty_row_start and tl_dirty_row_end. Once in a
* while, if the window is visible, the screen contents is drawn.
*
* If the terminal window has keyboard focus, typed keys are converted to the
* terminal encoding and writting to the job over a channel.
*
* If the job produces output, it is written to the VTerm.
* This will result in screen updates.
*
* TODO:
* - +terminal in features[] in version.c
* - free b_term when closing terminal.
* - remove term from first_term list when closing terminal.
* - set buffer options to be scratch, hidden, nomodifiable, etc.
* - set buffer name to command, add (1) to avoid duplicates.
* - command line completion (command name)
* - support fixed size when 'termsize' is "rowsXcols".
* - support minimal size when 'termsize' is "rows*cols".
* - support minimal size when 'termsize' is empty.
* - implement ":buf {term-buf-name}"
* - implement term_getsize()
* - implement term_setsize()
* - implement term_sendkeys() send keystrokes to a terminal
* - implement term_wait() wait for screen to be updated
* - implement term_scrape() inspect terminal screen
* - implement term_open() open terminal window
* - implement term_getjob()
*/
#include "vim.h"
#ifdef FEAT_TERMINAL
#include "libvterm/include/vterm.h"
/* typedef term_T in structs.h */
struct terminal_S {
term_T *tl_next;
VTerm *tl_vterm;
job_T *tl_job;
/* Range of screen rows to update. Zero based. */
int tl_dirty_row_start; /* -1 if nothing dirty */
int tl_dirty_row_end; /* row below last one to update */
pos_T tl_cursor;
};
#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
/*
* List of all active terminals.
*/
static term_T *first_term = NULL;
static int handle_damage(VTermRect rect, void *user);
static int handle_moverect(VTermRect dest, VTermRect src, void *user);
static int handle_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user);
static int handle_resize(int rows, int cols, void *user);
static VTermScreenCallbacks screen_callbacks = {
handle_damage, /* damage */
handle_moverect, /* moverect */
handle_movecursor, /* movecursor */
NULL, /* settermprop */
NULL, /* bell */
handle_resize, /* resize */
NULL, /* sb_pushline */
NULL /* sb_popline */
};
/*
* ":terminal": open a terminal window and execute a job in it.
*/
void
ex_terminal(exarg_T *eap)
{
int rows;
int cols;
exarg_T split_ea;
win_T *old_curwin = curwin;
typval_T argvars[2];
term_T *term;
VTerm *vterm;
VTermScreen *screen;
if (check_restricted() || check_secure())
return;
term = (term_T *)alloc_clear(sizeof(term_T));
if (term == NULL)
return;
term->tl_dirty_row_end = MAX_ROW;
/* Open a new window or tab. */
vim_memset(&split_ea, 0, sizeof(split_ea));
split_ea.cmdidx = CMD_new;
split_ea.cmd = (char_u *)"new";
split_ea.arg = (char_u *)"";
ex_splitview(&split_ea);
if (curwin == old_curwin)
{
/* split failed */
vim_free(term);
return;
}
curbuf->b_term = term;
term->tl_next = first_term;
first_term = term;
/* TODO: set buffer type, hidden, etc. */
if (*curwin->w_p_tms != NUL)
{
char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
rows = atoi((char *)curwin->w_p_tms);
cols = atoi((char *)p);
/* TODO: resize window if possible. */
}
else
{
rows = curwin->w_height;
cols = curwin->w_width;
}
vterm = vterm_new(rows, cols);
term->tl_vterm = vterm;
screen = vterm_obtain_screen(vterm);
vterm_screen_set_callbacks(screen, &screen_callbacks, term);
argvars[0].v_type = VAR_STRING;
argvars[0].vval.v_string = eap->arg;
argvars[1].v_type = VAR_UNKNOWN;
term->tl_job = job_start(argvars);
/* TODO: setup channels to/from job */
/* Setup pty, see mch_call_shell(). */
}
static int
handle_damage(VTermRect rect, void *user)
{
term_T *term = (term_T *)user;
term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
return 1;
}
static int
handle_moverect(VTermRect dest, VTermRect src, void *user)
{
/* TODO */
return 1;
}
static int
handle_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
{
/* TODO: handle moving the cursor. */
return 1;
}
static int
handle_resize(int rows, int cols, void *user)
{
/* TODO: handle terminal resize. */
return 1;
}
/* TODO: Use win_del_lines() to make scroll up efficient. */
/* TODO: function to update the window.
* Get the screen contents from vterm with vterm_screen_get_cell().
* put in current_ScreenLine and call screen_line().
*/
/* TODO: function to wait for input and send it to the job.
* Return when a CTRL-W command is typed that moves to another window.
* Convert special keys to vterm keys:
* - Write keys to vterm: vterm_keyboard_key()
* - read the output (xterm escape sequences): vterm_output_read()
* - Write output to channel.
*/
/* TODO: function to read job output from the channel.
* write to vterm: vterm_input_write()
* This will invoke screen callbacks.
* call vterm_screen_flush_damage()
*/
#endif /* FEAT_TERMINAL */

View File

@ -764,6 +764,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
693,
/**/
692,
/**/