Source: https://github.com/AmokHuginnsson/replxxtags/2.0
SET(LINENOISESRC linenoise.c) | |||||
ADD_LIBRARY(rspamd-linenoise STATIC ${LINENOISESRC}) | |||||
SET_TARGET_PROPERTIES(rspamd-linenoise PROPERTIES VERSION ${RSPAMD_VERSION}) | |||||
IF(ENABLE_FULL_DEBUG MATCHES "OFF") | |||||
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") | |||||
SET_TARGET_PROPERTIES(rspamd-linenoise PROPERTIES COMPILE_FLAGS "-O3") | |||||
endif () | |||||
ENDIF() |
Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com> | |||||
Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||||
All rights reserved. | |||||
Redistribution and use in source and binary forms, with or without | |||||
modification, are permitted provided that the following conditions are met: | |||||
* Redistributions of source code must retain the above copyright notice, | |||||
this list of conditions and the following disclaimer. | |||||
* Redistributions in binary form must reproduce the above copyright notice, | |||||
this list of conditions and the following disclaimer in the documentation | |||||
and/or other materials provided with the distribution. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | |||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
/* linenoise.h -- VERSION 1.0 | |||||
* | |||||
* Guerrilla line editing library against the idea that a line editing lib | |||||
* needs to be 20,000 lines of C code. | |||||
* | |||||
* See linenoise.c for more information. | |||||
* | |||||
* ------------------------------------------------------------------------ | |||||
* | |||||
* Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com> | |||||
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||||
* | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions are | |||||
* met: | |||||
* | |||||
* * Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* * Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#ifndef __LINENOISE_H | |||||
#define __LINENOISE_H | |||||
#ifdef __cplusplus | |||||
extern "C" { | |||||
#endif | |||||
typedef struct linenoiseCompletions { | |||||
size_t len; | |||||
char **cvec; | |||||
} linenoiseCompletions; | |||||
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); | |||||
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); | |||||
typedef void(linenoiseFreeHintsCallback)(void *); | |||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); | |||||
void linenoiseSetHintsCallback(linenoiseHintsCallback *); | |||||
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); | |||||
void linenoiseAddCompletion(linenoiseCompletions *, const char *); | |||||
char *linenoise(const char *prompt); | |||||
void linenoiseFree(void *ptr); | |||||
int linenoiseHistoryAdd(const char *line); | |||||
int linenoiseHistorySetMaxLen(int len); | |||||
int linenoiseHistorySave(const char *filename); | |||||
int linenoiseHistoryLoad(const char *filename); | |||||
void linenoiseClearScreen(void); | |||||
void linenoiseSetMultiLine(int ml); | |||||
void linenoisePrintKeyCodes(void); | |||||
/* Rspamd specific */ | |||||
int linenoiseGetColumns(int ifd, int ofd); | |||||
#ifdef __cplusplus | |||||
} | |||||
#endif | |||||
#endif /* __LINENOISE_H */ |
# -*- mode: CMAKE; -*- | |||||
cmake_minimum_required(VERSION 3.0) | |||||
project( replxx VERSION 0.0.2 LANGUAGES CXX C ) | |||||
message(STATUS "Build mode: ${CMAKE_BUILD_TYPE}") | |||||
# INFO | |||||
set(REPLXX_DISPLAY_NAME "replxx") | |||||
set(REPLXX_URL_INFO_ABOUT "https://github.com/AmokHuginnsson/replxx") | |||||
set(REPLXX_CONTACT "amok@codestation.org") | |||||
set(REPLXX_FRIENDLY_STRING "replxx - Read Evaluate Print Loop library") | |||||
# compiler options | |||||
if(CMAKE_COMPILER_IS_GNUCXX) | |||||
message(STATUS "Compiler type GNU: ${CMAKE_CXX_COMPILER}") | |||||
set(BASE_COMPILER_OPTIONS "-std=c++11 -Wall -D_GNU_SOURCE -pthread") | |||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_OPTIONS}") | |||||
set(CMAKE_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_OPTIONS} -O0 --coverage -fno-inline -fno-default-inline -fno-inline-small-functions") | |||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${BASE_COMPILER_OPTIONS} -O0 -g -ggdb -g3 -ggdb3") | |||||
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${BASE_COMPILER_OPTIONS} -Os") | |||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${BASE_COMPILER_OPTIONS} -O3 -fomit-frame-pointer") | |||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${BASE_COMPILER_OPTIONS} -O3 -g") | |||||
set(CMAKE_C_FLAGS "-std=c99") | |||||
elseif(CMAKE_COMPILER_IS_CLANGCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") | |||||
# using regular Clang or AppleClang | |||||
message(STATUS "Compiler type CLANG: ${CMAKE_CXX_COMPILER}") | |||||
set(BASE_COMPILER_OPTIONS "-std=c++11 -Wall -Wextra -D_GNU_SOURCE -pthread") | |||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_OPTIONS}") | |||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${BASE_COMPILER_OPTIONS} -O0 -g") | |||||
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${BASE_COMPILER_OPTIONS} -Os") | |||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${BASE_COMPILER_OPTIONS} -O3 -fomit-frame-pointer") | |||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${BASE_COMPILER_OPTIONS} -O3 -g") | |||||
set(CMAKE_C_FLAGS "-std=c99") | |||||
elseif(MSVC) | |||||
message(STATUS "Compiler type MSVC: ${CMAKE_CXX_COMPILER}") | |||||
add_definitions("-D_CRT_SECURE_NO_WARNINGS=1") | |||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /INCREMENTAL:NO /SUBSYSTEM:CONSOLE /LTCG /ignore:4099") | |||||
set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL} /SUBSYSTEM:CONSOLE /ignore:4099") | |||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /SUBSYSTEM:CONSOLE /ignore:4099") | |||||
set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /SUBSYSTEM:CONSOLE /ignore:4099") | |||||
else() | |||||
# unknown compiler | |||||
message(STATUS "Compiler type UNKNOWN: ${CMAKE_CXX_COMPILER}") | |||||
set(BASE_COMPILER_OPTIONS "-std=c++11 -Wall -pthread") | |||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_OPTIONS}") | |||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${BASE_COMPILER_OPTIONS} -O0 -g") | |||||
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${BASE_COMPILER_OPTIONS} -Os") | |||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${BASE_COMPILER_OPTIONS} -O3 -fomit-frame-pointer") | |||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${BASE_COMPILER_OPTIONS} -O3 -g") | |||||
set(CMAKE_C_FLAGS "-std=c99") | |||||
endif() | |||||
# build libreplxx | |||||
set( | |||||
REPLXX_SOURCES | |||||
src/conversion.cxx | |||||
src/ConvertUTF.cpp | |||||
src/escape.cxx | |||||
src/history.cxx | |||||
src/replxx_impl.cxx | |||||
src/io.cxx | |||||
src/prompt.cxx | |||||
src/replxx.cxx | |||||
src/util.cxx | |||||
src/wcwidth.cpp | |||||
src/windows.cxx | |||||
) | |||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) | |||||
add_library(rspamd-replxx SHARED ${REPLXX_SOURCES}) | |||||
target_include_directories( | |||||
rspamd-replxx | |||||
PUBLIC ${PROJECT_SOURCE_DIR}/include | |||||
PRIVATE ${PROJECT_SOURCE_DIR}/src | |||||
) | |||||
set( TARGETS ${TARGETS} rspamd-replxx ) | |||||
target_compile_definitions(rspamd-replxx PRIVATE REPLXX_BUILDING_DLL) | |||||
install( TARGETS ${TARGETS} LIBRARY DESTINATION ${RSPAMD_LIBDIR}) |
Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) | |||||
Copyright (c) 2010, Salvatore Sanfilippo (antirez at gmail dot com) | |||||
Copyright (c) 2010, Pieter Noordhuis (pcnoordhuis at gmail dot com) | |||||
All rights reserved. | |||||
Redistribution and use in source and binary forms, with or without | |||||
modification, are permitted provided that the following conditions are met: | |||||
* Redistributions of source code must retain the above copyright notice, | |||||
this list of conditions and the following disclaimer. | |||||
* Redistributions in binary form must reproduce the above copyright | |||||
notice, this list of conditions and the following disclaimer in the | |||||
documentation and/or other materials provided with the distribution. | |||||
* Neither the name of Redis nor the names of its contributors may be used | |||||
to endorse or promote products derived from this software without | |||||
specific prior written permission. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||||
POSSIBILITY OF SUCH DAMAGE. | |||||
wcwidth.cpp | |||||
=========== | |||||
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. | |||||
ConvertUTF.cpp | |||||
============== | |||||
Copyright 2001-2004 Unicode, Inc. | |||||
Disclaimer | |||||
This source code is provided as is by Unicode, Inc. No claims are | |||||
made as to fitness for any particular purpose. No warranties of any | |||||
kind are expressed or implied. The recipient agrees to determine | |||||
applicability of information provided. If this file has been | |||||
purchased on magnetic or optical media from Unicode, Inc., the | |||||
sole remedy for any claim will be exchange of defective media | |||||
within 90 days of receipt. | |||||
Limitations on Rights to Redistribute This Code | |||||
Unicode, Inc. hereby grants the right to freely use the information | |||||
supplied in this file in the creation of products supporting the | |||||
Unicode Standard, and to make copies of this file in any form | |||||
for internal or external distribution as long as this notice | |||||
remains attached. |
# Read Evaluate Print Loop ++ | |||||
![demo](https://drive.google.com/uc?export=download&id=0B53g2Y3z7rWNT2dCRGVVNldaRnc) | |||||
[![Build Status](https://travis-ci.org/AmokHuginnsson/replxx.svg?branch=master)](https://travis-ci.org/AmokHuginnsson/replxx) | |||||
A small, portable GNU readline replacement for Linux, Windows and | |||||
MacOS which is capable of handling UTF-8 characters. Unlike GNU | |||||
readline, which is GPL, this library uses a BSD license and can be | |||||
used in any kind of program. | |||||
## Origin | |||||
This replxx implementation is based on the work by | |||||
[ArangoDB Team](https://github.com/arangodb/linenoise-ng) and | |||||
[Salvatore Sanfilippo](https://github.com/antirez/linenoise) and | |||||
10gen Inc. The goal is to create a zero-config, BSD | |||||
licensed, readline replacement usable in Apache2 or BSD licensed | |||||
programs. | |||||
## Features | |||||
* single-line and multi-line editing mode with the usual key bindings implemented | |||||
* history handling | |||||
* completion | |||||
* syntax highlighting | |||||
* hints | |||||
* BSD license source code | |||||
* Only uses a subset of VT100 escapes (ANSI.SYS compatible) | |||||
* UTF8 aware | |||||
* support for Linux, MacOS and Windows | |||||
It deviates from Salvatore's original goal to have a minimal readline | |||||
replacement for the sake of supporting UTF8 and Windows. It deviates | |||||
from 10gen Inc.'s goal to create a C++ interface to linenoise. This | |||||
library uses C++ internally, but to the user it provides a pure C | |||||
interface that is compatible with the original linenoise API. | |||||
C interface. | |||||
## Requirements | |||||
To build this library, you will need a C++11-enabled compiler and | |||||
some recent version of CMake. | |||||
## Build instructions | |||||
### *nix | |||||
1. Create a build directory | |||||
```bash | |||||
mkdir -p build && cd build | |||||
``` | |||||
2. Build the library | |||||
```bash | |||||
cmake -DCMAKE_BUILD_TYPE=Release .. && make | |||||
``` | |||||
3. Install the library at the default target location | |||||
```bash | |||||
sudo make install | |||||
``` | |||||
The default installation location can be adjusted by setting the `DESTDIR` | |||||
variable when invoking `make install`: | |||||
```bash | |||||
make DESTDIR=/tmp install | |||||
``` | |||||
### Windows | |||||
1. Create a build directory in MS-DOS command prompt | |||||
``` | |||||
md build | |||||
cd build | |||||
``` | |||||
2. Generate Visual Studio solution file with cmake | |||||
* 32 bit: | |||||
```bash | |||||
cmake -G "Visual Studio 12 2013" -DCMAKE_BUILD_TYPE=Release ..` | |||||
``` | |||||
* 64 bit: | |||||
```bash | |||||
`cmake -G "Visual Studio 12 2013 Win64" -DCMAKE_BUILD_TYPE=Release ..` | |||||
``` | |||||
3. Open the generated file `replxx.sln` in the `build` subdirectory with Visual Studio. | |||||
## Tested with... | |||||
* Linux text only console ($TERM = linux) | |||||
* Linux KDE terminal application ($TERM = xterm) | |||||
* Linux xterm ($TERM = xterm) | |||||
* Linux Buildroot ($TERM = vt100) | |||||
* Mac OS X iTerm ($TERM = xterm) | |||||
* Mac OS X default Terminal.app ($TERM = xterm) | |||||
* OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen) | |||||
* IBM AIX 6.1 | |||||
* FreeBSD xterm ($TERM = xterm) | |||||
* ANSI.SYS | |||||
* Emacs comint mode ($TERM = dumb) | |||||
* Windows | |||||
Please test it everywhere you can and report back! | |||||
## Let's push this forward! | |||||
Patches should be provided in the respect of linenoise sensibility for | |||||
small and easy to understand code that and the license | |||||
restrictions. Extensions must be submitted under a BSD license-style. | |||||
A contributor license is required for contributions. | |||||
/* linenoise.h -- guerrilla line editing library against the idea that a | |||||
* line editing lib needs to be 20,000 lines of C code. | |||||
* | |||||
* See linenoise.c for more information. | |||||
* | |||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com> | |||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||||
* | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions are met: | |||||
* | |||||
* * Redistributions of source code must retain the above copyright notice, | |||||
* this list of conditions and the following disclaimer. | |||||
* * Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* * Neither the name of Redis nor the names of its contributors may be used | |||||
* to endorse or promote products derived from this software without | |||||
* specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||||
* POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#ifndef __REPLXX_H | |||||
#define __REPLXX_H | |||||
#define REPLXX_VERSION "0.0.2" | |||||
#define REPLXX_VERSION_MAJOR 0 | |||||
#define REPLXX_VERSION_MINOR 0 | |||||
#ifdef __cplusplus | |||||
extern "C" { | |||||
#endif | |||||
/* | |||||
* For use in Windows DLLs: | |||||
* | |||||
* If you are building replxx into a DLL, | |||||
* unless you are using supplied CMake based build, | |||||
* ensure that 'REPLXX_BUILDING_DLL' is defined when | |||||
* building the DLL so that proper symbols are exported. | |||||
*/ | |||||
#if defined( _WIN32 ) && ! defined( REPLXX_STATIC ) | |||||
# ifdef REPLXX_BUILDING_DLL | |||||
# define REPLXX_IMPEXP __declspec( dllexport ) | |||||
# else | |||||
# define REPLXX_IMPEXP __declspec( dllimport ) | |||||
# endif | |||||
#else | |||||
# define REPLXX_IMPEXP /**/ | |||||
#endif | |||||
/*! \brief Color definitions to use in highlighter callbacks. | |||||
*/ | |||||
typedef enum { | |||||
REPLXX_COLOR_BLACK = 0, | |||||
REPLXX_COLOR_RED = 1, | |||||
REPLXX_COLOR_GREEN = 2, | |||||
REPLXX_COLOR_BROWN = 3, | |||||
REPLXX_COLOR_BLUE = 4, | |||||
REPLXX_COLOR_MAGENTA = 5, | |||||
REPLXX_COLOR_CYAN = 6, | |||||
REPLXX_COLOR_LIGHTGRAY = 7, | |||||
REPLXX_COLOR_GRAY = 8, | |||||
REPLXX_COLOR_BRIGHTRED = 9, | |||||
REPLXX_COLOR_BRIGHTGREEN = 10, | |||||
REPLXX_COLOR_YELLOW = 11, | |||||
REPLXX_COLOR_BRIGHTBLUE = 12, | |||||
REPLXX_COLOR_BRIGHTMAGENTA = 13, | |||||
REPLXX_COLOR_BRIGHTCYAN = 14, | |||||
REPLXX_COLOR_WHITE = 15, | |||||
REPLXX_COLOR_NORMAL = REPLXX_COLOR_LIGHTGRAY, | |||||
REPLXX_COLOR_DEFAULT = -1, | |||||
REPLXX_COLOR_ERROR = -2 | |||||
} ReplxxColor; | |||||
enum { REPLXX_KEY_BASE = 0x0010ffff + 1 }; | |||||
enum { REPLXX_KEY_BASE_SHIFT = 0x01000000 }; | |||||
enum { REPLXX_KEY_BASE_CONTROL = 0x02000000 }; | |||||
enum { REPLXX_KEY_BASE_META = 0x04000000 }; | |||||
enum { REPLXX_KEY_ESCAPE = 27 }; | |||||
enum { REPLXX_KEY_PAGE_UP = REPLXX_KEY_BASE + 1 }; | |||||
enum { REPLXX_KEY_PAGE_DOWN = REPLXX_KEY_PAGE_UP + 1 }; | |||||
enum { REPLXX_KEY_DOWN = REPLXX_KEY_PAGE_DOWN + 1 }; | |||||
enum { REPLXX_KEY_UP = REPLXX_KEY_DOWN + 1 }; | |||||
enum { REPLXX_KEY_LEFT = REPLXX_KEY_UP + 1 }; | |||||
enum { REPLXX_KEY_RIGHT = REPLXX_KEY_LEFT + 1 }; | |||||
enum { REPLXX_KEY_HOME = REPLXX_KEY_RIGHT + 1 }; | |||||
enum { REPLXX_KEY_END = REPLXX_KEY_HOME + 1 }; | |||||
enum { REPLXX_KEY_DELETE = REPLXX_KEY_END + 1 }; | |||||
enum { REPLXX_KEY_INSERT = REPLXX_KEY_DELETE + 1 }; | |||||
enum { REPLXX_KEY_F1 = REPLXX_KEY_INSERT + 1 }; | |||||
enum { REPLXX_KEY_F2 = REPLXX_KEY_F1 + 1 }; | |||||
enum { REPLXX_KEY_F3 = REPLXX_KEY_F2 + 1 }; | |||||
enum { REPLXX_KEY_F4 = REPLXX_KEY_F3 + 1 }; | |||||
enum { REPLXX_KEY_F5 = REPLXX_KEY_F4 + 1 }; | |||||
enum { REPLXX_KEY_F6 = REPLXX_KEY_F5 + 1 }; | |||||
enum { REPLXX_KEY_F7 = REPLXX_KEY_F6 + 1 }; | |||||
enum { REPLXX_KEY_F8 = REPLXX_KEY_F7 + 1 }; | |||||
enum { REPLXX_KEY_F9 = REPLXX_KEY_F8 + 1 }; | |||||
enum { REPLXX_KEY_F10 = REPLXX_KEY_F9 + 1 }; | |||||
enum { REPLXX_KEY_F11 = REPLXX_KEY_F10 + 1 }; | |||||
enum { REPLXX_KEY_F12 = REPLXX_KEY_F11 + 1 }; | |||||
enum { REPLXX_KEY_F13 = REPLXX_KEY_F12 + 1 }; | |||||
enum { REPLXX_KEY_F14 = REPLXX_KEY_F13 + 1 }; | |||||
enum { REPLXX_KEY_F15 = REPLXX_KEY_F14 + 1 }; | |||||
enum { REPLXX_KEY_F16 = REPLXX_KEY_F15 + 1 }; | |||||
enum { REPLXX_KEY_F17 = REPLXX_KEY_F16 + 1 }; | |||||
enum { REPLXX_KEY_F18 = REPLXX_KEY_F17 + 1 }; | |||||
enum { REPLXX_KEY_F19 = REPLXX_KEY_F18 + 1 }; | |||||
enum { REPLXX_KEY_F20 = REPLXX_KEY_F19 + 1 }; | |||||
enum { REPLXX_KEY_F21 = REPLXX_KEY_F20 + 1 }; | |||||
enum { REPLXX_KEY_F22 = REPLXX_KEY_F21 + 1 }; | |||||
enum { REPLXX_KEY_F23 = REPLXX_KEY_F22 + 1 }; | |||||
enum { REPLXX_KEY_F24 = REPLXX_KEY_F23 + 1 }; | |||||
enum { REPLXX_KEY_MOUSE = REPLXX_KEY_F24 + 1 }; | |||||
#define REPLXX_KEY_SHIFT( key ) ( ( key ) | REPLXX_KEY_BASE_SHIFT ) | |||||
#define REPLXX_KEY_CONTROL( key ) ( ( key ) | REPLXX_KEY_BASE_CONTROL ) | |||||
#define REPLXX_KEY_META( key ) ( ( key ) | REPLXX_KEY_BASE_META ) | |||||
enum { REPLXX_KEY_BACKSPACE = REPLXX_KEY_CONTROL( 'H' ) }; | |||||
enum { REPLXX_KEY_TAB = REPLXX_KEY_CONTROL( 'I' ) }; | |||||
enum { REPLXX_KEY_ENTER = REPLXX_KEY_CONTROL( 'M' ) }; | |||||
/*! \brief List of built-in actions that act upon user input. | |||||
*/ | |||||
typedef enum { | |||||
REPLXX_ACTION_INSERT_CHARACTER, | |||||
REPLXX_ACTION_DELETE_CHARACTER_UNDER_CURSOR, | |||||
REPLXX_ACTION_DELETE_CHARACTER_LEFT_OF_CURSOR, | |||||
REPLXX_ACTION_KILL_TO_END_OF_LINE, | |||||
REPLXX_ACTION_KILL_TO_BEGINING_OF_LINE, | |||||
REPLXX_ACTION_KILL_TO_END_OF_WORD, | |||||
REPLXX_ACTION_KILL_TO_BEGINING_OF_WORD, | |||||
REPLXX_ACTION_KILL_TO_WHITESPACE_ON_LEFT, | |||||
REPLXX_ACTION_YANK, | |||||
REPLXX_ACTION_YANK_CYCLE, | |||||
REPLXX_ACTION_MOVE_CURSOR_TO_BEGINING_OF_LINE, | |||||
REPLXX_ACTION_MOVE_CURSOR_TO_END_OF_LINE, | |||||
REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_LEFT, | |||||
REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_RIGHT, | |||||
REPLXX_ACTION_MOVE_CURSOR_LEFT, | |||||
REPLXX_ACTION_MOVE_CURSOR_RIGHT, | |||||
REPLXX_ACTION_HISTORY_NEXT, | |||||
REPLXX_ACTION_HISTORY_PREVIOUS, | |||||
REPLXX_ACTION_HISTORY_FIRST, | |||||
REPLXX_ACTION_HISTORY_LAST, | |||||
REPLXX_ACTION_HISTORY_INCREMENTAL_SEARCH, | |||||
REPLXX_ACTION_HISTORY_COMMON_PREFIX_SEARCH, | |||||
REPLXX_ACTION_HINT_NEXT, | |||||
REPLXX_ACTION_HINT_PREVIOUS, | |||||
REPLXX_ACTION_CAPITALIZE_WORD, | |||||
REPLXX_ACTION_LOWERCASE_WORD, | |||||
REPLXX_ACTION_UPPERCASE_WORD, | |||||
REPLXX_ACTION_TRANSPOSE_CHARACTERS, | |||||
REPLXX_ACTION_TOGGLE_OVERWRITE_MODE, | |||||
#ifndef _WIN32 | |||||
REPLXX_ACTION_VERBATIM_INSERT, | |||||
REPLXX_ACTION_SUSPEND, | |||||
#endif | |||||
REPLXX_ACTION_CLEAR_SCREEN, | |||||
REPLXX_ACTION_CLEAR_SELF, | |||||
REPLXX_ACTION_REPAINT, | |||||
REPLXX_ACTION_COMPLETE_LINE, | |||||
REPLXX_ACTION_COMPLETE_NEXT, | |||||
REPLXX_ACTION_COMPLETE_PREVIOUS, | |||||
REPLXX_ACTION_COMMIT_LINE, | |||||
REPLXX_ACTION_ABORT_LINE, | |||||
REPLXX_ACTION_SEND_EOF | |||||
} ReplxxAction; | |||||
/*! \brief Possible results of key-press handler actions. | |||||
*/ | |||||
typedef enum { | |||||
REPLXX_ACTION_RESULT_CONTINUE, /*!< Continue processing user input. */ | |||||
REPLXX_ACTION_RESULT_RETURN, /*!< Return user input entered so far. */ | |||||
REPLXX_ACTION_RESULT_BAIL /*!< Stop processing user input, returns nullptr from the \e input() call. */ | |||||
} ReplxxActionResult; | |||||
typedef struct ReplxxStateTag { | |||||
char const* text; | |||||
int cursorPosition; | |||||
} ReplxxState; | |||||
typedef struct Replxx Replxx; | |||||
/*! \brief Create Replxx library resouce holder. | |||||
* | |||||
* Use replxx_end() to free resoiurce acquired with this function. | |||||
* | |||||
* \return Replxx library resouce holder. | |||||
*/ | |||||
REPLXX_IMPEXP Replxx* replxx_init( void ); | |||||
/*! \brief Cleanup resources used by Replxx library. | |||||
* | |||||
* \param replxx - a Replxx library resource holder. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_end( Replxx* replxx ); | |||||
/*! \brief Highlighter callback type definition. | |||||
* | |||||
* If user want to have colorful input she must simply install highlighter callback. | |||||
* The callback would be invoked by the library after each change to the input done by | |||||
* the user. After callback returns library uses data from colors buffer to colorize | |||||
* displayed user input. | |||||
* | |||||
* \e size of \e colors buffer is equal to number of code points in user \e input | |||||
* which will be different from simple `strlen( input )`! | |||||
* | |||||
* \param input - an UTF-8 encoded input entered by the user so far. | |||||
* \param colors - output buffer for color information. | |||||
* \param size - size of output buffer for color information. | |||||
* \param userData - pointer to opaque user data block. | |||||
*/ | |||||
typedef void (replxx_highlighter_callback_t)(char const* input, ReplxxColor* colors, int size, void* userData); | |||||
/*! \brief Register highlighter callback. | |||||
* | |||||
* \param fn - user defined callback function. | |||||
* \param userData - pointer to opaque user data block to be passed into each invocation of the callback. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_highlighter_callback( Replxx*, replxx_highlighter_callback_t* fn, void* userData ); | |||||
typedef struct replxx_completions replxx_completions; | |||||
/*! \brief Completions callback type definition. | |||||
* | |||||
* \e contextLen is counted in Unicode code points (not in bytes!). | |||||
* | |||||
* For user input: | |||||
* if ( obj.me | |||||
* | |||||
* input == "if ( obj.me" | |||||
* contextLen == 2 (depending on \e replxx_set_word_break_characters()) | |||||
* | |||||
* Client application is free to update \e contextLen to be 6 (or any orther non-negative | |||||
* number not greated than the number of code points in input) if it makes better sense | |||||
* for given client application semantics. | |||||
* | |||||
* \param input - UTF-8 encoded input entered by the user until current cursor position. | |||||
* \param completions - pointer to opaque list of user completions. | |||||
* \param contextLen[in,out] - length of the additional context to provide while displaying completions. | |||||
* \param userData - pointer to opaque user data block. | |||||
*/ | |||||
typedef void(replxx_completion_callback_t)(const char* input, replxx_completions* completions, int* contextLen, void* userData); | |||||
/*! \brief Register completion callback. | |||||
* | |||||
* \param fn - user defined callback function. | |||||
* \param userData - pointer to opaque user data block to be passed into each invocation of the callback. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_completion_callback( Replxx*, replxx_completion_callback_t* fn, void* userData ); | |||||
/*! \brief Add another possible completion for current user input. | |||||
* | |||||
* \param completions - pointer to opaque list of user completions. | |||||
* \param str - UTF-8 encoded completion string. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_add_completion( replxx_completions* completions, const char* str ); | |||||
/*! \brief Add another possible completion for current user input. | |||||
* | |||||
* \param completions - pointer to opaque list of user completions. | |||||
* \param str - UTF-8 encoded completion string. | |||||
* \param color - a color for the completion. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_add_color_completion( replxx_completions* completions, const char* str, ReplxxColor color ); | |||||
typedef struct replxx_hints replxx_hints; | |||||
/*! \brief Hints callback type definition. | |||||
* | |||||
* \e contextLen is counted in Unicode code points (not in bytes!). | |||||
* | |||||
* For user input: | |||||
* if ( obj.me | |||||
* | |||||
* input == "if ( obj.me" | |||||
* contextLen == 2 (depending on \e replxx_set_word_break_characters()) | |||||
* | |||||
* Client application is free to update \e contextLen to be 6 (or any orther non-negative | |||||
* number not greated than the number of code points in input) if it makes better sense | |||||
* for given client application semantics. | |||||
* | |||||
* \param input - UTF-8 encoded input entered by the user until current cursor position. | |||||
* \param hints - pointer to opaque list of possible hints. | |||||
* \param contextLen[in,out] - length of the additional context to provide while displaying hints. | |||||
* \param color - a color used for displaying hints. | |||||
* \param userData - pointer to opaque user data block. | |||||
*/ | |||||
typedef void(replxx_hint_callback_t)(const char* input, replxx_hints* hints, int* contextLen, ReplxxColor* color, void* userData); | |||||
/*! \brief Register hints callback. | |||||
* | |||||
* \param fn - user defined callback function. | |||||
* \param userData - pointer to opaque user data block to be passed into each invocation of the callback. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_hint_callback( Replxx*, replxx_hint_callback_t* fn, void* userData ); | |||||
/*! \brief Key press handler type definition. | |||||
* | |||||
* \param code - the key code replxx got from terminal. | |||||
* \return Decition on how should input() behave after this key handler returns. | |||||
*/ | |||||
typedef ReplxxActionResult (key_press_handler_t)( int code, void* userData ); | |||||
/*! \brief Add another possible hint for current user input. | |||||
* | |||||
* \param hints - pointer to opaque list of hints. | |||||
* \param str - UTF-8 encoded hint string. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_add_hint( replxx_hints* hints, const char* str ); | |||||
/*! \brief Read line of user input. | |||||
* | |||||
* \param prompt - prompt to be displayed before getting user input. | |||||
* \return An UTF-8 encoded input given by the user (or nullptr on EOF). | |||||
*/ | |||||
REPLXX_IMPEXP char const* replxx_input( Replxx*, const char* prompt ); | |||||
/*! \brief Get current state data. | |||||
* | |||||
* This call is intended to be used in handlers. | |||||
* | |||||
* \param state - buffer for current state of the model. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_get_state( Replxx*, ReplxxState* state ); | |||||
/*! \brief Set new state data. | |||||
* | |||||
* This call is intended to be used in handlers. | |||||
* | |||||
* \param state - new state of the model. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_state( Replxx*, ReplxxState* state ); | |||||
/*! \brief Print formatted string to standard output. | |||||
* | |||||
* This function ensures proper handling of ANSI escape sequences | |||||
* contained in printed data, which is especially useful on Windows | |||||
* since Unixes handle them correctly out of the box. | |||||
* | |||||
* \param fmt - printf style format. | |||||
*/ | |||||
REPLXX_IMPEXP int replxx_print( Replxx*, char const* fmt, ... ); | |||||
/*! \brief Schedule an emulated key press event. | |||||
* | |||||
* \param code - key press code to be emulated. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_emulate_key_press( Replxx*, int unsigned code ); | |||||
/*! \brief Invoke built-in action handler. | |||||
* | |||||
* \pre This function can be called only from key-press handler. | |||||
* | |||||
* \param action - a built-in action to invoke. | |||||
* \param code - a supplementary key-code to consume by built-in action handler. | |||||
* \return The action result informing the replxx what shall happen next. | |||||
*/ | |||||
REPLXX_IMPEXP ReplxxActionResult replxx_invoke( Replxx*, ReplxxAction action, int unsigned code ); | |||||
/*! \brief Bind user defined action to handle given key-press event. | |||||
* | |||||
* \param code - handle this key-press event with following handler. | |||||
* \param handler - use this handler to handle key-press event. | |||||
* \param userData - supplementary user data passed to invoked handlers. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_bind_key( Replxx*, int code, key_press_handler_t handler, void* userData ); | |||||
REPLXX_IMPEXP void replxx_set_preload_buffer( Replxx*, const char* preloadText ); | |||||
REPLXX_IMPEXP void replxx_history_add( Replxx*, const char* line ); | |||||
REPLXX_IMPEXP int replxx_history_size( Replxx* ); | |||||
/*! \brief Set set of word break characters. | |||||
* | |||||
* This setting influences word based cursor movement and line editing capabilities. | |||||
* | |||||
* \param wordBreakers - 7-bit ASCII set of word breaking characters. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_word_break_characters( Replxx*, char const* wordBreakers ); | |||||
/*! \brief How many completions should trigger pagination. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_completion_count_cutoff( Replxx*, int count ); | |||||
/*! \brief Set maximum number of displayed hint rows. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_max_hint_rows( Replxx*, int count ); | |||||
/*! \brief Set a delay before hint are shown after user stopped typing.. | |||||
* | |||||
* \param milliseconds - a number of milliseconds to wait before showing hints. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_hint_delay( Replxx*, int milliseconds ); | |||||
/*! \brief Set tab completion behavior. | |||||
* | |||||
* \param val - use double tab to invoke completions (if != 0). | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_double_tab_completion( Replxx*, int val ); | |||||
/*! \brief Set tab completion behavior. | |||||
* | |||||
* \param val - invoke completion even if user input is empty (if != 0). | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_complete_on_empty( Replxx*, int val ); | |||||
/*! \brief Set tab completion behavior. | |||||
* | |||||
* \param val - beep if completion is ambiguous (if != 0). | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_beep_on_ambiguous_completion( Replxx*, int val ); | |||||
/*! \brief Disable output coloring. | |||||
* | |||||
* \param val - if set to non-zero disable output colors. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_no_color( Replxx*, int val ); | |||||
/*! \brief Set maximum number of entries in history list. | |||||
*/ | |||||
REPLXX_IMPEXP void replxx_set_max_history_size( Replxx*, int len ); | |||||
REPLXX_IMPEXP char const* replxx_history_line( Replxx*, int index ); | |||||
REPLXX_IMPEXP int replxx_history_save( Replxx*, const char* filename ); | |||||
REPLXX_IMPEXP int replxx_history_load( Replxx*, const char* filename ); | |||||
REPLXX_IMPEXP void replxx_clear_screen( Replxx* ); | |||||
#ifdef __REPLXX_DEBUG__ | |||||
void replxx_debug_dump_print_codes(void); | |||||
#endif | |||||
/* the following is extension to the original linenoise API */ | |||||
REPLXX_IMPEXP int replxx_install_window_change_handler( Replxx* ); | |||||
#ifdef __cplusplus | |||||
} | |||||
#endif | |||||
#endif /* __REPLXX_H */ | |||||
/* | |||||
* Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) | |||||
* | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions are met: | |||||
* | |||||
* * Redistributions of source code must retain the above copyright notice, | |||||
* this list of conditions and the following disclaimer. | |||||
* * Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* * Neither the name of Redis nor the names of its contributors may be used | |||||
* to endorse or promote products derived from this software without | |||||
* specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||||
* POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#ifndef HAVE_REPLXX_HXX_INCLUDED | |||||
#define HAVE_REPLXX_HXX_INCLUDED 1 | |||||
#include <memory> | |||||
#include <vector> | |||||
#include <string> | |||||
#include <functional> | |||||
/* | |||||
* For use in Windows DLLs: | |||||
* | |||||
* If you are building replxx into a DLL, | |||||
* unless you are using supplied CMake based build, | |||||
* ensure that 'REPLXX_BUILDING_DLL' is defined when | |||||
* building the DLL so that proper symbols are exported. | |||||
*/ | |||||
#if defined( _WIN32 ) && ! defined( REPLXX_STATIC ) | |||||
# ifdef REPLXX_BUILDING_DLL | |||||
# define REPLXX_IMPEXP __declspec( dllexport ) | |||||
# else | |||||
# define REPLXX_IMPEXP __declspec( dllimport ) | |||||
# endif | |||||
#else | |||||
# define REPLXX_IMPEXP /**/ | |||||
#endif | |||||
#ifdef ERROR | |||||
enum { ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 = ERROR }; | |||||
#undef ERROR | |||||
enum { ERROR = ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 }; | |||||
#endif | |||||
#ifdef DELETE | |||||
enum { DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E = DELETE }; | |||||
#undef DELETE | |||||
enum { DELETE = DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E }; | |||||
#endif | |||||
namespace replxx { | |||||
class REPLXX_IMPEXP Replxx { | |||||
public: | |||||
enum class Color { | |||||
BLACK = 0, | |||||
RED = 1, | |||||
GREEN = 2, | |||||
BROWN = 3, | |||||
BLUE = 4, | |||||
MAGENTA = 5, | |||||
CYAN = 6, | |||||
LIGHTGRAY = 7, | |||||
GRAY = 8, | |||||
BRIGHTRED = 9, | |||||
BRIGHTGREEN = 10, | |||||
YELLOW = 11, | |||||
BRIGHTBLUE = 12, | |||||
BRIGHTMAGENTA = 13, | |||||
BRIGHTCYAN = 14, | |||||
WHITE = 15, | |||||
NORMAL = LIGHTGRAY, | |||||
DEFAULT = -1, | |||||
ERROR = -2 | |||||
}; | |||||
struct KEY { | |||||
static char32_t const BASE = 0x0010ffff + 1; | |||||
static char32_t const BASE_SHIFT = 0x01000000; | |||||
static char32_t const BASE_CONTROL = 0x02000000; | |||||
static char32_t const BASE_META = 0x04000000; | |||||
static char32_t const ESCAPE = 27; | |||||
static char32_t const PAGE_UP = BASE + 1; | |||||
static char32_t const PAGE_DOWN = PAGE_UP + 1; | |||||
static char32_t const DOWN = PAGE_DOWN + 1; | |||||
static char32_t const UP = DOWN + 1; | |||||
static char32_t const LEFT = UP + 1; | |||||
static char32_t const RIGHT = LEFT + 1; | |||||
static char32_t const HOME = RIGHT + 1; | |||||
static char32_t const END = HOME + 1; | |||||
static char32_t const DELETE = END + 1; | |||||
static char32_t const INSERT = DELETE + 1; | |||||
static char32_t const F1 = INSERT + 1; | |||||
static char32_t const F2 = F1 + 1; | |||||
static char32_t const F3 = F2 + 1; | |||||
static char32_t const F4 = F3 + 1; | |||||
static char32_t const F5 = F4 + 1; | |||||
static char32_t const F6 = F5 + 1; | |||||
static char32_t const F7 = F6 + 1; | |||||
static char32_t const F8 = F7 + 1; | |||||
static char32_t const F9 = F8 + 1; | |||||
static char32_t const F10 = F9 + 1; | |||||
static char32_t const F11 = F10 + 1; | |||||
static char32_t const F12 = F11 + 1; | |||||
static char32_t const F13 = F12 + 1; | |||||
static char32_t const F14 = F13 + 1; | |||||
static char32_t const F15 = F14 + 1; | |||||
static char32_t const F16 = F15 + 1; | |||||
static char32_t const F17 = F16 + 1; | |||||
static char32_t const F18 = F17 + 1; | |||||
static char32_t const F19 = F18 + 1; | |||||
static char32_t const F20 = F19 + 1; | |||||
static char32_t const F21 = F20 + 1; | |||||
static char32_t const F22 = F21 + 1; | |||||
static char32_t const F23 = F22 + 1; | |||||
static char32_t const F24 = F23 + 1; | |||||
static char32_t const MOUSE = F24 + 1; | |||||
static constexpr char32_t shift( char32_t key_ ) { | |||||
return ( key_ | BASE_SHIFT ); | |||||
} | |||||
static constexpr char32_t control( char32_t key_ ) { | |||||
return ( key_ | BASE_CONTROL ); | |||||
} | |||||
static constexpr char32_t meta( char32_t key_ ) { | |||||
return ( key_ | BASE_META ); | |||||
} | |||||
static char32_t const BACKSPACE = 'H' | BASE_CONTROL; | |||||
static char32_t const TAB = 'I' | BASE_CONTROL; | |||||
static char32_t const ENTER = 'M' | BASE_CONTROL; | |||||
}; | |||||
/*! \brief List of built-in actions that act upon user input. | |||||
*/ | |||||
enum class ACTION { | |||||
INSERT_CHARACTER, | |||||
DELETE_CHARACTER_UNDER_CURSOR, | |||||
DELETE_CHARACTER_LEFT_OF_CURSOR, | |||||
KILL_TO_END_OF_LINE, | |||||
KILL_TO_BEGINING_OF_LINE, | |||||
KILL_TO_END_OF_WORD, | |||||
KILL_TO_BEGINING_OF_WORD, | |||||
KILL_TO_WHITESPACE_ON_LEFT, | |||||
YANK, | |||||
YANK_CYCLE, | |||||
MOVE_CURSOR_TO_BEGINING_OF_LINE, | |||||
MOVE_CURSOR_TO_END_OF_LINE, | |||||
MOVE_CURSOR_ONE_WORD_LEFT, | |||||
MOVE_CURSOR_ONE_WORD_RIGHT, | |||||
MOVE_CURSOR_LEFT, | |||||
MOVE_CURSOR_RIGHT, | |||||
HISTORY_NEXT, | |||||
HISTORY_PREVIOUS, | |||||
HISTORY_FIRST, | |||||
HISTORY_LAST, | |||||
HISTORY_INCREMENTAL_SEARCH, | |||||
HISTORY_COMMON_PREFIX_SEARCH, | |||||
HINT_NEXT, | |||||
HINT_PREVIOUS, | |||||
CAPITALIZE_WORD, | |||||
LOWERCASE_WORD, | |||||
UPPERCASE_WORD, | |||||
TRANSPOSE_CHARACTERS, | |||||
TOGGLE_OVERWRITE_MODE, | |||||
#ifndef _WIN32 | |||||
VERBATIM_INSERT, | |||||
SUSPEND, | |||||
#endif | |||||
CLEAR_SCREEN, | |||||
CLEAR_SELF, | |||||
REPAINT, | |||||
COMPLETE_LINE, | |||||
COMPLETE_NEXT, | |||||
COMPLETE_PREVIOUS, | |||||
COMMIT_LINE, | |||||
ABORT_LINE, | |||||
SEND_EOF | |||||
}; | |||||
/*! \brief Possible results of key-press handler actions. | |||||
*/ | |||||
enum class ACTION_RESULT { | |||||
CONTINUE, /*!< Continue processing user input. */ | |||||
RETURN, /*!< Return user input entered so far. */ | |||||
BAIL /*!< Stop processing user input, returns nullptr from the \e input() call. */ | |||||
}; | |||||
typedef std::vector<Color> colors_t; | |||||
class Completion { | |||||
std::string _text; | |||||
Color _color; | |||||
public: | |||||
Completion( char const* text_ ) | |||||
: _text( text_ ) | |||||
, _color( Color::DEFAULT ) { | |||||
} | |||||
Completion( std::string const& text_ ) | |||||
: _text( text_ ) | |||||
, _color( Color::DEFAULT ) { | |||||
} | |||||
Completion( std::string const& text_, Color color_ ) | |||||
: _text( text_ ) | |||||
, _color( color_ ) { | |||||
} | |||||
std::string const& text( void ) const { | |||||
return ( _text ); | |||||
} | |||||
Color color( void ) const { | |||||
return ( _color ); | |||||
} | |||||
}; | |||||
typedef std::vector<Completion> completions_t; | |||||
typedef std::vector<std::string> hints_t; | |||||
/*! \brief Completions callback type definition. | |||||
* | |||||
* \e contextLen is counted in Unicode code points (not in bytes!). | |||||
* | |||||
* For user input: | |||||
* if ( obj.me | |||||
* | |||||
* input == "if ( obj.me" | |||||
* contextLen == 2 (depending on \e set_word_break_characters()) | |||||
* | |||||
* Client application is free to update \e contextLen to be 6 (or any orther non-negative | |||||
* number not greated than the number of code points in input) if it makes better sense | |||||
* for given client application semantics. | |||||
* | |||||
* \param input - UTF-8 encoded input entered by the user until current cursor position. | |||||
* \param[in,out] contextLen - length of the additional context to provide while displaying completions. | |||||
* \return A list of user completions. | |||||
*/ | |||||
typedef std::function<completions_t ( std::string const& input, int& contextLen )> completion_callback_t; | |||||
/*! \brief Highlighter callback type definition. | |||||
* | |||||
* If user want to have colorful input she must simply install highlighter callback. | |||||
* The callback would be invoked by the library after each change to the input done by | |||||
* the user. After callback returns library uses data from colors buffer to colorize | |||||
* displayed user input. | |||||
* | |||||
* Size of \e colors buffer is equal to number of code points in user \e input | |||||
* which will be different from simple `input.lenght()`! | |||||
* | |||||
* \param input - an UTF-8 encoded input entered by the user so far. | |||||
* \param colors - output buffer for color information. | |||||
*/ | |||||
typedef std::function<void ( std::string const& input, colors_t& colors )> highlighter_callback_t; | |||||
/*! \brief Hints callback type definition. | |||||
* | |||||
* \e contextLen is counted in Unicode code points (not in bytes!). | |||||
* | |||||
* For user input: | |||||
* if ( obj.me | |||||
* | |||||
* input == "if ( obj.me" | |||||
* contextLen == 2 (depending on \e set_word_break_characters()) | |||||
* | |||||
* Client application is free to update \e contextLen to be 6 (or any orther non-negative | |||||
* number not greated than the number of code points in input) if it makes better sense | |||||
* for given client application semantics. | |||||
* | |||||
* \param input - UTF-8 encoded input entered by the user until current cursor position. | |||||
* \param contextLen[in,out] - length of the additional context to provide while displaying hints. | |||||
* \param color - a color used for displaying hints. | |||||
* \return A list of possible hints. | |||||
*/ | |||||
typedef std::function<hints_t ( std::string const& input, int& contextLen, Color& color )> hint_callback_t; | |||||
/*! \brief Key press handler type definition. | |||||
* | |||||
* \param code - the key code replxx got from terminal. | |||||
* \return Decition on how should input() behave after this key handler returns. | |||||
*/ | |||||
typedef std::function<ACTION_RESULT ( char32_t code )> key_press_handler_t; | |||||
struct State { | |||||
char const* _text; | |||||
int _cursorPosition; | |||||
State( char const* text_, int cursorPosition_ = -1 ) | |||||
: _text( text_ ) | |||||
, _cursorPosition( cursorPosition_ ) { | |||||
} | |||||
State( State const& ) = default; | |||||
State& operator = ( State const& ) = default; | |||||
char const* text( void ) const { | |||||
return ( _text ); | |||||
} | |||||
int cursor_position( void ) const { | |||||
return ( _cursorPosition ); | |||||
} | |||||
}; | |||||
class ReplxxImpl; | |||||
private: | |||||
typedef std::unique_ptr<ReplxxImpl, void (*)( ReplxxImpl* )> impl_t; | |||||
#ifdef _WIN32 | |||||
#pragma warning(push) | |||||
#pragma warning(disable:4251) | |||||
#endif | |||||
impl_t _impl; | |||||
#ifdef _WIN32 | |||||
#pragma warning(pop) | |||||
#endif | |||||
public: | |||||
Replxx( void ); | |||||
Replxx( Replxx&& ) = default; | |||||
Replxx& operator = ( Replxx&& ) = default; | |||||
/*! \brief Register completion callback. | |||||
* | |||||
* \param fn - user defined callback function. | |||||
*/ | |||||
void set_completion_callback( completion_callback_t const& fn ); | |||||
/*! \brief Register highlighter callback. | |||||
* | |||||
* \param fn - user defined callback function. | |||||
*/ | |||||
void set_highlighter_callback( highlighter_callback_t const& fn ); | |||||
/*! \brief Register hints callback. | |||||
* | |||||
* \param fn - user defined callback function. | |||||
*/ | |||||
void set_hint_callback( hint_callback_t const& fn ); | |||||
/*! \brief Read line of user input. | |||||
* | |||||
* \param prompt - prompt to be displayed before getting user input. | |||||
* \return An UTF-8 encoded input given by the user (or nullptr on EOF). | |||||
*/ | |||||
char const* input( std::string const& prompt ); | |||||
/*! \brief Get current state data. | |||||
* | |||||
* This call is intended to be used in handlers. | |||||
* | |||||
* \return Current state of the model. | |||||
*/ | |||||
State get_state( void ) const; | |||||
/*! \brief Set new state data. | |||||
* | |||||
* This call is intended to be used in handlers. | |||||
* | |||||
* \param state - new state of the model. | |||||
*/ | |||||
void set_state( State const& state ); | |||||
/*! \brief Print formatted string to standard output. | |||||
* | |||||
* This function ensures proper handling of ANSI escape sequences | |||||
* contained in printed data, which is especially useful on Windows | |||||
* since Unixes handle them correctly out of the box. | |||||
* | |||||
* \param fmt - printf style format. | |||||
*/ | |||||
void print( char const* fmt, ... ); | |||||
/*! \brief Schedule an emulated key press event. | |||||
* | |||||
* \param code - key press code to be emulated. | |||||
*/ | |||||
void emulate_key_press( char32_t code ); | |||||
/*! \brief Invoke built-in action handler. | |||||
* | |||||
* \pre This method can be called only from key-press handler. | |||||
* | |||||
* \param action - a built-in action to invoke. | |||||
* \param code - a supplementary key-code to consume by built-in action handler. | |||||
* \return The action result informing the replxx what shall happen next. | |||||
*/ | |||||
ACTION_RESULT invoke( ACTION action, char32_t code ); | |||||
/*! \brief Bind user defined action to handle given key-press event. | |||||
* | |||||
* \param code - handle this key-press event with following handler. | |||||
* \param handle - use this handler to handle key-press event. | |||||
*/ | |||||
void bind_key( char32_t code, key_press_handler_t handler ); | |||||
void history_add( std::string const& line ); | |||||
int history_save( std::string const& filename ); | |||||
int history_load( std::string const& filename ); | |||||
int history_size( void ) const; | |||||
std::string history_line( int index ); | |||||
void set_preload_buffer( std::string const& preloadText ); | |||||
/*! \brief Set set of word break characters. | |||||
* | |||||
* This setting influences word based cursor movement and line editing capabilities. | |||||
* | |||||
* \param wordBreakers - 7-bit ASCII set of word breaking characters. | |||||
*/ | |||||
void set_word_break_characters( char const* wordBreakers ); | |||||
/*! \brief How many completions should trigger pagination. | |||||
*/ | |||||
void set_completion_count_cutoff( int count ); | |||||
/*! \brief Set maximum number of displayed hint rows. | |||||
*/ | |||||
void set_max_hint_rows( int count ); | |||||
/*! \brief Set a delay before hint are shown after user stopped typing.. | |||||
* | |||||
* \param milliseconds - a number of milliseconds to wait before showing hints. | |||||
*/ | |||||
void set_hint_delay( int milliseconds ); | |||||
/*! \brief Set tab completion behavior. | |||||
* | |||||
* \param val - use double tab to invoke completions. | |||||
*/ | |||||
void set_double_tab_completion( bool val ); | |||||
/*! \brief Set tab completion behavior. | |||||
* | |||||
* \param val - invoke completion even if user input is empty. | |||||
*/ | |||||
void set_complete_on_empty( bool val ); | |||||
/*! \brief Set tab completion behavior. | |||||
* | |||||
* \param val - beep if completion is ambiguous. | |||||
*/ | |||||
void set_beep_on_ambiguous_completion( bool val ); | |||||
/*! \brief Disable output coloring. | |||||
* | |||||
* \param val - if set to non-zero disable output colors. | |||||
*/ | |||||
void set_no_color( bool val ); | |||||
/*! \brief Set maximum number of entries in history list. | |||||
*/ | |||||
void set_max_history_size( int len ); | |||||
void clear_screen( void ); | |||||
int install_window_change_handler( void ); | |||||
private: | |||||
Replxx( Replxx const& ) = delete; | |||||
Replxx& operator = ( Replxx const& ) = delete; | |||||
}; | |||||
} | |||||
#endif /* HAVE_REPLXX_HXX_INCLUDED */ | |||||
/* | |||||
* Copyright 2001-2004 Unicode, Inc. | |||||
* | |||||
* Disclaimer | |||||
* | |||||
* This source code is provided as is by Unicode, Inc. No claims are | |||||
* made as to fitness for any particular purpose. No warranties of any | |||||
* kind are expressed or implied. The recipient agrees to determine | |||||
* applicability of information provided. If this file has been | |||||
* purchased on magnetic or optical media from Unicode, Inc., the | |||||
* sole remedy for any claim will be exchange of defective media | |||||
* within 90 days of receipt. | |||||
* | |||||
* Limitations on Rights to Redistribute This Code | |||||
* | |||||
* Unicode, Inc. hereby grants the right to freely use the information | |||||
* supplied in this file in the creation of products supporting the | |||||
* Unicode Standard, and to make copies of this file in any form | |||||
* for internal or external distribution as long as this notice | |||||
* remains attached. | |||||
*/ | |||||
/* --------------------------------------------------------------------- | |||||
Conversions between UTF32, UTF-16, and UTF-8. Source code file. | |||||
Author: Mark E. Davis, 1994. | |||||
Rev History: Rick McGowan, fixes & updates May 2001. | |||||
Sept 2001: fixed const & error conditions per | |||||
mods suggested by S. Parent & A. Lillich. | |||||
June 2002: Tim Dodd added detection and handling of incomplete | |||||
source sequences, enhanced error detection, added casts | |||||
to eliminate compiler warnings. | |||||
July 2003: slight mods to back out aggressive FFFE detection. | |||||
Jan 2004: updated switches in from-UTF8 conversions. | |||||
Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. | |||||
See the header file "ConvertUTF.h" for complete documentation. | |||||
------------------------------------------------------------------------ */ | |||||
#include "ConvertUTF.h" | |||||
#ifdef CVTUTF_DEBUG | |||||
#include <stdio.h> | |||||
#endif | |||||
namespace replxx { | |||||
#define UNI_SUR_HIGH_START (UTF32)0xD800 | |||||
#define UNI_SUR_LOW_END (UTF32)0xDFFF | |||||
/* --------------------------------------------------------------------- */ | |||||
/* | |||||
* Index into the table below with the first byte of a UTF-8 sequence to | |||||
* get the number of trailing bytes that are supposed to follow it. | |||||
* Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is | |||||
* left as-is for anyone who may want to do such conversion, which was | |||||
* allowed in earlier algorithms. | |||||
*/ | |||||
static const char trailingBytesForUTF8[256] = { | |||||
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,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,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,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,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,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,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,0,0,0,0,0,0,0,0,0,0, | |||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, | |||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 | |||||
}; | |||||
/* | |||||
* Magic values subtracted from a buffer value during UTF8 conversion. | |||||
* This table contains as many values as there might be trailing bytes | |||||
* in a UTF-8 sequence. | |||||
*/ | |||||
static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, | |||||
0x03C82080UL, 0xFA082080UL, 0x82082080UL }; | |||||
/* | |||||
* Once the bits are split out into bytes of UTF-8, this is a mask OR-ed | |||||
* into the first byte, depending on how many bytes follow. There are | |||||
* as many entries in this table as there are UTF-8 sequence types. | |||||
* (I.e., one byte sequence, two byte... etc.). Remember that sequencs | |||||
* for *legal* UTF-8 will be 4 or fewer bytes total. | |||||
*/ | |||||
static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; | |||||
/* --------------------------------------------------------------------- */ | |||||
/* The interface converts a whole buffer to avoid function-call overhead. | |||||
* Constants have been gathered. Loops & conditionals have been removed as | |||||
* much as possible for efficiency, in favor of drop-through switches. | |||||
* (See "Note A" at the bottom of the file for equivalent code.) | |||||
* If your compiler supports it, the "isLegalUTF8" call can be turned | |||||
* into an inline function. | |||||
*/ | |||||
/* --------------------------------------------------------------------- */ | |||||
/* | |||||
* Utility routine to tell whether a sequence of bytes is legal UTF-8. | |||||
* This must be called with the length pre-determined by the first byte. | |||||
* If not calling this from ConvertUTF8to*, then the length can be set by: | |||||
* length = trailingBytesForUTF8[*source]+1; | |||||
* and the sequence is illegal right away if there aren't that many bytes | |||||
* available. | |||||
* If presented with a length > 4, this returns false. The Unicode | |||||
* definition of UTF-8 goes up to 4-byte sequences. | |||||
*/ | |||||
static bool isLegalUTF8(const UTF8 *source, int length) { | |||||
UTF8 a; | |||||
const UTF8 *srcptr = source+length; | |||||
switch (length) { | |||||
default: return false; | |||||
/* Everything else falls through when "true"... */ | |||||
case 4: { if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; } /* fall through */ | |||||
case 3: { if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; } /* fall through */ | |||||
case 2: { | |||||
if ((a = (*--srcptr)) > 0xBF) return false; | |||||
switch (*source) { | |||||
/* no fall-through in this inner switch */ | |||||
case 0xE0: if (a < 0xA0) return false; break; | |||||
case 0xED: if (a > 0x9F) return false; break; | |||||
case 0xF0: if (a < 0x90) return false; break; | |||||
case 0xF4: if (a > 0x8F) return false; break; | |||||
default: if (a < 0x80) return false; | |||||
} | |||||
} /* fall through */ | |||||
case 1: { if (*source >= 0x80 && *source < 0xC2) return false; } /* fall through */ | |||||
} | |||||
if (*source > 0xF4) return false; | |||||
return true; | |||||
} | |||||
/* --------------------------------------------------------------------- */ | |||||
ConversionResult ConvertUTF32toUTF8 ( | |||||
const UTF32** sourceStart, const UTF32* sourceEnd, | |||||
UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { | |||||
ConversionResult result = conversionOK; | |||||
const UTF32* source = *sourceStart; | |||||
UTF8* target = *targetStart; | |||||
while (source < sourceEnd) { | |||||
UTF32 ch; | |||||
unsigned short bytesToWrite = 0; | |||||
const UTF32 byteMask = 0xBF; | |||||
const UTF32 byteMark = 0x80; | |||||
ch = *source++; | |||||
if (flags == strictConversion ) { | |||||
/* UTF-16 surrogate values are illegal in UTF-32 */ | |||||
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { | |||||
--source; /* return to the illegal value itself */ | |||||
result = sourceIllegal; | |||||
break; | |||||
} | |||||
} | |||||
/* | |||||
* Figure out how many bytes the result will require. Turn any | |||||
* illegally large UTF32 things (> Plane 17) into replacement chars. | |||||
*/ | |||||
if (ch < (UTF32)0x80) { bytesToWrite = 1; | |||||
} else if (ch < (UTF32)0x800) { bytesToWrite = 2; | |||||
} else if (ch < (UTF32)0x10000) { bytesToWrite = 3; | |||||
} else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; | |||||
} else { bytesToWrite = 3; | |||||
ch = UNI_REPLACEMENT_CHAR; | |||||
result = sourceIllegal; | |||||
} | |||||
target += bytesToWrite; | |||||
if (target > targetEnd) { | |||||
--source; /* Back up source pointer! */ | |||||
target -= bytesToWrite; result = targetExhausted; break; | |||||
} | |||||
switch (bytesToWrite) { /* note: everything falls through. */ | |||||
case 4: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */ | |||||
case 3: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */ | |||||
case 2: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */ | |||||
case 1: { *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); } /* fall through */ | |||||
} | |||||
target += bytesToWrite; | |||||
} | |||||
*sourceStart = source; | |||||
*targetStart = target; | |||||
return result; | |||||
} | |||||
/* --------------------------------------------------------------------- */ | |||||
ConversionResult ConvertUTF8toUTF32 ( | |||||
const UTF8** sourceStart, const UTF8* sourceEnd, | |||||
UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { | |||||
ConversionResult result = conversionOK; | |||||
const UTF8* source = *sourceStart; | |||||
UTF32* target = *targetStart; | |||||
while (source < sourceEnd) { | |||||
UTF32 ch = 0; | |||||
unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; | |||||
if (source + extraBytesToRead >= sourceEnd) { | |||||
result = sourceExhausted; break; | |||||
} | |||||
/* Do this check whether lenient or strict */ | |||||
if (! isLegalUTF8(source, extraBytesToRead+1)) { | |||||
result = sourceIllegal; | |||||
break; | |||||
} | |||||
/* | |||||
* The cases all fall through. See "Note A" below. | |||||
*/ | |||||
switch (extraBytesToRead) { | |||||
case 5: { ch += *source++; ch <<= 6; } /* fall through */ | |||||
case 4: { ch += *source++; ch <<= 6; } /* fall through */ | |||||
case 3: { ch += *source++; ch <<= 6; } /* fall through */ | |||||
case 2: { ch += *source++; ch <<= 6; } /* fall through */ | |||||
case 1: { ch += *source++; ch <<= 6; } /* fall through */ | |||||
case 0: { ch += *source++; } /* fall through */ | |||||
} | |||||
ch -= offsetsFromUTF8[extraBytesToRead]; | |||||
if (target >= targetEnd) { | |||||
source -= (extraBytesToRead+1); /* Back up the source pointer! */ | |||||
result = targetExhausted; break; | |||||
} | |||||
if (ch <= UNI_MAX_LEGAL_UTF32) { | |||||
/* | |||||
* UTF-16 surrogate values are illegal in UTF-32, and anything | |||||
* over Plane 17 (> 0x10FFFF) is illegal. | |||||
*/ | |||||
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { | |||||
if (flags == strictConversion) { | |||||
source -= (extraBytesToRead+1); /* return to the illegal value itself */ | |||||
result = sourceIllegal; | |||||
break; | |||||
} else { | |||||
*target++ = UNI_REPLACEMENT_CHAR; | |||||
} | |||||
} else { | |||||
*target++ = ch; | |||||
} | |||||
} else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ | |||||
result = sourceIllegal; | |||||
*target++ = UNI_REPLACEMENT_CHAR; | |||||
} | |||||
} | |||||
*sourceStart = source; | |||||
*targetStart = target; | |||||
return result; | |||||
} | |||||
} | |||||
/* --------------------------------------------------------------------- | |||||
Note A. | |||||
The fall-through switches in UTF-8 reading code save a | |||||
temp variable, some decrements & conditionals. The switches | |||||
are equivalent to the following loop: | |||||
{ | |||||
int tmpBytesToRead = extraBytesToRead+1; | |||||
do { | |||||
ch += *source++; | |||||
--tmpBytesToRead; | |||||
if (tmpBytesToRead) ch <<= 6; | |||||
} while (tmpBytesToRead > 0); | |||||
} | |||||
In UTF-8 writing code, the switches on "bytesToWrite" are | |||||
similarly unrolled loops. | |||||
--------------------------------------------------------------------- */ |
/* | |||||
* Copyright 2001-2004 Unicode, Inc. | |||||
* | |||||
* Disclaimer | |||||
* | |||||
* This source code is provided as is by Unicode, Inc. No claims are | |||||
* made as to fitness for any particular purpose. No warranties of any | |||||
* kind are expressed or implied. The recipient agrees to determine | |||||
* applicability of information provided. If this file has been | |||||
* purchased on magnetic or optical media from Unicode, Inc., the | |||||
* sole remedy for any claim will be exchange of defective media | |||||
* within 90 days of receipt. | |||||
* | |||||
* Limitations on Rights to Redistribute This Code | |||||
* | |||||
* Unicode, Inc. hereby grants the right to freely use the information | |||||
* supplied in this file in the creation of products supporting the | |||||
* Unicode Standard, and to make copies of this file in any form | |||||
* for internal or external distribution as long as this notice | |||||
* remains attached. | |||||
*/ | |||||
/* --------------------------------------------------------------------- | |||||
Conversions between UTF32, UTF-16, and UTF-8. Header file. | |||||
Several funtions are included here, forming a complete set of | |||||
conversions between the three formats. UTF-7 is not included | |||||
here, but is handled in a separate source file. | |||||
Each of these routines takes pointers to input buffers and output | |||||
buffers. The input buffers are const. | |||||
Each routine converts the text between *sourceStart and sourceEnd, | |||||
putting the result into the buffer between *targetStart and | |||||
targetEnd. Note: the end pointers are *after* the last item: e.g. | |||||
*(sourceEnd - 1) is the last item. | |||||
The return result indicates whether the conversion was successful, | |||||
and if not, whether the problem was in the source or target buffers. | |||||
(Only the first encountered problem is indicated.) | |||||
After the conversion, *sourceStart and *targetStart are both | |||||
updated to point to the end of last text successfully converted in | |||||
the respective buffers. | |||||
Input parameters: | |||||
sourceStart - pointer to a pointer to the source buffer. | |||||
The contents of this are modified on return so that | |||||
it points at the next thing to be converted. | |||||
targetStart - similarly, pointer to pointer to the target buffer. | |||||
sourceEnd, targetEnd - respectively pointers to the ends of the | |||||
two buffers, for overflow checking only. | |||||
These conversion functions take a ConversionFlags argument. When this | |||||
flag is set to strict, both irregular sequences and isolated surrogates | |||||
will cause an error. When the flag is set to lenient, both irregular | |||||
sequences and isolated surrogates are converted. | |||||
Whether the flag is strict or lenient, all illegal sequences will cause | |||||
an error return. This includes sequences such as: <F4 90 80 80>, <C0 80>, | |||||
or <A0> in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code | |||||
must check for illegal sequences. | |||||
When the flag is set to lenient, characters over 0x10FFFF are converted | |||||
to the replacement character; otherwise (when the flag is set to strict) | |||||
they constitute an error. | |||||
Output parameters: | |||||
The value "sourceIllegal" is returned from some routines if the input | |||||
sequence is malformed. When "sourceIllegal" is returned, the source | |||||
value will point to the illegal value that caused the problem. E.g., | |||||
in UTF-8 when a sequence is malformed, it points to the start of the | |||||
malformed sequence. | |||||
Author: Mark E. Davis, 1994. | |||||
Rev History: Rick McGowan, fixes & updates May 2001. | |||||
Fixes & updates, Sept 2001. | |||||
------------------------------------------------------------------------ */ | |||||
/* --------------------------------------------------------------------- | |||||
The following 4 definitions are compiler-specific. | |||||
The C standard does not guarantee that wchar_t has at least | |||||
16 bits, so wchar_t is no less portable than unsigned short! | |||||
All should be unsigned values to avoid sign extension during | |||||
bit mask & shift operations. | |||||
------------------------------------------------------------------------ */ | |||||
#ifndef REPLXX_CONVERT_UTF8_H_INCLUDED | |||||
#define REPLXX_CONVERT_UTF8_H_INCLUDED 1 | |||||
#if 0 | |||||
typedef unsigned long UTF32; /* at least 32 bits */ | |||||
typedef unsigned short UTF16; /* at least 16 bits */ | |||||
typedef unsigned char UTF8; /* typically 8 bits */ | |||||
#endif | |||||
#include <stdint.h> | |||||
#include <string> | |||||
namespace replxx { | |||||
typedef uint32_t UTF32; | |||||
typedef uint16_t UTF16; | |||||
typedef uint8_t UTF8; | |||||
/* Some fundamental constants */ | |||||
#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD | |||||
#define UNI_MAX_BMP (UTF32)0x0000FFFF | |||||
#define UNI_MAX_UTF16 (UTF32)0x0010FFFF | |||||
#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF | |||||
#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF | |||||
typedef enum { | |||||
conversionOK, /* conversion successful */ | |||||
sourceExhausted, /* partial character in source, but hit end */ | |||||
targetExhausted, /* insuff. room in target for conversion */ | |||||
sourceIllegal /* source sequence is illegal/malformed */ | |||||
} ConversionResult; | |||||
typedef enum { | |||||
strictConversion = 0, | |||||
lenientConversion | |||||
} ConversionFlags; | |||||
ConversionResult ConvertUTF8toUTF32 ( | |||||
const UTF8** sourceStart, const UTF8* sourceEnd, | |||||
UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); | |||||
ConversionResult ConvertUTF32toUTF8 ( | |||||
const UTF32** sourceStart, const UTF32* sourceEnd, | |||||
UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); | |||||
} | |||||
#endif /* REPLXX_CONVERT_UTF8_H_INCLUDED */ | |||||
/* --------------------------------------------------------------------- */ |
#include <algorithm> | |||||
#include <string> | |||||
#include <cstring> | |||||
#include <cctype> | |||||
#include <locale.h> | |||||
#include "conversion.hxx" | |||||
#ifdef _WIN32 | |||||
#define strdup _strdup | |||||
#endif | |||||
using namespace std; | |||||
namespace replxx { | |||||
namespace locale { | |||||
void to_lower( std::string& s_ ) { | |||||
transform( s_.begin(), s_.end(), s_.begin(), static_cast<int(*)(int)>( &tolower ) ); | |||||
} | |||||
bool is_8bit_encoding( void ) { | |||||
bool is8BitEncoding( false ); | |||||
string origLC( setlocale( LC_CTYPE, nullptr ) ); | |||||
string lc( origLC ); | |||||
to_lower( lc ); | |||||
if ( lc == "c" ) { | |||||
setlocale( LC_CTYPE, "" ); | |||||
} | |||||
lc = setlocale( LC_CTYPE, nullptr ); | |||||
setlocale( LC_CTYPE, origLC.c_str() ); | |||||
to_lower( lc ); | |||||
if ( lc.find( "8859" ) != std::string::npos ) { | |||||
is8BitEncoding = true; | |||||
} | |||||
return ( is8BitEncoding ); | |||||
} | |||||
bool is8BitEncoding( is_8bit_encoding() ); | |||||
} | |||||
ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char* src) { | |||||
ConversionResult res = ConversionResult::conversionOK; | |||||
if ( ! locale::is8BitEncoding ) { | |||||
const UTF8* sourceStart = reinterpret_cast<const UTF8*>(src); | |||||
const UTF8* sourceEnd = sourceStart + strlen(src); | |||||
UTF32* targetStart = reinterpret_cast<UTF32*>(dst); | |||||
UTF32* targetEnd = targetStart + dstSize; | |||||
res = ConvertUTF8toUTF32( | |||||
&sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); | |||||
if (res == conversionOK) { | |||||
dstCount = targetStart - reinterpret_cast<UTF32*>(dst); | |||||
if (dstCount < dstSize) { | |||||
*targetStart = 0; | |||||
} | |||||
} | |||||
} else { | |||||
for ( dstCount = 0; ( dstCount < dstSize ) && src[dstCount]; ++ dstCount ) { | |||||
dst[dstCount] = src[dstCount]; | |||||
} | |||||
} | |||||
return res; | |||||
} | |||||
ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char8_t* src) { | |||||
return copyString8to32( | |||||
dst, dstSize, dstCount, reinterpret_cast<const char*>(src) | |||||
); | |||||
} | |||||
void copyString32to8( | |||||
char* dst, int dstSize, const char32_t* src, int srcSize, int* dstCount | |||||
) { | |||||
if ( ! locale::is8BitEncoding ) { | |||||
const UTF32* sourceStart = reinterpret_cast<const UTF32*>(src); | |||||
const UTF32* sourceEnd = sourceStart + srcSize; | |||||
UTF8* targetStart = reinterpret_cast<UTF8*>(dst); | |||||
UTF8* targetEnd = targetStart + dstSize; | |||||
ConversionResult res = ConvertUTF32toUTF8( | |||||
&sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); | |||||
if (res == conversionOK) { | |||||
int resCount( targetStart - reinterpret_cast<UTF8*>( dst ) ); | |||||
if ( resCount < dstSize ) { | |||||
*targetStart = 0; | |||||
} | |||||
if ( dstCount ) { | |||||
*dstCount = resCount; | |||||
} | |||||
} | |||||
} else { | |||||
int i( 0 ); | |||||
for ( i = 0; ( i < dstSize ) && ( i < srcSize ) && src[i]; ++ i ) { | |||||
dst[i] = static_cast<char>( src[i] ); | |||||
} | |||||
if ( dstCount ) { | |||||
*dstCount = i; | |||||
} | |||||
if ( i < dstSize ) { | |||||
dst[i] = 0; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
#ifndef REPLXX_CONVERSION_HXX_INCLUDED | |||||
#define REPLXX_CONVERSION_HXX_INCLUDED 1 | |||||
#include "ConvertUTF.h" | |||||
namespace replxx { | |||||
typedef unsigned char char8_t; | |||||
ConversionResult copyString8to32( char32_t* dst, int dstSize, int& dstCount, char const* src ); | |||||
ConversionResult copyString8to32( char32_t* dst, int dstSize, int& dstCount, char8_t const* src ); | |||||
void copyString32to8( char* dst, int dstSize, char32_t const* src, int srcSize, int* dstCount = nullptr ); | |||||
namespace locale { | |||||
extern bool is8BitEncoding; | |||||
} | |||||
} | |||||
#endif |
#include "escape.hxx" | |||||
#include "io.hxx" | |||||
#include "replxx.hxx" | |||||
#ifndef _WIN32 | |||||
namespace replxx { | |||||
namespace EscapeSequenceProcessing { // move these out of global namespace | |||||
// This chunk of code does parsing of the escape sequences sent by various Linux | |||||
// terminals. | |||||
// | |||||
// It handles arrow keys, Home, End and Delete keys by interpreting the | |||||
// sequences sent by | |||||
// gnome terminal, xterm, rxvt, konsole, aterm and yakuake including the Alt and | |||||
// Ctrl key | |||||
// combinations that are understood by replxx. | |||||
// | |||||
// The parsing uses tables, a bunch of intermediate dispatch routines and a | |||||
// doDispatch | |||||
// loop that reads the tables and sends control to "deeper" routines to continue | |||||
// the | |||||
// parsing. The starting call to doDispatch( c, initialDispatch ) will | |||||
// eventually return | |||||
// either a character (with optional CTRL and META bits set), or -1 if parsing | |||||
// fails, or | |||||
// zero if an attempt to read from the keyboard fails. | |||||
// | |||||
// This is rather sloppy escape sequence processing, since we're not paying | |||||
// attention to what the | |||||
// actual TERM is set to and are processing all key sequences for all terminals, | |||||
// but it works with | |||||
// the most common keystrokes on the most common terminals. It's intricate, but | |||||
// the nested 'if' | |||||
// statements required to do it directly would be worse. This way has the | |||||
// advantage of allowing | |||||
// changes and extensions without having to touch a lot of code. | |||||
static char32_t thisKeyMetaCtrl = 0; // holds pre-set Meta and/or Ctrl modifiers | |||||
// This dispatch routine is given a dispatch table and then farms work out to | |||||
// routines | |||||
// listed in the table based on the character it is called with. The dispatch | |||||
// routines can | |||||
// read more input characters to decide what should eventually be returned. | |||||
// Eventually, | |||||
// a called routine returns either a character or -1 to indicate parsing | |||||
// failure. | |||||
// | |||||
char32_t doDispatch(char32_t c, CharacterDispatch& dispatchTable) { | |||||
for (unsigned int i = 0; i < dispatchTable.len; ++i) { | |||||
if (static_cast<unsigned char>(dispatchTable.chars[i]) == c) { | |||||
return dispatchTable.dispatch[i](c); | |||||
} | |||||
} | |||||
return dispatchTable.dispatch[dispatchTable.len](c); | |||||
} | |||||
// Final dispatch routines -- return something | |||||
// | |||||
static char32_t normalKeyRoutine(char32_t c) { return thisKeyMetaCtrl | c; } | |||||
static char32_t upArrowKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::UP;; | |||||
} | |||||
static char32_t downArrowKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::DOWN; | |||||
} | |||||
static char32_t rightArrowKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::RIGHT; | |||||
} | |||||
static char32_t leftArrowKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::LEFT; | |||||
} | |||||
static char32_t homeKeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::HOME; } | |||||
static char32_t endKeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::END; } | |||||
static char32_t shiftTabRoutine(char32_t) { return Replxx::KEY::BASE_SHIFT | Replxx::KEY::TAB; } | |||||
static char32_t f1KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F1; } | |||||
static char32_t f2KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F2; } | |||||
static char32_t f3KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F3; } | |||||
static char32_t f4KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F4; } | |||||
static char32_t f5KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F5; } | |||||
static char32_t f6KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F6; } | |||||
static char32_t f7KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F7; } | |||||
static char32_t f8KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F8; } | |||||
static char32_t f9KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F9; } | |||||
static char32_t f10KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F10; } | |||||
static char32_t f11KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F11; } | |||||
static char32_t f12KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F12; } | |||||
static char32_t pageUpKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::PAGE_UP; | |||||
} | |||||
static char32_t pageDownKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::PAGE_DOWN; | |||||
} | |||||
static char32_t deleteCharRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::BACKSPACE; | |||||
} // key labeled Backspace | |||||
static char32_t insertKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::INSERT; | |||||
} // key labeled Delete | |||||
static char32_t deleteKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::DELETE; | |||||
} // key labeled Delete | |||||
static char32_t ctrlUpArrowKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::UP; | |||||
} | |||||
static char32_t ctrlDownArrowKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::DOWN; | |||||
} | |||||
static char32_t ctrlRightArrowKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::RIGHT; | |||||
} | |||||
static char32_t ctrlLeftArrowKeyRoutine(char32_t) { | |||||
return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::LEFT; | |||||
} | |||||
static char32_t escFailureRoutine(char32_t) { | |||||
beep(); | |||||
return -1; | |||||
} | |||||
// Handle ESC [ 1 ; 2 or 3 (or 5) <more stuff> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escLeftBracket1Semicolon2or3or5Routines[] = { | |||||
upArrowKeyRoutine, | |||||
downArrowKeyRoutine, | |||||
rightArrowKeyRoutine, | |||||
leftArrowKeyRoutine, | |||||
homeKeyRoutine, | |||||
endKeyRoutine, | |||||
f1KeyRoutine, | |||||
f2KeyRoutine, | |||||
f3KeyRoutine, | |||||
f4KeyRoutine, | |||||
escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket1Semicolon2or3or5Dispatch = { | |||||
10, "ABCDHFPQRS", escLeftBracket1Semicolon2or3or5Routines | |||||
}; | |||||
// Handle ESC [ 1 ; <more stuff> escape sequences | |||||
// | |||||
static char32_t escLeftBracket1Semicolon2Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; | |||||
return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch); | |||||
} | |||||
static char32_t escLeftBracket1Semicolon3Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_META; | |||||
return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch); | |||||
} | |||||
static char32_t escLeftBracket1Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket1SemicolonRoutines[] = { | |||||
escLeftBracket1Semicolon2Routine, | |||||
escLeftBracket1Semicolon3Routine, | |||||
escLeftBracket1Semicolon5Routine, | |||||
escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket1SemicolonDispatch = { | |||||
3, "235", escLeftBracket1SemicolonRoutines | |||||
}; | |||||
// Handle ESC [ 1 ; <more stuff> escape sequences | |||||
// | |||||
static char32_t escLeftBracket1SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket1SemicolonDispatch); | |||||
} | |||||
// (S)-F5 | |||||
static CharacterDispatchRoutine escLeftBracket15Semicolon2Routines[] = { | |||||
f5KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket15Semicolon2Dispatch = { | |||||
1, "~", escLeftBracket15Semicolon2Routines | |||||
}; | |||||
static char32_t escLeftBracket15Semicolon2Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; | |||||
return doDispatch(c, escLeftBracket15Semicolon2Dispatch); | |||||
} | |||||
// (C)-F5 | |||||
static CharacterDispatchRoutine escLeftBracket15Semicolon5Routines[] = { | |||||
f5KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket15Semicolon5Dispatch = { | |||||
1, "~", escLeftBracket15Semicolon5Routines | |||||
}; | |||||
static char32_t escLeftBracket15Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket15Semicolon5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket15SemicolonRoutines[] = { | |||||
escLeftBracket15Semicolon2Routine, escLeftBracket15Semicolon5Routine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket15SemicolonDispatch = { | |||||
2, "25", escLeftBracket15SemicolonRoutines | |||||
}; | |||||
static char32_t escLeftBracket15SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket15SemicolonDispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket15Routines[] = { | |||||
f5KeyRoutine, escLeftBracket15SemicolonRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket15Dispatch = { | |||||
2, "~;", escLeftBracket15Routines | |||||
}; | |||||
static char32_t escLeftBracket15Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket15Dispatch); | |||||
} | |||||
// (S)-F6 | |||||
static CharacterDispatchRoutine escLeftBracket17Semicolon2Routines[] = { | |||||
f6KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket17Semicolon2Dispatch = { | |||||
1, "~", escLeftBracket17Semicolon2Routines | |||||
}; | |||||
static char32_t escLeftBracket17Semicolon2Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; | |||||
return doDispatch(c, escLeftBracket17Semicolon2Dispatch); | |||||
} | |||||
// (C)-F6 | |||||
static CharacterDispatchRoutine escLeftBracket17Semicolon5Routines[] = { | |||||
f6KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket17Semicolon5Dispatch = { | |||||
1, "~", escLeftBracket17Semicolon5Routines | |||||
}; | |||||
static char32_t escLeftBracket17Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket17Semicolon5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket17SemicolonRoutines[] = { | |||||
escLeftBracket17Semicolon2Routine, escLeftBracket17Semicolon5Routine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket17SemicolonDispatch = { | |||||
2, "25", escLeftBracket17SemicolonRoutines | |||||
}; | |||||
static char32_t escLeftBracket17SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket17SemicolonDispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket17Routines[] = { | |||||
f6KeyRoutine, escLeftBracket17SemicolonRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket17Dispatch = { | |||||
2, "~;", escLeftBracket17Routines | |||||
}; | |||||
static char32_t escLeftBracket17Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket17Dispatch); | |||||
} | |||||
// (S)-F7 | |||||
static CharacterDispatchRoutine escLeftBracket18Semicolon2Routines[] = { | |||||
f7KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket18Semicolon2Dispatch = { | |||||
1, "~", escLeftBracket18Semicolon2Routines | |||||
}; | |||||
static char32_t escLeftBracket18Semicolon2Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; | |||||
return doDispatch(c, escLeftBracket18Semicolon2Dispatch); | |||||
} | |||||
// (C)-F7 | |||||
static CharacterDispatchRoutine escLeftBracket18Semicolon5Routines[] = { | |||||
f7KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket18Semicolon5Dispatch = { | |||||
1, "~", escLeftBracket18Semicolon5Routines | |||||
}; | |||||
static char32_t escLeftBracket18Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket18Semicolon5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket18SemicolonRoutines[] = { | |||||
escLeftBracket18Semicolon2Routine, escLeftBracket18Semicolon5Routine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket18SemicolonDispatch = { | |||||
2, "25", escLeftBracket18SemicolonRoutines | |||||
}; | |||||
static char32_t escLeftBracket18SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket18SemicolonDispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket18Routines[] = { | |||||
f7KeyRoutine, escLeftBracket18SemicolonRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket18Dispatch = { | |||||
2, "~;", escLeftBracket18Routines | |||||
}; | |||||
static char32_t escLeftBracket18Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket18Dispatch); | |||||
} | |||||
// (S)-F8 | |||||
static CharacterDispatchRoutine escLeftBracket19Semicolon2Routines[] = { | |||||
f8KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket19Semicolon2Dispatch = { | |||||
1, "~", escLeftBracket19Semicolon2Routines | |||||
}; | |||||
static char32_t escLeftBracket19Semicolon2Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; | |||||
return doDispatch(c, escLeftBracket19Semicolon2Dispatch); | |||||
} | |||||
// (C)-F8 | |||||
static CharacterDispatchRoutine escLeftBracket19Semicolon5Routines[] = { | |||||
f8KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket19Semicolon5Dispatch = { | |||||
1, "~", escLeftBracket19Semicolon5Routines | |||||
}; | |||||
static char32_t escLeftBracket19Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket19Semicolon5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket19SemicolonRoutines[] = { | |||||
escLeftBracket19Semicolon2Routine, escLeftBracket19Semicolon5Routine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket19SemicolonDispatch = { | |||||
2, "25", escLeftBracket19SemicolonRoutines | |||||
}; | |||||
static char32_t escLeftBracket19SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket19SemicolonDispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket19Routines[] = { | |||||
f8KeyRoutine, escLeftBracket19SemicolonRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket19Dispatch = { | |||||
2, "~;", escLeftBracket19Routines | |||||
}; | |||||
static char32_t escLeftBracket19Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket19Dispatch); | |||||
} | |||||
// Handle ESC [ 1 <more stuff> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escLeftBracket1Routines[] = { | |||||
homeKeyRoutine, escLeftBracket1SemicolonRoutine, | |||||
escLeftBracket15Routine, | |||||
escLeftBracket17Routine, | |||||
escLeftBracket18Routine, | |||||
escLeftBracket19Routine, | |||||
escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket1Dispatch = { | |||||
6, "~;5789", escLeftBracket1Routines | |||||
}; | |||||
// Handle ESC [ 2 <more stuff> escape sequences | |||||
// | |||||
// (S)-F9 | |||||
static CharacterDispatchRoutine escLeftBracket20Semicolon2Routines[] = { | |||||
f9KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket20Semicolon2Dispatch = { | |||||
1, "~", escLeftBracket20Semicolon2Routines | |||||
}; | |||||
static char32_t escLeftBracket20Semicolon2Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; | |||||
return doDispatch(c, escLeftBracket20Semicolon2Dispatch); | |||||
} | |||||
// (C)-F9 | |||||
static CharacterDispatchRoutine escLeftBracket20Semicolon5Routines[] = { | |||||
f9KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket20Semicolon5Dispatch = { | |||||
1, "~", escLeftBracket20Semicolon5Routines | |||||
}; | |||||
static char32_t escLeftBracket20Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket20Semicolon5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket20SemicolonRoutines[] = { | |||||
escLeftBracket20Semicolon2Routine, escLeftBracket20Semicolon5Routine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket20SemicolonDispatch = { | |||||
2, "25", escLeftBracket20SemicolonRoutines | |||||
}; | |||||
static char32_t escLeftBracket20SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket20SemicolonDispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket20Routines[] = { | |||||
f9KeyRoutine, escLeftBracket20SemicolonRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket20Dispatch = { | |||||
2, "~;", escLeftBracket20Routines | |||||
}; | |||||
static char32_t escLeftBracket20Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket20Dispatch); | |||||
} | |||||
// (S)-F10 | |||||
static CharacterDispatchRoutine escLeftBracket21Semicolon2Routines[] = { | |||||
f10KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket21Semicolon2Dispatch = { | |||||
1, "~", escLeftBracket21Semicolon2Routines | |||||
}; | |||||
static char32_t escLeftBracket21Semicolon2Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; | |||||
return doDispatch(c, escLeftBracket21Semicolon2Dispatch); | |||||
} | |||||
// (C)-F10 | |||||
static CharacterDispatchRoutine escLeftBracket21Semicolon5Routines[] = { | |||||
f10KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket21Semicolon5Dispatch = { | |||||
1, "~", escLeftBracket21Semicolon5Routines | |||||
}; | |||||
static char32_t escLeftBracket21Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket21Semicolon5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket21SemicolonRoutines[] = { | |||||
escLeftBracket21Semicolon2Routine, escLeftBracket21Semicolon5Routine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket21SemicolonDispatch = { | |||||
2, "25", escLeftBracket21SemicolonRoutines | |||||
}; | |||||
static char32_t escLeftBracket21SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket21SemicolonDispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket21Routines[] = { | |||||
f10KeyRoutine, escLeftBracket21SemicolonRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket21Dispatch = { | |||||
2, "~;", escLeftBracket21Routines | |||||
}; | |||||
static char32_t escLeftBracket21Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket21Dispatch); | |||||
} | |||||
// (S)-F11 | |||||
static CharacterDispatchRoutine escLeftBracket23Semicolon2Routines[] = { | |||||
f11KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket23Semicolon2Dispatch = { | |||||
1, "~", escLeftBracket23Semicolon2Routines | |||||
}; | |||||
static char32_t escLeftBracket23Semicolon2Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; | |||||
return doDispatch(c, escLeftBracket23Semicolon2Dispatch); | |||||
} | |||||
// (C)-F11 | |||||
static CharacterDispatchRoutine escLeftBracket23Semicolon5Routines[] = { | |||||
f11KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket23Semicolon5Dispatch = { | |||||
1, "~", escLeftBracket23Semicolon5Routines | |||||
}; | |||||
static char32_t escLeftBracket23Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket23Semicolon5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket23SemicolonRoutines[] = { | |||||
escLeftBracket23Semicolon2Routine, escLeftBracket23Semicolon5Routine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket23SemicolonDispatch = { | |||||
2, "25", escLeftBracket23SemicolonRoutines | |||||
}; | |||||
static char32_t escLeftBracket23SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket23SemicolonDispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket23Routines[] = { | |||||
f11KeyRoutine, escLeftBracket23SemicolonRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket23Dispatch = { | |||||
2, "~;", escLeftBracket23Routines | |||||
}; | |||||
static char32_t escLeftBracket23Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket23Dispatch); | |||||
} | |||||
// (S)-F12 | |||||
static CharacterDispatchRoutine escLeftBracket24Semicolon2Routines[] = { | |||||
f12KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket24Semicolon2Dispatch = { | |||||
1, "~", escLeftBracket24Semicolon2Routines | |||||
}; | |||||
static char32_t escLeftBracket24Semicolon2Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; | |||||
return doDispatch(c, escLeftBracket24Semicolon2Dispatch); | |||||
} | |||||
// (C)-F12 | |||||
static CharacterDispatchRoutine escLeftBracket24Semicolon5Routines[] = { | |||||
f12KeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket24Semicolon5Dispatch = { | |||||
1, "~", escLeftBracket24Semicolon5Routines | |||||
}; | |||||
static char32_t escLeftBracket24Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket24Semicolon5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket24SemicolonRoutines[] = { | |||||
escLeftBracket24Semicolon2Routine, escLeftBracket24Semicolon5Routine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket24SemicolonDispatch = { | |||||
2, "25", escLeftBracket24SemicolonRoutines | |||||
}; | |||||
static char32_t escLeftBracket24SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket24SemicolonDispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket24Routines[] = { | |||||
f12KeyRoutine, escLeftBracket24SemicolonRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket24Dispatch = { | |||||
2, "~;", escLeftBracket24Routines | |||||
}; | |||||
static char32_t escLeftBracket24Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket24Dispatch); | |||||
} | |||||
// Handle ESC [ 2 <more stuff> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escLeftBracket2Routines[] = { | |||||
insertKeyRoutine, | |||||
escLeftBracket20Routine, | |||||
escLeftBracket21Routine, | |||||
escLeftBracket23Routine, | |||||
escLeftBracket24Routine, | |||||
escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket2Dispatch = { | |||||
5, "~0134", escLeftBracket2Routines | |||||
}; | |||||
// Handle ESC [ 3 <more stuff> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escLeftBracket3Routines[] = { | |||||
deleteKeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket3Dispatch = { | |||||
1, "~", escLeftBracket3Routines | |||||
}; | |||||
// Handle ESC [ 4 <more stuff> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escLeftBracket4Routines[] = { | |||||
endKeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket4Dispatch = { | |||||
1, "~", escLeftBracket4Routines | |||||
}; | |||||
// Handle ESC [ 5 <more stuff> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escLeftBracket5Semicolon5Routines[] = { | |||||
pageUpKeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket5Semicolon5Dispatch = { | |||||
1, "~", escLeftBracket5Semicolon5Routines | |||||
}; | |||||
static char32_t escLeftBracket5Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket5Semicolon5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket5SemicolonRoutines[] = { | |||||
escLeftBracket5Semicolon5Routine, | |||||
escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket5SemicolonDispatch = { | |||||
1, "5", escLeftBracket5SemicolonRoutines | |||||
}; | |||||
static char32_t escLeftBracket5SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket5SemicolonDispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket5Routines[] = { | |||||
pageUpKeyRoutine, escLeftBracket5SemicolonRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket5Dispatch = { | |||||
2, "~;", escLeftBracket5Routines | |||||
}; | |||||
// Handle ESC [ 6 <more stuff> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escLeftBracket6Semicolon5Routines[] = { | |||||
pageDownKeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket6Semicolon5Dispatch = { | |||||
1, "~", escLeftBracket6Semicolon5Routines | |||||
}; | |||||
static char32_t escLeftBracket6Semicolon5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; | |||||
return doDispatch(c, escLeftBracket6Semicolon5Dispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket6SemicolonRoutines[] = { | |||||
escLeftBracket6Semicolon5Routine, | |||||
escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket6SemicolonDispatch = { | |||||
1, "5", escLeftBracket6SemicolonRoutines | |||||
}; | |||||
static char32_t escLeftBracket6SemicolonRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket6SemicolonDispatch); | |||||
} | |||||
static CharacterDispatchRoutine escLeftBracket6Routines[] = { | |||||
pageDownKeyRoutine, escLeftBracket6SemicolonRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket6Dispatch = { | |||||
2, "~;", escLeftBracket6Routines | |||||
}; | |||||
// Handle ESC [ 7 <more stuff> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escLeftBracket7Routines[] = { | |||||
homeKeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket7Dispatch = { | |||||
1, "~", escLeftBracket7Routines | |||||
}; | |||||
// Handle ESC [ 8 <more stuff> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escLeftBracket8Routines[] = { | |||||
endKeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracket8Dispatch = { | |||||
1, "~", escLeftBracket8Routines | |||||
}; | |||||
// Handle ESC [ <digit> escape sequences | |||||
// | |||||
static char32_t escLeftBracket0Routine(char32_t c) { | |||||
return escFailureRoutine(c); | |||||
} | |||||
static char32_t escLeftBracket1Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket1Dispatch); | |||||
} | |||||
static char32_t escLeftBracket2Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket2Dispatch); | |||||
} | |||||
static char32_t escLeftBracket3Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket3Dispatch); | |||||
} | |||||
static char32_t escLeftBracket4Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket4Dispatch); | |||||
} | |||||
static char32_t escLeftBracket5Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket5Dispatch); | |||||
} | |||||
static char32_t escLeftBracket6Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket6Dispatch); | |||||
} | |||||
static char32_t escLeftBracket7Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket7Dispatch); | |||||
} | |||||
static char32_t escLeftBracket8Routine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracket8Dispatch); | |||||
} | |||||
static char32_t escLeftBracket9Routine(char32_t c) { | |||||
return escFailureRoutine(c); | |||||
} | |||||
// Handle ESC [ <more stuff> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escLeftBracketRoutines[] = { | |||||
upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine, | |||||
leftArrowKeyRoutine, homeKeyRoutine, endKeyRoutine, | |||||
shiftTabRoutine, | |||||
escLeftBracket0Routine, escLeftBracket1Routine, escLeftBracket2Routine, | |||||
escLeftBracket3Routine, escLeftBracket4Routine, escLeftBracket5Routine, | |||||
escLeftBracket6Routine, escLeftBracket7Routine, escLeftBracket8Routine, | |||||
escLeftBracket9Routine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escLeftBracketDispatch = {17, "ABCDHFZ0123456789", | |||||
escLeftBracketRoutines}; | |||||
// Handle ESC O <char> escape sequences | |||||
// | |||||
static CharacterDispatchRoutine escORoutines[] = { | |||||
upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine, | |||||
leftArrowKeyRoutine, homeKeyRoutine, endKeyRoutine, | |||||
f1KeyRoutine, f2KeyRoutine, f3KeyRoutine, | |||||
f4KeyRoutine, | |||||
ctrlUpArrowKeyRoutine, ctrlDownArrowKeyRoutine, ctrlRightArrowKeyRoutine, | |||||
ctrlLeftArrowKeyRoutine, escFailureRoutine | |||||
}; | |||||
static CharacterDispatch escODispatch = {14, "ABCDHFPQRSabcd", escORoutines}; | |||||
// Initial ESC dispatch -- could be a Meta prefix or the start of an escape | |||||
// sequence | |||||
// | |||||
static char32_t escLeftBracketRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escLeftBracketDispatch); | |||||
} | |||||
static char32_t escORoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escODispatch); | |||||
} | |||||
static char32_t setMetaRoutine(char32_t c); // need forward reference | |||||
static CharacterDispatchRoutine escRoutines[] = { | |||||
escLeftBracketRoutine, escORoutine, setMetaRoutine | |||||
}; | |||||
static CharacterDispatch escDispatch = {2, "[O", escRoutines}; | |||||
// Initial dispatch -- we are not in the middle of anything yet | |||||
// | |||||
static char32_t escRoutine(char32_t c) { | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escDispatch); | |||||
} | |||||
static CharacterDispatchRoutine initialRoutines[] = { | |||||
escRoutine, deleteCharRoutine, normalKeyRoutine | |||||
}; | |||||
static CharacterDispatch initialDispatch = {2, "\x1B\x7F", initialRoutines}; | |||||
// Special handling for the ESC key because it does double duty | |||||
// | |||||
static char32_t setMetaRoutine(char32_t c) { | |||||
thisKeyMetaCtrl = Replxx::KEY::BASE_META; | |||||
if (c == 0x1B) { // another ESC, stay in ESC processing mode | |||||
c = read_unicode_character(); | |||||
if (c == 0) return 0; | |||||
return doDispatch(c, escDispatch); | |||||
} | |||||
return doDispatch(c, initialDispatch); | |||||
} | |||||
char32_t doDispatch(char32_t c) { | |||||
EscapeSequenceProcessing::thisKeyMetaCtrl = 0; // no modifiers yet at initialDispatch | |||||
return doDispatch(c, initialDispatch); | |||||
} | |||||
} // namespace EscapeSequenceProcessing // move these out of global namespace | |||||
} | |||||
#endif /* #ifndef _WIN32 */ | |||||
#ifndef REPLXX_ESCAPE_HXX_INCLUDED | |||||
#define REPLXX_ESCAPE_HXX_INCLUDED 1 | |||||
namespace replxx { | |||||
namespace EscapeSequenceProcessing { | |||||
// This is a typedef for the routine called by doDispatch(). It takes the | |||||
// current character | |||||
// as input, does any required processing including reading more characters and | |||||
// calling other | |||||
// dispatch routines, then eventually returns the final (possibly extended or | |||||
// special) character. | |||||
// | |||||
typedef char32_t (*CharacterDispatchRoutine)(char32_t); | |||||
// This structure is used by doDispatch() to hold a list of characters to test | |||||
// for and | |||||
// a list of routines to call if the character matches. The dispatch routine | |||||
// list is one | |||||
// longer than the character list; the final entry is used if no character | |||||
// matches. | |||||
// | |||||
struct CharacterDispatch { | |||||
unsigned int len; // length of the chars list | |||||
const char* chars; // chars to test | |||||
CharacterDispatchRoutine* dispatch; // array of routines to call | |||||
}; | |||||
char32_t doDispatch(char32_t c); | |||||
} | |||||
} | |||||
#endif | |||||
#include <fstream> | |||||
#include <cstring> | |||||
#ifndef _WIN32 | |||||
#include <unistd.h> | |||||
#include <sys/stat.h> | |||||
#endif /* _WIN32 */ | |||||
#include "history.hxx" | |||||
#include "utf8string.hxx" | |||||
using namespace std; | |||||
namespace replxx { | |||||
static int const REPLXX_DEFAULT_HISTORY_MAX_LEN( 1000 ); | |||||
History::History( void ) | |||||
: _data() | |||||
, _maxSize( REPLXX_DEFAULT_HISTORY_MAX_LEN ) | |||||
, _maxLineLength( 0 ) | |||||
, _index( 0 ) | |||||
, _previousIndex( -2 ) | |||||
, _recallMostRecent( false ) { | |||||
} | |||||
void History::add( UnicodeString const& line ) { | |||||
if ( ( _maxSize > 0 ) && ( _data.empty() || ( line != _data.back() ) ) ) { | |||||
if ( size() > _maxSize ) { | |||||
_data.erase( _data.begin() ); | |||||
if ( -- _previousIndex < -1 ) { | |||||
_previousIndex = -2; | |||||
} | |||||
} | |||||
if ( static_cast<int>( line.length() ) > _maxLineLength ) { | |||||
_maxLineLength = static_cast<int>( line.length() ); | |||||
} | |||||
_data.push_back( line ); | |||||
} | |||||
} | |||||
int History::save( std::string const& filename ) { | |||||
#ifndef _WIN32 | |||||
mode_t old_umask = umask( S_IXUSR | S_IRWXG| S_IRWXO ); | |||||
#endif | |||||
ofstream histFile( filename ); | |||||
if ( ! histFile ) { | |||||
return ( -1 ); | |||||
} | |||||
#ifndef _WIN32 | |||||
umask( old_umask ); | |||||
chmod( filename.c_str(), S_IRUSR | S_IWUSR ); | |||||
#endif | |||||
Utf8String utf8; | |||||
for ( UnicodeString const& h : _data ) { | |||||
if ( ! h.is_empty() ) { | |||||
utf8.assign( h ); | |||||
histFile << utf8.get() << endl; | |||||
} | |||||
} | |||||
return ( 0 ); | |||||
} | |||||
int History::load( std::string const& filename ) { | |||||
ifstream histFile( filename ); | |||||
if ( ! histFile ) { | |||||
return ( -1 ); | |||||
} | |||||
string line; | |||||
while ( getline( histFile, line ).good() ) { | |||||
string::size_type eol( line.find_first_of( "\r\n" ) ); | |||||
if ( eol != string::npos ) { | |||||
line.erase( eol ); | |||||
} | |||||
if ( ! line.empty() ) { | |||||
add( UnicodeString( line ) ); | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
void History::set_max_size( int size_ ) { | |||||
if ( size_ >= 0 ) { | |||||
_maxSize = size_; | |||||
int curSize( size() ); | |||||
if ( _maxSize < curSize ) { | |||||
_data.erase( _data.begin(), _data.begin() + ( curSize - _maxSize ) ); | |||||
} | |||||
} | |||||
} | |||||
void History::reset_pos( int pos_ ) { | |||||
if ( pos_ == -1 ) { | |||||
_index = size() - 1; | |||||
_recallMostRecent = false; | |||||
} else { | |||||
_index = pos_; | |||||
} | |||||
} | |||||
bool History::move( bool up_ ) { | |||||
if (_previousIndex != -2 && ! up_ ) { | |||||
_index = 1 + _previousIndex; // emulate Windows down-arrow | |||||
} else { | |||||
_index += up_ ? -1 : 1; | |||||
} | |||||
_previousIndex = -2; | |||||
if (_index < 0) { | |||||
_index = 0; | |||||
return ( false ); | |||||
} else if ( _index >= size() ) { | |||||
_index = size() - 1; | |||||
return ( false ); | |||||
} | |||||
_recallMostRecent = true; | |||||
return ( true ); | |||||
} | |||||
void History::jump( bool start_ ) { | |||||
_index = start_ ? 0 : size() - 1; | |||||
_previousIndex = -2; | |||||
_recallMostRecent = true; | |||||
} | |||||
bool History::common_prefix_search( UnicodeString const& prefix_, int prefixSize_, bool back_ ) { | |||||
int direct( size() + ( back_ ? -1 : 1 ) ); | |||||
int i( ( _index + direct ) % _data.size() ); | |||||
while ( i != _index ) { | |||||
if ( _data[i].starts_with( prefix_.begin(), prefix_.begin() + prefixSize_ ) ) { | |||||
_index = i; | |||||
_previousIndex = -2; | |||||
_recallMostRecent = true; | |||||
return ( true ); | |||||
} | |||||
i += direct; | |||||
i %= _data.size(); | |||||
} | |||||
return ( false ); | |||||
} | |||||
UnicodeString const& History::operator[] ( int idx_ ) const { | |||||
return ( _data[ idx_ ] ); | |||||
} | |||||
} | |||||
#ifndef REPLXX_HISTORY_HXX_INCLUDED | |||||
#define REPLXX_HISTORY_HXX_INCLUDED 1 | |||||
#include <vector> | |||||
#include "unicodestring.hxx" | |||||
#include "conversion.hxx" | |||||
namespace replxx { | |||||
class History { | |||||
public: | |||||
typedef std::vector<UnicodeString> lines_t; | |||||
private: | |||||
lines_t _data; | |||||
int _maxSize; | |||||
int _maxLineLength; | |||||
int _index; | |||||
int _previousIndex; | |||||
bool _recallMostRecent; | |||||
public: | |||||
History( void ); | |||||
void add( UnicodeString const& line ); | |||||
int save( std::string const& filename ); | |||||
int load( std::string const& filename ); | |||||
void set_max_size( int len ); | |||||
void reset_pos( int = -1 ); | |||||
UnicodeString const& operator[] ( int ) const; | |||||
void set_recall_most_recent( void ) { | |||||
_recallMostRecent = true; | |||||
} | |||||
void reset_recall_most_recent( void ) { | |||||
_recallMostRecent = false; | |||||
} | |||||
void drop_last( void ) { | |||||
_data.pop_back(); | |||||
} | |||||
void commit_index( void ) { | |||||
_previousIndex = _recallMostRecent ? _index : -2; | |||||
} | |||||
int current_pos( void ) const { | |||||
return ( _index ); | |||||
} | |||||
bool is_last( void ) const { | |||||
return ( _index == ( size() - 1 ) ); | |||||
} | |||||
bool is_empty( void ) const { | |||||
return ( _data.empty() ); | |||||
} | |||||
void update_last( UnicodeString const& line_ ) { | |||||
_data.back() = line_; | |||||
} | |||||
bool move( bool ); | |||||
UnicodeString const& current( void ) const { | |||||
return ( _data[_index] ); | |||||
} | |||||
void jump( bool ); | |||||
bool common_prefix_search( UnicodeString const&, int, bool ); | |||||
int size( void ) const { | |||||
return ( static_cast<int>( _data.size() ) ); | |||||
} | |||||
int max_line_length( void ) { | |||||
return ( _maxLineLength ); | |||||
} | |||||
private: | |||||
History( History const& ) = delete; | |||||
History& operator = ( History const& ) = delete; | |||||
}; | |||||
} | |||||
#endif | |||||
#include <memory> | |||||
#include <cerrno> | |||||
#include <cstdlib> | |||||
#include <cstring> | |||||
#include <array> | |||||
#ifdef _WIN32 | |||||
#include <conio.h> | |||||
#include <windows.h> | |||||
#include <io.h> | |||||
#define isatty _isatty | |||||
#define strcasecmp _stricmp | |||||
#define strdup _strdup | |||||
#define write _write | |||||
#define STDIN_FILENO 0 | |||||
#include "windows.hxx" | |||||
#else /* _WIN32 */ | |||||
#include <unistd.h> | |||||
#include <sys/ioctl.h> | |||||
#include <sys/select.h> | |||||
#include <fcntl.h> | |||||
#endif /* _WIN32 */ | |||||
#include "io.hxx" | |||||
#include "conversion.hxx" | |||||
#include "escape.hxx" | |||||
#include "replxx.hxx" | |||||
#include "util.hxx" | |||||
using namespace std; | |||||
namespace replxx { | |||||
namespace tty { | |||||
bool is_a_tty( int fd_ ) { | |||||
bool aTTY( isatty( fd_ ) != 0 ); | |||||
#ifdef _WIN32 | |||||
do { | |||||
if ( aTTY ) { | |||||
break; | |||||
} | |||||
HANDLE h( (HANDLE)_get_osfhandle( fd_ ) ); | |||||
if ( h == INVALID_HANDLE_VALUE ) { | |||||
break; | |||||
} | |||||
DWORD st( 0 ); | |||||
if ( ! GetConsoleMode( h, &st ) ) { | |||||
break; | |||||
} | |||||
aTTY = true; | |||||
} while ( false ); | |||||
#endif | |||||
return ( aTTY ); | |||||
} | |||||
bool in( is_a_tty( 0 ) ); | |||||
bool out( is_a_tty( 1 ) ); | |||||
} | |||||
Terminal::Terminal( void ) | |||||
#ifdef _WIN32 | |||||
: _consoleOut( INVALID_HANDLE_VALUE ) | |||||
, _consoleIn( INVALID_HANDLE_VALUE ) | |||||
, _oldMode() | |||||
, _oldDisplayAttribute() | |||||
, _inputCodePage( GetConsoleCP() ) | |||||
, _outputCodePage( GetConsoleOutputCP() ) | |||||
, _interrupt( INVALID_HANDLE_VALUE ) | |||||
, _events() | |||||
#else | |||||
: _origTermios() | |||||
, _interrupt() | |||||
#endif | |||||
, _rawMode( false ) { | |||||
#ifdef _WIN32 | |||||
_interrupt = CreateEvent( nullptr, true, false, TEXT( "replxx_interrupt_event" ) ); | |||||
#else | |||||
static_cast<void>( ::pipe( _interrupt ) == 0 ); | |||||
#endif | |||||
} | |||||
Terminal::~Terminal( void ) { | |||||
if ( _rawMode ) { | |||||
disable_raw_mode(); | |||||
} | |||||
#ifdef _WIN32 | |||||
CloseHandle( _interrupt ); | |||||
#else | |||||
static_cast<void>( ::close( _interrupt[0] ) == 0 ); | |||||
static_cast<void>( ::close( _interrupt[1] ) == 0 ); | |||||
#endif | |||||
} | |||||
void Terminal::write32( char32_t const* text32, int len32 ) { | |||||
int len8 = 4 * len32 + 1; | |||||
unique_ptr<char[]> text8(new char[len8]); | |||||
int count8 = 0; | |||||
copyString32to8(text8.get(), len8, text32, len32, &count8); | |||||
int nWritten( 0 ); | |||||
#ifdef _WIN32 | |||||
nWritten = win_write( text8.get(), count8 ); | |||||
#else | |||||
nWritten = write( 1, text8.get(), count8 ); | |||||
#endif | |||||
if ( nWritten != count8 ) { | |||||
throw std::runtime_error( "write failed" ); | |||||
} | |||||
return; | |||||
} | |||||
void Terminal::write8( char const* data_, int size_ ) { | |||||
#ifdef _WIN32 | |||||
int nWritten( win_write( data_, size_ ) ); | |||||
#else | |||||
int nWritten( write( 1, data_, size_ ) ); | |||||
#endif | |||||
if ( nWritten != size_ ) { | |||||
throw std::runtime_error( "write failed" ); | |||||
} | |||||
return; | |||||
} | |||||
int Terminal::get_screen_columns( void ) { | |||||
int cols( 0 ); | |||||
#ifdef _WIN32 | |||||
CONSOLE_SCREEN_BUFFER_INFO inf; | |||||
GetConsoleScreenBufferInfo( _consoleOut, &inf ); | |||||
cols = inf.dwSize.X; | |||||
#else | |||||
struct winsize ws; | |||||
cols = ( ioctl( 1, TIOCGWINSZ, &ws ) == -1 ) ? 80 : ws.ws_col; | |||||
#endif | |||||
// cols is 0 in certain circumstances like inside debugger, which creates | |||||
// further issues | |||||
return ( cols > 0 ) ? cols : 80; | |||||
} | |||||
int Terminal::get_screen_rows( void ) { | |||||
int rows; | |||||
#ifdef _WIN32 | |||||
CONSOLE_SCREEN_BUFFER_INFO inf; | |||||
GetConsoleScreenBufferInfo( _consoleOut, &inf ); | |||||
rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top; | |||||
#else | |||||
struct winsize ws; | |||||
rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row; | |||||
#endif | |||||
return (rows > 0) ? rows : 24; | |||||
} | |||||
namespace { | |||||
inline int notty( void ) { | |||||
errno = ENOTTY; | |||||
return ( -1 ); | |||||
} | |||||
} | |||||
int Terminal::enable_raw_mode( void ) { | |||||
if ( ! _rawMode ) { | |||||
#ifdef _WIN32 | |||||
_consoleIn = GetStdHandle( STD_INPUT_HANDLE ); | |||||
_consoleOut = GetStdHandle( STD_OUTPUT_HANDLE ); | |||||
SetConsoleCP( 65001 ); | |||||
SetConsoleOutputCP( 65001 ); | |||||
GetConsoleMode( _consoleIn, &_oldMode ); | |||||
SetConsoleMode( | |||||
_consoleIn, | |||||
_oldMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT ) | |||||
); | |||||
#else | |||||
struct termios raw; | |||||
if ( ! tty::in ) { | |||||
return ( notty() ); | |||||
} | |||||
if ( tcgetattr( 0, &_origTermios ) == -1 ) { | |||||
return ( notty() ); | |||||
} | |||||
raw = _origTermios; /* modify the original mode */ | |||||
/* input modes: no break, no CR to NL, no parity check, no strip char, | |||||
* no start/stop output control. */ | |||||
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); | |||||
/* output modes - disable post processing */ | |||||
// this is wrong, we don't want raw output, it turns newlines into straight | |||||
// linefeeds | |||||
// raw.c_oflag &= ~(OPOST); | |||||
/* control modes - set 8 bit chars */ | |||||
raw.c_cflag |= (CS8); | |||||
/* local modes - echoing off, canonical off, no extended functions, | |||||
* no signal chars (^Z,^C) */ | |||||
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); | |||||
/* control chars - set return condition: min number of bytes and timer. | |||||
* We want read to return every single byte, without timeout. */ | |||||
raw.c_cc[VMIN] = 1; | |||||
raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ | |||||
/* put terminal in raw mode after flushing */ | |||||
if ( tcsetattr(0, TCSADRAIN, &raw) < 0 ) { | |||||
return ( notty() ); | |||||
} | |||||
#endif | |||||
_rawMode = true; | |||||
} | |||||
return 0; | |||||
} | |||||
void Terminal::disable_raw_mode(void) { | |||||
if ( _rawMode ) { | |||||
#ifdef _WIN32 | |||||
SetConsoleMode( _consoleIn, _oldMode ); | |||||
SetConsoleCP( _inputCodePage ); | |||||
SetConsoleOutputCP( _outputCodePage ); | |||||
_consoleIn = INVALID_HANDLE_VALUE; | |||||
_consoleOut = INVALID_HANDLE_VALUE; | |||||
#else | |||||
if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) { | |||||
return; | |||||
} | |||||
#endif | |||||
_rawMode = false; | |||||
} | |||||
} | |||||
#ifndef _WIN32 | |||||
/** | |||||
* Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode | |||||
* (char32_t) character it encodes | |||||
* | |||||
* @return char32_t Unicode character | |||||
*/ | |||||
char32_t read_unicode_character(void) { | |||||
static char8_t utf8String[5]; | |||||
static size_t utf8Count = 0; | |||||
while (true) { | |||||
char8_t c; | |||||
/* Continue reading if interrupted by signal. */ | |||||
ssize_t nread; | |||||
do { | |||||
nread = read( STDIN_FILENO, &c, 1 ); | |||||
} while ((nread == -1) && (errno == EINTR)); | |||||
if (nread <= 0) return 0; | |||||
if (c <= 0x7F || locale::is8BitEncoding) { // short circuit ASCII | |||||
utf8Count = 0; | |||||
return c; | |||||
} else if (utf8Count < sizeof(utf8String) - 1) { | |||||
utf8String[utf8Count++] = c; | |||||
utf8String[utf8Count] = 0; | |||||
char32_t unicodeChar[2]; | |||||
int ucharCount( 0 ); | |||||
ConversionResult res = copyString8to32(unicodeChar, 2, ucharCount, utf8String); | |||||
if (res == conversionOK && ucharCount) { | |||||
utf8Count = 0; | |||||
return unicodeChar[0]; | |||||
} | |||||
} else { | |||||
utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character | |||||
} | |||||
} | |||||
} | |||||
#endif // #ifndef _WIN32 | |||||
void beep() { | |||||
fprintf(stderr, "\x7"); // ctrl-G == bell/beep | |||||
fflush(stderr); | |||||
} | |||||
// replxx_read_char -- read a keystroke or keychord from the keyboard, and translate it | |||||
// into an encoded "keystroke". When convenient, extended keys are translated into their | |||||
// simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B. | |||||
// | |||||
// A return value of zero means "no input available", and a return value of -1 | |||||
// means "invalid key". | |||||
// | |||||
char32_t Terminal::read_char( void ) { | |||||
char32_t c( 0 ); | |||||
#ifdef _WIN32 | |||||
INPUT_RECORD rec; | |||||
DWORD count; | |||||
char32_t modifierKeys = 0; | |||||
bool escSeen = false; | |||||
int highSurrogate( 0 ); | |||||
while (true) { | |||||
ReadConsoleInputW( _consoleIn, &rec, 1, &count ); | |||||
#if __REPLXX_DEBUG__ // helper for debugging keystrokes, display info in the debug "Output" | |||||
// window in the debugger | |||||
{ | |||||
if ( rec.EventType == KEY_EVENT ) { | |||||
//if ( rec.Event.KeyEvent.uChar.UnicodeChar ) { | |||||
char buf[1024]; | |||||
sprintf( | |||||
buf, | |||||
"Unicode character 0x%04X, repeat count %d, virtual keycode 0x%04X, " | |||||
"virtual scancode 0x%04X, key %s%s%s%s%s\n", | |||||
rec.Event.KeyEvent.uChar.UnicodeChar, | |||||
rec.Event.KeyEvent.wRepeatCount, | |||||
rec.Event.KeyEvent.wVirtualKeyCode, | |||||
rec.Event.KeyEvent.wVirtualScanCode, | |||||
rec.Event.KeyEvent.bKeyDown ? "down" : "up", | |||||
(rec.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) ? " L-Ctrl" : "", | |||||
(rec.Event.KeyEvent.dwControlKeyState & RIGHT_CTRL_PRESSED) ? " R-Ctrl" : "", | |||||
(rec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) ? " L-Alt" : "", | |||||
(rec.Event.KeyEvent.dwControlKeyState & RIGHT_ALT_PRESSED) ? " R-Alt" : "" | |||||
); | |||||
OutputDebugStringA( buf ); | |||||
//} | |||||
} | |||||
} | |||||
#endif | |||||
if (rec.EventType != KEY_EVENT) { | |||||
continue; | |||||
} | |||||
// Windows provides for entry of characters that are not on your keyboard by | |||||
// sending the | |||||
// Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == | |||||
// Alt key) ... | |||||
// accept these characters, otherwise only process characters on "key down" | |||||
if (!rec.Event.KeyEvent.bKeyDown && | |||||
rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU) { | |||||
continue; | |||||
} | |||||
modifierKeys = 0; | |||||
// AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't | |||||
// treat this | |||||
// combination as either CTRL or META we just turn off those two bits, so it | |||||
// is still | |||||
// possible to combine CTRL and/or META with an AltGr key by using | |||||
// right-Ctrl and/or | |||||
// left-Alt | |||||
if ((rec.Event.KeyEvent.dwControlKeyState & | |||||
(LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) == | |||||
(LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) { | |||||
rec.Event.KeyEvent.dwControlKeyState &= | |||||
~(LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); | |||||
} | |||||
if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED ) ) { | |||||
modifierKeys |= Replxx::KEY::BASE_CONTROL; | |||||
} | |||||
if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) { | |||||
modifierKeys |= Replxx::KEY::BASE_META; | |||||
} | |||||
if (escSeen) { | |||||
modifierKeys |= Replxx::KEY::BASE_META; | |||||
} | |||||
int key( rec.Event.KeyEvent.uChar.UnicodeChar ); | |||||
if ( key == 0 ) { | |||||
switch (rec.Event.KeyEvent.wVirtualKeyCode) { | |||||
case VK_LEFT: | |||||
return modifierKeys | Replxx::KEY::LEFT; | |||||
case VK_RIGHT: | |||||
return modifierKeys | Replxx::KEY::RIGHT; | |||||
case VK_UP: | |||||
return modifierKeys | Replxx::KEY::UP; | |||||
case VK_DOWN: | |||||
return modifierKeys | Replxx::KEY::DOWN; | |||||
case VK_DELETE: | |||||
return modifierKeys | Replxx::KEY::DELETE; | |||||
case VK_HOME: | |||||
return modifierKeys | Replxx::KEY::HOME; | |||||
case VK_END: | |||||
return modifierKeys | Replxx::KEY::END; | |||||
case VK_PRIOR: | |||||
return modifierKeys | Replxx::KEY::PAGE_UP; | |||||
case VK_NEXT: | |||||
return modifierKeys | Replxx::KEY::PAGE_DOWN; | |||||
case VK_F1: | |||||
return modifierKeys | Replxx::KEY::F1; | |||||
case VK_F2: | |||||
return modifierKeys | Replxx::KEY::F2; | |||||
case VK_F3: | |||||
return modifierKeys | Replxx::KEY::F3; | |||||
case VK_F4: | |||||
return modifierKeys | Replxx::KEY::F4; | |||||
case VK_F5: | |||||
return modifierKeys | Replxx::KEY::F5; | |||||
case VK_F6: | |||||
return modifierKeys | Replxx::KEY::F6; | |||||
case VK_F7: | |||||
return modifierKeys | Replxx::KEY::F7; | |||||
case VK_F8: | |||||
return modifierKeys | Replxx::KEY::F8; | |||||
case VK_F9: | |||||
return modifierKeys | Replxx::KEY::F9; | |||||
case VK_F10: | |||||
return modifierKeys | Replxx::KEY::F10; | |||||
case VK_F11: | |||||
return modifierKeys | Replxx::KEY::F11; | |||||
case VK_F12: | |||||
return modifierKeys | Replxx::KEY::F12; | |||||
default: | |||||
continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them | |||||
} | |||||
} else if ( key == Replxx::KEY::ESCAPE ) { // ESC, set flag for later | |||||
escSeen = true; | |||||
continue; | |||||
} else if ( ( key >= 0xD800 ) && ( key <= 0xDBFF ) ) { | |||||
highSurrogate = key - 0xD800; | |||||
continue; | |||||
} else { | |||||
// we got a real character, return it | |||||
if ( ( key >= 0xDC00 ) && ( key <= 0xDFFF ) ) { | |||||
key -= 0xDC00; | |||||
key |= ( highSurrogate << 10 ); | |||||
key += 0x10000; | |||||
} | |||||
if ( is_control_code( key ) ) { | |||||
key += 0x40; | |||||
modifierKeys |= Replxx::KEY::BASE_CONTROL; | |||||
} | |||||
key |= modifierKeys; | |||||
highSurrogate = 0; | |||||
c = key; | |||||
break; | |||||
} | |||||
} | |||||
#else | |||||
c = read_unicode_character(); | |||||
if (c == 0) { | |||||
return 0; | |||||
} | |||||
// If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard | |||||
// debugging mode | |||||
// where we print out decimal and decoded values for whatever the "terminal" | |||||
// program | |||||
// gives us on different keystrokes. Hit ctrl-C to exit this mode. | |||||
// | |||||
#ifdef __REPLXX_DEBUG__ | |||||
if (c == ctrlChar('^')) { // ctrl-^, special debug mode, prints all keys hit, | |||||
// ctrl-C to get out | |||||
printf( | |||||
"\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit " | |||||
"this mode\n"); | |||||
while (true) { | |||||
unsigned char keys[10]; | |||||
int ret = read(0, keys, 10); | |||||
if (ret <= 0) { | |||||
printf("\nret: %d\n", ret); | |||||
} | |||||
for (int i = 0; i < ret; ++i) { | |||||
char32_t key = static_cast<char32_t>(keys[i]); | |||||
char* friendlyTextPtr; | |||||
char friendlyTextBuf[10]; | |||||
const char* prefixText = (key < 0x80) ? "" : "0x80+"; | |||||
char32_t keyCopy = (key < 0x80) ? key : key - 0x80; | |||||
if (keyCopy >= '!' && keyCopy <= '~') { // printable | |||||
friendlyTextBuf[0] = '\''; | |||||
friendlyTextBuf[1] = keyCopy; | |||||
friendlyTextBuf[2] = '\''; | |||||
friendlyTextBuf[3] = 0; | |||||
friendlyTextPtr = friendlyTextBuf; | |||||
} else if (keyCopy == ' ') { | |||||
friendlyTextPtr = const_cast<char*>("space"); | |||||
} else if (keyCopy == 27) { | |||||
friendlyTextPtr = const_cast<char*>("ESC"); | |||||
} else if (keyCopy == 0) { | |||||
friendlyTextPtr = const_cast<char*>("NUL"); | |||||
} else if (keyCopy == 127) { | |||||
friendlyTextPtr = const_cast<char*>("DEL"); | |||||
} else { | |||||
friendlyTextBuf[0] = '^'; | |||||
friendlyTextBuf[1] = keyCopy + 0x40; | |||||
friendlyTextBuf[2] = 0; | |||||
friendlyTextPtr = friendlyTextBuf; | |||||
} | |||||
printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr); | |||||
} | |||||
printf("\x1b[1G\n"); // go to first column of new line | |||||
// drop out of this loop on ctrl-C | |||||
if (keys[0] == ctrlChar('C')) { | |||||
printf("Leaving keyboard debugging mode (on ctrl-C)\n"); | |||||
fflush(stdout); | |||||
return -2; | |||||
} | |||||
} | |||||
} | |||||
#endif // __REPLXX_DEBUG__ | |||||
c = EscapeSequenceProcessing::doDispatch(c); | |||||
if ( is_control_code( c ) ) { | |||||
c = Replxx::KEY::control( c + 0x40 ); | |||||
} | |||||
#endif // #_WIN32 | |||||
return ( c ); | |||||
} | |||||
Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) { | |||||
#ifdef _WIN32 | |||||
std::array<HANDLE,2> handles = { _consoleIn, _interrupt }; | |||||
while ( true ) { | |||||
DWORD event( WaitForMultipleObjects( handles.size (), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) ); | |||||
switch ( event ) { | |||||
case ( WAIT_OBJECT_0 + 0 ): { | |||||
// peek events that will be skipped | |||||
INPUT_RECORD rec; | |||||
DWORD count; | |||||
PeekConsoleInputW( _consoleIn, &rec, 1, &count ); | |||||
if ( | |||||
( rec.EventType != KEY_EVENT ) | |||||
|| ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) ) | |||||
) { | |||||
// read the event to unsignal the handle | |||||
ReadConsoleInputW( _consoleIn, &rec, 1, &count ); | |||||
continue; | |||||
} else if (rec.EventType == KEY_EVENT) { | |||||
int key(rec.Event.KeyEvent.uChar.UnicodeChar); | |||||
if (key == 0) { | |||||
switch (rec.Event.KeyEvent.wVirtualKeyCode) { | |||||
case VK_LEFT: | |||||
case VK_RIGHT: | |||||
case VK_UP: | |||||
case VK_DOWN: | |||||
case VK_DELETE: | |||||
case VK_HOME: | |||||
case VK_END: | |||||
case VK_PRIOR: | |||||
case VK_NEXT: | |||||
break; | |||||
default: | |||||
ReadConsoleInputW(_consoleIn, &rec, 1, &count); | |||||
continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them | |||||
} | |||||
} | |||||
} | |||||
return ( EVENT_TYPE::KEY_PRESS ); | |||||
} | |||||
case ( WAIT_OBJECT_0 + 1 ): { | |||||
ResetEvent( _interrupt ); | |||||
if ( _events.empty() ) { | |||||
continue; | |||||
} | |||||
EVENT_TYPE eventType( _events.front() ); | |||||
_events.pop_front(); | |||||
return ( eventType ); | |||||
} | |||||
case ( WAIT_TIMEOUT ): { | |||||
return ( EVENT_TYPE::TIMEOUT ); | |||||
} | |||||
} | |||||
} | |||||
#else | |||||
fd_set fdSet; | |||||
int nfds( max( _interrupt[0], _interrupt[1] ) + 1 ); | |||||
while ( true ) { | |||||
FD_ZERO( &fdSet ); | |||||
FD_SET( 0, &fdSet ); | |||||
FD_SET( _interrupt[0], &fdSet ); | |||||
timeval tv{ timeout_ / 1000, static_cast<suseconds_t>( ( timeout_ % 1000 ) * 1000 ) }; | |||||
int err( select( nfds, &fdSet, nullptr, nullptr, timeout_ > 0 ? &tv : nullptr ) ); | |||||
if ( ( err == -1 ) && ( errno == EINTR ) ) { | |||||
continue; | |||||
} | |||||
if ( err == 0 ) { | |||||
return ( EVENT_TYPE::TIMEOUT ); | |||||
} | |||||
if ( FD_ISSET( _interrupt[0], &fdSet ) ) { | |||||
char data( 0 ); | |||||
static_cast<void>( read( _interrupt[0], &data, 1 ) == 1 ); | |||||
if ( data == 'k' ) { | |||||
return ( EVENT_TYPE::KEY_PRESS ); | |||||
} | |||||
if ( data == 'm' ) { | |||||
return ( EVENT_TYPE::MESSAGE ); | |||||
} | |||||
} | |||||
if ( FD_ISSET( 0, &fdSet ) ) { | |||||
return ( EVENT_TYPE::KEY_PRESS ); | |||||
} | |||||
} | |||||
#endif | |||||
} | |||||
void Terminal::notify_event( EVENT_TYPE eventType_ ) { | |||||
#ifdef _WIN32 | |||||
_events.push_back( eventType_ ); | |||||
SetEvent( _interrupt ); | |||||
#else | |||||
char data( eventType_ == EVENT_TYPE::KEY_PRESS ? 'k' : 'm' ); | |||||
static_cast<void>( write( _interrupt[1], &data, 1 ) == 1 ); | |||||
#endif | |||||
} | |||||
/** | |||||
* Clear the screen ONLY (no redisplay of anything) | |||||
*/ | |||||
void Terminal::clear_screen( CLEAR_SCREEN clearScreen_ ) { | |||||
#ifdef _WIN32 | |||||
COORD coord = { 0, 0 }; | |||||
CONSOLE_SCREEN_BUFFER_INFO inf; | |||||
bool toEnd( clearScreen_ == CLEAR_SCREEN::TO_END ); | |||||
HANDLE consoleOut( _consoleOut != INVALID_HANDLE_VALUE ? _consoleOut : GetStdHandle( STD_OUTPUT_HANDLE ) ); | |||||
GetConsoleScreenBufferInfo( consoleOut, &inf ); | |||||
if ( ! toEnd ) { | |||||
SetConsoleCursorPosition( consoleOut, coord ); | |||||
} else { | |||||
coord = inf.dwCursorPosition; | |||||
} | |||||
DWORD nWritten( 0 ); | |||||
DWORD toWrite( | |||||
toEnd | |||||
? ( inf.dwSize.Y - inf.dwCursorPosition.Y ) * inf.dwSize.X - inf.dwCursorPosition.X | |||||
: inf.dwSize.X * inf.dwSize.Y | |||||
); | |||||
FillConsoleOutputCharacterA( consoleOut, ' ', toWrite, coord, &nWritten ); | |||||
#else | |||||
if ( clearScreen_ == CLEAR_SCREEN::WHOLE ) { | |||||
char const clearCode[] = "\033c\033[H\033[2J\033[0m"; | |||||
static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); | |||||
} else { | |||||
char const clearCode[] = "\033[J"; | |||||
static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); | |||||
} | |||||
#endif | |||||
} | |||||
void Terminal::jump_cursor( int xPos_, int yOffset_ ) { | |||||
#ifdef _WIN32 | |||||
CONSOLE_SCREEN_BUFFER_INFO inf; | |||||
GetConsoleScreenBufferInfo( _consoleOut, &inf ); | |||||
inf.dwCursorPosition.X = xPos_; | |||||
inf.dwCursorPosition.Y += yOffset_; | |||||
SetConsoleCursorPosition( _consoleOut, inf.dwCursorPosition ); | |||||
#else | |||||
char seq[64]; | |||||
if ( yOffset_ != 0 ) { // move the cursor up as required | |||||
snprintf( seq, sizeof seq, "\033[%d%c", abs( yOffset_ ), yOffset_ > 0 ? 'B' : 'A' ); | |||||
write8( seq, strlen( seq ) ); | |||||
} | |||||
// position at the end of the prompt, clear to end of screen | |||||
snprintf( | |||||
seq, sizeof seq, "\033[%dG", | |||||
xPos_ + 1 /* 1-based on VT100 */ | |||||
); | |||||
write8( seq, strlen( seq ) ); | |||||
#endif | |||||
} | |||||
#ifndef _WIN32 | |||||
int Terminal::read_verbatim( char32_t* buffer_, int size_ ) { | |||||
int len( 0 ); | |||||
buffer_[len ++] = read_unicode_character(); | |||||
int statusFlags( ::fcntl( STDIN_FILENO, F_GETFL, 0 ) ); | |||||
::fcntl( STDIN_FILENO, F_SETFL, statusFlags | O_NONBLOCK ); | |||||
while ( len < size_ ) { | |||||
char32_t c( read_unicode_character() ); | |||||
if ( c == 0 ) { | |||||
break; | |||||
} | |||||
buffer_[len ++] = c; | |||||
} | |||||
::fcntl( STDIN_FILENO, F_SETFL, statusFlags ); | |||||
return ( len ); | |||||
} | |||||
#endif | |||||
} | |||||
#ifndef REPLXX_IO_HXX_INCLUDED | |||||
#define REPLXX_IO_HXX_INCLUDED 1 | |||||
#include <deque> | |||||
#ifdef _WIN32 | |||||
#include <windows.h> | |||||
#else | |||||
#include <termios.h> | |||||
#endif | |||||
namespace replxx { | |||||
class Terminal { | |||||
public: | |||||
enum class EVENT_TYPE { | |||||
KEY_PRESS, | |||||
MESSAGE, | |||||
TIMEOUT | |||||
}; | |||||
private: | |||||
#ifdef _WIN32 | |||||
HANDLE _consoleOut; | |||||
HANDLE _consoleIn; | |||||
DWORD _oldMode; | |||||
WORD _oldDisplayAttribute; | |||||
UINT const _inputCodePage; | |||||
UINT const _outputCodePage; | |||||
HANDLE _interrupt; | |||||
typedef std::deque<EVENT_TYPE> events_t; | |||||
events_t _events; | |||||
#else | |||||
struct termios _origTermios; /* in order to restore at exit */ | |||||
int _interrupt[2]; | |||||
#endif | |||||
bool _rawMode; /* for destructor to check if restore is needed */ | |||||
public: | |||||
enum class CLEAR_SCREEN { | |||||
WHOLE, | |||||
TO_END | |||||
}; | |||||
public: | |||||
Terminal( void ); | |||||
~Terminal( void ); | |||||
void write32( char32_t const*, int ); | |||||
void write8( char const*, int ); | |||||
int get_screen_columns(void); | |||||
int get_screen_rows(void); | |||||
int enable_raw_mode(void); | |||||
void disable_raw_mode(void); | |||||
char32_t read_char(void); | |||||
void clear_screen( CLEAR_SCREEN ); | |||||
EVENT_TYPE wait_for_input( int long = 0 ); | |||||
void notify_event( EVENT_TYPE ); | |||||
void jump_cursor( int, int ); | |||||
#ifndef _WIN32 | |||||
int read_verbatim( char32_t*, int ); | |||||
#endif | |||||
private: | |||||
Terminal( Terminal const& ) = delete; | |||||
Terminal& operator = ( Terminal const& ) = delete; | |||||
Terminal( Terminal&& ) = delete; | |||||
Terminal& operator = ( Terminal&& ) = delete; | |||||
}; | |||||
void beep(); | |||||
char32_t read_unicode_character(void); | |||||
namespace tty { | |||||
extern bool in; | |||||
extern bool out; | |||||
} | |||||
} | |||||
#endif | |||||
#ifndef REPLXX_KILLRING_HXX_INCLUDED | |||||
#define REPLXX_KILLRING_HXX_INCLUDED 1 | |||||
#include <vector> | |||||
#include "unicodestring.hxx" | |||||
namespace replxx { | |||||
class KillRing { | |||||
static const int capacity = 10; | |||||
int size; | |||||
int index; | |||||
char indexToSlot[10]; | |||||
std::vector<UnicodeString> theRing; | |||||
public: | |||||
enum action { actionOther, actionKill, actionYank }; | |||||
action lastAction; | |||||
size_t lastYankSize; | |||||
KillRing() : size(0), index(0), lastAction(actionOther) { | |||||
theRing.reserve(capacity); | |||||
} | |||||
void kill(const char32_t* text, int textLen, bool forward) { | |||||
if (textLen == 0) { | |||||
return; | |||||
} | |||||
UnicodeString killedText(text, textLen); | |||||
if (lastAction == actionKill && size > 0) { | |||||
int slot = indexToSlot[0]; | |||||
int currentLen = static_cast<int>(theRing[slot].length()); | |||||
UnicodeString temp; | |||||
if ( forward ) { | |||||
temp.append( theRing[slot].get(), currentLen ).append( killedText.get(), textLen ); | |||||
} else { | |||||
temp.append( killedText.get(), textLen ).append( theRing[slot].get(), currentLen ); | |||||
} | |||||
theRing[slot] = temp; | |||||
} else { | |||||
if (size < capacity) { | |||||
if (size > 0) { | |||||
memmove(&indexToSlot[1], &indexToSlot[0], size); | |||||
} | |||||
indexToSlot[0] = size; | |||||
size++; | |||||
theRing.push_back(killedText); | |||||
} else { | |||||
int slot = indexToSlot[capacity - 1]; | |||||
theRing[slot] = killedText; | |||||
memmove(&indexToSlot[1], &indexToSlot[0], capacity - 1); | |||||
indexToSlot[0] = slot; | |||||
} | |||||
index = 0; | |||||
} | |||||
} | |||||
UnicodeString* yank() { return (size > 0) ? &theRing[indexToSlot[index]] : 0; } | |||||
UnicodeString* yankPop() { | |||||
if (size == 0) { | |||||
return 0; | |||||
} | |||||
++index; | |||||
if (index == size) { | |||||
index = 0; | |||||
} | |||||
return &theRing[indexToSlot[index]]; | |||||
} | |||||
}; | |||||
} | |||||
#endif | |||||
#ifdef _WIN32 | |||||
#include <conio.h> | |||||
#include <windows.h> | |||||
#include <io.h> | |||||
#if _MSC_VER < 1900 && defined (_MSC_VER) | |||||
#define snprintf _snprintf // Microsoft headers use underscores in some names | |||||
#endif | |||||
#define strcasecmp _stricmp | |||||
#define strdup _strdup | |||||
#define write _write | |||||
#define STDIN_FILENO 0 | |||||
#else /* _WIN32 */ | |||||
#include <unistd.h> | |||||
#endif /* _WIN32 */ | |||||
#include "prompt.hxx" | |||||
#include "util.hxx" | |||||
namespace replxx { | |||||
Prompt::Prompt( Terminal& terminal_ ) | |||||
: _extraLines( 0 ) | |||||
, _lastLinePosition( 0 ) | |||||
, _previousInputLen( 0 ) | |||||
, _previousLen( 0 ) | |||||
, _screenColumns( 0 ) | |||||
, _terminal( terminal_ ) { | |||||
} | |||||
void Prompt::write() { | |||||
_terminal.write32( _text.get(), _byteCount ); | |||||
} | |||||
void Prompt::update_screen_columns( void ) { | |||||
_screenColumns = _terminal.get_screen_columns(); | |||||
} | |||||
void Prompt::set_text( UnicodeString const& text_ ) { | |||||
update_screen_columns(); | |||||
// strip control characters from the prompt -- we do allow newline | |||||
_text = text_; | |||||
UnicodeString::const_iterator in( text_.begin() ); | |||||
UnicodeString::iterator out( _text.begin() ); | |||||
int len = 0; | |||||
int x = 0; | |||||
bool const strip = !tty::out; | |||||
while (in != text_.end()) { | |||||
char32_t c = *in; | |||||
if ('\n' == c || !is_control_code(c)) { | |||||
*out = c; | |||||
++out; | |||||
++in; | |||||
++len; | |||||
if ('\n' == c || ++x >= _screenColumns) { | |||||
x = 0; | |||||
++_extraLines; | |||||
_lastLinePosition = len; | |||||
} | |||||
} else if (c == '\x1b') { | |||||
if ( strip ) { | |||||
// jump over control chars | |||||
++in; | |||||
if (*in == '[') { | |||||
++in; | |||||
while ( ( in != text_.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) { | |||||
++in; | |||||
} | |||||
if (*in == 'm') { | |||||
++in; | |||||
} | |||||
} | |||||
} else { | |||||
// copy control chars | |||||
*out = *in; | |||||
++out; | |||||
++in; | |||||
if (*in == '[') { | |||||
*out = *in; | |||||
++out; | |||||
++in; | |||||
while ( ( in != text_.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) { | |||||
*out = *in; | |||||
++out; | |||||
++in; | |||||
} | |||||
if (*in == 'm') { | |||||
*out = *in; | |||||
++out; | |||||
++in; | |||||
} | |||||
} | |||||
} | |||||
} else { | |||||
++in; | |||||
} | |||||
} | |||||
_characterCount = len; | |||||
_byteCount = static_cast<int>(out - _text.begin()); | |||||
_indentation = len - _lastLinePosition; | |||||
_cursorRowOffset = _extraLines; | |||||
} | |||||
// Used with DynamicPrompt (history search) | |||||
// | |||||
const UnicodeString forwardSearchBasePrompt("(i-search)`"); | |||||
const UnicodeString reverseSearchBasePrompt("(reverse-i-search)`"); | |||||
const UnicodeString endSearchBasePrompt("': "); | |||||
UnicodeString previousSearchText; // remembered across invocations of replxx_input() | |||||
DynamicPrompt::DynamicPrompt( Terminal& terminal_, int initialDirection ) | |||||
: Prompt( terminal_ ) | |||||
, _searchText() | |||||
, _direction( initialDirection ) { | |||||
update_screen_columns(); | |||||
_cursorRowOffset = 0; | |||||
const UnicodeString* basePrompt = | |||||
(_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; | |||||
size_t promptStartLength = basePrompt->length(); | |||||
_characterCount = static_cast<int>(promptStartLength + endSearchBasePrompt.length()); | |||||
_byteCount = _characterCount; | |||||
_lastLinePosition = _characterCount; // TODO fix this, we are asssuming | |||||
// that the history prompt won't wrap (!) | |||||
_previousLen = _characterCount; | |||||
_text.assign( *basePrompt ).append( endSearchBasePrompt ); | |||||
calculate_screen_position( | |||||
0, 0, screen_columns(), _characterCount, | |||||
_indentation, _extraLines | |||||
); | |||||
} | |||||
void DynamicPrompt::updateSearchPrompt(void) { | |||||
const UnicodeString* basePrompt = | |||||
(_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; | |||||
size_t promptStartLength = basePrompt->length(); | |||||
_characterCount = static_cast<int>(promptStartLength + _searchText.length() + | |||||
endSearchBasePrompt.length()); | |||||
_byteCount = _characterCount; | |||||
_text.assign( *basePrompt ).append( _searchText ).append( endSearchBasePrompt ); | |||||
} | |||||
} | |||||
#ifndef REPLXX_PROMPT_HXX_INCLUDED | |||||
#define REPLXX_PROMPT_HXX_INCLUDED 1 | |||||
#include <cstdlib> | |||||
#include "unicodestring.hxx" | |||||
#include "io.hxx" | |||||
namespace replxx { | |||||
class Prompt { // a convenience struct for grouping prompt info | |||||
public: | |||||
UnicodeString _text; // our copy of the prompt text, edited | |||||
int _characterCount; // chars in _text | |||||
int _byteCount; // bytes in _text | |||||
int _extraLines; // extra lines (beyond 1) occupied by prompt | |||||
int _indentation; // column offset to end of prompt | |||||
int _lastLinePosition; // index into _text where last line begins | |||||
int _previousInputLen; // _characterCount of previous input line, for clearing | |||||
int _cursorRowOffset; // where the cursor is relative to the start of the prompt | |||||
int _previousLen; // help erasing | |||||
private: | |||||
int _screenColumns; // width of screen in columns [cache] | |||||
Terminal& _terminal; | |||||
public: | |||||
Prompt( Terminal& ); | |||||
void set_text( UnicodeString const& textPtr ); | |||||
void update_screen_columns( void ); | |||||
int screen_columns() const { | |||||
return ( _screenColumns ); | |||||
} | |||||
void write(); | |||||
}; | |||||
extern UnicodeString previousSearchText; // remembered across invocations of replxx_input() | |||||
// changing prompt for "(reverse-i-search)`text':" etc. | |||||
// | |||||
struct DynamicPrompt : public Prompt { | |||||
UnicodeString _searchText; // text we are searching for | |||||
int _direction; // current search _direction, 1=forward, -1=reverse | |||||
DynamicPrompt( Terminal&, int initialDirection ); | |||||
void updateSearchPrompt(void); | |||||
}; | |||||
} | |||||
#endif |
/* | |||||
* Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) | |||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com> | |||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||||
* | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions are met: | |||||
* | |||||
* * Redistributions of source code must retain the above copyright notice, | |||||
* this list of conditions and the following disclaimer. | |||||
* * Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* * Neither the name of Redis nor the names of its contributors may be used | |||||
* to endorse or promote products derived from this software without | |||||
* specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||||
* POSSIBILITY OF SUCH DAMAGE. | |||||
* | |||||
* line editing lib needs to be 20,000 lines of C code. | |||||
* | |||||
* You can find the latest source code at: | |||||
* | |||||
* http://github.com/antirez/linenoise | |||||
* | |||||
* Does a number of crazy assumptions that happen to be true in 99.9999% of | |||||
* the 2010 UNIX computers around. | |||||
* | |||||
* References: | |||||
* - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html | |||||
* - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html | |||||
* | |||||
* Todo list: | |||||
* - Switch to gets() if $TERM is something we can't support. | |||||
* - Filter bogus Ctrl+<char> combinations. | |||||
* - Win32 support | |||||
* | |||||
* Bloat: | |||||
* - Completion? | |||||
* - History search like Ctrl+r in readline? | |||||
* | |||||
* List of escape sequences used by this program, we do everything just | |||||
* with three sequences. In order to be so cheap we may have some | |||||
* flickering effect with some slow terminal, but the lesser sequences | |||||
* the more compatible. | |||||
* | |||||
* CHA (Cursor Horizontal Absolute) | |||||
* Sequence: ESC [ n G | |||||
* Effect: moves cursor to column n (1 based) | |||||
* | |||||
* EL (Erase Line) | |||||
* Sequence: ESC [ n K | |||||
* Effect: if n is 0 or missing, clear from cursor to end of line | |||||
* Effect: if n is 1, clear from beginning of line to cursor | |||||
* Effect: if n is 2, clear entire line | |||||
* | |||||
* CUF (Cursor Forward) | |||||
* Sequence: ESC [ n C | |||||
* Effect: moves cursor forward of n chars | |||||
* | |||||
* The following are used to clear the screen: ESC [ H ESC [ 2 J | |||||
* This is actually composed of two sequences: | |||||
* | |||||
* cursorhome | |||||
* Sequence: ESC [ H | |||||
* Effect: moves the cursor to upper left corner | |||||
* | |||||
* ED2 (Clear entire screen) | |||||
* Sequence: ESC [ 2 J | |||||
* Effect: clear the whole screen | |||||
* | |||||
*/ | |||||
#include <algorithm> | |||||
#include <cstdarg> | |||||
#ifdef _WIN32 | |||||
#include <io.h> | |||||
#define STDIN_FILENO 0 | |||||
#else /* _WIN32 */ | |||||
#include <signal.h> | |||||
#include <unistd.h> | |||||
#include <sys/stat.h> | |||||
#endif /* _WIN32 */ | |||||
#include "replxx.h" | |||||
#include "replxx.hxx" | |||||
#include "replxx_impl.hxx" | |||||
#include "io.hxx" | |||||
using namespace std; | |||||
using namespace std::placeholders; | |||||
using namespace replxx; | |||||
namespace replxx { | |||||
namespace { | |||||
void delete_ReplxxImpl( Replxx::ReplxxImpl* impl_ ) { | |||||
delete impl_; | |||||
} | |||||
} | |||||
Replxx::Replxx( void ) | |||||
: _impl( new Replxx::ReplxxImpl( nullptr, nullptr, nullptr ), delete_ReplxxImpl ) { | |||||
} | |||||
void Replxx::set_completion_callback( completion_callback_t const& fn ) { | |||||
_impl->set_completion_callback( fn ); | |||||
} | |||||
void Replxx::set_highlighter_callback( highlighter_callback_t const& fn ) { | |||||
_impl->set_highlighter_callback( fn ); | |||||
} | |||||
void Replxx::set_hint_callback( hint_callback_t const& fn ) { | |||||
_impl->set_hint_callback( fn ); | |||||
} | |||||
char const* Replxx::input( std::string const& prompt ) { | |||||
return ( _impl->input( prompt ) ); | |||||
} | |||||
void Replxx::history_add( std::string const& line ) { | |||||
_impl->history_add( line ); | |||||
} | |||||
int Replxx::history_save( std::string const& filename ) { | |||||
return ( _impl->history_save( filename ) ); | |||||
} | |||||
int Replxx::history_load( std::string const& filename ) { | |||||
return ( _impl->history_load( filename ) ); | |||||
} | |||||
int Replxx::history_size( void ) const { | |||||
return ( _impl->history_size() ); | |||||
} | |||||
std::string Replxx::history_line( int index ) { | |||||
return ( _impl->history_line( index ) ); | |||||
} | |||||
void Replxx::set_preload_buffer( std::string const& preloadText ) { | |||||
_impl->set_preload_buffer( preloadText ); | |||||
} | |||||
void Replxx::set_word_break_characters( char const* wordBreakers ) { | |||||
_impl->set_word_break_characters( wordBreakers ); | |||||
} | |||||
void Replxx::set_max_hint_rows( int count ) { | |||||
_impl->set_max_hint_rows( count ); | |||||
} | |||||
void Replxx::set_hint_delay( int milliseconds ) { | |||||
_impl->set_hint_delay( milliseconds ); | |||||
} | |||||
void Replxx::set_completion_count_cutoff( int count ) { | |||||
_impl->set_completion_count_cutoff( count ); | |||||
} | |||||
void Replxx::set_double_tab_completion( bool val ) { | |||||
_impl->set_double_tab_completion( val ); | |||||
} | |||||
void Replxx::set_complete_on_empty( bool val ) { | |||||
_impl->set_complete_on_empty( val ); | |||||
} | |||||
void Replxx::set_beep_on_ambiguous_completion( bool val ) { | |||||
_impl->set_beep_on_ambiguous_completion( val ); | |||||
} | |||||
void Replxx::set_no_color( bool val ) { | |||||
_impl->set_no_color( val ); | |||||
} | |||||
void Replxx::set_max_history_size( int len ) { | |||||
_impl->set_max_history_size( len ); | |||||
} | |||||
void Replxx::clear_screen( void ) { | |||||
_impl->clear_screen( 0 ); | |||||
} | |||||
void Replxx::emulate_key_press( char32_t keyPress_ ) { | |||||
_impl->emulate_key_press( keyPress_ ); | |||||
} | |||||
Replxx::ACTION_RESULT Replxx::invoke( ACTION action_, char32_t keyPress_ ) { | |||||
return ( _impl->invoke( action_, keyPress_ ) ); | |||||
} | |||||
void Replxx::bind_key( char32_t keyPress_, key_press_handler_t handler_ ) { | |||||
_impl->bind_key( keyPress_, handler_ ); | |||||
} | |||||
Replxx::State Replxx::get_state( void ) const { | |||||
return ( _impl->get_state() ); | |||||
} | |||||
void Replxx::set_state( Replxx::State const& state_ ) { | |||||
_impl->set_state( state_ ); | |||||
} | |||||
int Replxx::install_window_change_handler( void ) { | |||||
return ( _impl->install_window_change_handler() ); | |||||
} | |||||
void Replxx::print( char const* format_, ... ) { | |||||
::std::va_list ap; | |||||
va_start( ap, format_ ); | |||||
int size = static_cast<int>( vsnprintf( nullptr, 0, format_, ap ) ); | |||||
va_end( ap ); | |||||
va_start( ap, format_ ); | |||||
unique_ptr<char[]> buf( new char[size + 1] ); | |||||
vsnprintf( buf.get(), static_cast<size_t>( size + 1 ), format_, ap ); | |||||
va_end( ap ); | |||||
return ( _impl->print( buf.get(), size ) ); | |||||
} | |||||
} | |||||
::Replxx* replxx_init() { | |||||
typedef ::Replxx* replxx_data_t; | |||||
return ( reinterpret_cast<replxx_data_t>( new replxx::Replxx::ReplxxImpl( nullptr, nullptr, nullptr ) ) ); | |||||
} | |||||
void replxx_end( ::Replxx* replxx_ ) { | |||||
delete reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ); | |||||
} | |||||
void replxx_clear_screen( ::Replxx* replxx_ ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->clear_screen( 0 ); | |||||
} | |||||
void replxx_emulate_key_press( ::Replxx* replxx_, int unsigned keyPress_ ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->emulate_key_press( keyPress_ ); | |||||
} | |||||
ReplxxActionResult replxx_invoke( ::Replxx* replxx_, ReplxxAction action_, int unsigned keyPress_ ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
return ( static_cast<ReplxxActionResult>( replxx->invoke( static_cast<replxx::Replxx::ACTION>( action_ ), keyPress_ ) ) ); | |||||
} | |||||
replxx::Replxx::ACTION_RESULT key_press_handler_forwarder( key_press_handler_t handler_, char32_t code_, void* userData_ ) { | |||||
return ( static_cast<replxx::Replxx::ACTION_RESULT>( handler_( code_, userData_ ) ) ); | |||||
} | |||||
void replxx_bind_key( ::Replxx* replxx_, int code_, key_press_handler_t handler_, void* userData_ ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->bind_key( code_, std::bind( key_press_handler_forwarder, handler_, _1, userData_ ) ); | |||||
} | |||||
void replxx_get_state( ::Replxx* replxx_, ReplxxState* state ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx::Replxx::State s( replxx->get_state() ); | |||||
state->text = s.text(); | |||||
state->cursorPosition = s.cursor_position(); | |||||
} | |||||
void replxx_set_state( ::Replxx* replxx_, ReplxxState* state ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_state( replxx::Replxx::State( state->text, state->cursorPosition ) ); | |||||
} | |||||
/** | |||||
* replxx_set_preload_buffer provides text to be inserted into the command buffer | |||||
* | |||||
* the provided text will be processed to be usable and will be used to preload | |||||
* the input buffer on the next call to replxx_input() | |||||
* | |||||
* @param preloadText text to begin with on the next call to replxx_input() | |||||
*/ | |||||
void replxx_set_preload_buffer(::Replxx* replxx_, const char* preloadText) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_preload_buffer( preloadText ? preloadText : "" ); | |||||
} | |||||
/** | |||||
* replxx_input is a readline replacement. | |||||
* | |||||
* call it with a prompt to display and it will return a line of input from the | |||||
* user | |||||
* | |||||
* @param prompt text of prompt to display to the user | |||||
* @return the returned string belongs to the caller on return and must be | |||||
* freed to prevent memory leaks | |||||
*/ | |||||
char const* replxx_input( ::Replxx* replxx_, const char* prompt ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
return ( replxx->input( prompt ) ); | |||||
} | |||||
int replxx_print( ::Replxx* replxx_, char const* format_, ... ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
::std::va_list ap; | |||||
va_start( ap, format_ ); | |||||
int size = static_cast<int>( vsnprintf( nullptr, 0, format_, ap ) ); | |||||
va_end( ap ); | |||||
va_start( ap, format_ ); | |||||
unique_ptr<char[]> buf( new char[size + 1] ); | |||||
vsnprintf( buf.get(), static_cast<size_t>( size + 1 ), format_, ap ); | |||||
va_end( ap ); | |||||
try { | |||||
replxx->print( buf.get(), size ); | |||||
} catch ( ... ) { | |||||
return ( -1 ); | |||||
} | |||||
return ( size ); | |||||
} | |||||
struct replxx_completions { | |||||
replxx::Replxx::completions_t data; | |||||
}; | |||||
struct replxx_hints { | |||||
replxx::Replxx::hints_t data; | |||||
}; | |||||
replxx::Replxx::completions_t completions_fwd( replxx_completion_callback_t fn, std::string const& input_, int& contextLen_, void* userData ) { | |||||
replxx_completions completions; | |||||
fn( input_.c_str(), &completions, &contextLen_, userData ); | |||||
return ( completions.data ); | |||||
} | |||||
/* Register a callback function to be called for tab-completion. */ | |||||
void replxx_set_completion_callback(::Replxx* replxx_, replxx_completion_callback_t* fn, void* userData) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_completion_callback( std::bind( &completions_fwd, fn, _1, _2, userData ) ); | |||||
} | |||||
void highlighter_fwd( replxx_highlighter_callback_t fn, std::string const& input, replxx::Replxx::colors_t& colors, void* userData ) { | |||||
std::vector<ReplxxColor> colorsTmp( colors.size() ); | |||||
std::transform( | |||||
colors.begin(), | |||||
colors.end(), | |||||
colorsTmp.begin(), | |||||
[]( replxx::Replxx::Color c ) { | |||||
return ( static_cast<ReplxxColor>( c ) ); | |||||
} | |||||
); | |||||
fn( input.c_str(), colorsTmp.data(), colors.size(), userData ); | |||||
std::transform( | |||||
colorsTmp.begin(), | |||||
colorsTmp.end(), | |||||
colors.begin(), | |||||
[]( ReplxxColor c ) { | |||||
return ( static_cast<replxx::Replxx::Color>( c ) ); | |||||
} | |||||
); | |||||
} | |||||
void replxx_set_highlighter_callback( ::Replxx* replxx_, replxx_highlighter_callback_t* fn, void* userData ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_highlighter_callback( std::bind( &highlighter_fwd, fn, _1, _2, userData ) ); | |||||
} | |||||
replxx::Replxx::hints_t hints_fwd( replxx_hint_callback_t fn, std::string const& input_, int& contextLen_, replxx::Replxx::Color& color_, void* userData ) { | |||||
replxx_hints hints; | |||||
ReplxxColor c( static_cast<ReplxxColor>( color_ ) ); | |||||
fn( input_.c_str(), &hints, &contextLen_, &c, userData ); | |||||
return ( hints.data ); | |||||
} | |||||
void replxx_set_hint_callback( ::Replxx* replxx_, replxx_hint_callback_t* fn, void* userData ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_hint_callback( std::bind( &hints_fwd, fn, _1, _2, _3, userData ) ); | |||||
} | |||||
void replxx_add_hint(replxx_hints* lh, const char* str) { | |||||
lh->data.emplace_back(str); | |||||
} | |||||
void replxx_add_completion( replxx_completions* lc, const char* str ) { | |||||
lc->data.emplace_back( str ); | |||||
} | |||||
void replxx_add_completion( replxx_completions* lc, const char* str, ReplxxColor color ) { | |||||
lc->data.emplace_back( str, static_cast<replxx::Replxx::Color>( color ) ); | |||||
} | |||||
void replxx_history_add( ::Replxx* replxx_, const char* line ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->history_add( line ); | |||||
} | |||||
void replxx_set_max_history_size( ::Replxx* replxx_, int len ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_max_history_size( len ); | |||||
} | |||||
void replxx_set_max_hint_rows( ::Replxx* replxx_, int count ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_max_hint_rows( count ); | |||||
} | |||||
void replxx_set_hint_delay( ::Replxx* replxx_, int milliseconds ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_hint_delay( milliseconds ); | |||||
} | |||||
void replxx_set_completion_count_cutoff( ::Replxx* replxx_, int count ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_completion_count_cutoff( count ); | |||||
} | |||||
void replxx_set_word_break_characters( ::Replxx* replxx_, char const* breakChars_ ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_word_break_characters( breakChars_ ); | |||||
} | |||||
void replxx_set_double_tab_completion( ::Replxx* replxx_, int val ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_double_tab_completion( val ? true : false ); | |||||
} | |||||
void replxx_set_complete_on_empty( ::Replxx* replxx_, int val ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_complete_on_empty( val ? true : false ); | |||||
} | |||||
void replxx_set_no_color( ::Replxx* replxx_, int val ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_no_color( val ? true : false ); | |||||
} | |||||
void replxx_set_beep_on_ambiguous_completion( ::Replxx* replxx_, int val ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
replxx->set_beep_on_ambiguous_completion( val ? true : false ); | |||||
} | |||||
/* Fetch a line of the history by (zero-based) index. If the requested | |||||
* line does not exist, NULL is returned. The return value is a heap-allocated | |||||
* copy of the line. */ | |||||
char const* replxx_history_line( ::Replxx* replxx_, int index ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
return ( replxx->history_line( index ).c_str() ); | |||||
} | |||||
/* Save the history in the specified file. On success 0 is returned | |||||
* otherwise -1 is returned. */ | |||||
int replxx_history_save( ::Replxx* replxx_, const char* filename ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
return ( replxx->history_save( filename ) ); | |||||
} | |||||
/* Load the history from the specified file. If the file does not exist | |||||
* zero is returned and no operation is performed. | |||||
* | |||||
* If the file exists and the operation succeeded 0 is returned, otherwise | |||||
* on error -1 is returned. */ | |||||
int replxx_history_load( ::Replxx* replxx_, const char* filename ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
return ( replxx->history_load( filename ) ); | |||||
} | |||||
int replxx_history_size( ::Replxx* replxx_ ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
return ( replxx->history_size() ); | |||||
} | |||||
/* This special mode is used by replxx in order to print scan codes | |||||
* on screen for debugging / development purposes. It is implemented | |||||
* by the replxx-c-api-example program using the --keycodes option. */ | |||||
#ifdef __REPLXX_DEBUG__ | |||||
void replxx_debug_dump_print_codes(void) { | |||||
char quit[4]; | |||||
printf( | |||||
"replxx key codes debugging mode.\n" | |||||
"Press keys to see scan codes. Type 'quit' at any time to exit.\n"); | |||||
if (enableRawMode() == -1) return; | |||||
memset(quit, ' ', 4); | |||||
while (1) { | |||||
char c; | |||||
int nread; | |||||
#if _WIN32 | |||||
nread = _read(STDIN_FILENO, &c, 1); | |||||
#else | |||||
nread = read(STDIN_FILENO, &c, 1); | |||||
#endif | |||||
if (nread <= 0) continue; | |||||
memmove(quit, quit + 1, sizeof(quit) - 1); /* shift string to left. */ | |||||
quit[sizeof(quit) - 1] = c; /* Insert current char on the right. */ | |||||
if (memcmp(quit, "quit", sizeof(quit)) == 0) break; | |||||
printf("'%c' %02x (%d) (type quit to exit)\n", isprint(c) ? c : '?', (int)c, | |||||
(int)c); | |||||
printf("\r"); /* Go left edge manually, we are in raw mode. */ | |||||
fflush(stdout); | |||||
} | |||||
disableRawMode(); | |||||
} | |||||
#endif // __REPLXX_DEBUG__ | |||||
int replxx_install_window_change_handler( ::Replxx* replxx_ ) { | |||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) ); | |||||
return ( replxx->install_window_change_handler() ); | |||||
} | |||||
/* | |||||
* Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) | |||||
* | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions are met: | |||||
* | |||||
* * Redistributions of source code must retain the above copyright notice, | |||||
* this list of conditions and the following disclaimer. | |||||
* * Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* * Neither the name of Redis nor the names of its contributors may be used | |||||
* to endorse or promote products derived from this software without | |||||
* specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||||
* POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#ifndef HAVE_REPLXX_REPLXX_IMPL_HXX_INCLUDED | |||||
#define HAVE_REPLXX_REPLXX_IMPL_HXX_INCLUDED 1 | |||||
#include <vector> | |||||
#include <deque> | |||||
#include <memory> | |||||
#include <string> | |||||
#include <unordered_map> | |||||
#include <thread> | |||||
#include <mutex> | |||||
#include "replxx.hxx" | |||||
#include "history.hxx" | |||||
#include "killring.hxx" | |||||
#include "utf8string.hxx" | |||||
#include "prompt.hxx" | |||||
#include "io.hxx" | |||||
namespace replxx { | |||||
class Replxx::ReplxxImpl { | |||||
public: | |||||
class Completion { | |||||
UnicodeString _text; | |||||
Replxx::Color _color; | |||||
public: | |||||
Completion( UnicodeString const& text_, Replxx::Color color_ ) | |||||
: _text( text_ ) | |||||
, _color( color_ ) { | |||||
} | |||||
Completion( Replxx::Completion const& completion_ ) | |||||
: _text( completion_.text() ) | |||||
, _color( completion_.color() ) { | |||||
} | |||||
Completion( Completion const& ) = default; | |||||
Completion& operator = ( Completion const& ) = default; | |||||
Completion( Completion&& ) = default; | |||||
Completion& operator = ( Completion&& ) = default; | |||||
UnicodeString const& text( void ) const { | |||||
return ( _text ); | |||||
} | |||||
Replxx::Color color( void ) const { | |||||
return ( _color ); | |||||
} | |||||
}; | |||||
typedef std::vector<Completion> completions_t; | |||||
typedef std::vector<UnicodeString> hints_t; | |||||
typedef std::unique_ptr<char[]> utf8_buffer_t; | |||||
typedef std::unique_ptr<char32_t[]> input_buffer_t; | |||||
typedef std::vector<char> char_widths_t; | |||||
typedef std::vector<char32_t> display_t; | |||||
typedef std::deque<char32_t> key_presses_t; | |||||
typedef std::deque<std::string> messages_t; | |||||
enum class HINT_ACTION { | |||||
REGENERATE, | |||||
REPAINT, | |||||
TRIM, | |||||
SKIP | |||||
}; | |||||
typedef Replxx::ACTION_RESULT ( ReplxxImpl::* key_press_handler_raw_t )( char32_t ); | |||||
typedef std::unordered_map<int, Replxx::key_press_handler_t> key_press_handlers_t; | |||||
private: | |||||
typedef int long long unsigned action_trait_t; | |||||
static action_trait_t const NOOP = 0; | |||||
static action_trait_t const WANT_REFRESH = 1; | |||||
static action_trait_t const RESET_KILL_ACTION = 2; | |||||
static action_trait_t const SET_KILL_ACTION = 4; | |||||
static action_trait_t const DONT_RESET_PREFIX = 8; | |||||
static action_trait_t const DONT_RESET_COMPLETIONS = 16; | |||||
private: | |||||
mutable Utf8String _utf8Buffer; | |||||
UnicodeString _data; | |||||
char_widths_t _charWidths; // character widths from mk_wcwidth() | |||||
display_t _display; | |||||
int _displayInputLength; | |||||
UnicodeString _hint; | |||||
int _pos; // character position in buffer ( 0 <= _pos <= _len ) | |||||
int _prefix; // prefix length used in common prefix search | |||||
int _hintSelection; // Currently selected hint. | |||||
History _history; | |||||
KillRing _killRing; | |||||
int _maxHintRows; | |||||
int _hintDelay; | |||||
char const* _breakChars; | |||||
int _completionCountCutoff; | |||||
bool _overwrite; | |||||
bool _doubleTabCompletion; | |||||
bool _completeOnEmpty; | |||||
bool _beepOnAmbiguousCompletion; | |||||
bool _noColor; | |||||
key_press_handlers_t _keyPressHandlers; | |||||
Terminal _terminal; | |||||
std::thread::id _currentThread; | |||||
Prompt _prompt; | |||||
Replxx::completion_callback_t _completionCallback; | |||||
Replxx::highlighter_callback_t _highlighterCallback; | |||||
Replxx::hint_callback_t _hintCallback; | |||||
key_presses_t _keyPresses; | |||||
messages_t _messages; | |||||
completions_t _completions; | |||||
int _completionContextLength; | |||||
int _completionSelection; | |||||
std::string _preloadedBuffer; // used with set_preload_buffer | |||||
std::string _errorMessage; | |||||
bool _modifiedState; | |||||
mutable std::mutex _mutex; | |||||
public: | |||||
ReplxxImpl( FILE*, FILE*, FILE* ); | |||||
void set_completion_callback( Replxx::completion_callback_t const& fn ); | |||||
void set_highlighter_callback( Replxx::highlighter_callback_t const& fn ); | |||||
void set_hint_callback( Replxx::hint_callback_t const& fn ); | |||||
char const* input( std::string const& prompt ); | |||||
void history_add( std::string const& line ); | |||||
int history_save( std::string const& filename ); | |||||
int history_load( std::string const& filename ); | |||||
std::string history_line( int index ); | |||||
int history_size( void ) const; | |||||
void set_preload_buffer(std::string const& preloadText); | |||||
void set_word_break_characters( char const* wordBreakers ); | |||||
void set_max_hint_rows( int count ); | |||||
void set_hint_delay( int milliseconds ); | |||||
void set_double_tab_completion( bool val ); | |||||
void set_complete_on_empty( bool val ); | |||||
void set_beep_on_ambiguous_completion( bool val ); | |||||
void set_no_color( bool val ); | |||||
void set_max_history_size( int len ); | |||||
void set_completion_count_cutoff( int len ); | |||||
int install_window_change_handler( void ); | |||||
completions_t call_completer( std::string const& input, int& ) const; | |||||
hints_t call_hinter( std::string const& input, int&, Replxx::Color& color ) const; | |||||
void print( char const*, int ); | |||||
Replxx::ACTION_RESULT clear_screen( char32_t ); | |||||
void emulate_key_press( char32_t ); | |||||
Replxx::ACTION_RESULT invoke( Replxx::ACTION, char32_t ); | |||||
void bind_key( char32_t, Replxx::key_press_handler_t ); | |||||
Replxx::State get_state( void ) const; | |||||
void set_state( Replxx::State const& ); | |||||
private: | |||||
ReplxxImpl( ReplxxImpl const& ) = delete; | |||||
ReplxxImpl& operator = ( ReplxxImpl const& ) = delete; | |||||
private: | |||||
void preload_puffer( char const* preloadText ); | |||||
int get_input_line( void ); | |||||
Replxx::ACTION_RESULT action( action_trait_t, key_press_handler_raw_t const&, char32_t ); | |||||
Replxx::ACTION_RESULT insert_character( char32_t ); | |||||
Replxx::ACTION_RESULT go_to_begining_of_line( char32_t ); | |||||
Replxx::ACTION_RESULT go_to_end_of_line( char32_t ); | |||||
Replxx::ACTION_RESULT move_one_char_left( char32_t ); | |||||
Replxx::ACTION_RESULT move_one_char_right( char32_t ); | |||||
Replxx::ACTION_RESULT move_one_word_left( char32_t ); | |||||
Replxx::ACTION_RESULT move_one_word_right( char32_t ); | |||||
Replxx::ACTION_RESULT kill_word_to_left( char32_t ); | |||||
Replxx::ACTION_RESULT kill_word_to_right( char32_t ); | |||||
Replxx::ACTION_RESULT kill_to_whitespace_to_left( char32_t ); | |||||
Replxx::ACTION_RESULT kill_to_begining_of_line( char32_t ); | |||||
Replxx::ACTION_RESULT kill_to_end_of_line( char32_t ); | |||||
Replxx::ACTION_RESULT yank( char32_t ); | |||||
Replxx::ACTION_RESULT yank_cycle( char32_t ); | |||||
Replxx::ACTION_RESULT capitalize_word( char32_t ); | |||||
Replxx::ACTION_RESULT lowercase_word( char32_t ); | |||||
Replxx::ACTION_RESULT uppercase_word( char32_t ); | |||||
Replxx::ACTION_RESULT transpose_characters( char32_t ); | |||||
Replxx::ACTION_RESULT abort_line( char32_t ); | |||||
Replxx::ACTION_RESULT send_eof( char32_t ); | |||||
Replxx::ACTION_RESULT delete_character( char32_t ); | |||||
Replxx::ACTION_RESULT backspace_character( char32_t ); | |||||
Replxx::ACTION_RESULT commit_line( char32_t ); | |||||
Replxx::ACTION_RESULT history_next( char32_t ); | |||||
Replxx::ACTION_RESULT history_previous( char32_t ); | |||||
Replxx::ACTION_RESULT history_move( bool ); | |||||
Replxx::ACTION_RESULT history_first( char32_t ); | |||||
Replxx::ACTION_RESULT history_last( char32_t ); | |||||
Replxx::ACTION_RESULT history_jump( bool ); | |||||
Replxx::ACTION_RESULT hint_next( char32_t ); | |||||
Replxx::ACTION_RESULT hint_previous( char32_t ); | |||||
Replxx::ACTION_RESULT hint_move( bool ); | |||||
Replxx::ACTION_RESULT toggle_overwrite_mode( char32_t ); | |||||
#ifndef _WIN32 | |||||
Replxx::ACTION_RESULT verbatim_insert( char32_t ); | |||||
Replxx::ACTION_RESULT suspend( char32_t ); | |||||
#endif | |||||
Replxx::ACTION_RESULT complete_line( char32_t ); | |||||
Replxx::ACTION_RESULT complete_next( char32_t ); | |||||
Replxx::ACTION_RESULT complete_previous( char32_t ); | |||||
Replxx::ACTION_RESULT complete( bool ); | |||||
Replxx::ACTION_RESULT incremental_history_search( char32_t startChar ); | |||||
Replxx::ACTION_RESULT common_prefix_search( char32_t startChar ); | |||||
char32_t read_char( HINT_ACTION = HINT_ACTION::SKIP ); | |||||
char const* read_from_stdin( void ); | |||||
char32_t do_complete_line( bool ); | |||||
void refresh_line( HINT_ACTION = HINT_ACTION::REGENERATE ); | |||||
void render( char32_t ); | |||||
void render( HINT_ACTION ); | |||||
int handle_hints( HINT_ACTION ); | |||||
void set_color( Replxx::Color ); | |||||
int context_length( void ); | |||||
void clear( void ); | |||||
void repaint( void ); | |||||
bool is_word_break_character( char32_t ) const; | |||||
void dynamicRefresh(Prompt& pi, char32_t* buf32, int len, int pos); | |||||
char const* finalize_input( char const* ); | |||||
void clear_self_to_end_of_screen( void ); | |||||
typedef struct { | |||||
int index; | |||||
bool error; | |||||
} paren_info_t; | |||||
paren_info_t matching_paren( void ); | |||||
}; | |||||
} | |||||
#endif | |||||
#ifndef REPLXX_UNICODESTRING_HXX_INCLUDED | |||||
#define REPLXX_UNICODESTRING_HXX_INCLUDED | |||||
#include <vector> | |||||
#include <cstring> | |||||
#include "conversion.hxx" | |||||
namespace replxx { | |||||
class UnicodeString { | |||||
public: | |||||
typedef std::vector<char32_t> data_buffer_t; | |||||
typedef data_buffer_t::const_iterator const_iterator; | |||||
typedef data_buffer_t::iterator iterator; | |||||
private: | |||||
data_buffer_t _data; | |||||
public: | |||||
UnicodeString() | |||||
: _data() { | |||||
} | |||||
explicit UnicodeString( std::string const& src ) | |||||
: _data() { | |||||
assign( src ); | |||||
} | |||||
explicit UnicodeString( char const* src ) | |||||
: _data() { | |||||
assign( src ); | |||||
} | |||||
explicit UnicodeString( char8_t const* src ) | |||||
: UnicodeString( reinterpret_cast<const char*>( src ) ) { | |||||
} | |||||
explicit UnicodeString( char32_t const* src ) | |||||
: _data() { | |||||
int len( 0 ); | |||||
while ( src[len] != 0 ) { | |||||
++ len; | |||||
} | |||||
_data.assign( src, src + len ); | |||||
} | |||||
explicit UnicodeString( char32_t const* src, int len ) | |||||
: _data() { | |||||
_data.assign( src, src + len ); | |||||
} | |||||
explicit UnicodeString( int len ) | |||||
: _data() { | |||||
_data.resize( len ); | |||||
} | |||||
UnicodeString& assign( std::string const& str_ ) { | |||||
_data.resize( str_.length() ); | |||||
int len( 0 ); | |||||
copyString8to32( _data.data(), str_.length(), len, str_.c_str() ); | |||||
_data.resize( len ); | |||||
return *this; | |||||
} | |||||
UnicodeString& assign( char const* str_ ) { | |||||
size_t byteCount( strlen( str_ ) ); | |||||
_data.resize( byteCount ); | |||||
int len( 0 ); | |||||
copyString8to32( _data.data(), byteCount, len, str_ ); | |||||
_data.resize( len ); | |||||
return *this; | |||||
} | |||||
UnicodeString& assign( UnicodeString const& other_ ) { | |||||
_data = other_._data; | |||||
return *this; | |||||
} | |||||
explicit UnicodeString( UnicodeString const& ) = default; | |||||
UnicodeString& operator = ( UnicodeString const& ) = default; | |||||
UnicodeString( UnicodeString&& ) = default; | |||||
UnicodeString& operator = ( UnicodeString&& ) = default; | |||||
bool operator == ( UnicodeString const& other_ ) const { | |||||
return ( _data == other_._data ); | |||||
} | |||||
bool operator != ( UnicodeString const& other_ ) const { | |||||
return ( _data != other_._data ); | |||||
} | |||||
UnicodeString& append( UnicodeString const& other ) { | |||||
_data.insert( _data.end(), other._data.begin(), other._data.end() ); | |||||
return *this; | |||||
} | |||||
UnicodeString& append( char32_t const* src, int len ) { | |||||
_data.insert( _data.end(), src, src + len ); | |||||
return *this; | |||||
} | |||||
UnicodeString& insert( int pos_, UnicodeString const& str_, int offset_, int len_ ) { | |||||
_data.insert( _data.begin() + pos_, str_._data.begin() + offset_, str_._data.begin() + offset_ + len_ ); | |||||
return *this; | |||||
} | |||||
UnicodeString& insert( int pos_, char32_t c_ ) { | |||||
_data.insert( _data.begin() + pos_, c_ ); | |||||
return *this; | |||||
} | |||||
UnicodeString& erase( int pos_ ) { | |||||
_data.erase( _data.begin() + pos_ ); | |||||
return *this; | |||||
} | |||||
UnicodeString& erase( int pos_, int len_ ) { | |||||
_data.erase( _data.begin() + pos_, _data.begin() + pos_ + len_ ); | |||||
return *this; | |||||
} | |||||
char32_t const* get() const { | |||||
return _data.data(); | |||||
} | |||||
char32_t* get() { | |||||
return _data.data(); | |||||
} | |||||
int length() const { | |||||
return static_cast<int>( _data.size() ); | |||||
} | |||||
void clear( void ) { | |||||
_data.clear(); | |||||
} | |||||
const char32_t& operator[]( size_t pos ) const { | |||||
return _data[pos]; | |||||
} | |||||
char32_t& operator[]( size_t pos ) { | |||||
return _data[pos]; | |||||
} | |||||
bool starts_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const { | |||||
return ( | |||||
( std::distance( first_, last_ ) <= length() ) | |||||
&& ( std::equal( first_, last_, _data.begin() ) ) | |||||
); | |||||
} | |||||
bool is_empty( void ) const { | |||||
return ( _data.size() == 0 ); | |||||
} | |||||
void swap( UnicodeString& other_ ) { | |||||
_data.swap( other_._data ); | |||||
} | |||||
const_iterator begin( void ) const { | |||||
return ( _data.begin() ); | |||||
} | |||||
const_iterator end( void ) const { | |||||
return ( _data.end() ); | |||||
} | |||||
iterator begin( void ) { | |||||
return ( _data.begin() ); | |||||
} | |||||
iterator end( void ) { | |||||
return ( _data.end() ); | |||||
} | |||||
}; | |||||
} | |||||
#endif | |||||
#ifndef REPLXX_UTF8STRING_HXX_INCLUDED | |||||
#define REPLXX_UTF8STRING_HXX_INCLUDED | |||||
#include <memory> | |||||
#include "unicodestring.hxx" | |||||
namespace replxx { | |||||
class Utf8String { | |||||
private: | |||||
typedef std::unique_ptr<char[]> buffer_t; | |||||
buffer_t _data; | |||||
int _bufSize; | |||||
public: | |||||
Utf8String( void ) | |||||
: _data() | |||||
, _bufSize( 0 ) { | |||||
} | |||||
explicit Utf8String( UnicodeString const& src ) | |||||
: _data() | |||||
, _bufSize( 0 ) { | |||||
assign( src, src.length() ); | |||||
} | |||||
Utf8String( UnicodeString const& src_, int len_ ) | |||||
: _data() | |||||
, _bufSize( 0 ) { | |||||
assign( src_, len_ ); | |||||
} | |||||
void assign( UnicodeString const& str_ ) { | |||||
assign( str_, str_.length() ); | |||||
} | |||||
void assign( UnicodeString const& str_, int len_ ) { | |||||
int len( len_ * 4 ); | |||||
realloc( len ); | |||||
copyString32to8( _data.get(), len, str_.get(), len_ ); | |||||
} | |||||
void assign( std::string const& str_ ) { | |||||
realloc( str_.length() ); | |||||
strncpy( _data.get(), str_.c_str(), str_.length() ); | |||||
} | |||||
char const* get() const { | |||||
return _data.get(); | |||||
} | |||||
private: | |||||
void realloc( int reqLen ) { | |||||
if ( ( reqLen + 1 ) > _bufSize ) { | |||||
_bufSize = 1; | |||||
while ( ( reqLen + 1 ) > _bufSize ) { | |||||
_bufSize *= 2; | |||||
} | |||||
_data.reset( new char[_bufSize] ); | |||||
memset( _data.get(), 0, _bufSize ); | |||||
} | |||||
_data[reqLen] = 0; | |||||
return; | |||||
} | |||||
Utf8String(const Utf8String&) = delete; | |||||
Utf8String& operator=(const Utf8String&) = delete; | |||||
}; | |||||
} | |||||
#endif | |||||
#include <cstdlib> | |||||
#include <cstring> | |||||
#include <wctype.h> | |||||
#include "util.hxx" | |||||
namespace replxx { | |||||
int mk_wcwidth( char32_t ); | |||||
/** | |||||
* Recompute widths of all characters in a char32_t buffer | |||||
* @param text - input buffer of Unicode characters | |||||
* @param widths - output buffer of character widths | |||||
* @param charCount - number of characters in buffer | |||||
*/ | |||||
void recompute_character_widths( char32_t const* text, char* widths, int charCount ) { | |||||
for (int i = 0; i < charCount; ++i) { | |||||
widths[i] = mk_wcwidth(text[i]); | |||||
} | |||||
} | |||||
/** | |||||
* Calculate a new screen position given a starting position, screen width and | |||||
* character count | |||||
* @param x - initial x position (zero-based) | |||||
* @param y - initial y position (zero-based) | |||||
* @param screenColumns - screen column count | |||||
* @param charCount - character positions to advance | |||||
* @param xOut - returned x position (zero-based) | |||||
* @param yOut - returned y position (zero-based) | |||||
*/ | |||||
void calculate_screen_position( | |||||
int x, int y, int screenColumns, | |||||
int charCount, int& xOut, int& yOut | |||||
) { | |||||
xOut = x; | |||||
yOut = y; | |||||
int charsRemaining = charCount; | |||||
while ( charsRemaining > 0 ) { | |||||
int charsThisRow = ( ( x + charsRemaining ) < screenColumns ) | |||||
? charsRemaining | |||||
: screenColumns - x; | |||||
xOut = x + charsThisRow; | |||||
yOut = y; | |||||
charsRemaining -= charsThisRow; | |||||
x = 0; | |||||
++ y; | |||||
} | |||||
if ( xOut == screenColumns ) { // we have to special-case line wrap | |||||
xOut = 0; | |||||
++ yOut; | |||||
} | |||||
} | |||||
/** | |||||
* Calculate a column width using mk_wcswidth() | |||||
* @param buf32 - text to calculate | |||||
* @param len - length of text to calculate | |||||
*/ | |||||
int calculate_displayed_length( char32_t const* buf32_, int size_ ) { | |||||
int len( 0 ); | |||||
for ( int i( 0 ); i < size_; ++ i ) { | |||||
char32_t c( buf32_[i] ); | |||||
if ( c == '\033' ) { | |||||
int escStart( i ); | |||||
++ i; | |||||
if ( ( i < size_ ) && ( buf32_[i] != '[' ) ) { | |||||
i = escStart; | |||||
++ len; | |||||
continue; | |||||
} | |||||
++ i; | |||||
for ( ; i < size_; ++ i ) { | |||||
c = buf32_[i]; | |||||
if ( ( c != ';' ) && ( ( c < '0' ) || ( c > '9' ) ) ) { | |||||
break; | |||||
} | |||||
} | |||||
if ( ( i < size_ ) && ( buf32_[i] == 'm' ) ) { | |||||
continue; | |||||
} | |||||
i = escStart; | |||||
len += 2; | |||||
} else if ( is_control_code( c ) ) { | |||||
len += 2; | |||||
} else { | |||||
int wcw( mk_wcwidth( c ) ); | |||||
if ( wcw < 0 ) { | |||||
len = -1; | |||||
break; | |||||
} | |||||
len += wcw; | |||||
} | |||||
} | |||||
return ( len ); | |||||
} | |||||
char const* ansi_color( Replxx::Color color_ ) { | |||||
static char const reset[] = "\033[0m"; | |||||
static char const black[] = "\033[0;22;30m"; | |||||
static char const red[] = "\033[0;22;31m"; | |||||
static char const green[] = "\033[0;22;32m"; | |||||
static char const brown[] = "\033[0;22;33m"; | |||||
static char const blue[] = "\033[0;22;34m"; | |||||
static char const magenta[] = "\033[0;22;35m"; | |||||
static char const cyan[] = "\033[0;22;36m"; | |||||
static char const lightgray[] = "\033[0;22;37m"; | |||||
#ifdef _WIN32 | |||||
static bool const has256colorDefault( true ); | |||||
#else | |||||
static bool const has256colorDefault( false ); | |||||
#endif | |||||
static char const* TERM( getenv( "TERM" ) ); | |||||
static bool const has256color( TERM ? ( strstr( TERM, "256" ) != nullptr ) : has256colorDefault ); | |||||
static char const* gray = has256color ? "\033[0;1;90m" : "\033[0;1;30m"; | |||||
static char const* brightred = has256color ? "\033[0;1;91m" : "\033[0;1;31m"; | |||||
static char const* brightgreen = has256color ? "\033[0;1;92m" : "\033[0;1;32m"; | |||||
static char const* yellow = has256color ? "\033[0;1;93m" : "\033[0;1;33m"; | |||||
static char const* brightblue = has256color ? "\033[0;1;94m" : "\033[0;1;34m"; | |||||
static char const* brightmagenta = has256color ? "\033[0;1;95m" : "\033[0;1;35m"; | |||||
static char const* brightcyan = has256color ? "\033[0;1;96m" : "\033[0;1;36m"; | |||||
static char const* white = has256color ? "\033[0;1;97m" : "\033[0;1;37m"; | |||||
static char const error[] = "\033[101;1;33m"; | |||||
char const* code( reset ); | |||||
switch ( color_ ) { | |||||
case Replxx::Color::BLACK: code = black; break; | |||||
case Replxx::Color::RED: code = red; break; | |||||
case Replxx::Color::GREEN: code = green; break; | |||||
case Replxx::Color::BROWN: code = brown; break; | |||||
case Replxx::Color::BLUE: code = blue; break; | |||||
case Replxx::Color::MAGENTA: code = magenta; break; | |||||
case Replxx::Color::CYAN: code = cyan; break; | |||||
case Replxx::Color::LIGHTGRAY: code = lightgray; break; | |||||
case Replxx::Color::GRAY: code = gray; break; | |||||
case Replxx::Color::BRIGHTRED: code = brightred; break; | |||||
case Replxx::Color::BRIGHTGREEN: code = brightgreen; break; | |||||
case Replxx::Color::YELLOW: code = yellow; break; | |||||
case Replxx::Color::BRIGHTBLUE: code = brightblue; break; | |||||
case Replxx::Color::BRIGHTMAGENTA: code = brightmagenta; break; | |||||
case Replxx::Color::BRIGHTCYAN: code = brightcyan; break; | |||||
case Replxx::Color::WHITE: code = white; break; | |||||
case Replxx::Color::ERROR: code = error; break; | |||||
case Replxx::Color::DEFAULT: code = reset; break; | |||||
} | |||||
return ( code ); | |||||
} | |||||
} | |||||
#ifndef REPLXX_UTIL_HXX_INCLUDED | |||||
#define REPLXX_UTIL_HXX_INCLUDED 1 | |||||
#include "replxx.hxx" | |||||
namespace replxx { | |||||
inline bool is_control_code(char32_t testChar) { | |||||
return (testChar < ' ') || // C0 controls | |||||
(testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls | |||||
} | |||||
void recompute_character_widths( char32_t const* text, char* widths, int charCount ); | |||||
void calculate_screen_position( int x, int y, int screenColumns, int charCount, int& xOut, int& yOut ); | |||||
int calculate_displayed_length( char32_t const* buf32, int size ); | |||||
char const* ansi_color( Replxx::Color ); | |||||
} | |||||
#endif | |||||
/* | |||||
* 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 | |||||
*/ | |||||
#include <wchar.h> | |||||
#include <string> | |||||
#include <memory> | |||||
namespace replxx { | |||||
struct interval { | |||||
char32_t first; | |||||
char32_t last; | |||||
}; | |||||
/* auxiliary function for binary search in interval table */ | |||||
static int bisearch(char32_t ucs, const struct interval *table, int max) { | |||||
int min = 0; | |||||
int mid; | |||||
if (ucs < table[0].first || ucs > table[max].last) | |||||
return 0; | |||||
while (max >= min) { | |||||
mid = (min + max) / 2; | |||||
if (ucs > table[mid].last) | |||||
min = mid + 1; | |||||
else if (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 wchar_t characters are encoded | |||||
* in ISO 10646. | |||||
*/ | |||||
int mk_is_wide_char(char32_t ucs) { | |||||
static const struct interval wide[] = { | |||||
{0x1100, 0x115f}, {0x231a, 0x231b}, {0x2329, 0x232a}, | |||||
{0x23e9, 0x23ec}, {0x23f0, 0x23f0}, {0x23f3, 0x23f3}, | |||||
{0x25fd, 0x25fe}, {0x2614, 0x2615}, {0x2648, 0x2653}, | |||||
{0x267f, 0x267f}, {0x2693, 0x2693}, {0x26a1, 0x26a1}, | |||||
{0x26aa, 0x26ab}, {0x26bd, 0x26be}, {0x26c4, 0x26c5}, | |||||
{0x26ce, 0x26ce}, {0x26d4, 0x26d4}, {0x26ea, 0x26ea}, | |||||
{0x26f2, 0x26f3}, {0x26f5, 0x26f5}, {0x26fa, 0x26fa}, | |||||
{0x26fd, 0x26fd}, {0x2705, 0x2705}, {0x270a, 0x270b}, | |||||
{0x2728, 0x2728}, {0x274c, 0x274c}, {0x274e, 0x274e}, | |||||
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797}, | |||||
{0x27b0, 0x27b0}, {0x27bf, 0x27bf}, {0x2b1b, 0x2b1c}, | |||||
{0x2b50, 0x2b50}, {0x2b55, 0x2b55}, {0x2e80, 0x2fdf}, | |||||
{0x2ff0, 0x303e}, {0x3040, 0x3247}, {0x3250, 0x4dbf}, | |||||
{0x4e00, 0xa4cf}, {0xa960, 0xa97f}, {0xac00, 0xd7a3}, | |||||
{0xf900, 0xfaff}, {0xfe10, 0xfe19}, {0xfe30, 0xfe6f}, | |||||
{0xff01, 0xff60}, {0xffe0, 0xffe6}, {0x16fe0, 0x16fe1}, | |||||
{0x17000, 0x18aff}, {0x1b000, 0x1b12f}, {0x1b170, 0x1b2ff}, | |||||
{0x1f004, 0x1f004}, {0x1f0cf, 0x1f0cf}, {0x1f18e, 0x1f18e}, | |||||
{0x1f191, 0x1f19a}, {0x1f200, 0x1f202}, {0x1f210, 0x1f23b}, | |||||
{0x1f240, 0x1f248}, {0x1f250, 0x1f251}, {0x1f260, 0x1f265}, | |||||
{0x1f300, 0x1f320}, {0x1f32d, 0x1f335}, {0x1f337, 0x1f37c}, | |||||
{0x1f37e, 0x1f393}, {0x1f3a0, 0x1f3ca}, {0x1f3cf, 0x1f3d3}, | |||||
{0x1f3e0, 0x1f3f0}, {0x1f3f4, 0x1f3f4}, {0x1f3f8, 0x1f43e}, | |||||
{0x1f440, 0x1f440}, {0x1f442, 0x1f4fc}, {0x1f4ff, 0x1f53d}, | |||||
{0x1f54b, 0x1f54e}, {0x1f550, 0x1f567}, {0x1f57a, 0x1f57a}, | |||||
{0x1f595, 0x1f596}, {0x1f5a4, 0x1f5a4}, {0x1f5fb, 0x1f64f}, | |||||
{0x1f680, 0x1f6c5}, {0x1f6cc, 0x1f6cc}, {0x1f6d0, 0x1f6d2}, | |||||
{0x1f6eb, 0x1f6ec}, {0x1f6f4, 0x1f6f8}, {0x1f910, 0x1f93e}, | |||||
{0x1f940, 0x1f94c}, {0x1f950, 0x1f96b}, {0x1f980, 0x1f997}, | |||||
{0x1f9c0, 0x1f9c0}, {0x1f9d0, 0x1f9e6}, {0x20000, 0x2fffd}, | |||||
{0x30000, 0x3fffd}, | |||||
}; | |||||
if ( bisearch(ucs, wide, sizeof(wide) / sizeof(struct interval) - 1) ) { | |||||
return 1; | |||||
} | |||||
return 0; | |||||
} | |||||
int mk_wcwidth(char32_t ucs) { | |||||
/* 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[] = { | |||||
{0x00ad, 0x00ad}, {0x0300, 0x036f}, {0x0483, 0x0489}, | |||||
{0x0591, 0x05bd}, {0x05bf, 0x05bf}, {0x05c1, 0x05c2}, | |||||
{0x05c4, 0x05c5}, {0x05c7, 0x05c7}, {0x0610, 0x061a}, | |||||
{0x061c, 0x061c}, {0x064b, 0x065f}, {0x0670, 0x0670}, | |||||
{0x06d6, 0x06dc}, {0x06df, 0x06e4}, {0x06e7, 0x06e8}, | |||||
{0x06ea, 0x06ed}, {0x0711, 0x0711}, {0x0730, 0x074a}, | |||||
{0x07a6, 0x07b0}, {0x07eb, 0x07f3}, {0x0816, 0x0819}, | |||||
{0x081b, 0x0823}, {0x0825, 0x0827}, {0x0829, 0x082d}, | |||||
{0x0859, 0x085b}, {0x08d4, 0x08e1}, {0x08e3, 0x0902}, | |||||
{0x093a, 0x093a}, {0x093c, 0x093c}, {0x0941, 0x0948}, | |||||
{0x094d, 0x094d}, {0x0951, 0x0957}, {0x0962, 0x0963}, | |||||
{0x0981, 0x0981}, {0x09bc, 0x09bc}, {0x09c1, 0x09c4}, | |||||
{0x09cd, 0x09cd}, {0x09e2, 0x09e3}, {0x0a01, 0x0a02}, | |||||
{0x0a3c, 0x0a3c}, {0x0a41, 0x0a42}, {0x0a47, 0x0a48}, | |||||
{0x0a4b, 0x0a4d}, {0x0a51, 0x0a51}, {0x0a70, 0x0a71}, | |||||
{0x0a75, 0x0a75}, {0x0a81, 0x0a82}, {0x0abc, 0x0abc}, | |||||
{0x0ac1, 0x0ac5}, {0x0ac7, 0x0ac8}, {0x0acd, 0x0acd}, | |||||
{0x0ae2, 0x0ae3}, {0x0afa, 0x0aff}, {0x0b01, 0x0b01}, | |||||
{0x0b3c, 0x0b3c}, {0x0b3f, 0x0b3f}, {0x0b41, 0x0b44}, | |||||
{0x0b4d, 0x0b4d}, {0x0b56, 0x0b56}, {0x0b62, 0x0b63}, | |||||
{0x0b82, 0x0b82}, {0x0bc0, 0x0bc0}, {0x0bcd, 0x0bcd}, | |||||
{0x0c00, 0x0c00}, {0x0c3e, 0x0c40}, {0x0c46, 0x0c48}, | |||||
{0x0c4a, 0x0c4d}, {0x0c55, 0x0c56}, {0x0c62, 0x0c63}, | |||||
{0x0c81, 0x0c81}, {0x0cbc, 0x0cbc}, {0x0cbf, 0x0cbf}, | |||||
{0x0cc6, 0x0cc6}, {0x0ccc, 0x0ccd}, {0x0ce2, 0x0ce3}, | |||||
{0x0d00, 0x0d01}, {0x0d3b, 0x0d3c}, {0x0d41, 0x0d44}, | |||||
{0x0d4d, 0x0d4d}, {0x0d62, 0x0d63}, {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}, {0x0f8d, 0x0f97}, {0x0f99, 0x0fbc}, | |||||
{0x0fc6, 0x0fc6}, {0x102d, 0x1030}, {0x1032, 0x1037}, | |||||
{0x1039, 0x103a}, {0x103d, 0x103e}, {0x1058, 0x1059}, | |||||
{0x105e, 0x1060}, {0x1071, 0x1074}, {0x1082, 0x1082}, | |||||
{0x1085, 0x1086}, {0x108d, 0x108d}, {0x109d, 0x109d}, | |||||
{0x1160, 0x11ff}, {0x135d, 0x135f}, {0x1712, 0x1714}, | |||||
{0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, | |||||
{0x17b4, 0x17b5}, {0x17b7, 0x17bd}, {0x17c6, 0x17c6}, | |||||
{0x17c9, 0x17d3}, {0x17dd, 0x17dd}, {0x180b, 0x180e}, | |||||
{0x1885, 0x1886}, {0x18a9, 0x18a9}, {0x1920, 0x1922}, | |||||
{0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193b}, | |||||
{0x1a17, 0x1a18}, {0x1a1b, 0x1a1b}, {0x1a56, 0x1a56}, | |||||
{0x1a58, 0x1a5e}, {0x1a60, 0x1a60}, {0x1a62, 0x1a62}, | |||||
{0x1a65, 0x1a6c}, {0x1a73, 0x1a7c}, {0x1a7f, 0x1a7f}, | |||||
{0x1ab0, 0x1abe}, {0x1b00, 0x1b03}, {0x1b34, 0x1b34}, | |||||
{0x1b36, 0x1b3a}, {0x1b3c, 0x1b3c}, {0x1b42, 0x1b42}, | |||||
{0x1b6b, 0x1b73}, {0x1b80, 0x1b81}, {0x1ba2, 0x1ba5}, | |||||
{0x1ba8, 0x1ba9}, {0x1bab, 0x1bad}, {0x1be6, 0x1be6}, | |||||
{0x1be8, 0x1be9}, {0x1bed, 0x1bed}, {0x1bef, 0x1bf1}, | |||||
{0x1c2c, 0x1c33}, {0x1c36, 0x1c37}, {0x1cd0, 0x1cd2}, | |||||
{0x1cd4, 0x1ce0}, {0x1ce2, 0x1ce8}, {0x1ced, 0x1ced}, | |||||
{0x1cf4, 0x1cf4}, {0x1cf8, 0x1cf9}, {0x1dc0, 0x1df9}, | |||||
{0x1dfb, 0x1dff}, {0x200b, 0x200f}, {0x202a, 0x202e}, | |||||
{0x2060, 0x2064}, {0x2066, 0x206f}, {0x20d0, 0x20f0}, | |||||
{0x2cef, 0x2cf1}, {0x2d7f, 0x2d7f}, {0x2de0, 0x2dff}, | |||||
{0x302a, 0x302d}, {0x3099, 0x309a}, {0xa66f, 0xa672}, | |||||
{0xa674, 0xa67d}, {0xa69e, 0xa69f}, {0xa6f0, 0xa6f1}, | |||||
{0xa802, 0xa802}, {0xa806, 0xa806}, {0xa80b, 0xa80b}, | |||||
{0xa825, 0xa826}, {0xa8c4, 0xa8c5}, {0xa8e0, 0xa8f1}, | |||||
{0xa926, 0xa92d}, {0xa947, 0xa951}, {0xa980, 0xa982}, | |||||
{0xa9b3, 0xa9b3}, {0xa9b6, 0xa9b9}, {0xa9bc, 0xa9bc}, | |||||
{0xa9e5, 0xa9e5}, {0xaa29, 0xaa2e}, {0xaa31, 0xaa32}, | |||||
{0xaa35, 0xaa36}, {0xaa43, 0xaa43}, {0xaa4c, 0xaa4c}, | |||||
{0xaa7c, 0xaa7c}, {0xaab0, 0xaab0}, {0xaab2, 0xaab4}, | |||||
{0xaab7, 0xaab8}, {0xaabe, 0xaabf}, {0xaac1, 0xaac1}, | |||||
{0xaaec, 0xaaed}, {0xaaf6, 0xaaf6}, {0xabe5, 0xabe5}, | |||||
{0xabe8, 0xabe8}, {0xabed, 0xabed}, {0xfb1e, 0xfb1e}, | |||||
{0xfe00, 0xfe0f}, {0xfe20, 0xfe2f}, {0xfeff, 0xfeff}, | |||||
{0xfff9, 0xfffb}, {0x101fd, 0x101fd}, {0x102e0, 0x102e0}, | |||||
{0x10376, 0x1037a}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06}, | |||||
{0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f}, | |||||
{0x10ae5, 0x10ae6}, {0x11001, 0x11001}, {0x11038, 0x11046}, | |||||
{0x1107f, 0x11081}, {0x110b3, 0x110b6}, {0x110b9, 0x110ba}, | |||||
{0x11100, 0x11102}, {0x11127, 0x1112b}, {0x1112d, 0x11134}, | |||||
{0x11173, 0x11173}, {0x11180, 0x11181}, {0x111b6, 0x111be}, | |||||
{0x111ca, 0x111cc}, {0x1122f, 0x11231}, {0x11234, 0x11234}, | |||||
{0x11236, 0x11237}, {0x1123e, 0x1123e}, {0x112df, 0x112df}, | |||||
{0x112e3, 0x112ea}, {0x11300, 0x11301}, {0x1133c, 0x1133c}, | |||||
{0x11340, 0x11340}, {0x11366, 0x1136c}, {0x11370, 0x11374}, | |||||
{0x11438, 0x1143f}, {0x11442, 0x11444}, {0x11446, 0x11446}, | |||||
{0x114b3, 0x114b8}, {0x114ba, 0x114ba}, {0x114bf, 0x114c0}, | |||||
{0x114c2, 0x114c3}, {0x115b2, 0x115b5}, {0x115bc, 0x115bd}, | |||||
{0x115bf, 0x115c0}, {0x115dc, 0x115dd}, {0x11633, 0x1163a}, | |||||
{0x1163d, 0x1163d}, {0x1163f, 0x11640}, {0x116ab, 0x116ab}, | |||||
{0x116ad, 0x116ad}, {0x116b0, 0x116b5}, {0x116b7, 0x116b7}, | |||||
{0x1171d, 0x1171f}, {0x11722, 0x11725}, {0x11727, 0x1172b}, | |||||
{0x11a01, 0x11a06}, {0x11a09, 0x11a0a}, {0x11a33, 0x11a38}, | |||||
{0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a56}, | |||||
{0x11a59, 0x11a5b}, {0x11a8a, 0x11a96}, {0x11a98, 0x11a99}, | |||||
{0x11c30, 0x11c36}, {0x11c38, 0x11c3d}, {0x11c3f, 0x11c3f}, | |||||
{0x11c92, 0x11ca7}, {0x11caa, 0x11cb0}, {0x11cb2, 0x11cb3}, | |||||
{0x11cb5, 0x11cb6}, {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a}, | |||||
{0x11d3c, 0x11d3d}, {0x11d3f, 0x11d45}, {0x11d47, 0x11d47}, | |||||
{0x16af0, 0x16af4}, {0x16b30, 0x16b36}, {0x16f8f, 0x16f92}, | |||||
{0x1bc9d, 0x1bc9e}, {0x1bca0, 0x1bca3}, {0x1d167, 0x1d169}, | |||||
{0x1d173, 0x1d182}, {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad}, | |||||
{0x1d242, 0x1d244}, {0x1da00, 0x1da36}, {0x1da3b, 0x1da6c}, | |||||
{0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f}, | |||||
{0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018}, | |||||
{0x1e01b, 0x1e021}, {0x1e023, 0x1e024}, {0x1e026, 0x1e02a}, | |||||
{0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a}, {0xe0001, 0xe0001}, | |||||
{0xe0020, 0xe007f}, {0xe0100, 0xe01ef}, | |||||
}; | |||||
/* 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 ( mk_is_wide_char( ucs ) ? 2 : 1 ); | |||||
} | |||||
} | |||||
#ifdef _WIN32 | |||||
#include <iostream> | |||||
#include "windows.hxx" | |||||
#include "conversion.hxx" | |||||
#include "io.hxx" | |||||
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING | |||||
static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; | |||||
#endif | |||||
using namespace std; | |||||
namespace replxx { | |||||
WinAttributes WIN_ATTR; | |||||
template<typename T> | |||||
T* HandleEsc(T* p, T* end) { | |||||
if (*p == '[') { | |||||
int code = 0; | |||||
int thisBackground( WIN_ATTR._defaultBackground ); | |||||
for (++p; p < end; ++p) { | |||||
char32_t c = *p; | |||||
if ('0' <= c && c <= '9') { | |||||
code = code * 10 + (c - '0'); | |||||
} else if (c == 'm' || c == ';') { | |||||
switch (code) { | |||||
case 0: | |||||
WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute; | |||||
WIN_ATTR._consoleColor = WIN_ATTR._defaultColor | thisBackground; | |||||
break; | |||||
case 1: // BOLD | |||||
case 5: // BLINK | |||||
WIN_ATTR._consoleAttribute = (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY; | |||||
break; | |||||
case 22: | |||||
WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute; | |||||
break; | |||||
case 30: | |||||
case 90: | |||||
WIN_ATTR._consoleColor = thisBackground; | |||||
break; | |||||
case 31: | |||||
case 91: | |||||
WIN_ATTR._consoleColor = FOREGROUND_RED | thisBackground; | |||||
break; | |||||
case 32: | |||||
case 92: | |||||
WIN_ATTR._consoleColor = FOREGROUND_GREEN | thisBackground; | |||||
break; | |||||
case 33: | |||||
case 93: | |||||
WIN_ATTR._consoleColor = FOREGROUND_RED | FOREGROUND_GREEN | thisBackground; | |||||
break; | |||||
case 34: | |||||
case 94: | |||||
WIN_ATTR._consoleColor = FOREGROUND_BLUE | thisBackground; | |||||
break; | |||||
case 35: | |||||
case 95: | |||||
WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_RED | thisBackground; | |||||
break; | |||||
case 36: | |||||
case 96: | |||||
WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_GREEN | thisBackground; | |||||
break; | |||||
case 37: | |||||
case 97: | |||||
WIN_ATTR._consoleColor = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | thisBackground; | |||||
break; | |||||
case 101: | |||||
thisBackground = BACKGROUND_RED; | |||||
break; | |||||
} | |||||
if ( ( code >= 90 ) && ( code <= 97 ) ) { | |||||
WIN_ATTR._consoleAttribute = (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY; | |||||
} | |||||
code = 0; | |||||
} | |||||
if (*p == 'm') { | |||||
++p; | |||||
break; | |||||
} | |||||
} | |||||
} else { | |||||
++p; | |||||
} | |||||
auto handle = GetStdHandle(STD_OUTPUT_HANDLE); | |||||
SetConsoleTextAttribute( | |||||
handle, | |||||
WIN_ATTR._consoleAttribute | WIN_ATTR._consoleColor | |||||
); | |||||
return p; | |||||
} | |||||
int win_write( char const* str_, int size_ ) { | |||||
int count( 0 ); | |||||
DWORD currentMode( 0 ); | |||||
HANDLE consoleOut( GetStdHandle( STD_OUTPUT_HANDLE ) ); | |||||
if ( tty::out && GetConsoleMode( consoleOut, ¤tMode ) ) { | |||||
UINT inputCodePage( GetConsoleCP() ); | |||||
UINT outputCodePage( GetConsoleOutputCP() ); | |||||
SetConsoleCP( 65001 ); | |||||
SetConsoleOutputCP( 65001 ); | |||||
DWORD nWritten( 0 ); | |||||
if ( SetConsoleMode( consoleOut, currentMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) ) { | |||||
WriteConsoleA( consoleOut, str_, size_, &nWritten, nullptr ); | |||||
count = nWritten; | |||||
SetConsoleMode( consoleOut, currentMode ); | |||||
} else { | |||||
char const* s( str_ ); | |||||
char const* e( str_ + size_ ); | |||||
while ( str_ < e ) { | |||||
if ( *str_ == 27 ) { | |||||
if ( s < str_ ) { | |||||
int toWrite( str_ - s ); | |||||
WriteConsoleA( consoleOut, s, static_cast<DWORD>( toWrite ), &nWritten, nullptr ); | |||||
count += nWritten; | |||||
if ( nWritten != toWrite ) { | |||||
s = str_ = nullptr; | |||||
break; | |||||
} | |||||
} | |||||
s = HandleEsc( str_ + 1, e ); | |||||
int escaped( s - str_); | |||||
count += escaped; | |||||
str_ = s; | |||||
} else { | |||||
++ str_; | |||||
} | |||||
} | |||||
if ( s < str_ ) { | |||||
WriteConsoleA( consoleOut, s, static_cast<DWORD>( str_ - s ), &nWritten, nullptr ); | |||||
count += nWritten; | |||||
} | |||||
} | |||||
SetConsoleCP( inputCodePage ); | |||||
SetConsoleOutputCP( outputCodePage ); | |||||
} else { | |||||
count = _write( 1, str_, size_ ); | |||||
} | |||||
return ( count ); | |||||
} | |||||
} | |||||
#endif | |||||
#ifndef REPLXX_WINDOWS_HXX_INCLUDED | |||||
#define REPLXX_WINDOWS_HXX_INCLUDED 1 | |||||
#include <conio.h> | |||||
#include <windows.h> | |||||
#include <io.h> | |||||
namespace replxx { | |||||
static const int FOREGROUND_WHITE = | |||||
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; | |||||
static const int BACKGROUND_WHITE = | |||||
BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; | |||||
static const int INTENSITY = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; | |||||
class WinAttributes { | |||||
public: | |||||
WinAttributes() { | |||||
CONSOLE_SCREEN_BUFFER_INFO info; | |||||
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); | |||||
_defaultAttribute = info.wAttributes & INTENSITY; | |||||
_defaultColor = info.wAttributes & FOREGROUND_WHITE; | |||||
_defaultBackground = info.wAttributes & BACKGROUND_WHITE; | |||||
_consoleAttribute = _defaultAttribute; | |||||
_consoleColor = _defaultColor | _defaultBackground; | |||||
} | |||||
public: | |||||
int _defaultAttribute; | |||||
int _defaultColor; | |||||
int _defaultBackground; | |||||
int _consoleAttribute; | |||||
int _consoleColor; | |||||
}; | |||||
int win_write( char const*, int ); | |||||
extern WinAttributes WIN_ATTR; | |||||
} | |||||
#endif |