Source: https://github.com/AmokHuginnsson/replxxtags/2.0
@@ -1,10 +0,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() |
@@ -1,25 +0,0 @@ | |||
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. |
@@ -1,76 +0,0 @@ | |||
/* 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 */ |
@@ -0,0 +1,82 @@ | |||
# -*- 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}) |
@@ -0,0 +1,63 @@ | |||
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. |
@@ -0,0 +1,119 @@ | |||
# 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. | |||
@@ -0,0 +1,454 @@ | |||
/* 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 */ | |||
@@ -0,0 +1,466 @@ | |||
/* | |||
* 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 */ | |||
@@ -0,0 +1,271 @@ | |||
/* | |||
* 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. | |||
--------------------------------------------------------------------- */ |
@@ -0,0 +1,139 @@ | |||
/* | |||
* 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 */ | |||
/* --------------------------------------------------------------------- */ |
@@ -0,0 +1,113 @@ | |||
#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; | |||
} | |||
} | |||
} | |||
} | |||
@@ -0,0 +1,20 @@ | |||
#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 |
@@ -0,0 +1,860 @@ | |||
#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 */ | |||
@@ -0,0 +1,37 @@ | |||
#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 | |||
@@ -0,0 +1,148 @@ | |||
#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_ ] ); | |||
} | |||
} | |||
@@ -0,0 +1,73 @@ | |||
#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 | |||
@@ -0,0 +1,674 @@ | |||
#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 | |||
} | |||
@@ -0,0 +1,79 @@ | |||
#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 | |||
@@ -0,0 +1,76 @@ | |||
#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 | |||
@@ -0,0 +1,150 @@ | |||
#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 ); | |||
} | |||
} | |||
@@ -0,0 +1,49 @@ | |||
#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 |
@@ -0,0 +1,521 @@ | |||
/* | |||
* 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() ); | |||
} | |||
@@ -0,0 +1,243 @@ | |||
/* | |||
* 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 | |||
@@ -0,0 +1,179 @@ | |||
#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 | |||
@@ -0,0 +1,71 @@ | |||
#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 | |||
@@ -0,0 +1,152 @@ | |||
#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 ); | |||
} | |||
} | |||
@@ -0,0 +1,21 @@ | |||
#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 | |||
@@ -0,0 +1,296 @@ | |||
/* | |||
* 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 ); | |||
} | |||
} | |||
@@ -0,0 +1,158 @@ | |||
#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 | |||
@@ -0,0 +1,44 @@ | |||
#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 |