Merge branch 'repl' into staged-0.5
This commit is contained in:
commit
4ef724041b
@ -15,7 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Moved from `bestline` to `replxx` Readline implementation due to lack of Windows support from bestline
|
||||||
- New parameter passing convention for command line invocation. `musique help` to learn how it changed
|
- New parameter passing convention for command line invocation. `musique help` to learn how it changed
|
||||||
|
- `CTRL-C` handler that turns notes that are playing off
|
||||||
|
|
||||||
## [0.4.0]
|
## [0.4.0]
|
||||||
|
|
||||||
|
6
Makefile
6
Makefile
@ -18,11 +18,6 @@ include scripts/test.mk
|
|||||||
bin/$(Target): bin/$(os)/$(Target)
|
bin/$(Target): bin/$(os)/$(Target)
|
||||||
ln -f $< $@
|
ln -f $< $@
|
||||||
|
|
||||||
# http://www.music.mcgill.ca/~gary/rtmidi/#compiling
|
|
||||||
bin/$(os)/rtmidi.o: lib/rtmidi/RtMidi.cpp lib/rtmidi/RtMidi.h
|
|
||||||
@echo "CXX $@"
|
|
||||||
@$(CXX) $< -c -O2 -o $@ $(CPPFLAGS) -std=c++20
|
|
||||||
|
|
||||||
doc: Doxyfile musique/*.cc musique/*.hh
|
doc: Doxyfile musique/*.cc musique/*.hh
|
||||||
doxygen
|
doxygen
|
||||||
|
|
||||||
@ -56,4 +51,5 @@ musique.zip:
|
|||||||
.PHONY: clean doc doc-open all test unit-tests release install musique.zip
|
.PHONY: clean doc doc-open all test unit-tests release install musique.zip
|
||||||
|
|
||||||
$(shell mkdir -p $(subst musique/,bin/$(os)/,$(shell find musique/* -type d)))
|
$(shell mkdir -p $(subst musique/,bin/$(os)/,$(shell find musique/* -type d)))
|
||||||
|
$(shell mkdir -p bin/$(os)/replxx/)
|
||||||
$(shell mkdir -p $(subst musique/,bin/$(os)/debug/,$(shell find musique/* -type d)))
|
$(shell mkdir -p $(subst musique/,bin/$(os)/debug/,$(shell find musique/* -type d)))
|
||||||
|
@ -13,7 +13,7 @@ VERSION := $(MAJOR).$(MINOR).$(PATCH)-dev+$(COMMIT)
|
|||||||
|
|
||||||
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result
|
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result
|
||||||
CPPFLAGS:=$(CPPFLAGS) -DMusique_Version='"$(VERSION)"' \
|
CPPFLAGS:=$(CPPFLAGS) -DMusique_Version='"$(VERSION)"' \
|
||||||
-Ilib/expected/ -I. -Ilib/bestline/ -Ilib/rtmidi/ -Ilib/link/include -Ilib/asio/include/ -Ilib/edit_distance.cc/
|
-Ilib/expected/ -I. -Ilib/bestline/ -Ilib/rtmidi/ -Ilib/link/include -Ilib/asio/include/ -Ilib/edit_distance.cc/ -Ilib/replxx/include -DREPLXX_STATIC
|
||||||
LDFLAGS=-flto
|
LDFLAGS=-flto
|
||||||
LDLIBS= -lpthread
|
LDLIBS= -lpthread
|
||||||
|
|
||||||
|
63
lib/replxx/LICENSE.md
Normal file
63
lib/replxx/LICENSE.md
Normal file
@ -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.
|
646
lib/replxx/include/replxx.h
Normal file
646
lib/replxx/include/replxx.h
Normal file
@ -0,0 +1,646 @@
|
|||||||
|
/* 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_DEFAULT = 1u << 16u
|
||||||
|
} 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 };
|
||||||
|
enum { REPLXX_KEY_PASTE_START = REPLXX_KEY_MOUSE + 1 };
|
||||||
|
enum { REPLXX_KEY_PASTE_FINISH = REPLXX_KEY_PASTE_START + 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' ) };
|
||||||
|
enum { REPLXX_KEY_ABORT = REPLXX_KEY_META( REPLXX_KEY_CONTROL( 'M' ) ) };
|
||||||
|
|
||||||
|
/*! \brief List of built-in actions that act upon user input.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
REPLXX_ACTION_INSERT_CHARACTER,
|
||||||
|
REPLXX_ACTION_NEW_LINE,
|
||||||
|
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_END_OF_SUBWORD,
|
||||||
|
REPLXX_ACTION_KILL_TO_BEGINING_OF_SUBWORD,
|
||||||
|
REPLXX_ACTION_KILL_TO_WHITESPACE_ON_LEFT,
|
||||||
|
REPLXX_ACTION_YANK,
|
||||||
|
REPLXX_ACTION_YANK_CYCLE,
|
||||||
|
REPLXX_ACTION_YANK_LAST_ARG,
|
||||||
|
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_ONE_SUBWORD_LEFT,
|
||||||
|
REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_RIGHT,
|
||||||
|
REPLXX_ACTION_MOVE_CURSOR_LEFT,
|
||||||
|
REPLXX_ACTION_MOVE_CURSOR_RIGHT,
|
||||||
|
REPLXX_ACTION_LINE_NEXT,
|
||||||
|
REPLXX_ACTION_LINE_PREVIOUS,
|
||||||
|
REPLXX_ACTION_HISTORY_MOVE_NEXT,
|
||||||
|
REPLXX_ACTION_HISTORY_MOVE_PREVIOUS,
|
||||||
|
REPLXX_ACTION_HISTORY_FIRST,
|
||||||
|
REPLXX_ACTION_HISTORY_LAST,
|
||||||
|
REPLXX_ACTION_HISTORY_RESTORE,
|
||||||
|
REPLXX_ACTION_HISTORY_RESTORE_CURRENT,
|
||||||
|
REPLXX_ACTION_HISTORY_INCREMENTAL_SEARCH,
|
||||||
|
REPLXX_ACTION_HISTORY_SEEDED_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_CAPITALIZE_SUBWORD,
|
||||||
|
REPLXX_ACTION_LOWERCASE_SUBWORD,
|
||||||
|
REPLXX_ACTION_UPPERCASE_SUBWORD,
|
||||||
|
REPLXX_ACTION_TRANSPOSE_CHARACTERS,
|
||||||
|
REPLXX_ACTION_TOGGLE_OVERWRITE_MODE,
|
||||||
|
#ifndef _WIN32
|
||||||
|
REPLXX_ACTION_VERBATIM_INSERT,
|
||||||
|
REPLXX_ACTION_SUSPEND,
|
||||||
|
#endif
|
||||||
|
REPLXX_ACTION_BRACKETED_PASTE,
|
||||||
|
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;
|
||||||
|
typedef struct ReplxxHistoryScan ReplxxHistoryScan;
|
||||||
|
typedef struct ReplxxHistoryEntryTag {
|
||||||
|
char const* timestamp;
|
||||||
|
char const* text;
|
||||||
|
} ReplxxHistoryEntry;
|
||||||
|
|
||||||
|
/*! \brief Create Replxx library resource holder.
|
||||||
|
*
|
||||||
|
* Use replxx_end() to free resources acquired with this function.
|
||||||
|
*
|
||||||
|
* \return Replxx library resource 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 Line modification callback type definition.
|
||||||
|
*
|
||||||
|
* User can observe and modify line contents (and cursor position)
|
||||||
|
* in response to changes to both introduced by the user through
|
||||||
|
* normal interactions.
|
||||||
|
*
|
||||||
|
* When callback returns Replxx updates current line content
|
||||||
|
* and current cursor position to the ones updated by the callback.
|
||||||
|
*
|
||||||
|
* \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far.
|
||||||
|
* \param cursorPosition[in,out] - a R/W reference to current cursor position.
|
||||||
|
* \param userData - pointer to opaque user data block.
|
||||||
|
*/
|
||||||
|
typedef void (replxx_modify_callback_t)(char** input, int* contextLen, void* userData);
|
||||||
|
|
||||||
|
/*! \brief Register modify 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_modify_callback( Replxx*, replxx_modify_callback_t* fn, void* userData );
|
||||||
|
|
||||||
|
/*! \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 other non-negative
|
||||||
|
* number not greater 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 other non-negative
|
||||||
|
* number not greater 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 Decision 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.
|
||||||
|
*
|
||||||
|
* Returned pointer is managed by the library and is not to be freed in the client.
|
||||||
|
*
|
||||||
|
* \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 Enable/disable case insensitive history search and completion.
|
||||||
|
*
|
||||||
|
* \param val - if set to non-zero then history search and completion will be case insensitive.
|
||||||
|
*/
|
||||||
|
REPLXX_IMPEXP void replxx_set_ignore_case( Replxx*, int val );
|
||||||
|
|
||||||
|
/*! \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 Prints a char array with the given length to standard output.
|
||||||
|
*
|
||||||
|
* \copydetails print
|
||||||
|
*
|
||||||
|
* \param str - The char array to print.
|
||||||
|
* \param length - The length of the array.
|
||||||
|
*/
|
||||||
|
REPLXX_IMPEXP int replxx_write( Replxx*, char const* str, int length );
|
||||||
|
|
||||||
|
/*! \brief Asynchronously change the prompt while replxx_input() call is in efect.
|
||||||
|
*
|
||||||
|
* Can be used to change the prompt from callbacks or other threads.
|
||||||
|
*
|
||||||
|
* \param prompt - The prompt string to change to.
|
||||||
|
*/
|
||||||
|
REPLXX_IMPEXP void replxx_set_prompt( Replxx*, const char* prompt );
|
||||||
|
|
||||||
|
/*! \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 );
|
||||||
|
|
||||||
|
/*! \brief Bind internal `replxx` action (by name) to handle given key-press event.
|
||||||
|
*
|
||||||
|
* Action names are the same as unique part of names of ReplxxAction enumerations
|
||||||
|
* but in lower case, e.g.: an action for recalling previous history line
|
||||||
|
* is \e REPLXX_ACTION_LINE_PREVIOUS so action name to be used in this
|
||||||
|
* interface for the same effect is "line_previous".
|
||||||
|
*
|
||||||
|
* \param code - handle this key-press event with following handler.
|
||||||
|
* \param actionName - name of internal action to be invoked on key press.
|
||||||
|
* \return -1 if invalid action name was used, 0 otherwise.
|
||||||
|
*/
|
||||||
|
int replxx_bind_key_internal( Replxx*, int code, char const* actionName );
|
||||||
|
|
||||||
|
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 Set complete next/complete previous behavior.
|
||||||
|
*
|
||||||
|
* COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations,
|
||||||
|
* in case when a partial completion is possible complete only partial part (`false` setting)
|
||||||
|
* or complete first proposed completion fully (`true` setting).
|
||||||
|
* The default is to complete fully (a `true` setting - complete immediately).
|
||||||
|
*
|
||||||
|
* \param val - complete immediately.
|
||||||
|
*/
|
||||||
|
REPLXX_IMPEXP void replxx_set_immediate_completion( Replxx*, int val );
|
||||||
|
|
||||||
|
/*! \brief Set history duplicate entries behaviour.
|
||||||
|
*
|
||||||
|
* \param val - should history contain only unique entries?
|
||||||
|
*/
|
||||||
|
REPLXX_IMPEXP void replxx_set_unique_history( 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 Enable/disable (prompt width) indent for multiline entry.
|
||||||
|
*
|
||||||
|
* \param val - if set to non-zero then multiline indent will be enabled.
|
||||||
|
*/
|
||||||
|
REPLXX_IMPEXP void replxx_set_indent_multiline( 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 ReplxxHistoryScan* replxx_history_scan_start( Replxx* );
|
||||||
|
REPLXX_IMPEXP void replxx_history_scan_stop( Replxx*, ReplxxHistoryScan* );
|
||||||
|
REPLXX_IMPEXP int replxx_history_scan_next( Replxx*, ReplxxHistoryScan*, ReplxxHistoryEntry* );
|
||||||
|
|
||||||
|
/*! \brief Synchronize REPL's history with given file.
|
||||||
|
*
|
||||||
|
* Synchronizing means loading existing history from given file,
|
||||||
|
* merging it with current history sorted by timestamps,
|
||||||
|
* saving merged version to given file,
|
||||||
|
* keeping merged version as current REPL's history.
|
||||||
|
*
|
||||||
|
* This call is an equivalent of calling:
|
||||||
|
* replxx_history_save( rx, "some-file" );
|
||||||
|
* replxx_history_load( rx, "some-file" );
|
||||||
|
*
|
||||||
|
* \param filename - a path to the file with which REPL's current history should be synchronized.
|
||||||
|
* \return 0 iff history file was successfully created, -1 otherwise.
|
||||||
|
*/
|
||||||
|
REPLXX_IMPEXP int replxx_history_sync( Replxx*, const char* filename );
|
||||||
|
|
||||||
|
/*! \brief Save REPL's history into given file.
|
||||||
|
*
|
||||||
|
* Saving means loading existing history from given file,
|
||||||
|
* merging it with current history sorted by timestamps,
|
||||||
|
* saving merged version to given file,
|
||||||
|
* keeping original (NOT merged) version as current REPL's history.
|
||||||
|
*
|
||||||
|
* \param filename - a path to the file where REPL's history should be saved.
|
||||||
|
* \return 0 iff history file was successfully created, -1 otherwise.
|
||||||
|
*/
|
||||||
|
REPLXX_IMPEXP int replxx_history_save( Replxx*, const char* filename );
|
||||||
|
|
||||||
|
/*! \brief Load REPL's history from given file.
|
||||||
|
*
|
||||||
|
* \param filename - a path to the file which contains REPL's history that should be loaded.
|
||||||
|
* \return 0 iff history file was successfully opened, -1 otherwise.
|
||||||
|
*/
|
||||||
|
REPLXX_IMPEXP int replxx_history_load( Replxx*, const char* filename );
|
||||||
|
|
||||||
|
/*! \brief Clear REPL's in-memory history.
|
||||||
|
*/
|
||||||
|
REPLXX_IMPEXP void replxx_history_clear( Replxx* );
|
||||||
|
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* );
|
||||||
|
REPLXX_IMPEXP void replxx_enable_bracketed_paste( Replxx* );
|
||||||
|
REPLXX_IMPEXP void replxx_disable_bracketed_paste( Replxx* );
|
||||||
|
|
||||||
|
/*! \brief Combine two color definitions to get encompassing color definition.
|
||||||
|
*
|
||||||
|
* To be used only for combining foreground and background colors.
|
||||||
|
*
|
||||||
|
* \param color1 - first input color.
|
||||||
|
* \param color2 - second input color.
|
||||||
|
* \return A new color definition that represent combined input colors.
|
||||||
|
*/
|
||||||
|
ReplxxColor replxx_color_combine( ReplxxColor color1, ReplxxColor color2 );
|
||||||
|
|
||||||
|
/*! \brief Transform foreground color definition into a background color definition.
|
||||||
|
*
|
||||||
|
* \param color - an input foreground color definition.
|
||||||
|
* \return A background color definition that is a transformed input \e color.
|
||||||
|
*/
|
||||||
|
ReplxxColor replxx_color_bg( ReplxxColor color );
|
||||||
|
|
||||||
|
/*! \brief Add `bold` attribute to color definition.
|
||||||
|
*
|
||||||
|
* \param color - an input color definition.
|
||||||
|
* \return A new color definition with bold attribute set.
|
||||||
|
*/
|
||||||
|
ReplxxColor replxx_color_bold( ReplxxColor color );
|
||||||
|
|
||||||
|
/*! \brief Add `underline` attribute to color definition.
|
||||||
|
*
|
||||||
|
* \param color - an input color definition.
|
||||||
|
* \return A new color definition with underline attribute set.
|
||||||
|
*/
|
||||||
|
ReplxxColor replxx_color_underline( ReplxxColor color );
|
||||||
|
|
||||||
|
/*! \brief Create a new grayscale color of given brightness level.
|
||||||
|
*
|
||||||
|
* \param level - a brightness level for new color, must be between 0 (darkest) and 23 (brightest).
|
||||||
|
* \return A new grayscale color of a given brightest \e level.
|
||||||
|
*/
|
||||||
|
ReplxxColor replxx_color_grayscale( int level );
|
||||||
|
|
||||||
|
/*! \brief Create a new color in 6×6×6 RGB color space from base component levels.
|
||||||
|
*
|
||||||
|
* \param red - a red (of RGB) component level, must be 0 and 5.
|
||||||
|
* \param green - a green (of RGB) component level, must be 0 and 5.
|
||||||
|
* \param blue - a blue (of RGB) component level, must be 0 and 5.
|
||||||
|
* \return A new color in 6×6×6 RGB color space.
|
||||||
|
*/
|
||||||
|
ReplxxColor replxx_color_rgb666( int red, int green, int blue );
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __REPLXX_H */
|
||||||
|
|
710
lib/replxx/include/replxx.hxx
Normal file
710
lib/replxx/include/replxx.hxx
Normal file
@ -0,0 +1,710 @@
|
|||||||
|
/*
|
||||||
|
* 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>
|
||||||
|
#include <iosfwd>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 ABORT
|
||||||
|
enum { ABORT_8D12A2CA7E5A64036D7251A3EDA51A38 = ABORT };
|
||||||
|
#undef ABORT
|
||||||
|
enum { ABORT = ABORT_8D12A2CA7E5A64036D7251A3EDA51A38 };
|
||||||
|
#endif
|
||||||
|
#ifdef DELETE
|
||||||
|
enum { DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E = DELETE };
|
||||||
|
#undef DELETE
|
||||||
|
enum { DELETE = DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E };
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace replxx {
|
||||||
|
|
||||||
|
class REPLXX_IMPEXP Replxx {
|
||||||
|
public:
|
||||||
|
enum class Color : int {
|
||||||
|
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,
|
||||||
|
DEFAULT = 1u << 16u
|
||||||
|
};
|
||||||
|
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 char32_t const PASTE_START = MOUSE + 1;
|
||||||
|
static char32_t const PASTE_FINISH = PASTE_START + 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;
|
||||||
|
static char32_t const ABORT = 'C' | BASE_CONTROL | BASE_META;
|
||||||
|
};
|
||||||
|
/*! \brief List of built-in actions that act upon user input.
|
||||||
|
*/
|
||||||
|
enum class ACTION {
|
||||||
|
INSERT_CHARACTER,
|
||||||
|
NEW_LINE,
|
||||||
|
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_END_OF_SUBWORD,
|
||||||
|
KILL_TO_BEGINING_OF_SUBWORD,
|
||||||
|
KILL_TO_WHITESPACE_ON_LEFT,
|
||||||
|
YANK,
|
||||||
|
YANK_CYCLE,
|
||||||
|
YANK_LAST_ARG,
|
||||||
|
MOVE_CURSOR_TO_BEGINING_OF_LINE,
|
||||||
|
MOVE_CURSOR_TO_END_OF_LINE,
|
||||||
|
MOVE_CURSOR_ONE_WORD_LEFT,
|
||||||
|
MOVE_CURSOR_ONE_WORD_RIGHT,
|
||||||
|
MOVE_CURSOR_ONE_SUBWORD_LEFT,
|
||||||
|
MOVE_CURSOR_ONE_SUBWORD_RIGHT,
|
||||||
|
MOVE_CURSOR_LEFT,
|
||||||
|
MOVE_CURSOR_RIGHT,
|
||||||
|
LINE_NEXT,
|
||||||
|
LINE_PREVIOUS,
|
||||||
|
HISTORY_NEXT,
|
||||||
|
HISTORY_PREVIOUS,
|
||||||
|
HISTORY_FIRST,
|
||||||
|
HISTORY_LAST,
|
||||||
|
HISTORY_RESTORE,
|
||||||
|
HISTORY_RESTORE_CURRENT,
|
||||||
|
HISTORY_INCREMENTAL_SEARCH,
|
||||||
|
HISTORY_SEEDED_INCREMENTAL_SEARCH,
|
||||||
|
HISTORY_COMMON_PREFIX_SEARCH,
|
||||||
|
HINT_NEXT,
|
||||||
|
HINT_PREVIOUS,
|
||||||
|
CAPITALIZE_WORD,
|
||||||
|
LOWERCASE_WORD,
|
||||||
|
UPPERCASE_WORD,
|
||||||
|
CAPITALIZE_SUBWORD,
|
||||||
|
LOWERCASE_SUBWORD,
|
||||||
|
UPPERCASE_SUBWORD,
|
||||||
|
TRANSPOSE_CHARACTERS,
|
||||||
|
TOGGLE_OVERWRITE_MODE,
|
||||||
|
#ifndef _WIN32
|
||||||
|
VERBATIM_INSERT,
|
||||||
|
SUSPEND,
|
||||||
|
#endif
|
||||||
|
BRACKETED_PASTE,
|
||||||
|
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;
|
||||||
|
class HistoryEntry {
|
||||||
|
std::string _timestamp;
|
||||||
|
std::string _text;
|
||||||
|
public:
|
||||||
|
HistoryEntry( std::string const& timestamp_, std::string const& text_ )
|
||||||
|
: _timestamp( timestamp_ )
|
||||||
|
, _text( text_ ) {
|
||||||
|
}
|
||||||
|
std::string const& timestamp( void ) const {
|
||||||
|
return ( _timestamp );
|
||||||
|
}
|
||||||
|
std::string const& text( void ) const {
|
||||||
|
return ( _text );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
class HistoryScanImpl;
|
||||||
|
class HistoryScan {
|
||||||
|
public:
|
||||||
|
typedef std::unique_ptr<HistoryScanImpl, void (*)( HistoryScanImpl* )> impl_t;
|
||||||
|
private:
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable:4251)
|
||||||
|
#endif
|
||||||
|
impl_t _impl;
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
public:
|
||||||
|
HistoryScan( impl_t );
|
||||||
|
HistoryScan( HistoryScan&& ) = default;
|
||||||
|
HistoryScan& operator = ( HistoryScan&& ) = default;
|
||||||
|
bool next( void );
|
||||||
|
HistoryEntry const& get( void ) const;
|
||||||
|
private:
|
||||||
|
HistoryScan( HistoryScan const& ) = delete;
|
||||||
|
HistoryScan& operator = ( HistoryScan const& ) = delete;
|
||||||
|
};
|
||||||
|
typedef std::vector<std::string> hints_t;
|
||||||
|
|
||||||
|
/*! \brief Line modification callback type definition.
|
||||||
|
*
|
||||||
|
* User can observe and modify line contents (and cursor position)
|
||||||
|
* in response to changes to both introduced by the user through
|
||||||
|
* normal interactions.
|
||||||
|
*
|
||||||
|
* When callback returns Replxx updates current line content
|
||||||
|
* and current cursor position to the ones updated by the callback.
|
||||||
|
*
|
||||||
|
* \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far.
|
||||||
|
* \param cursorPosition[in,out] - a R/W reference to current cursor position.
|
||||||
|
*/
|
||||||
|
typedef std::function<void ( std::string& line, int& cursorPosition )> modify_callback_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 other non-negative
|
||||||
|
* number not greater 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.length()`!
|
||||||
|
*
|
||||||
|
* \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 other non-negative
|
||||||
|
* number not greater 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 Decision 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 _MSC_VER
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable:4251)
|
||||||
|
#endif
|
||||||
|
impl_t _impl;
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
Replxx( void );
|
||||||
|
Replxx( Replxx&& ) = default;
|
||||||
|
Replxx& operator = ( Replxx&& ) = default;
|
||||||
|
|
||||||
|
/*! \brief Register modify callback.
|
||||||
|
*
|
||||||
|
* \param fn - user defined callback function.
|
||||||
|
*/
|
||||||
|
void set_modify_callback( modify_callback_t const& fn );
|
||||||
|
|
||||||
|
/*! \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.
|
||||||
|
*
|
||||||
|
* Returned pointer is managed by the library and is not to be freed in the client.
|
||||||
|
*
|
||||||
|
* \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 Enable/disable case insensitive history search and completion.
|
||||||
|
*
|
||||||
|
* \param val - if set to non-zero then history search and completion will be case insensitive.
|
||||||
|
*/
|
||||||
|
void set_ignore_case( bool val );
|
||||||
|
|
||||||
|
/*! \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 Prints a char array with the given length to standard output.
|
||||||
|
*
|
||||||
|
* \copydetails print
|
||||||
|
*
|
||||||
|
* \param str - The char array to print.
|
||||||
|
* \param length - The length of the array.
|
||||||
|
*/
|
||||||
|
void write( char const* str, int length );
|
||||||
|
|
||||||
|
/*! \brief Asynchronously change the prompt while replxx_input() call is in efect.
|
||||||
|
*
|
||||||
|
* Can be used to change the prompt from callbacks or other threads.
|
||||||
|
*
|
||||||
|
* \param prompt - The prompt string to change to.
|
||||||
|
*/
|
||||||
|
void set_prompt( std::string prompt );
|
||||||
|
|
||||||
|
/*! \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 );
|
||||||
|
|
||||||
|
/*! \brief Bind internal `replxx` action (by name) to handle given key-press event.
|
||||||
|
*
|
||||||
|
* Action names are the same as names of Replxx::ACTION enumerations
|
||||||
|
* but in lower case, e.g.: an action for recalling previous history line
|
||||||
|
* is \e Replxx::ACTION::LINE_PREVIOUS so action name to be used in this
|
||||||
|
* interface for the same effect is "line_previous".
|
||||||
|
*
|
||||||
|
* \param code - handle this key-press event with following handler.
|
||||||
|
* \param actionName - name of internal action to be invoked on key press.
|
||||||
|
*/
|
||||||
|
void bind_key_internal( char32_t code, char const* actionName );
|
||||||
|
|
||||||
|
void history_add( std::string const& line );
|
||||||
|
|
||||||
|
/*! \brief Synchronize REPL's history with given file.
|
||||||
|
*
|
||||||
|
* Synchronizing means loading existing history from given file,
|
||||||
|
* merging it with current history sorted by timestamps,
|
||||||
|
* saving merged version to given file,
|
||||||
|
* keeping merged version as current REPL's history.
|
||||||
|
*
|
||||||
|
* This call is an equivalent of calling:
|
||||||
|
* history_save( "some-file" );
|
||||||
|
* history_load( "some-file" );
|
||||||
|
*
|
||||||
|
* \param filename - a path to the file with which REPL's current history should be synchronized.
|
||||||
|
* \return True iff history file was successfully created.
|
||||||
|
*/
|
||||||
|
bool history_sync( std::string const& filename );
|
||||||
|
|
||||||
|
/*! \brief Save REPL's history into given file.
|
||||||
|
*
|
||||||
|
* Saving means loading existing history from given file,
|
||||||
|
* merging it with current history sorted by timestamps,
|
||||||
|
* saving merged version to given file,
|
||||||
|
* keeping original (NOT merged) version as current REPL's history.
|
||||||
|
*
|
||||||
|
* \param filename - a path to the file where REPL's history should be saved.
|
||||||
|
* \return True iff history file was successfully created.
|
||||||
|
*/
|
||||||
|
bool history_save( std::string const& filename );
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \copydoc history_save
|
||||||
|
*/
|
||||||
|
void history_save( std::ostream& out );
|
||||||
|
|
||||||
|
/*! \brief Load REPL's history from given file.
|
||||||
|
*
|
||||||
|
* \param filename - a path to the file which contains REPL's history that should be loaded.
|
||||||
|
* \return True iff history file was successfully opened.
|
||||||
|
*/
|
||||||
|
bool history_load( std::string const& filename );
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \copydoc history_load
|
||||||
|
*/
|
||||||
|
void history_load( std::istream& in );
|
||||||
|
|
||||||
|
/*! \brief Clear REPL's in-memory history.
|
||||||
|
*/
|
||||||
|
void history_clear( void );
|
||||||
|
int history_size( void ) const;
|
||||||
|
HistoryScan history_scan( void ) const;
|
||||||
|
|
||||||
|
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 Set complete next/complete previous behavior.
|
||||||
|
*
|
||||||
|
* COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations,
|
||||||
|
* in case when a partial completion is possible complete only partial part (`false` setting)
|
||||||
|
* or complete first proposed completion fully (`true` setting).
|
||||||
|
* The default is to complete fully (a `true` setting - complete immediately).
|
||||||
|
*
|
||||||
|
* \param val - complete immediately.
|
||||||
|
*/
|
||||||
|
void set_immediate_completion( bool val );
|
||||||
|
|
||||||
|
/*! \brief Set history duplicate entries behaviour.
|
||||||
|
*
|
||||||
|
* \param val - should history contain only unique entries?
|
||||||
|
*/
|
||||||
|
void set_unique_history( bool val );
|
||||||
|
|
||||||
|
/*! \brief Disable output coloring.
|
||||||
|
*
|
||||||
|
* \param val - if set to non-zero disable output colors.
|
||||||
|
*/
|
||||||
|
void set_no_color( bool val );
|
||||||
|
|
||||||
|
/*! \brief Enable/disable (prompt width) indent for multiline entry.
|
||||||
|
*
|
||||||
|
* \param val - if set to true then multiline indent will be enabled.
|
||||||
|
*/
|
||||||
|
void set_indent_multiline( 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 );
|
||||||
|
void enable_bracketed_paste( void );
|
||||||
|
void disable_bracketed_paste( void );
|
||||||
|
|
||||||
|
private:
|
||||||
|
Replxx( Replxx const& ) = delete;
|
||||||
|
Replxx& operator = ( Replxx const& ) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Color definition related helper function.
|
||||||
|
*
|
||||||
|
* To be used to leverage 256 color terminal capabilities.
|
||||||
|
*/
|
||||||
|
namespace color {
|
||||||
|
|
||||||
|
/*! \brief Combine two color definitions to get encompassing color definition.
|
||||||
|
*
|
||||||
|
* To be used only for combining foreground and background colors.
|
||||||
|
*
|
||||||
|
* \param color1 - first input color.
|
||||||
|
* \param color2 - second input color.
|
||||||
|
* \return A new color definition that represent combined input colors.
|
||||||
|
*/
|
||||||
|
Replxx::Color operator | ( Replxx::Color color1, Replxx::Color color2 );
|
||||||
|
|
||||||
|
/*! \brief Transform foreground color definition into a background color definition.
|
||||||
|
*
|
||||||
|
* \param color - an input foreground color definition.
|
||||||
|
* \return A background color definition that is a transformed input \e color.
|
||||||
|
*/
|
||||||
|
Replxx::Color bg( Replxx::Color color );
|
||||||
|
|
||||||
|
/*! \brief Add `bold` attribute to color definition.
|
||||||
|
*
|
||||||
|
* \param color - an input color definition.
|
||||||
|
* \return A new color definition with bold attribute set.
|
||||||
|
*/
|
||||||
|
Replxx::Color bold( Replxx::Color color );
|
||||||
|
|
||||||
|
/*! \brief Add `underline` attribute to color definition.
|
||||||
|
*
|
||||||
|
* \param color - an input color definition.
|
||||||
|
* \return A new color definition with underline attribute set.
|
||||||
|
*/
|
||||||
|
Replxx::Color underline( Replxx::Color color );
|
||||||
|
|
||||||
|
/*! \brief Create a new grayscale color of given brightness level.
|
||||||
|
*
|
||||||
|
* \param level - a brightness level for new color, must be between 0 (darkest) and 23 (brightest).
|
||||||
|
* \return A new grayscale color of a given brightest \e level.
|
||||||
|
*/
|
||||||
|
Replxx::Color grayscale( int level );
|
||||||
|
|
||||||
|
/*! \brief Create a new color in 6×6×6 RGB color space from base component levels.
|
||||||
|
*
|
||||||
|
* \param red - a red (of RGB) component level, must be 0 and 5.
|
||||||
|
* \param green - a green (of RGB) component level, must be 0 and 5.
|
||||||
|
* \param blue - a blue (of RGB) component level, must be 0 and 5.
|
||||||
|
* \return A new color in 6×6×6 RGB color space.
|
||||||
|
*/
|
||||||
|
Replxx::Color rgb666( int red, int green, int blue );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HAVE_REPLXX_HXX_INCLUDED */
|
||||||
|
|
271
lib/replxx/src/ConvertUTF.cpp
Normal file
271
lib/replxx/src/ConvertUTF.cpp
Normal file
@ -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.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------- */
|
139
lib/replxx/src/ConvertUTF.h
Normal file
139
lib/replxx/src/ConvertUTF.h
Normal file
@ -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 */
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
108
lib/replxx/src/conversion.cxx
Normal file
108
lib/replxx/src/conversion.cxx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
#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 = static_cast<int>( 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int copyString32to8( char* dst, int dstSize, const char32_t* src, int srcSize ) {
|
||||||
|
int resCount( 0 );
|
||||||
|
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 ) {
|
||||||
|
resCount = static_cast<int>( targetStart - reinterpret_cast<UTF8*>( dst ) );
|
||||||
|
if ( resCount < dstSize ) {
|
||||||
|
*targetStart = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int i( 0 );
|
||||||
|
for ( i = 0; ( i < dstSize ) && ( i < srcSize ) && src[i]; ++ i ) {
|
||||||
|
dst[i] = static_cast<char>( src[i] );
|
||||||
|
}
|
||||||
|
resCount = i;
|
||||||
|
if ( i < dstSize ) {
|
||||||
|
dst[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ( resCount );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
30
lib/replxx/src/conversion.hxx
Normal file
30
lib/replxx/src/conversion.hxx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#ifndef REPLXX_CONVERSION_HXX_INCLUDED
|
||||||
|
#define REPLXX_CONVERSION_HXX_INCLUDED 1
|
||||||
|
|
||||||
|
#include "ConvertUTF.h"
|
||||||
|
|
||||||
|
#ifdef __has_include
|
||||||
|
#if __has_include( <version> )
|
||||||
|
#include <version>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! ( defined( __cpp_lib_char8_t ) || ( defined( __clang_major__ ) && ( __clang_major__ >= 8 ) && ( __cplusplus > 201703L ) ) )
|
||||||
|
namespace replxx {
|
||||||
|
typedef unsigned char char8_t;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace replxx {
|
||||||
|
|
||||||
|
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 );
|
||||||
|
int copyString32to8( char* dst, int dstSize, char32_t const* src, int srcSize );
|
||||||
|
|
||||||
|
namespace locale {
|
||||||
|
extern bool is8BitEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
890
lib/replxx/src/escape.cxx
Normal file
890
lib/replxx/src/escape.cxx
Normal file
@ -0,0 +1,890 @@
|
|||||||
|
#include "escape.hxx"
|
||||||
|
#include "terminal.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 bracketPasteStartKeyRoutine(char32_t) {
|
||||||
|
return thisKeyMetaCtrl | Replxx::KEY::PASTE_START;
|
||||||
|
}
|
||||||
|
static char32_t bracketPasteFinishKeyRoutine(char32_t) {
|
||||||
|
return thisKeyMetaCtrl | Replxx::KEY::PASTE_FINISH;
|
||||||
|
}
|
||||||
|
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 escLeftBracket200Routines[] = {
|
||||||
|
bracketPasteStartKeyRoutine, escFailureRoutine
|
||||||
|
};
|
||||||
|
static CharacterDispatch escLeftBracket200Dispatch = {
|
||||||
|
1, "~", escLeftBracket200Routines
|
||||||
|
};
|
||||||
|
static char32_t escLeftBracket200Routine(char32_t c) {
|
||||||
|
c = read_unicode_character();
|
||||||
|
if (c == 0) return 0;
|
||||||
|
return doDispatch(c, escLeftBracket200Dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CharacterDispatchRoutine escLeftBracket201Routines[] = {
|
||||||
|
bracketPasteFinishKeyRoutine, escFailureRoutine
|
||||||
|
};
|
||||||
|
static CharacterDispatch escLeftBracket201Dispatch = {
|
||||||
|
1, "~", escLeftBracket201Routines
|
||||||
|
};
|
||||||
|
static char32_t escLeftBracket201Routine(char32_t c) {
|
||||||
|
c = read_unicode_character();
|
||||||
|
if (c == 0) return 0;
|
||||||
|
return doDispatch(c, escLeftBracket201Dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CharacterDispatchRoutine escLeftBracket20Routines[] = {
|
||||||
|
f9KeyRoutine, escLeftBracket20SemicolonRoutine, escLeftBracket200Routine, escLeftBracket201Routine, escFailureRoutine
|
||||||
|
};
|
||||||
|
static CharacterDispatch escLeftBracket20Dispatch = {
|
||||||
|
4, "~;01", 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 */
|
||||||
|
|
37
lib/replxx/src/escape.hxx
Normal file
37
lib/replxx/src/escape.hxx
Normal file
@ -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
|
||||||
|
|
428
lib/replxx/src/history.cxx
Normal file
428
lib/replxx/src/history.cxx
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
#include <fstream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <istream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
|
#include "replxx.hxx"
|
||||||
|
#include "history.hxx"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace replxx {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void delete_ReplxxHistoryScanImpl( Replxx::HistoryScanImpl* impl_ ) {
|
||||||
|
delete impl_;
|
||||||
|
}
|
||||||
|
static int const ETB = 0x17;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int const REPLXX_DEFAULT_HISTORY_MAX_LEN( 1000 );
|
||||||
|
|
||||||
|
Replxx::HistoryScan::HistoryScan( impl_t impl_ )
|
||||||
|
: _impl( std::move( impl_ ) ) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Replxx::HistoryScan::next( void ) {
|
||||||
|
return ( _impl->next() );
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::HistoryScanImpl::HistoryScanImpl( History::entries_t const& entries_ )
|
||||||
|
: _entries( entries_ )
|
||||||
|
, _it( _entries.end() )
|
||||||
|
, _utf8Cache()
|
||||||
|
, _entryCache( std::string(), std::string() )
|
||||||
|
, _cacheValid( false ) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::HistoryEntry const& Replxx::HistoryScan::get( void ) const {
|
||||||
|
return ( _impl->get() );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Replxx::HistoryScanImpl::next( void ) {
|
||||||
|
if ( _it == _entries.end() ) {
|
||||||
|
_it = _entries.begin();
|
||||||
|
} else {
|
||||||
|
++ _it;
|
||||||
|
}
|
||||||
|
_cacheValid = false;
|
||||||
|
return ( _it != _entries.end() );
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::HistoryEntry const& Replxx::HistoryScanImpl::get( void ) const {
|
||||||
|
if ( _cacheValid ) {
|
||||||
|
return ( _entryCache );
|
||||||
|
}
|
||||||
|
_utf8Cache.assign( _it->text() );
|
||||||
|
_entryCache = Replxx::HistoryEntry( _it->timestamp(), _utf8Cache.get() );
|
||||||
|
_cacheValid = true;
|
||||||
|
return ( _entryCache );
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::HistoryScan::impl_t History::scan( void ) const {
|
||||||
|
return ( Replxx::HistoryScan::impl_t( new Replxx::HistoryScanImpl( _entries ), delete_ReplxxHistoryScanImpl ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
History::History( void )
|
||||||
|
: _entries()
|
||||||
|
, _maxSize( REPLXX_DEFAULT_HISTORY_MAX_LEN )
|
||||||
|
, _current( _entries.begin() )
|
||||||
|
, _yankPos( _entries.end() )
|
||||||
|
, _previous( _entries.begin() )
|
||||||
|
, _recallMostRecent( false )
|
||||||
|
, _unique( true ) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::add( UnicodeString const& line, std::string const& when ) {
|
||||||
|
if ( _maxSize <= 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( ! _entries.empty() && ( line == _entries.back().text() ) ) {
|
||||||
|
_entries.back() = Entry( now_ms_str(), line );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
remove_duplicate( line );
|
||||||
|
trim_to_max_size();
|
||||||
|
_entries.emplace_back( when, line );
|
||||||
|
_locations.insert( make_pair( line, last() ) );
|
||||||
|
if ( _current == _entries.end() ) {
|
||||||
|
_current = last();
|
||||||
|
}
|
||||||
|
_yankPos = _entries.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
class FileLock {
|
||||||
|
std::string _path;
|
||||||
|
int _lockFd;
|
||||||
|
public:
|
||||||
|
FileLock( std::string const& name_ )
|
||||||
|
: _path( name_ + ".lock" )
|
||||||
|
, _lockFd( ::open( _path.c_str(), O_CREAT | O_RDWR, 0600 ) ) {
|
||||||
|
static_cast<void>( ::lockf( _lockFd, F_LOCK, 0 ) == 0 );
|
||||||
|
}
|
||||||
|
~FileLock( void ) {
|
||||||
|
static_cast<void>( ::lockf( _lockFd, F_ULOCK, 0 ) == 0 );
|
||||||
|
::close( _lockFd );
|
||||||
|
::unlink( _path.c_str() );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool History::save( std::string const& filename, bool sync_ ) {
|
||||||
|
#ifndef _WIN32
|
||||||
|
mode_t old_umask = umask( S_IXUSR | S_IRWXG | S_IRWXO );
|
||||||
|
FileLock fileLock( filename );
|
||||||
|
#endif
|
||||||
|
entries_t entries;
|
||||||
|
locations_t locations;
|
||||||
|
if ( ! sync_ ) {
|
||||||
|
entries.swap( _entries );
|
||||||
|
locations.swap( _locations );
|
||||||
|
_entries = entries;
|
||||||
|
reset_iters();
|
||||||
|
}
|
||||||
|
/* scope for ifstream object auto-close */ {
|
||||||
|
ifstream histFile( filename );
|
||||||
|
if ( histFile ) {
|
||||||
|
do_load( histFile );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort();
|
||||||
|
remove_duplicates();
|
||||||
|
trim_to_max_size();
|
||||||
|
ofstream histFile( filename );
|
||||||
|
if ( ! histFile ) {
|
||||||
|
return ( false );
|
||||||
|
}
|
||||||
|
#ifndef _WIN32
|
||||||
|
umask( old_umask );
|
||||||
|
chmod( filename.c_str(), S_IRUSR | S_IWUSR );
|
||||||
|
#endif
|
||||||
|
save( histFile );
|
||||||
|
if ( ! sync_ ) {
|
||||||
|
_entries = std::move( entries );
|
||||||
|
_locations = std::move( locations );
|
||||||
|
}
|
||||||
|
reset_iters();
|
||||||
|
return ( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::save( std::ostream& histFile ) {
|
||||||
|
Utf8String utf8;
|
||||||
|
UnicodeString us;
|
||||||
|
for ( Entry& h : _entries ) {
|
||||||
|
h.reset_scratch();
|
||||||
|
if ( ! h.text().is_empty() ) {
|
||||||
|
us.assign( h.text() );
|
||||||
|
std::replace( us.begin(), us.end(), char32_t( '\n' ), char32_t( ETB ) );
|
||||||
|
utf8.assign( us );
|
||||||
|
histFile << "### " << h.timestamp() << "\n" << utf8.get() << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool is_timestamp( std::string const& s ) {
|
||||||
|
static char const TIMESTAMP_PATTERN[] = "### dddd-dd-dd dd:dd:dd.ddd";
|
||||||
|
static int const TIMESTAMP_LENGTH( sizeof ( TIMESTAMP_PATTERN ) - 1 );
|
||||||
|
if ( s.length() != TIMESTAMP_LENGTH ) {
|
||||||
|
return ( false );
|
||||||
|
}
|
||||||
|
for ( int i( 0 ); i < TIMESTAMP_LENGTH; ++ i ) {
|
||||||
|
if ( TIMESTAMP_PATTERN[i] == 'd' ) {
|
||||||
|
if ( ! isdigit( s[i] ) ) {
|
||||||
|
return ( false );
|
||||||
|
}
|
||||||
|
} else if ( s[i] != TIMESTAMP_PATTERN[i] ) {
|
||||||
|
return ( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::do_load( std::istream& histFile ) {
|
||||||
|
string line;
|
||||||
|
string when( "0000-00-00 00:00:00.000" );
|
||||||
|
UnicodeString us;
|
||||||
|
while ( getline( histFile, line ).good() ) {
|
||||||
|
string::size_type eol( line.find_first_of( "\r\n" ) );
|
||||||
|
if ( eol != string::npos ) {
|
||||||
|
line.erase( eol );
|
||||||
|
}
|
||||||
|
if ( is_timestamp( line ) ) {
|
||||||
|
when.assign( line, 4, std::string::npos );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( ! line.empty() ) {
|
||||||
|
us.assign( line );
|
||||||
|
std::replace( us.begin(), us.end(), char32_t( ETB ), char32_t( '\n' ) );
|
||||||
|
_entries.emplace_back( when, us );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool History::load( std::string const& filename ) {
|
||||||
|
ifstream histFile( filename );
|
||||||
|
if ( ! histFile ) {
|
||||||
|
clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
load(histFile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::load( std::istream& histFile ) {
|
||||||
|
clear();
|
||||||
|
do_load( histFile );
|
||||||
|
sort();
|
||||||
|
remove_duplicates();
|
||||||
|
trim_to_max_size();
|
||||||
|
_previous = _current = last();
|
||||||
|
_yankPos = _entries.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::sort( void ) {
|
||||||
|
typedef std::vector<Entry> sortable_entries_t;
|
||||||
|
_locations.clear();
|
||||||
|
sortable_entries_t sortableEntries( _entries.begin(), _entries.end() );
|
||||||
|
std::stable_sort( sortableEntries.begin(), sortableEntries.end() );
|
||||||
|
_entries.clear();
|
||||||
|
_entries.insert( _entries.begin(), sortableEntries.begin(), sortableEntries.end() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::clear( void ) {
|
||||||
|
_locations.clear();
|
||||||
|
_entries.clear();
|
||||||
|
_current = _entries.begin();
|
||||||
|
_recallMostRecent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::set_max_size( int size_ ) {
|
||||||
|
if ( size_ >= 0 ) {
|
||||||
|
_maxSize = size_;
|
||||||
|
trim_to_max_size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::reset_yank_iterator( void ) {
|
||||||
|
_yankPos = _entries.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool History::next_yank_position( void ) {
|
||||||
|
bool resetYankSize( false );
|
||||||
|
if ( _yankPos == _entries.end() ) {
|
||||||
|
resetYankSize = true;
|
||||||
|
}
|
||||||
|
if ( ( _yankPos != _entries.begin() ) && ( _yankPos != _entries.end() ) ) {
|
||||||
|
-- _yankPos;
|
||||||
|
} else {
|
||||||
|
_yankPos = moved( _entries.end(), -2 );
|
||||||
|
}
|
||||||
|
return ( resetYankSize );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool History::move( bool up_ ) {
|
||||||
|
bool doRecall( _recallMostRecent && ! up_ );
|
||||||
|
if ( doRecall ) {
|
||||||
|
_current = _previous; // emulate Windows down-arrow
|
||||||
|
}
|
||||||
|
_recallMostRecent = false;
|
||||||
|
return ( doRecall || move( _current, up_ ? -1 : 1 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::jump( bool start_, bool reset_ ) {
|
||||||
|
if ( start_ ) {
|
||||||
|
_current = _entries.begin();
|
||||||
|
} else {
|
||||||
|
_current = last();
|
||||||
|
}
|
||||||
|
if ( reset_ ) {
|
||||||
|
_recallMostRecent = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::save_pos( void ) {
|
||||||
|
_previous = _current;
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::restore_pos( void ) {
|
||||||
|
_current = _previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool History::common_prefix_search( UnicodeString const& prefix_, int prefixSize_, bool back_, bool ignoreCase ) {
|
||||||
|
int step( back_ ? -1 : 1 );
|
||||||
|
entries_t::iterator it( moved( _current, step, true ) );
|
||||||
|
bool lowerCaseContext( std::none_of( prefix_.begin(), prefix_.end(), []( char32_t x ) { return iswupper( static_cast<wint_t>( x ) ); } ) );
|
||||||
|
while ( it != _current ) {
|
||||||
|
if ( it->text().starts_with( prefix_.begin(), prefix_.begin() + prefixSize_, ignoreCase && lowerCaseContext ? case_insensitive_equal : case_sensitive_equal ) ) {
|
||||||
|
_current = it;
|
||||||
|
commit_index();
|
||||||
|
return ( true );
|
||||||
|
}
|
||||||
|
move( it, step, true );
|
||||||
|
}
|
||||||
|
return ( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool History::move( entries_t::iterator& it_, int by_, bool wrapped_ ) {
|
||||||
|
if ( by_ > 0 ) {
|
||||||
|
for ( int i( 0 ); i < by_; ++ i ) {
|
||||||
|
++ it_;
|
||||||
|
if ( it_ != _entries.end() ) {
|
||||||
|
} else if ( wrapped_ ) {
|
||||||
|
it_ = _entries.begin();
|
||||||
|
} else {
|
||||||
|
-- it_;
|
||||||
|
return ( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for ( int i( 0 ); i > by_; -- i ) {
|
||||||
|
if ( it_ != _entries.begin() ) {
|
||||||
|
-- it_;
|
||||||
|
} else if ( wrapped_ ) {
|
||||||
|
it_ = last();
|
||||||
|
} else {
|
||||||
|
return ( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
History::entries_t::iterator History::moved( entries_t::iterator it_, int by_, bool wrapped_ ) {
|
||||||
|
move( it_, by_, wrapped_ );
|
||||||
|
return ( it_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::erase( entries_t::iterator it_ ) {
|
||||||
|
bool invalidated( it_ == _current );
|
||||||
|
_locations.erase( it_->text() );
|
||||||
|
it_ = _entries.erase( it_ );
|
||||||
|
if ( invalidated ) {
|
||||||
|
_current = it_;
|
||||||
|
}
|
||||||
|
if ( ( _current == _entries.end() ) && ! _entries.empty() ) {
|
||||||
|
-- _current;
|
||||||
|
}
|
||||||
|
_yankPos = _entries.end();
|
||||||
|
_previous = _current;
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::trim_to_max_size( void ) {
|
||||||
|
while ( size() > _maxSize ) {
|
||||||
|
erase( _entries.begin() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::remove_duplicate( UnicodeString const& line_ ) {
|
||||||
|
if ( ! _unique ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
locations_t::iterator it( _locations.find( line_ ) );
|
||||||
|
if ( it == _locations.end() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
erase( it->second );
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::remove_duplicates( void ) {
|
||||||
|
if ( ! _unique ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_locations.clear();
|
||||||
|
typedef std::pair<locations_t::iterator, bool> locations_insertion_result_t;
|
||||||
|
for ( entries_t::iterator it( _entries.begin() ), end( _entries.end() ); it != end; ++ it ) {
|
||||||
|
it->reset_scratch();
|
||||||
|
locations_insertion_result_t locationsInsertionResult( _locations.insert( make_pair( it->text(), it ) ) );
|
||||||
|
if ( ! locationsInsertionResult.second ) {
|
||||||
|
_entries.erase( locationsInsertionResult.first->second );
|
||||||
|
locationsInsertionResult.first->second = it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::update_last( UnicodeString const& line_ ) {
|
||||||
|
if ( _unique ) {
|
||||||
|
_locations.erase( _entries.back().text() );
|
||||||
|
remove_duplicate( line_ );
|
||||||
|
_locations.insert( make_pair( line_, last() ) );
|
||||||
|
}
|
||||||
|
_entries.back() = Entry( now_ms_str(), line_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::drop_last( void ) {
|
||||||
|
reset_current_scratch();
|
||||||
|
erase( last() );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool History::is_last( void ) {
|
||||||
|
return ( _current == last() );
|
||||||
|
}
|
||||||
|
|
||||||
|
History::entries_t::iterator History::last( void ) {
|
||||||
|
return ( moved( _entries.end(), -1 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::reset_iters( void ) {
|
||||||
|
_previous = _current = last();
|
||||||
|
_yankPos = _entries.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
162
lib/replxx/src/history.hxx
Normal file
162
lib/replxx/src/history.hxx
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
#ifndef REPLXX_HISTORY_HXX_INCLUDED
|
||||||
|
#define REPLXX_HISTORY_HXX_INCLUDED 1
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "unicodestring.hxx"
|
||||||
|
#include "utf8string.hxx"
|
||||||
|
#include "conversion.hxx"
|
||||||
|
#include "util.hxx"
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template<>
|
||||||
|
struct hash<replxx::UnicodeString> {
|
||||||
|
std::size_t operator()( replxx::UnicodeString const& us_ ) const {
|
||||||
|
std::size_t h( 0 );
|
||||||
|
char32_t const* p( us_.get() );
|
||||||
|
char32_t const* e( p + us_.length() );
|
||||||
|
while ( p != e ) {
|
||||||
|
h *= 31;
|
||||||
|
h += *p;
|
||||||
|
++ p;
|
||||||
|
}
|
||||||
|
return ( h );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace replxx {
|
||||||
|
|
||||||
|
class History {
|
||||||
|
public:
|
||||||
|
class Entry {
|
||||||
|
std::string _timestamp;
|
||||||
|
UnicodeString _text;
|
||||||
|
UnicodeString _scratch;
|
||||||
|
public:
|
||||||
|
Entry( std::string const& timestamp_, UnicodeString const& text_ )
|
||||||
|
: _timestamp( timestamp_ )
|
||||||
|
, _text( text_ )
|
||||||
|
, _scratch( text_ ) {
|
||||||
|
}
|
||||||
|
std::string const& timestamp( void ) const {
|
||||||
|
return ( _timestamp );
|
||||||
|
}
|
||||||
|
UnicodeString const& text( void ) const {
|
||||||
|
return ( _scratch );
|
||||||
|
}
|
||||||
|
void set_scratch( UnicodeString const& s ) {
|
||||||
|
_scratch = s;
|
||||||
|
}
|
||||||
|
void reset_scratch( void ) {
|
||||||
|
_scratch = _text;
|
||||||
|
}
|
||||||
|
bool operator < ( Entry const& other_ ) const {
|
||||||
|
return ( _timestamp < other_._timestamp );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
typedef std::list<Entry> entries_t;
|
||||||
|
typedef std::unordered_map<UnicodeString, entries_t::iterator> locations_t;
|
||||||
|
private:
|
||||||
|
entries_t _entries;
|
||||||
|
locations_t _locations;
|
||||||
|
int _maxSize;
|
||||||
|
entries_t::iterator _current;
|
||||||
|
entries_t::const_iterator _yankPos;
|
||||||
|
/*
|
||||||
|
* _previous and _recallMostRecent are used to allow
|
||||||
|
* HISTORY_NEXT action (a down-arrow key) to have a special meaning
|
||||||
|
* if invoked after a line from history was accepted without
|
||||||
|
* any modification.
|
||||||
|
* Special meaning is: a down arrow shall jump to the line one
|
||||||
|
* after previously accepted from history.
|
||||||
|
*/
|
||||||
|
entries_t::iterator _previous;
|
||||||
|
bool _recallMostRecent;
|
||||||
|
bool _unique;
|
||||||
|
public:
|
||||||
|
History( void );
|
||||||
|
void add( UnicodeString const& line, std::string const& when = now_ms_str() );
|
||||||
|
bool save( std::string const& filename, bool );
|
||||||
|
void save( std::ostream& histFile );
|
||||||
|
bool load( std::string const& filename );
|
||||||
|
void load( std::istream& histFile );
|
||||||
|
void clear( void );
|
||||||
|
void set_max_size( int len );
|
||||||
|
void set_unique( bool unique_ ) {
|
||||||
|
_unique = unique_;
|
||||||
|
remove_duplicates();
|
||||||
|
}
|
||||||
|
void reset_yank_iterator();
|
||||||
|
bool next_yank_position( void );
|
||||||
|
void reset_recall_most_recent( void ) {
|
||||||
|
_recallMostRecent = false;
|
||||||
|
}
|
||||||
|
void commit_index( void ) {
|
||||||
|
_previous = _current;
|
||||||
|
_recallMostRecent = true;
|
||||||
|
}
|
||||||
|
bool is_empty( void ) const {
|
||||||
|
return ( _entries.empty() );
|
||||||
|
}
|
||||||
|
void update_last( UnicodeString const& );
|
||||||
|
void drop_last( void );
|
||||||
|
bool is_last( void );
|
||||||
|
bool move( bool );
|
||||||
|
void set_current_scratch( UnicodeString const& s ) {
|
||||||
|
_current->set_scratch( s );
|
||||||
|
}
|
||||||
|
void reset_scratches( void ) {
|
||||||
|
for ( Entry& entry : _entries ) {
|
||||||
|
entry.reset_scratch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void reset_current_scratch( void ) {
|
||||||
|
_current->reset_scratch();
|
||||||
|
}
|
||||||
|
UnicodeString const& current( void ) const {
|
||||||
|
return ( _current->text() );
|
||||||
|
}
|
||||||
|
UnicodeString const& yank_line( void ) const {
|
||||||
|
return ( _yankPos->text() );
|
||||||
|
}
|
||||||
|
void jump( bool, bool = true );
|
||||||
|
bool common_prefix_search( UnicodeString const&, int, bool, bool );
|
||||||
|
int size( void ) const {
|
||||||
|
return ( static_cast<int>( _entries.size() ) );
|
||||||
|
}
|
||||||
|
Replxx::HistoryScan::impl_t scan( void ) const;
|
||||||
|
void save_pos( void );
|
||||||
|
void restore_pos( void );
|
||||||
|
private:
|
||||||
|
History( History const& ) = delete;
|
||||||
|
History& operator = ( History const& ) = delete;
|
||||||
|
bool move( entries_t::iterator&, int, bool = false );
|
||||||
|
entries_t::iterator moved( entries_t::iterator, int, bool = false );
|
||||||
|
void erase( entries_t::iterator );
|
||||||
|
void trim_to_max_size( void );
|
||||||
|
void remove_duplicate( UnicodeString const& );
|
||||||
|
void remove_duplicates( void );
|
||||||
|
void do_load( std::istream& );
|
||||||
|
entries_t::iterator last( void );
|
||||||
|
void sort( void );
|
||||||
|
void reset_iters( void );
|
||||||
|
};
|
||||||
|
|
||||||
|
class Replxx::HistoryScanImpl {
|
||||||
|
History::entries_t const& _entries;
|
||||||
|
History::entries_t::const_iterator _it;
|
||||||
|
mutable Utf8String _utf8Cache;
|
||||||
|
mutable Replxx::HistoryEntry _entryCache;
|
||||||
|
mutable bool _cacheValid;
|
||||||
|
public:
|
||||||
|
HistoryScanImpl( History::entries_t const& );
|
||||||
|
bool next( void );
|
||||||
|
Replxx::HistoryEntry const& get( void ) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
78
lib/replxx/src/killring.hxx
Normal file
78
lib/replxx/src/killring.hxx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#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;
|
||||||
|
|
||||||
|
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
|
||||||
|
|
90
lib/replxx/src/prompt.cxx
Normal file
90
lib/replxx/src/prompt.cxx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#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 )
|
||||||
|
, _cursorRowOffset( 0 )
|
||||||
|
, _screenColumns( 0 )
|
||||||
|
, _terminal( terminal_ ) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Prompt::write() {
|
||||||
|
_terminal.write32( _text.get(), _text.length() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Prompt::update_screen_columns( void ) {
|
||||||
|
_screenColumns = _terminal.get_screen_columns();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Prompt::set_text( UnicodeString const& text_ ) {
|
||||||
|
_text = text_;
|
||||||
|
update_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Prompt::update_state() {
|
||||||
|
_cursorRowOffset -= _extraLines;
|
||||||
|
_extraLines = 0;
|
||||||
|
_lastLinePosition = 0;
|
||||||
|
_screenColumns = 0;
|
||||||
|
update_screen_columns();
|
||||||
|
// strip control characters from the prompt -- we do allow newline
|
||||||
|
UnicodeString::const_iterator in( _text.begin() );
|
||||||
|
|
||||||
|
int x = 0;
|
||||||
|
int renderedSize( 0 );
|
||||||
|
_characterCount = virtual_render( _text.get(), _text.length(), x, _extraLines, _screenColumns, 0, _text.get(), &renderedSize );
|
||||||
|
_lastLinePosition = _characterCount - x;
|
||||||
|
_text.erase( renderedSize, _text.length() - renderedSize );
|
||||||
|
|
||||||
|
_cursorRowOffset += _extraLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Prompt::indentation() const {
|
||||||
|
return _characterCount - _lastLinePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used with DynamicPrompt (history search)
|
||||||
|
//
|
||||||
|
const UnicodeString forwardSearchBasePrompt("(i-search)`");
|
||||||
|
const UnicodeString reverseSearchBasePrompt("(reverse-i-search)`");
|
||||||
|
const UnicodeString endSearchBasePrompt("': ");
|
||||||
|
|
||||||
|
DynamicPrompt::DynamicPrompt( Terminal& terminal_, int initialDirection )
|
||||||
|
: Prompt( terminal_ )
|
||||||
|
, _searchText()
|
||||||
|
, _direction( initialDirection ) {
|
||||||
|
updateSearchPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicPrompt::updateSearchPrompt(void) {
|
||||||
|
update_screen_columns();
|
||||||
|
const UnicodeString* basePrompt =
|
||||||
|
(_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt;
|
||||||
|
_text.assign( *basePrompt ).append( _searchText ).append( endSearchBasePrompt );
|
||||||
|
update_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
46
lib/replxx/src/prompt.hxx
Normal file
46
lib/replxx/src/prompt.hxx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#ifndef REPLXX_PROMPT_HXX_INCLUDED
|
||||||
|
#define REPLXX_PROMPT_HXX_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "unicodestring.hxx"
|
||||||
|
#include "terminal.hxx"
|
||||||
|
|
||||||
|
namespace replxx {
|
||||||
|
|
||||||
|
class Prompt { // a convenience struct for grouping prompt info
|
||||||
|
public:
|
||||||
|
UnicodeString _text; // our copy of the prompt text, edited
|
||||||
|
int _characterCount{0}; // visible characters in _text
|
||||||
|
int _extraLines{0}; // extra lines (beyond 1) occupied by prompt
|
||||||
|
int _lastLinePosition{0}; // index into _text where last line begins
|
||||||
|
int _cursorRowOffset{0}; // where the cursor is relative to the start of the prompt
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _screenColumns{0}; // width of screen in columns [cache]
|
||||||
|
Terminal& _terminal;
|
||||||
|
public:
|
||||||
|
Prompt( Terminal& );
|
||||||
|
void set_text( UnicodeString const& textPtr );
|
||||||
|
void update_state();
|
||||||
|
void update_screen_columns( void );
|
||||||
|
int screen_columns() const {
|
||||||
|
return ( _screenColumns );
|
||||||
|
}
|
||||||
|
void write();
|
||||||
|
int indentation() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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
|
743
lib/replxx/src/replxx.cxx
Normal file
743
lib/replxx/src/replxx.cxx
Normal file
@ -0,0 +1,743 @@
|
|||||||
|
/*
|
||||||
|
* 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 "history.hxx"
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
static_cast<int>( replxx::Replxx::ACTION::SEND_EOF ) == static_cast<int>( REPLXX_ACTION_SEND_EOF ),
|
||||||
|
"C and C++ `ACTION` APIs are missaligned!"
|
||||||
|
);
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
static_cast<int>( replxx::Replxx::KEY::PASTE_FINISH ) == static_cast<int>( REPLXX_KEY_PASTE_FINISH ),
|
||||||
|
"C and C++ `KEY` APIs are missaligned!"
|
||||||
|
);
|
||||||
|
|
||||||
|
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_modify_callback( modify_callback_t const& fn ) {
|
||||||
|
_impl->set_modify_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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Replxx::history_sync( std::string const& filename ) {
|
||||||
|
return ( _impl->history_sync( filename ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Replxx::history_save( std::string const& filename ) {
|
||||||
|
return ( _impl->history_save( filename ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::history_save( std::ostream& out ) {
|
||||||
|
_impl->history_save( out );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Replxx::history_load( std::string const& filename ) {
|
||||||
|
return ( _impl->history_load( filename ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::history_load( std::istream& in ) {
|
||||||
|
_impl->history_load( in );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::history_clear( void ) {
|
||||||
|
_impl->history_clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Replxx::history_size( void ) const {
|
||||||
|
return ( _impl->history_size() );
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::HistoryScan Replxx::history_scan( void ) const {
|
||||||
|
return ( _impl->history_scan() );
|
||||||
|
}
|
||||||
|
|
||||||
|
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_immediate_completion( bool val ) {
|
||||||
|
_impl->set_immediate_completion( val );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::set_unique_history( bool val ) {
|
||||||
|
_impl->set_unique_history( val );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::set_no_color( bool val ) {
|
||||||
|
_impl->set_no_color( val );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::set_indent_multiline( bool val ) {
|
||||||
|
_impl->set_indent_multiline( 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_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::bind_key_internal( char32_t keyPress_, char const* actionName_ ) {
|
||||||
|
_impl->bind_key_internal( keyPress_, actionName_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::State Replxx::get_state( void ) const {
|
||||||
|
return ( _impl->get_state() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::set_state( Replxx::State const& state_ ) {
|
||||||
|
_impl->set_state( state_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::set_ignore_case( bool val ) {
|
||||||
|
_impl->set_ignore_case( val );
|
||||||
|
}
|
||||||
|
|
||||||
|
int Replxx::install_window_change_handler( void ) {
|
||||||
|
return ( _impl->install_window_change_handler() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::enable_bracketed_paste( void ) {
|
||||||
|
_impl->enable_bracketed_paste();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::disable_bracketed_paste( void ) {
|
||||||
|
_impl->disable_bracketed_paste();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::write( char const* str, int length ) {
|
||||||
|
return ( _impl->print( str, length ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Replxx::set_prompt( std::string prompt ) {
|
||||||
|
return ( _impl->set_prompt( std::move( prompt ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace color {
|
||||||
|
|
||||||
|
Replxx::Color operator | ( Replxx::Color color1_, Replxx::Color color2_ ) {
|
||||||
|
return static_cast<Replxx::Color>( static_cast<int unsigned>( color1_ ) | static_cast<int unsigned>( color2_ ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::Color bg( Replxx::Color color_ ) {
|
||||||
|
return static_cast<Replxx::Color>( ( ( static_cast<int unsigned>( color_ ) & 0xFFu ) << 8 ) | color::BACKGROUND_COLOR_SET );
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::Color bold( Replxx::Color color_ ) {
|
||||||
|
return static_cast<Replxx::Color>( static_cast<int unsigned>( color_ ) | color::BOLD );
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::Color underline( Replxx::Color color_ ) {
|
||||||
|
return static_cast<Replxx::Color>( static_cast<int unsigned>( color_ ) | color::UNDERLINE );
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::Color grayscale( int level_ ) {
|
||||||
|
assert( ( level_ >= 0 ) && ( level_ < 24 ) );
|
||||||
|
return static_cast<Replxx::Color>( abs( level_ ) % 24 + static_cast<int unsigned>( color::GRAYSCALE ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
Replxx::Color rgb666( int red_, int green_, int blue_ ) {
|
||||||
|
assert( ( red_ >= 0 ) && ( red_ < 6 ) && ( green_ >= 0 ) && ( green_ < 6 ) && ( blue_ >= 0 ) && ( blue_ < 6 ) );
|
||||||
|
return static_cast<Replxx::Color>(
|
||||||
|
( abs( red_ ) % 6 ) * 36
|
||||||
|
+ ( abs( green_ ) % 6 ) * 6
|
||||||
|
+ ( abs( blue_ ) % 6 )
|
||||||
|
+ static_cast<int unsigned>( color::RGB666 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
::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_ ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
int replxx_bind_key_internal( ::Replxx* replxx_, int code_, char const* actionName_ ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
try {
|
||||||
|
replxx->bind_key_internal( code_, actionName_ );
|
||||||
|
} catch ( ... ) {
|
||||||
|
return ( -1 );
|
||||||
|
}
|
||||||
|
return ( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void replxx_set_ignore_case( ::Replxx* replxx_, int val ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
replxx->set_ignore_case( val );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 is managed by replxx library
|
||||||
|
* and it must NOT be freed in the client.
|
||||||
|
*/
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
int replxx_write( ::Replxx* replxx_, char const* str, int length ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
try {
|
||||||
|
replxx->print( str, length );
|
||||||
|
} catch ( ... ) {
|
||||||
|
return ( -1 );
|
||||||
|
}
|
||||||
|
return static_cast<int>( length );
|
||||||
|
}
|
||||||
|
|
||||||
|
void replxx_set_prompt( ::Replxx* replxx_, const char* prompt ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
replxx->set_prompt( prompt );
|
||||||
|
}
|
||||||
|
|
||||||
|
struct replxx_completions {
|
||||||
|
replxx::Replxx::completions_t data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct replxx_hints {
|
||||||
|
replxx::Replxx::hints_t data;
|
||||||
|
};
|
||||||
|
|
||||||
|
void modify_fwd( replxx_modify_callback_t fn, std::string& line_, int& cursorPosition_, void* userData_ ) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define strdup _strdup
|
||||||
|
#endif
|
||||||
|
char* s( strdup( line_.c_str() ) );
|
||||||
|
#undef strdup
|
||||||
|
fn( &s, &cursorPosition_, userData_ );
|
||||||
|
line_ = s;
|
||||||
|
free( s );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void replxx_set_modify_callback(::Replxx* replxx_, replxx_modify_callback_t* fn, void* userData) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
replxx->set_modify_callback( std::bind( &modify_fwd, fn, _1, _2, userData ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
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(), static_cast<int>( 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_color_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_indent_multiline( ::Replxx* replxx_, int val ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
replxx->set_indent_multiline( 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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
void replxx_set_immediate_completion( ::Replxx* replxx_, int val ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
replxx->set_immediate_completion( val ? true : false );
|
||||||
|
}
|
||||||
|
|
||||||
|
void replxx_set_unique_history( ::Replxx* replxx_, int val ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
replxx->set_unique_history( val ? true : false );
|
||||||
|
}
|
||||||
|
|
||||||
|
void replxx_enable_bracketed_paste( ::Replxx* replxx_ ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
replxx->enable_bracketed_paste();
|
||||||
|
}
|
||||||
|
|
||||||
|
void replxx_disable_bracketed_paste( ::Replxx* replxx_ ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
replxx->disable_bracketed_paste();
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplxxHistoryScan* replxx_history_scan_start( ::Replxx* replxx_ ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
return ( reinterpret_cast<ReplxxHistoryScan*>( replxx->history_scan().release() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void replxx_history_scan_stop( ::Replxx*, ReplxxHistoryScan* historyScan_ ) {
|
||||||
|
delete reinterpret_cast<replxx::Replxx::HistoryScanImpl*>( historyScan_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
int replxx_history_scan_next( ::Replxx*, ReplxxHistoryScan* historyScan_, ReplxxHistoryEntry* historyEntry_ ) {
|
||||||
|
replxx::Replxx::HistoryScanImpl* historyScan( reinterpret_cast<replxx::Replxx::HistoryScanImpl*>( historyScan_ ) );
|
||||||
|
bool hasNext( historyScan->next() );
|
||||||
|
if ( hasNext ) {
|
||||||
|
replxx::Replxx::HistoryEntry const& historyEntry( historyScan->get() );
|
||||||
|
historyEntry_->timestamp = historyEntry.timestamp().c_str();
|
||||||
|
historyEntry_->text = historyEntry.text().c_str();
|
||||||
|
}
|
||||||
|
return ( hasNext ? 0 : -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save the history in the specified file. On success 0 is returned
|
||||||
|
* otherwise -1 is returned. */
|
||||||
|
int replxx_history_sync( ::Replxx* replxx_, const char* filename ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
return ( replxx->history_sync( filename ) ? 0 : -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 ) ? 0 : -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 ) ? 0 : -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
void replxx_history_clear( ::Replxx* replxx_ ) {
|
||||||
|
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||||
|
replxx->history_clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
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() );
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace replxx::color;
|
||||||
|
ReplxxColor replxx_color_combine( ReplxxColor color1_, ReplxxColor color2_ ) {
|
||||||
|
return static_cast<ReplxxColor>( static_cast<replxx::Replxx::Color>( color1_ ) | static_cast<replxx::Replxx::Color>( color2_ ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplxxColor replxx_color_bg( ReplxxColor color_ ) {
|
||||||
|
return static_cast<ReplxxColor>( color::bg( static_cast<replxx::Replxx::Color>( color_ ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplxxColor replxx_color_bold( ReplxxColor color_ ) {
|
||||||
|
return static_cast<ReplxxColor>( color::bold( static_cast<replxx::Replxx::Color>( color_ ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplxxColor replxx_color_underline( ReplxxColor color_ ) {
|
||||||
|
return static_cast<ReplxxColor>( color::underline( static_cast<replxx::Replxx::Color>( color_ ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplxxColor replxx_color_grayscale( int level_ ) {
|
||||||
|
return static_cast<ReplxxColor>( color::grayscale( level_ ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplxxColor replxx_color_rgb666( int r_, int g_, int b_ ) {
|
||||||
|
return static_cast<ReplxxColor>( color::rgb666( r_, g_, b_ ) );
|
||||||
|
}
|
||||||
|
|
2577
lib/replxx/src/replxx_impl.cxx
Normal file
2577
lib/replxx/src/replxx_impl.cxx
Normal file
File diff suppressed because it is too large
Load Diff
304
lib/replxx/src/replxx_impl.hxx
Normal file
304
lib/replxx/src/replxx_impl.hxx
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
/*
|
||||||
|
* 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 <chrono>
|
||||||
|
#include <iosfwd>
|
||||||
|
|
||||||
|
#include "replxx.hxx"
|
||||||
|
#include "history.hxx"
|
||||||
|
#include "killring.hxx"
|
||||||
|
#include "utf8string.hxx"
|
||||||
|
#include "prompt.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> data_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<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 std::unordered_map<std::string, Replxx::key_press_handler_t> named_actions_t;
|
||||||
|
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 MOVE_CURSOR = 2;
|
||||||
|
static action_trait_t const RESET_KILL_ACTION = 4;
|
||||||
|
static action_trait_t const SET_KILL_ACTION = 8;
|
||||||
|
static action_trait_t const DONT_RESET_PREFIX = 16;
|
||||||
|
static action_trait_t const DONT_RESET_COMPLETIONS = 32;
|
||||||
|
static action_trait_t const HISTORY_RECALL_MOST_RECENT = 64;
|
||||||
|
static action_trait_t const DONT_RESET_HIST_YANK_INDEX = 128;
|
||||||
|
private:
|
||||||
|
mutable Utf8String _utf8Buffer;
|
||||||
|
UnicodeString _data;
|
||||||
|
int _pos; // character position in buffer ( 0 <= _pos <= _data[_line].length() )
|
||||||
|
display_t _display;
|
||||||
|
int _displayInputLength;
|
||||||
|
UnicodeString _hint;
|
||||||
|
int _prefix; // prefix length used in common prefix search
|
||||||
|
int _hintSelection; // Currently selected hint.
|
||||||
|
History _history;
|
||||||
|
KillRing _killRing;
|
||||||
|
int long long _lastRefreshTime;
|
||||||
|
bool _refreshSkipped;
|
||||||
|
int _lastYankSize;
|
||||||
|
int _maxHintRows;
|
||||||
|
int _hintDelay;
|
||||||
|
std::string _wordBreakChars;
|
||||||
|
std::string _subwordBreakChars;
|
||||||
|
int _completionCountCutoff;
|
||||||
|
bool _overwrite;
|
||||||
|
bool _doubleTabCompletion;
|
||||||
|
bool _completeOnEmpty;
|
||||||
|
bool _beepOnAmbiguousCompletion;
|
||||||
|
bool _immediateCompletion;
|
||||||
|
bool _bracketedPaste;
|
||||||
|
bool _noColor;
|
||||||
|
bool _indentMultiline;
|
||||||
|
named_actions_t _namedActions;
|
||||||
|
key_press_handlers_t _keyPressHandlers;
|
||||||
|
Terminal _terminal;
|
||||||
|
std::thread::id _currentThread;
|
||||||
|
Prompt _prompt;
|
||||||
|
Replxx::modify_callback_t _modifyCallback;
|
||||||
|
Replxx::completion_callback_t _completionCallback;
|
||||||
|
Replxx::highlighter_callback_t _highlighterCallback;
|
||||||
|
Replxx::hint_callback_t _hintCallback;
|
||||||
|
key_presses_t _keyPresses;
|
||||||
|
messages_t _messages;
|
||||||
|
std::string _asyncPrompt;
|
||||||
|
bool _updatePrompt;
|
||||||
|
completions_t _completions;
|
||||||
|
int _completionContextLength;
|
||||||
|
int _completionSelection;
|
||||||
|
std::string _preloadedBuffer; // used with set_preload_buffer
|
||||||
|
std::string _errorMessage;
|
||||||
|
UnicodeString _previousSearchText; // remembered across invocations of replxx_input()
|
||||||
|
bool _modifiedState;
|
||||||
|
Replxx::Color _hintColor;
|
||||||
|
hints_t _hintsCache;
|
||||||
|
int _hintContextLenght;
|
||||||
|
Utf8String _hintSeed;
|
||||||
|
bool _hasNewlines;
|
||||||
|
int _oldPos;
|
||||||
|
bool _moveCursor;
|
||||||
|
bool _ignoreCase;
|
||||||
|
mutable std::mutex _mutex;
|
||||||
|
public:
|
||||||
|
ReplxxImpl( FILE*, FILE*, FILE* );
|
||||||
|
virtual ~ReplxxImpl( void );
|
||||||
|
void set_modify_callback( Replxx::modify_callback_t const& fn );
|
||||||
|
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 );
|
||||||
|
bool history_sync( std::string const& filename );
|
||||||
|
bool history_save( std::string const& filename );
|
||||||
|
void history_save( std::ostream& out );
|
||||||
|
bool history_load( std::string const& filename );
|
||||||
|
void history_load( std::istream& in );
|
||||||
|
void history_clear( void );
|
||||||
|
Replxx::HistoryScan::impl_t history_scan( void ) const;
|
||||||
|
int history_size( void ) const;
|
||||||
|
void set_preload_buffer(std::string const& preloadText);
|
||||||
|
void set_word_break_characters( char const* wordBreakers );
|
||||||
|
void set_subword_break_characters( char const* subwordBreakers );
|
||||||
|
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_immediate_completion( bool val );
|
||||||
|
void set_unique_history( bool );
|
||||||
|
void set_no_color( bool val );
|
||||||
|
void set_indent_multiline( bool val );
|
||||||
|
void set_max_history_size( int len );
|
||||||
|
void set_completion_count_cutoff( int len );
|
||||||
|
int install_window_change_handler( void );
|
||||||
|
void enable_bracketed_paste( void );
|
||||||
|
void disable_bracketed_paste( void );
|
||||||
|
void print( char const*, int );
|
||||||
|
void set_prompt( std::string prompt );
|
||||||
|
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 );
|
||||||
|
void bind_key_internal( char32_t, char const* );
|
||||||
|
Replxx::State get_state( void ) const;
|
||||||
|
void set_state( Replxx::State const& );
|
||||||
|
void set_ignore_case( bool val );
|
||||||
|
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 new_line( 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 );
|
||||||
|
template <bool subword>
|
||||||
|
Replxx::ACTION_RESULT move_one_word_left( char32_t );
|
||||||
|
template <bool subword>
|
||||||
|
Replxx::ACTION_RESULT move_one_word_right( char32_t );
|
||||||
|
template <bool subword>
|
||||||
|
Replxx::ACTION_RESULT kill_word_to_left( char32_t );
|
||||||
|
template <bool subword>
|
||||||
|
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 yank_last_arg( char32_t );
|
||||||
|
template <bool subword>
|
||||||
|
Replxx::ACTION_RESULT capitalize_word( char32_t );
|
||||||
|
template <bool subword>
|
||||||
|
Replxx::ACTION_RESULT lowercase_word( char32_t );
|
||||||
|
template <bool subword>
|
||||||
|
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 line_next( char32_t );
|
||||||
|
Replxx::ACTION_RESULT line_previous( 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_restore( char32_t );
|
||||||
|
Replxx::ACTION_RESULT history_restore_current( 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 );
|
||||||
|
Replxx::ACTION_RESULT bracketed_paste( 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 call_modify_callback( 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 refresh_line( HINT_ACTION = HINT_ACTION::REGENERATE );
|
||||||
|
void move_cursor( void );
|
||||||
|
void indent( void );
|
||||||
|
int virtual_render( char32_t const*, int, int&, int&, Prompt const* = nullptr );
|
||||||
|
void render( char32_t );
|
||||||
|
void render( HINT_ACTION );
|
||||||
|
void handle_hints( HINT_ACTION );
|
||||||
|
void set_color( Replxx::Color );
|
||||||
|
int context_length( void );
|
||||||
|
int prev_newline_position( int ) const;
|
||||||
|
int next_newline_position( int ) const;
|
||||||
|
int pos_in_line( void ) const;
|
||||||
|
void clear( void );
|
||||||
|
void repaint( void );
|
||||||
|
template <bool subword>
|
||||||
|
bool is_word_break_character( char32_t ) const;
|
||||||
|
void dynamic_refresh(Prompt& oldPrompt, Prompt& newPrompt, char32_t* buf32, int len, int pos);
|
||||||
|
char const* finalize_input( char const* );
|
||||||
|
void clear_self_to_end_of_screen( Prompt const* = nullptr );
|
||||||
|
typedef struct {
|
||||||
|
int index;
|
||||||
|
bool error;
|
||||||
|
} paren_info_t;
|
||||||
|
paren_info_t matching_paren( void );
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
781
lib/replxx/src/terminal.cxx
Normal file
781
lib/replxx/src/terminal.cxx
Normal file
@ -0,0 +1,781 @@
|
|||||||
|
#include <memory>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <array>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
|
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||||
|
static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "windows.hxx"
|
||||||
|
|
||||||
|
#else /* _WIN32 */
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
|
#include "terminal.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 ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
Terminal* _terminal_ = nullptr;
|
||||||
|
static void WindowSizeChanged( int ) {
|
||||||
|
if ( ! _terminal_ ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_terminal_->notify_event( Terminal::EVENT_TYPE::RESIZE );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
Terminal::Terminal( void )
|
||||||
|
#ifdef _WIN32
|
||||||
|
: _consoleOut( INVALID_HANDLE_VALUE )
|
||||||
|
, _consoleIn( INVALID_HANDLE_VALUE )
|
||||||
|
, _origOutMode()
|
||||||
|
, _origInMode()
|
||||||
|
, _oldDisplayAttribute()
|
||||||
|
, _inputCodePage( GetConsoleCP() )
|
||||||
|
, _outputCodePage( GetConsoleOutputCP() )
|
||||||
|
, _interrupt( INVALID_HANDLE_VALUE )
|
||||||
|
, _events()
|
||||||
|
, _empty()
|
||||||
|
#else
|
||||||
|
: _origTermios()
|
||||||
|
, _rawModeTermios()
|
||||||
|
, _interrupt()
|
||||||
|
#endif
|
||||||
|
, _rawMode( false )
|
||||||
|
, _utf8() {
|
||||||
|
#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 ) {
|
||||||
|
_utf8.assign( text32, len32 );
|
||||||
|
write8( _utf8.get(), _utf8.size() );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::write8( char const* data_, int size_ ) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
bool temporarilyEnabled( false );
|
||||||
|
if ( _consoleOut == INVALID_HANDLE_VALUE ) {
|
||||||
|
enable_out();
|
||||||
|
temporarilyEnabled = true;
|
||||||
|
}
|
||||||
|
int nWritten( win_write( _consoleOut, _autoEscape, data_, size_ ) );
|
||||||
|
if ( temporarilyEnabled ) {
|
||||||
|
disable_out();
|
||||||
|
}
|
||||||
|
#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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::enable_out( void ) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetConsoleOutputCP( 65001 );
|
||||||
|
_consoleOut = GetStdHandle( STD_OUTPUT_HANDLE );
|
||||||
|
GetConsoleMode( _consoleOut, &_origOutMode );
|
||||||
|
_autoEscape = SetConsoleMode( _consoleOut, _origOutMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::disable_out( void ) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetConsoleMode( _consoleOut, _origOutMode );
|
||||||
|
SetConsoleOutputCP( _outputCodePage );
|
||||||
|
_consoleOut = INVALID_HANDLE_VALUE;
|
||||||
|
_autoEscape = false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::enable_bracketed_paste( void ) {
|
||||||
|
static char const BRACK_PASTE_INIT[] = "\033[?2004h";
|
||||||
|
write8( BRACK_PASTE_INIT, sizeof ( BRACK_PASTE_INIT ) - 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::disable_bracketed_paste( void ) {
|
||||||
|
static char const BRACK_PASTE_DISABLE[] = "\033[?2004l";
|
||||||
|
write8( BRACK_PASTE_DISABLE, sizeof ( BRACK_PASTE_DISABLE ) - 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
int Terminal::enable_raw_mode( void ) {
|
||||||
|
if ( _rawMode ) {
|
||||||
|
return ( 0 );
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
_consoleIn = GetStdHandle( STD_INPUT_HANDLE );
|
||||||
|
GetConsoleMode( _consoleIn, &_origInMode );
|
||||||
|
#else
|
||||||
|
|
||||||
|
if ( ! tty::in ) {
|
||||||
|
return ( notty() );
|
||||||
|
}
|
||||||
|
if ( tcgetattr( 0, &_origTermios ) == -1 ) {
|
||||||
|
return ( notty() );
|
||||||
|
}
|
||||||
|
|
||||||
|
_rawModeTermios = _origTermios; /* modify the original mode */
|
||||||
|
/* input modes: no break, no CR to NL, no parity check, no strip char,
|
||||||
|
* no start/stop output control. */
|
||||||
|
_rawModeTermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||||
|
/* output modes - disable post processing */
|
||||||
|
// this is wrong, we don't want _rawModeTermios output, it turns newlines into straight
|
||||||
|
// linefeeds
|
||||||
|
// _rawModeTermios.c_oflag &= ~(OPOST);
|
||||||
|
/* control modes - set 8 bit chars */
|
||||||
|
_rawModeTermios.c_cflag |= (CS8);
|
||||||
|
/* local modes - echoing off, canonical off, no extended functions,
|
||||||
|
* no signal chars (^Z,^C) */
|
||||||
|
_rawModeTermios.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. */
|
||||||
|
_rawModeTermios.c_cc[VMIN] = 1;
|
||||||
|
_rawModeTermios.c_cc[VTIME] = 0; /* 1 byte, no timer */
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_rawMode = true;
|
||||||
|
if ( reset_raw_mode() < 0 ) {
|
||||||
|
_rawMode = false;
|
||||||
|
return ( notty() );
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
_terminal_ = this;
|
||||||
|
#endif
|
||||||
|
return ( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
int Terminal::reset_raw_mode( void ) {
|
||||||
|
if ( ! _rawMode ) {
|
||||||
|
return ( -1 );
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetConsoleMode(
|
||||||
|
_consoleIn,
|
||||||
|
( _origInMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT ) ) | ENABLE_QUICK_EDIT_MODE
|
||||||
|
);
|
||||||
|
SetConsoleCP( 65001 );
|
||||||
|
enable_out();
|
||||||
|
return ( 0 );
|
||||||
|
#else
|
||||||
|
/* put terminal in raw mode after flushing */
|
||||||
|
return ( tcsetattr( 0, TCSADRAIN, &_rawModeTermios ) );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::disable_raw_mode(void) {
|
||||||
|
if ( ! _rawMode ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
disable_out();
|
||||||
|
SetConsoleMode( _consoleIn, _origInMode );
|
||||||
|
SetConsoleCP( _inputCodePage );
|
||||||
|
_consoleIn = INVALID_HANDLE_VALUE;
|
||||||
|
#else
|
||||||
|
_terminal_ = nullptr;
|
||||||
|
if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
_rawMode = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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
|
||||||
|
DWORD const AltGr( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED );
|
||||||
|
if ( ( rec.Event.KeyEvent.dwControlKeyState & AltGr ) == AltGr ) {
|
||||||
|
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 ) {
|
||||||
|
if ( rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED ) {
|
||||||
|
modifierKeys |= Replxx::KEY::BASE_SHIFT;
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
if ( ( key == 13 ) && ( rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED ) ) {
|
||||||
|
key = 10;
|
||||||
|
}
|
||||||
|
// 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 = control_to_human( key );
|
||||||
|
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] = control_to_human( keyCopy );
|
||||||
|
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( control_to_human( c ) );
|
||||||
|
}
|
||||||
|
#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( static_cast<DWORD>( 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:
|
||||||
|
case VK_F1:
|
||||||
|
case VK_F2:
|
||||||
|
case VK_F3:
|
||||||
|
case VK_F4:
|
||||||
|
case VK_F5:
|
||||||
|
case VK_F6:
|
||||||
|
case VK_F7:
|
||||||
|
case VK_F8:
|
||||||
|
case VK_F9:
|
||||||
|
case VK_F10:
|
||||||
|
case VK_F11:
|
||||||
|
case VK_F12:
|
||||||
|
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 ( data == 'r' ) {
|
||||||
|
return ( EVENT_TYPE::RESIZE );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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' : ( eventType_ == EVENT_TYPE::MESSAGE ? 'm' : 'r' ) );
|
||||||
|
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
|
||||||
|
if ( _autoEscape ) {
|
||||||
|
#endif
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
#ifdef _WIN32
|
||||||
|
}
|
||||||
|
COORD coord = { 0, 0 };
|
||||||
|
CONSOLE_SCREEN_BUFFER_INFO inf;
|
||||||
|
HANDLE consoleOut( _consoleOut != INVALID_HANDLE_VALUE ? _consoleOut : GetStdHandle( STD_OUTPUT_HANDLE ) );
|
||||||
|
GetConsoleScreenBufferInfo( consoleOut, &inf );
|
||||||
|
if ( clearScreen_ == CLEAR_SCREEN::TO_END ) {
|
||||||
|
coord = inf.dwCursorPosition;
|
||||||
|
DWORD nWritten( 0 );
|
||||||
|
SHORT height( inf.srWindow.Bottom - inf.srWindow.Top );
|
||||||
|
DWORD yPos( inf.dwCursorPosition.Y - inf.srWindow.Top );
|
||||||
|
DWORD toWrite( ( height + 1 - yPos ) * inf.dwSize.X - inf.dwCursorPosition.X );
|
||||||
|
// FillConsoleOutputCharacterA( consoleOut, ' ', toWrite, coord, &nWritten );
|
||||||
|
_empty.resize( toWrite - 1, ' ' );
|
||||||
|
WriteConsoleA( consoleOut, _empty.data(), toWrite - 1, &nWritten, nullptr );
|
||||||
|
} else {
|
||||||
|
COORD scrollTarget = { 0, static_cast<SHORT>( -inf.dwSize.Y ) };
|
||||||
|
CHAR_INFO fill{ TEXT( ' ' ), inf.wAttributes };
|
||||||
|
SMALL_RECT scrollRect = { 0, 0, inf.dwSize.X, inf.dwSize.Y };
|
||||||
|
ScrollConsoleScreenBuffer( consoleOut, &scrollRect, nullptr, scrollTarget, &fill );
|
||||||
|
}
|
||||||
|
SetConsoleCursorPosition( consoleOut, coord );
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
void Terminal::set_cursor_visible( bool visible_ ) {
|
||||||
|
CONSOLE_CURSOR_INFO cursorInfo;
|
||||||
|
GetConsoleCursorInfo( _consoleOut, &cursorInfo );
|
||||||
|
cursorInfo.bVisible = visible_;
|
||||||
|
SetConsoleCursorInfo( _consoleOut, &cursorInfo );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void Terminal::set_cursor_visible( bool ) {}
|
||||||
|
#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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
int Terminal::install_window_change_handler( void ) {
|
||||||
|
struct sigaction sa;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
sa.sa_handler = &WindowSizeChanged;
|
||||||
|
|
||||||
|
if (sigaction(SIGWINCH, &sa, nullptr) == -1) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
96
lib/replxx/src/terminal.hxx
Normal file
96
lib/replxx/src/terminal.hxx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#ifndef REPLXX_IO_HXX_INCLUDED
|
||||||
|
#define REPLXX_IO_HXX_INCLUDED 1
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <vector>
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <termios.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "utf8string.hxx"
|
||||||
|
|
||||||
|
namespace replxx {
|
||||||
|
|
||||||
|
class Terminal {
|
||||||
|
public:
|
||||||
|
enum class EVENT_TYPE {
|
||||||
|
KEY_PRESS,
|
||||||
|
MESSAGE,
|
||||||
|
TIMEOUT,
|
||||||
|
RESIZE
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE _consoleOut;
|
||||||
|
HANDLE _consoleIn;
|
||||||
|
DWORD _origOutMode;
|
||||||
|
DWORD _origInMode;
|
||||||
|
bool _autoEscape;
|
||||||
|
WORD _oldDisplayAttribute;
|
||||||
|
UINT const _inputCodePage;
|
||||||
|
UINT const _outputCodePage;
|
||||||
|
HANDLE _interrupt;
|
||||||
|
typedef std::deque<EVENT_TYPE> events_t;
|
||||||
|
events_t _events;
|
||||||
|
std::vector<char> _empty;
|
||||||
|
#else
|
||||||
|
struct termios _origTermios; /* in order to restore at exit */
|
||||||
|
struct termios _rawModeTermios; /* in order to reset raw mode after callbacks */
|
||||||
|
int _interrupt[2];
|
||||||
|
#endif
|
||||||
|
bool _rawMode; /* for destructor to check if restore is needed */
|
||||||
|
Utf8String _utf8;
|
||||||
|
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);
|
||||||
|
void enable_bracketed_paste( void );
|
||||||
|
void disable_bracketed_paste( void );
|
||||||
|
int enable_raw_mode(void);
|
||||||
|
int reset_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 );
|
||||||
|
void set_cursor_visible( bool );
|
||||||
|
#ifndef _WIN32
|
||||||
|
int read_verbatim( char32_t*, int );
|
||||||
|
int install_window_change_handler( void );
|
||||||
|
#endif
|
||||||
|
private:
|
||||||
|
void enable_out( void );
|
||||||
|
void disable_out( void );
|
||||||
|
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
|
||||||
|
|
228
lib/replxx/src/unicodestring.hxx
Normal file
228
lib/replxx/src/unicodestring.hxx
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
#ifndef REPLXX_UNICODESTRING_HXX_INCLUDED
|
||||||
|
#define REPLXX_UNICODESTRING_HXX_INCLUDED
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cwctype>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "conversion.hxx"
|
||||||
|
|
||||||
|
namespace replxx {
|
||||||
|
|
||||||
|
inline bool case_sensitive_equal( char32_t l, char32_t r ) {
|
||||||
|
return l == r;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool case_insensitive_equal( char32_t l, char32_t r ) {
|
||||||
|
return towlower( static_cast<wint_t>( l ) ) == towlower( static_cast<wint_t>( r ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
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( UnicodeString const& other, int offset, int len = -1 )
|
||||||
|
: _data() {
|
||||||
|
_data.insert(
|
||||||
|
_data.end(),
|
||||||
|
other._data.begin() + offset,
|
||||||
|
len > 0 ? other._data.begin() + offset + len : other._data.end()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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( static_cast<int>( str_.length() ) );
|
||||||
|
int len( 0 );
|
||||||
|
copyString8to32( _data.data(), static_cast<int>( str_.length() ), len, str_.c_str() );
|
||||||
|
_data.resize( len );
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnicodeString& assign( char const* str_ ) {
|
||||||
|
int byteCount( static_cast<int>( 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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator < ( UnicodeString const& other_ ) const {
|
||||||
|
return std::lexicographical_compare(begin(), end(), other_.begin(), other_.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
UnicodeString& append( UnicodeString const& other ) {
|
||||||
|
_data.insert( _data.end(), other._data.begin(), other._data.end() );
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_back( char32_t c_ ) {
|
||||||
|
_data.push_back( c_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
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[]( int pos ) const {
|
||||||
|
assert( ( pos >= 0 ) && ( pos < static_cast<int>( _data.size() ) ) );
|
||||||
|
return _data[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
char32_t& operator[]( int pos ) {
|
||||||
|
assert( ( pos >= 0 ) && ( pos < static_cast<int>( _data.size() ) ) );
|
||||||
|
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() ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class BinaryPredicate>
|
||||||
|
bool starts_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_, BinaryPredicate&& pred ) const {
|
||||||
|
return (
|
||||||
|
( std::distance( first_, last_ ) <= length() )
|
||||||
|
&& ( std::equal( first_, last_, _data.begin(), std::forward<BinaryPredicate>( pred ) ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ends_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const {
|
||||||
|
int len( static_cast<int>( std::distance( first_, last_ ) ) );
|
||||||
|
return (
|
||||||
|
( len <= length() )
|
||||||
|
&& ( std::equal( first_, last_, _data.end() - len ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() );
|
||||||
|
}
|
||||||
|
|
||||||
|
char32_t back( void ) const {
|
||||||
|
return ( _data.back() );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
100
lib/replxx/src/utf8string.hxx
Normal file
100
lib/replxx/src/utf8string.hxx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
#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;
|
||||||
|
int _len;
|
||||||
|
public:
|
||||||
|
Utf8String( void )
|
||||||
|
: _data()
|
||||||
|
, _bufSize( 0 )
|
||||||
|
, _len( 0 ) {
|
||||||
|
}
|
||||||
|
explicit Utf8String( UnicodeString const& src )
|
||||||
|
: _data()
|
||||||
|
, _bufSize( 0 )
|
||||||
|
, _len( 0 ) {
|
||||||
|
assign( src, src.length() );
|
||||||
|
}
|
||||||
|
|
||||||
|
Utf8String( UnicodeString const& src_, int len_ )
|
||||||
|
: _data()
|
||||||
|
, _bufSize( 0 )
|
||||||
|
, _len( 0 ) {
|
||||||
|
assign( src_, len_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign( UnicodeString const& str_ ) {
|
||||||
|
assign( str_, str_.length() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign( UnicodeString const& str_, int len_ ) {
|
||||||
|
assign( str_.get(), len_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign( char32_t const* str_, int len_ ) {
|
||||||
|
int len( len_ * 4 );
|
||||||
|
realloc( len );
|
||||||
|
_len = copyString32to8( _data.get(), len, str_, len_ );
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign( std::string const& str_ ) {
|
||||||
|
realloc( static_cast<int>( str_.length() ) );
|
||||||
|
strncpy( _data.get(), str_.c_str(), str_.length() );
|
||||||
|
_len = static_cast<int>( str_.length() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign( Utf8String const& other_ ) {
|
||||||
|
realloc( other_._len );
|
||||||
|
strncpy( _data.get(), other_._data.get(), other_._len );
|
||||||
|
_len = other_._len;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* get() const {
|
||||||
|
return _data.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
int size( void ) const {
|
||||||
|
return ( _len );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator != ( Utf8String const& other_ ) {
|
||||||
|
return (
|
||||||
|
( other_._len != _len )
|
||||||
|
|| (
|
||||||
|
( _len != 0 )
|
||||||
|
&& ( memcmp( other_._data.get(), _data.get(), _len ) != 0 )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
179
lib/replxx/src/util.cxx
Normal file
179
lib/replxx/src/util.cxx
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
#include <wctype.h>
|
||||||
|
|
||||||
|
#include "util.hxx"
|
||||||
|
#include "terminal.hxx"
|
||||||
|
|
||||||
|
#undef min
|
||||||
|
|
||||||
|
namespace replxx {
|
||||||
|
|
||||||
|
int mk_wcwidth( char32_t );
|
||||||
|
|
||||||
|
int virtual_render( char32_t const* display_, int size_, int& x_, int& y_, int screenColumns_, int promptLen_, char32_t* rendered_, int* renderedSize_ ) {
|
||||||
|
char32_t* out( rendered_ );
|
||||||
|
int visibleCount( 0 );
|
||||||
|
auto render = [&rendered_, &renderedSize_, &out, &visibleCount]( char32_t c_, bool visible_, bool renderAttributes_ = true ) {
|
||||||
|
if ( rendered_ && renderedSize_ && renderAttributes_ ) {
|
||||||
|
*out = c_;
|
||||||
|
++ out;
|
||||||
|
if ( visible_ ) {
|
||||||
|
++ visibleCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bool wrapped( false );
|
||||||
|
auto advance_cursor = [&x_, &y_, &screenColumns_, &wrapped]( int by_ = 1 ) {
|
||||||
|
wrapped = false;
|
||||||
|
x_ += by_;
|
||||||
|
if ( x_ >= screenColumns_ ) {
|
||||||
|
x_ = 0;
|
||||||
|
++ y_;
|
||||||
|
wrapped = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bool const renderAttributes( !!tty::out );
|
||||||
|
int pos( 0 );
|
||||||
|
while ( pos < size_ ) {
|
||||||
|
char32_t c( display_[pos] );
|
||||||
|
if ( ( c == '\n' ) || ( c == '\r' ) ) {
|
||||||
|
render( c, true );
|
||||||
|
if ( ( c == '\n' ) && ! wrapped ) {
|
||||||
|
++ y_;
|
||||||
|
}
|
||||||
|
x_ = promptLen_;
|
||||||
|
++ pos;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( c == '\b' ) {
|
||||||
|
render( c, true );
|
||||||
|
-- x_;
|
||||||
|
if ( x_ < 0 ) {
|
||||||
|
x_ = screenColumns_ - 1;
|
||||||
|
-- y_;
|
||||||
|
}
|
||||||
|
++ pos;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( c == '\033' ) {
|
||||||
|
render( c, false, renderAttributes );
|
||||||
|
++ pos;
|
||||||
|
if ( pos >= size_ ) {
|
||||||
|
advance_cursor( 2 );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
c = display_[pos];
|
||||||
|
if ( c != '[' ) {
|
||||||
|
advance_cursor( 2 );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
render( c, false, renderAttributes );
|
||||||
|
++ pos;
|
||||||
|
if ( pos >= size_ ) {
|
||||||
|
advance_cursor( 3 );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int codeLen( 0 );
|
||||||
|
while ( pos < size_ ) {
|
||||||
|
c = display_[pos];
|
||||||
|
if ( ( c != ';' ) && ( ( c < '0' ) || ( c > '9' ) ) ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
render( c, false, renderAttributes );
|
||||||
|
++ codeLen;
|
||||||
|
++ pos;
|
||||||
|
}
|
||||||
|
if ( pos >= size_ ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
c = display_[pos];
|
||||||
|
if ( c != 'm' ) {
|
||||||
|
advance_cursor( 3 + codeLen );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
render( c, false, renderAttributes );
|
||||||
|
++ pos;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( is_control_code( c ) ) {
|
||||||
|
render( c, true );
|
||||||
|
advance_cursor( 2 );
|
||||||
|
++ pos;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int wcw( mk_wcwidth( c ) );
|
||||||
|
if ( wcw < 0 ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
render( c, true );
|
||||||
|
advance_cursor( wcw );
|
||||||
|
++ pos;
|
||||||
|
}
|
||||||
|
if ( rendered_ && renderedSize_ ) {
|
||||||
|
*renderedSize_ = out - rendered_;
|
||||||
|
}
|
||||||
|
return ( visibleCount );
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* ansi_color( Replxx::Color color_ ) {
|
||||||
|
int unsigned code( static_cast<int unsigned>( color_ ) );
|
||||||
|
int unsigned fg( code & 0xFFu );
|
||||||
|
int unsigned bg( ( code >> 8 ) & 0xFFu );
|
||||||
|
char const* bold( ( code & color::BOLD ) != 0 ? ";1" : "" );
|
||||||
|
char const* underline = ( ( code & color::UNDERLINE ) != 0 ? ";4" : "" );
|
||||||
|
static int const MAX_COLOR_CODE_SIZE( 32 );
|
||||||
|
static char colorBuffer[MAX_COLOR_CODE_SIZE];
|
||||||
|
int pos( 0 );
|
||||||
|
if ( ( code & static_cast<int unsigned>( Replxx::Color::DEFAULT ) ) != 0 ) {
|
||||||
|
pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, "\033[0%s%sm", underline, bold );
|
||||||
|
} else if ( fg <= static_cast<int unsigned>( Replxx::Color::LIGHTGRAY ) ) {
|
||||||
|
pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, "\033[0;22;3%d%s%sm", fg, underline, bold );
|
||||||
|
} else if ( fg <= static_cast<int unsigned>( Replxx::Color::WHITE ) ) {
|
||||||
|
#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* ansiEscapeCodeTemplate = has256color ? "\033[0;9%d%s%sm" : "\033[0;1;3%d%s%sm";
|
||||||
|
pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, ansiEscapeCodeTemplate, fg - static_cast<int>( Replxx::Color::GRAY ), underline, bold );
|
||||||
|
} else {
|
||||||
|
pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, "\033[0;38;5;%d%s%sm", fg, underline, bold );
|
||||||
|
}
|
||||||
|
if ( ( code & color::BACKGROUND_COLOR_SET ) == 0 ) {
|
||||||
|
return colorBuffer;
|
||||||
|
}
|
||||||
|
if ( bg <= static_cast<int unsigned>( Replxx::Color::WHITE ) ) {
|
||||||
|
if ( bg <= static_cast<int unsigned>( Replxx::Color::LIGHTGRAY ) ) {
|
||||||
|
snprintf( colorBuffer + pos, MAX_COLOR_CODE_SIZE - pos, "\033[4%dm", bg );
|
||||||
|
} else {
|
||||||
|
snprintf( colorBuffer + pos, MAX_COLOR_CODE_SIZE - pos, "\033[10%dm", bg - static_cast<int>( Replxx::Color::GRAY ) );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
snprintf( colorBuffer + pos, MAX_COLOR_CODE_SIZE - pos, "\033[48;5;%dm", bg );
|
||||||
|
}
|
||||||
|
return colorBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string now_ms_str( void ) {
|
||||||
|
std::chrono::milliseconds ms( std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ) );
|
||||||
|
time_t t( ms.count() / 1000 );
|
||||||
|
tm broken;
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define localtime_r( t, b ) localtime_s( ( b ), ( t ) )
|
||||||
|
#endif
|
||||||
|
localtime_r( &t, &broken );
|
||||||
|
#undef localtime_r
|
||||||
|
static int const BUFF_SIZE( 32 );
|
||||||
|
char str[BUFF_SIZE];
|
||||||
|
strftime( str, BUFF_SIZE, "%Y-%m-%d %H:%M:%S.", &broken );
|
||||||
|
snprintf( str + sizeof ( "YYYY-mm-dd HH:MM:SS" ), 5, "%03d", static_cast<int>( ms.count() % 1000 ) );
|
||||||
|
return ( str );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
32
lib/replxx/src/util.hxx
Normal file
32
lib/replxx/src/util.hxx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef REPLXX_UTIL_HXX_INCLUDED
|
||||||
|
#define REPLXX_UTIL_HXX_INCLUDED 1
|
||||||
|
|
||||||
|
#include "replxx.hxx"
|
||||||
|
|
||||||
|
namespace replxx {
|
||||||
|
|
||||||
|
namespace color {
|
||||||
|
static int unsigned const RGB666 = 16u;
|
||||||
|
static int unsigned const GRAYSCALE = 232u;
|
||||||
|
static int unsigned const BOLD = 1u << 17u;
|
||||||
|
static int unsigned const UNDERLINE = 1u << 18u;
|
||||||
|
static int unsigned const BACKGROUND_COLOR_SET = 1u << 19u;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool is_control_code(char32_t testChar) {
|
||||||
|
return (testChar < ' ') || // C0 controls
|
||||||
|
(testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls
|
||||||
|
}
|
||||||
|
|
||||||
|
inline char32_t control_to_human( char32_t key ) {
|
||||||
|
return ( key < 27 ? ( key + 0x40 ) : ( key + 0x18 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
int virtual_render( char32_t const*, int, int&, int&, int, int, char32_t* = nullptr, int* = nullptr );
|
||||||
|
char const* ansi_color( Replxx::Color );
|
||||||
|
std::string now_ms_str( void );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
296
lib/replxx/src/wcwidth.cpp
Normal file
296
lib/replxx/src/wcwidth.cpp
Normal file
@ -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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
144
lib/replxx/src/windows.cxx
Normal file
144
lib/replxx/src/windows.cxx
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "windows.hxx"
|
||||||
|
#include "conversion.hxx"
|
||||||
|
#include "terminal.hxx"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace replxx {
|
||||||
|
|
||||||
|
WinAttributes WIN_ATTR;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T* HandleEsc(HANDLE out_, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetConsoleTextAttribute(
|
||||||
|
out_,
|
||||||
|
WIN_ATTR._consoleAttribute | WIN_ATTR._consoleColor
|
||||||
|
);
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win_write( HANDLE out_, bool autoEscape_, char const* str_, int size_ ) {
|
||||||
|
int count( 0 );
|
||||||
|
if ( tty::out ) {
|
||||||
|
DWORD nWritten( 0 );
|
||||||
|
if ( autoEscape_ ) {
|
||||||
|
WriteConsoleA( out_, str_, size_, &nWritten, nullptr );
|
||||||
|
count = nWritten;
|
||||||
|
} else {
|
||||||
|
char const* s( str_ );
|
||||||
|
char const* e( str_ + size_ );
|
||||||
|
while ( str_ < e ) {
|
||||||
|
if ( *str_ == 27 ) {
|
||||||
|
if ( s < str_ ) {
|
||||||
|
int toWrite( static_cast<int>( str_ - s ) );
|
||||||
|
WriteConsoleA( out_, s, static_cast<DWORD>( toWrite ), &nWritten, nullptr );
|
||||||
|
count += nWritten;
|
||||||
|
if ( static_cast<int>( nWritten ) != toWrite ) {
|
||||||
|
s = str_ = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = HandleEsc( out_, str_ + 1, e );
|
||||||
|
int escaped( static_cast<int>( s - str_ ) );
|
||||||
|
count += escaped;
|
||||||
|
str_ = s;
|
||||||
|
} else {
|
||||||
|
++ str_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( s < str_ ) {
|
||||||
|
WriteConsoleA( out_, s, static_cast<DWORD>( str_ - s ), &nWritten, nullptr );
|
||||||
|
count += nWritten;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
count = _write( 1, str_, size_ );
|
||||||
|
}
|
||||||
|
return ( count );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
44
lib/replxx/src/windows.hxx
Normal file
44
lib/replxx/src/windows.hxx
Normal file
@ -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( HANDLE, bool, char const*, int );
|
||||||
|
|
||||||
|
extern WinAttributes WIN_ATTR;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -62,4 +62,11 @@ concept Three_Way_Comparable = requires (T const& lhs, T const& rhs) {
|
|||||||
{ lhs <=> rhs };
|
{ lhs <=> rhs };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename Needle, typename ...Heystack>
|
||||||
|
requires (std::equality_comparable_with<Needle const&, Heystack const&> && ...)
|
||||||
|
constexpr bool one_of(Needle const& needle, Heystack const& ...heystack)
|
||||||
|
{
|
||||||
|
return ((needle == heystack) || ...);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -430,7 +430,9 @@ static Result<Value> builtin_par(Interpreter &interpreter, std::vector<Value> ar
|
|||||||
|
|
||||||
for (auto const& note : chord->notes) {
|
for (auto const& note : chord->notes) {
|
||||||
if (note.base) {
|
if (note.base) {
|
||||||
interpreter.current_context->port->send_note_on(0, *note.into_midi_note(), 127);
|
auto const n = *note.into_midi_note();
|
||||||
|
interpreter.current_context->port->send_note_on(0, n, 127);
|
||||||
|
interpreter.active_notes.insert({ 0, n });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,7 +440,9 @@ static Result<Value> builtin_par(Interpreter &interpreter, std::vector<Value> ar
|
|||||||
|
|
||||||
for (auto const& note : chord->notes) {
|
for (auto const& note : chord->notes) {
|
||||||
if (note.base) {
|
if (note.base) {
|
||||||
interpreter.current_context->port->send_note_off(0, *note.into_midi_note(), 127);
|
auto const n = *note.into_midi_note();
|
||||||
|
interpreter.current_context->port->send_note_off(0, n, 127);
|
||||||
|
interpreter.active_notes.erase({ 0, n });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -548,12 +552,16 @@ static Result<Value> builtin_sim(Interpreter &interpreter, std::vector<Value> ar
|
|||||||
for (auto const& instruction : schedule) {
|
for (auto const& instruction : schedule) {
|
||||||
auto const dur = ctx.length_to_duration({instruction.when});
|
auto const dur = ctx.length_to_duration({instruction.when});
|
||||||
if (start_time < dur) {
|
if (start_time < dur) {
|
||||||
std::this_thread::sleep_for(dur - start_time);
|
interpreter.sleep(dur - start_time);
|
||||||
start_time = dur;
|
start_time = dur;
|
||||||
}
|
}
|
||||||
switch (instruction.action) {
|
switch (instruction.action) {
|
||||||
break; case Instruction::On: interpreter.current_context->port->send_note_on(0, instruction.note, 127);
|
break; case Instruction::On:
|
||||||
break; case Instruction::Off: interpreter.current_context->port->send_note_off(0, instruction.note, 127);
|
interpreter.current_context->port->send_note_on(0, instruction.note, 127);
|
||||||
|
interpreter.active_notes.insert({ 0, instruction.note });
|
||||||
|
break; case Instruction::Off:
|
||||||
|
interpreter.current_context->port->send_note_off(0, instruction.note, 127);
|
||||||
|
interpreter.active_notes.erase({ 0, instruction.note });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
std::unordered_map<std::string, Intrinsic> Interpreter::operators {};
|
std::unordered_map<std::string, Intrinsic> Interpreter::operators {};
|
||||||
|
|
||||||
@ -53,6 +55,8 @@ Interpreter::~Interpreter()
|
|||||||
|
|
||||||
Result<Value> Interpreter::eval(Ast &&ast)
|
Result<Value> Interpreter::eval(Ast &&ast)
|
||||||
{
|
{
|
||||||
|
handle_potential_interrupt();
|
||||||
|
|
||||||
switch (ast.type) {
|
switch (ast.type) {
|
||||||
case Ast::Type::Literal:
|
case Ast::Type::Literal:
|
||||||
switch (ast.token.type) {
|
switch (ast.token.type) {
|
||||||
@ -248,8 +252,10 @@ std::optional<Error> Interpreter::play(Chord chord)
|
|||||||
Try(ensure_midi_connection_available(*this, "play"));
|
Try(ensure_midi_connection_available(*this, "play"));
|
||||||
auto &ctx = *current_context;
|
auto &ctx = *current_context;
|
||||||
|
|
||||||
|
handle_potential_interrupt();
|
||||||
|
|
||||||
if (chord.notes.size() == 0) {
|
if (chord.notes.size() == 0) {
|
||||||
std::this_thread::sleep_for(ctx.length_to_duration(ctx.length));
|
sleep(ctx.length_to_duration(ctx.length));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,6 +271,7 @@ std::optional<Error> Interpreter::play(Chord chord)
|
|||||||
for (auto const& note : chord.notes) {
|
for (auto const& note : chord.notes) {
|
||||||
if (note.base) {
|
if (note.base) {
|
||||||
current_context->port->send_note_on(0, *note.into_midi_note(), 127);
|
current_context->port->send_note_on(0, *note.into_midi_note(), 127);
|
||||||
|
active_notes.emplace(0, *note.into_midi_note());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,16 +279,33 @@ std::optional<Error> Interpreter::play(Chord chord)
|
|||||||
for (auto const& note : chord.notes) {
|
for (auto const& note : chord.notes) {
|
||||||
if (max_time != Number(0)) {
|
if (max_time != Number(0)) {
|
||||||
max_time -= *note.length;
|
max_time -= *note.length;
|
||||||
std::this_thread::sleep_for(ctx.length_to_duration(*note.length));
|
sleep(ctx.length_to_duration(*note.length));
|
||||||
}
|
}
|
||||||
if (note.base) {
|
if (note.base) {
|
||||||
current_context->port->send_note_off(0, *note.into_midi_note(), 127);
|
current_context->port->send_note_off(0, *note.into_midi_note(), 127);
|
||||||
|
active_notes.erase(active_notes.lower_bound(std::pair<unsigned, unsigned>{0, *note.into_midi_note()}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Interpreter::turn_off_all_active_notes()
|
||||||
|
{
|
||||||
|
auto status = ensure_midi_connection_available(*this, "turn_off_all_active_notes");
|
||||||
|
if (!Try_Traits<decltype(status)>::is_ok(status)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO send to port that send_note_on was called on
|
||||||
|
for (auto [chan, note] : active_notes) {
|
||||||
|
current_context->port->send_note_off(chan, note, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
active_notes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
std::optional<Error> ensure_midi_connection_available(Interpreter &interpreter, std::string_view operation_name)
|
std::optional<Error> ensure_midi_connection_available(Interpreter &interpreter, std::string_view operation_name)
|
||||||
{
|
{
|
||||||
if (interpreter.current_context->port == nullptr || !interpreter.current_context->port->supports_output()) {
|
if (interpreter.current_context->port == nullptr || !interpreter.current_context->port->supports_output()) {
|
||||||
@ -424,3 +448,31 @@ void Interpreter::snapshot(std::ostream& out)
|
|||||||
}
|
}
|
||||||
out << std::flush;
|
out << std::flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO This only supports single-threaded interpreter execution
|
||||||
|
static std::atomic<bool> interrupted = false;
|
||||||
|
static std::condition_variable condvar;
|
||||||
|
static std::mutex mu;
|
||||||
|
|
||||||
|
void Interpreter::handle_potential_interrupt()
|
||||||
|
{
|
||||||
|
if (interrupted) {
|
||||||
|
interrupted = false;
|
||||||
|
throw KeyboardInterrupt{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::issue_interrupt()
|
||||||
|
{
|
||||||
|
interrupted = true;
|
||||||
|
condvar.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::sleep(std::chrono::duration<float> time)
|
||||||
|
{
|
||||||
|
if (std::unique_lock lock(mu); condvar.wait_for(lock, time) == std::cv_status::no_timeout) {
|
||||||
|
ensure(interrupted, "Only interruption can result in quiting conditional variable without timeout");
|
||||||
|
interrupted = false;
|
||||||
|
throw KeyboardInterrupt{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,6 +6,13 @@
|
|||||||
#include <musique/midi/midi.hh>
|
#include <musique/midi/midi.hh>
|
||||||
#include <musique/value/value.hh>
|
#include <musique/value/value.hh>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
struct KeyboardInterrupt : std::exception
|
||||||
|
{
|
||||||
|
~KeyboardInterrupt() = default;
|
||||||
|
char const* what() const noexcept override { return "KeyboardInterrupt"; }
|
||||||
|
};
|
||||||
|
|
||||||
/// Given program tree evaluates it into Value
|
/// Given program tree evaluates it into Value
|
||||||
struct Interpreter
|
struct Interpreter
|
||||||
@ -22,6 +29,8 @@ struct Interpreter
|
|||||||
|
|
||||||
std::function<std::optional<Error>(Interpreter&, Value)> default_action;
|
std::function<std::optional<Error>(Interpreter&, Value)> default_action;
|
||||||
|
|
||||||
|
std::multiset<std::pair<unsigned, unsigned>> active_notes;
|
||||||
|
|
||||||
Starter starter;
|
Starter starter;
|
||||||
|
|
||||||
Interpreter();
|
Interpreter();
|
||||||
@ -53,6 +62,18 @@ struct Interpreter
|
|||||||
|
|
||||||
/// Dumps snapshot of interpreter into stream
|
/// Dumps snapshot of interpreter into stream
|
||||||
void snapshot(std::ostream& out);
|
void snapshot(std::ostream& out);
|
||||||
|
|
||||||
|
/// Turn all notes that have been played but don't finished playing
|
||||||
|
void turn_off_all_active_notes();
|
||||||
|
|
||||||
|
/// Handles interrupt if any occured
|
||||||
|
void handle_potential_interrupt();
|
||||||
|
|
||||||
|
/// Issue new interrupt
|
||||||
|
void issue_interrupt();
|
||||||
|
|
||||||
|
/// Sleep for at least given time or until interrupt
|
||||||
|
void sleep(std::chrono::duration<float>);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<Error> ensure_midi_connection_available(Interpreter&, std::string_view operation_name);
|
std::optional<Error> ensure_midi_connection_available(Interpreter&, std::string_view operation_name);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <csignal>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <edit_distance.hh>
|
#include <edit_distance.hh>
|
||||||
@ -13,18 +14,24 @@
|
|||||||
#include <musique/lexer/lines.hh>
|
#include <musique/lexer/lines.hh>
|
||||||
#include <musique/midi/midi.hh>
|
#include <musique/midi/midi.hh>
|
||||||
#include <musique/parser/parser.hh>
|
#include <musique/parser/parser.hh>
|
||||||
|
#include <musique/platform.hh>
|
||||||
#include <musique/pretty.hh>
|
#include <musique/pretty.hh>
|
||||||
#include <musique/try.hh>
|
#include <musique/try.hh>
|
||||||
#include <musique/unicode.hh>
|
#include <musique/unicode.hh>
|
||||||
|
#include <musique/user_directory.hh>
|
||||||
#include <musique/value/block.hh>
|
#include <musique/value/block.hh>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
#ifndef _WIN32
|
#include <replxx.hxx>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <bestline.h>
|
#include <io.h>
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
@ -163,8 +170,14 @@ struct Runner
|
|||||||
dump(ast);
|
dump(ast);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
if (auto result = Try(interpreter.eval(std::move(ast))); output && not holds_alternative<Nil>(result)) {
|
try {
|
||||||
std::cout << Try(format(interpreter, result)) << std::endl;
|
if (auto result = Try(interpreter.eval(std::move(ast))); output && not holds_alternative<Nil>(result)) {
|
||||||
|
std::cout << Try(format(interpreter, result)) << std::endl;
|
||||||
|
}
|
||||||
|
} catch (KeyboardInterrupt const&) {
|
||||||
|
interpreter.turn_off_all_active_notes();
|
||||||
|
interpreter.starter.stop();
|
||||||
|
std::cout << std::endl;
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -174,7 +187,7 @@ struct Runner
|
|||||||
/// some of the strings are only views into source
|
/// some of the strings are only views into source
|
||||||
std::vector<std::string> eternal_sources;
|
std::vector<std::string> eternal_sources;
|
||||||
|
|
||||||
#ifndef _WIN32
|
#if 0
|
||||||
void completion(char const* buf, bestlineCompletions *lc)
|
void completion(char const* buf, bestlineCompletions *lc)
|
||||||
{
|
{
|
||||||
std::string_view in{buf};
|
std::string_view in{buf};
|
||||||
@ -287,6 +300,15 @@ static Result<bool> handle_repl_session_commands(std::string_view input, Runner
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Runner *runner;
|
||||||
|
|
||||||
|
void sigint_handler(int sig)
|
||||||
|
{
|
||||||
|
if (sig == SIGINT) {
|
||||||
|
runner->interpreter.issue_interrupt();
|
||||||
|
}
|
||||||
|
std::signal(SIGINT, sigint_handler);
|
||||||
|
}
|
||||||
|
|
||||||
/// Fancy main that supports Result forwarding on error (Try macro)
|
/// Fancy main that supports Result forwarding on error (Try macro)
|
||||||
static std::optional<Error> Main(std::span<char const*> args)
|
static std::optional<Error> Main(std::span<char const*> args)
|
||||||
@ -308,6 +330,8 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Runner runner;
|
Runner runner;
|
||||||
|
::runner = &runner;
|
||||||
|
std::signal(SIGINT, sigint_handler);
|
||||||
|
|
||||||
for (auto const& [type, argument] : runnables) {
|
for (auto const& [type, argument] : runnables) {
|
||||||
if (type == cmd::Run::Argument) {
|
if (type == cmd::Run::Argument) {
|
||||||
@ -342,44 +366,38 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
|
|
||||||
if (enable_repl) {
|
if (enable_repl) {
|
||||||
repl_line_number = 1;
|
repl_line_number = 1;
|
||||||
#ifndef _WIN32
|
|
||||||
bestlineSetCompletionCallback(completion);
|
|
||||||
#else
|
replxx::Replxx repl;
|
||||||
std::vector<std::string> repl_source_lines;
|
|
||||||
#endif
|
auto const history_path = (user_directory::data_home() / "history").string();
|
||||||
|
|
||||||
|
repl.set_max_history_size(2048);
|
||||||
|
repl.set_max_hint_rows(3);
|
||||||
|
repl.history_load(history_path);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
#ifndef _WIN32
|
char const* input = nullptr;
|
||||||
char const* input_buffer = bestlineWithHistory("> ", "musique");
|
do input = repl.input("> "); while((input == nullptr) && (errno == EAGAIN));
|
||||||
if (input_buffer == nullptr) {
|
|
||||||
|
if (input == nullptr) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
std::cout << "> " << std::flush;
|
|
||||||
char const* input_buffer;
|
|
||||||
if (std::string s{}; std::getline(std::cin, s)) {
|
|
||||||
repl_source_lines.push_back(std::move(s));
|
|
||||||
input_buffer = repl_source_lines.back().c_str();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// Raw input line used for execution in language
|
// Raw input line used for execution in language
|
||||||
std::string_view raw = input_buffer;
|
std::string_view raw = input;
|
||||||
|
|
||||||
// Used to recognize REPL commands
|
// Used to recognize REPL commands
|
||||||
std::string_view command = raw;
|
std::string_view command = raw;
|
||||||
trim(command);
|
trim(command);
|
||||||
|
|
||||||
if (command.empty()) {
|
if (command.empty()) {
|
||||||
// Line is empty so there is no need to execute it or parse it
|
|
||||||
#ifndef _WIN32
|
|
||||||
free(const_cast<char*>(input_buffer));
|
|
||||||
#else
|
|
||||||
repl_source_lines.pop_back();
|
|
||||||
#endif
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repl.history_add(std::string(command));
|
||||||
|
repl.history_save(history_path);
|
||||||
|
|
||||||
if (command.starts_with(':')) {
|
if (command.starts_with(':')) {
|
||||||
command.remove_prefix(1);
|
command.remove_prefix(1);
|
||||||
if (!Try(handle_repl_session_commands(command, runner))) {
|
if (!Try(handle_repl_session_commands(command, runner))) {
|
||||||
|
24
musique/platform.hh
Normal file
24
musique/platform.hh
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef MUSIQUE_PLATFORM_HH
|
||||||
|
#define MUSIQUE_PLATFORM_HH
|
||||||
|
|
||||||
|
namespace platform
|
||||||
|
{
|
||||||
|
enum class Operating_System
|
||||||
|
{
|
||||||
|
MacOS,
|
||||||
|
Unix,
|
||||||
|
Windows,
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
||||||
|
static constexpr Operating_System os = Operating_System::Windows;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
static constexpr Operating_System os = Operating_System::MacOS;
|
||||||
|
#elif defined(__linux__) || defined(__unix__)
|
||||||
|
static constexpr Operating_System os = Operating_System::Unix;
|
||||||
|
#else
|
||||||
|
#error "Unknown platform"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MUSIQUE_PLATFORM_HH
|
92
musique/user_directory.cc
Normal file
92
musique/user_directory.cc
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#include <musique/user_directory.hh>
|
||||||
|
#include <musique/errors.hh>
|
||||||
|
#include <musique/platform.hh>
|
||||||
|
|
||||||
|
static std::filesystem::path home()
|
||||||
|
{
|
||||||
|
if constexpr (platform::os == platform::Operating_System::Windows) {
|
||||||
|
if (auto home = std::getenv("USERPROFILE")) return home;
|
||||||
|
|
||||||
|
if (auto drive = std::getenv("HOMEDRIVE")) {
|
||||||
|
if (auto path = std::getenv("HOMEPATH")) {
|
||||||
|
return std::string(drive) + path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (auto home = std::getenv("HOME")) {
|
||||||
|
return home;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path user_directory::data_home()
|
||||||
|
{
|
||||||
|
std::filesystem::path path;
|
||||||
|
|
||||||
|
static_assert(one_of(platform::os,
|
||||||
|
platform::Operating_System::Unix,
|
||||||
|
platform::Operating_System::Windows,
|
||||||
|
platform::Operating_System::MacOS
|
||||||
|
));
|
||||||
|
|
||||||
|
if constexpr (platform::os == platform::Operating_System::Unix) {
|
||||||
|
if (auto data = std::getenv("XDG_DATA_HOME")) {
|
||||||
|
path = data;
|
||||||
|
} else {
|
||||||
|
path = home() / ".local" / "share";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (platform::os == platform::Operating_System::Windows) {
|
||||||
|
if (auto data = std::getenv("LOCALAPPDATA")) {
|
||||||
|
path = data;
|
||||||
|
} else {
|
||||||
|
path = home() / "AppData" / "Local";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (platform::os == platform::Operating_System::MacOS) {
|
||||||
|
path = home() / "Library";
|
||||||
|
}
|
||||||
|
|
||||||
|
path /= "musique";
|
||||||
|
std::filesystem::create_directories(path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path user_directory::config_home()
|
||||||
|
{
|
||||||
|
std::filesystem::path path;
|
||||||
|
|
||||||
|
static_assert(one_of(platform::os,
|
||||||
|
platform::Operating_System::Unix,
|
||||||
|
platform::Operating_System::Windows,
|
||||||
|
platform::Operating_System::MacOS
|
||||||
|
));
|
||||||
|
|
||||||
|
if constexpr (platform::os == platform::Operating_System::Unix) {
|
||||||
|
if (auto data = std::getenv("XDG_CONFIG_HOME")) {
|
||||||
|
path = data;
|
||||||
|
} else {
|
||||||
|
path = home() / ".config";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (platform::os == platform::Operating_System::Windows) {
|
||||||
|
if (auto data = std::getenv("LOCALAPPDATA")) {
|
||||||
|
path = data;
|
||||||
|
} else {
|
||||||
|
path = home() / "AppData" / "Local";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (platform::os == platform::Operating_System::MacOS) {
|
||||||
|
path = home() / "Library" / "Preferences";
|
||||||
|
}
|
||||||
|
|
||||||
|
path /= "musique";
|
||||||
|
std::filesystem::create_directories(path);
|
||||||
|
return path;
|
||||||
|
}
|
15
musique/user_directory.hh
Normal file
15
musique/user_directory.hh
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#ifndef MUSIQUE_USER_DIRECTORY_HH
|
||||||
|
#define MUSIQUE_USER_DIRECTORY_HH
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace user_directory
|
||||||
|
{
|
||||||
|
/// Returns system-specific user directory for data; same as XDG_DATA_HOME
|
||||||
|
std::filesystem::path data_home();
|
||||||
|
|
||||||
|
/// Returns system-specific user directory for config; same as XDG_CONFIG_HOME
|
||||||
|
std::filesystem::path config_home();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MUSIQUE_USER_DIRECTORY_HH
|
16
scripts/build.mk
Normal file → Executable file
16
scripts/build.mk
Normal file → Executable file
@ -1,16 +1,15 @@
|
|||||||
Release_Obj=$(addprefix bin/$(os)/,$(Obj)) bin/$(os)/builtin_function_documentation.o
|
Release_Obj=$(addprefix bin/$(os)/,$(Obj)) bin/$(os)/builtin_function_documentation.o
|
||||||
|
|
||||||
bin/$(os)/bestline.o: lib/bestline/bestline.c lib/bestline/bestline.h
|
# bin/$(os)/libreplxx.a:
|
||||||
@echo "CC $@"
|
# @CXX=$(CXX) os=$(os) scripts/build_replxx.sh
|
||||||
@$(CC) $< -c -O3 -o $@
|
|
||||||
|
|
||||||
bin/$(os)/%.o: musique/%.cc
|
bin/$(os)/%.o: musique/%.cc
|
||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
||||||
|
|
||||||
bin/$(os)/$(Target): $(Release_Obj) bin/$(os)/main.o bin/$(os)/rtmidi.o $(Bestline)
|
bin/$(os)/$(Target): $(Release_Obj) bin/$(os)/main.o bin/$(os)/rtmidi.o
|
||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/$(os)/rtmidi.o $(Bestline) $(LDFLAGS) $(LDLIBS)
|
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(shell CXX=$(CXX) os=$(os) scripts/build_replxx.sh) $(Release_Obj) bin/$(os)/rtmidi.o $(LDFLAGS) $(LDLIBS)
|
||||||
|
|
||||||
bin/$(os)/builtin_function_documentation.o: bin/$(os)/builtin_function_documentation.cc
|
bin/$(os)/builtin_function_documentation.o: bin/$(os)/builtin_function_documentation.cc
|
||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@ -29,6 +28,13 @@ bin/$(os)/debug/%.o: musique/%.cc
|
|||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
||||||
|
|
||||||
|
|
||||||
bin/$(os)/debug/builtin_function_documentation.o: bin/$(os)/builtin_function_documentation.cc
|
bin/$(os)/debug/builtin_function_documentation.o: bin/$(os)/builtin_function_documentation.cc
|
||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
||||||
|
|
||||||
|
# http://www.music.mcgill.ca/~gary/rtmidi/#compiling
|
||||||
|
bin/$(os)/rtmidi.o: lib/rtmidi/RtMidi.cpp lib/rtmidi/RtMidi.h
|
||||||
|
@echo "CXX $@"
|
||||||
|
@$(CXX) $< -c -O2 -o $@ $(CPPFLAGS) -std=c++20
|
||||||
|
|
||||||
|
20
scripts/build_replxx.sh
Executable file
20
scripts/build_replxx.sh
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
prefix="lib/replxx/src/"
|
||||||
|
|
||||||
|
mkdir -p "bin/${os}/replxx"
|
||||||
|
|
||||||
|
objects=""
|
||||||
|
|
||||||
|
find lib/replxx/src/ -name '*.cxx' -o -name '*.cpp' | while read -r src
|
||||||
|
do
|
||||||
|
dst="${src##"$prefix"}"
|
||||||
|
dst="bin/${os}/replxx/${dst%%.*}.o"
|
||||||
|
if [ ! -f "$dst" ]; then
|
||||||
|
"${CXX}" -Ilib/replxx/src/ -Ilib/replxx/include/ -c -o "$dst" "$src" -std=c++20 -O3 -DREPLXX_STATIC
|
||||||
|
fi
|
||||||
|
echo "${dst}"
|
||||||
|
done
|
||||||
|
|
Loading…
Reference in New Issue
Block a user