Compare commits

...

19 Commits

Author SHA1 Message Date
Robert Bendun
205868a868 0.5.0 2023-03-05 01:34:36 +01:00
Robert Bendun
1632ef74af Remove bestline from project 2023-03-05 01:31:32 +01:00
Robert Bendun
ee9cd987e7 Use new command line parameter syntax in test runner 2023-03-05 01:24:09 +01:00
Robert Bendun
4ef724041b Merge branch 'repl' into staged-0.5 2023-03-05 01:18:41 +01:00
Robert Bendun
943b065626 Merge branch 'integrated-documentation' into staged-0.5 2023-03-05 01:13:15 +01:00
Robert Bendun
719e8d4f26 Report similar names for builtins that user is searching for 2023-03-05 01:10:43 +01:00
Robert Bendun
92e5c3169c Started musique man implementation: structure of a page implemented 2023-03-04 21:23:13 +01:00
Robert Bendun
a2d8f28cd1 More colorfull error messages 2023-03-04 20:15:58 +01:00
Robert Bendun
20a6779e2f Usage generated from parameters short descriptions
Additionally printing short descriptions with list of proposed commands
2023-03-04 20:09:58 +01:00
Robert Bendun
c0f021e57f Introduce internal parameters; better condition for enabling repl 2023-03-04 19:00:15 +01:00
Robert Bendun
d351b49148 cleared mess in platform.hh 2023-03-04 18:35:46 +01:00
Robert Bendun
4dccde7830 Save history between command invocations 2023-03-04 17:59:33 +01:00
Robert Bendun
ff58e6506d Fix CTRL-C one time use on Windows 2023-03-04 16:39:16 +01:00
Robert Bendun
2d61896824 C++ standard only interrupts implementation 2023-02-22 19:34:24 +01:00
Robert Bendun
e26474a2d7 Replxx integration; Linux CTRL-C handler 2023-02-22 02:38:46 +01:00
Robert Bendun
a7ed7a0d65 Proper usage message 2023-02-20 18:33:32 +01:00
Robert Bendun
83ede4ef81 Support complex syntax for command line arguments
Support parameters prefixed by '-' or '--'.
Support parameters in form 'key=value'
2023-02-20 18:16:20 +01:00
Robert Bendun
aaf6e6ec0c New parameter passing convention
With suggestions on wrong parameter names
2023-02-20 17:38:17 +01:00
Robert Bendun
deabd1865a Introduced integrated documentation for builtin functions 2023-01-30 14:59:14 +01:00
56 changed files with 10328 additions and 3944 deletions

View File

@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.5.0]
### Added
- Builtin documentation for builtin functions display from repl and command line (`musique doc <builtin>`)
- Man documentation for commandline interface builtin (`musique man`)
- Suggestions which command line parameters user may wanted to use
### 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
- `CTRL-C` handler that turns notes that are playing off
## [0.4.0]
### Added

View File

@ -18,11 +18,6 @@ include scripts/test.mk
bin/$(Target): bin/$(os)/$(Target)
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
doxygen
@ -56,4 +51,5 @@ 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 bin/$(os)/replxx/)
$(shell mkdir -p $(subst musique/,bin/$(os)/debug/,$(shell find musique/* -type d)))

View File

@ -1,6 +1,6 @@
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
MAJOR := 4
MAJOR := 5
MINOR := 0
PATCH := 0
COMMIT := gc$(shell git rev-parse --short HEAD 2>/dev/null)
@ -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
CPPFLAGS:=$(CPPFLAGS) -DMusique_Version='"$(VERSION)"' \
-Ilib/expected/ -I. -Ilib/bestline/ -Ilib/rtmidi/ -Ilib/link/include -Ilib/asio/include/
-Ilib/expected/ -I. -Ilib/rtmidi/ -Ilib/link/include -Ilib/asio/include/ -Ilib/edit_distance.cc/ -Ilib/replxx/include -DREPLXX_STATIC
LDFLAGS=-flto
LDLIBS= -lpthread

View File

@ -1,30 +0,0 @@
Bestline is released under the 2-clause BSD license.
Copyright (c) 2018-2021 Justine Tunney <jtunney@gmail.com>
Copyright (c) 2010-2016 Salvatore Sanfilippo <antirez@gmail.com>
Copyright (c) 2010-2013 Pieter Noordhuis <pcnoordhuis@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +0,0 @@
#pragma once
typedef struct bestlineCompletions {
unsigned long len;
char **cvec;
} bestlineCompletions;
typedef void(bestlineCompletionCallback)(const char *, bestlineCompletions *);
typedef char *(bestlineHintsCallback)(const char *, const char **,
const char **);
typedef void(bestlineFreeHintsCallback)(void *);
typedef unsigned(bestlineXlatCallback)(unsigned);
void bestlineSetCompletionCallback(bestlineCompletionCallback *);
void bestlineSetHintsCallback(bestlineHintsCallback *);
void bestlineSetFreeHintsCallback(bestlineFreeHintsCallback *);
void bestlineAddCompletion(bestlineCompletions *, const char *);
void bestlineSetXlatCallback(bestlineXlatCallback *);
char *bestline(const char *);
char *bestlineRaw(const char *, int, int);
char *bestlineWithHistory(const char *, const char *);
int bestlineHistoryAdd(const char *);
int bestlineHistorySave(const char *);
int bestlineHistoryLoad(const char *);
void bestlineFreeCompletions(bestlineCompletions *);
void bestlineHistoryFree(void);
void bestlineClearScreen(int);
void bestlineMaskModeEnable(void);
void bestlineMaskModeDisable(void);
void bestlineDisableRawMode(void);
void bestlineFree(void *);
char bestlineIsSeparator(unsigned);
char bestlineNotSeparator(unsigned);
char bestlineIsXeparator(unsigned);
unsigned bestlineUppercase(unsigned);
unsigned bestlineLowercase(unsigned);
long bestlineReadCharacter(int, char *, unsigned long);

View File

@ -1,79 +0,0 @@
#include "bestline.h"
#ifndef __COSMOPOLITAN__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#endif
#if 0
// should be ~50kb statically linked
// will save history to ~/.foo_history
// cc -fno-jump-tables -Os -o foo foo.c bestline.c
int main() {
char *line;
while ((line = bestlineWithHistory("IN> ", "foo"))) {
fputs("OUT> ", stdout);
fputs(line, stdout);
fputs("\n", stdout);
free(line);
}
}
#endif
void completion(const char *buf, bestlineCompletions *lc) {
if (buf[0] == 'h') {
bestlineAddCompletion(lc,"hello");
bestlineAddCompletion(lc,"hello there");
}
}
char *hints(const char *buf, const char **ansi1, const char **ansi2) {
if (!strcmp(buf,"hello")) {
*ansi1 = "\033[35m"; /* magenta foreground */
*ansi2 = "\033[39m"; /* reset foreground */
return " World";
}
return NULL;
}
int main(int argc, char **argv) {
char *line;
/* Set the completion callback. This will be called every time the
* user uses the <tab> key. */
bestlineSetCompletionCallback(completion);
bestlineSetHintsCallback(hints);
/* Load history from file. The history file is just a plain text file
* where entries are separated by newlines. */
bestlineHistoryLoad("history.txt"); /* Load the history at startup */
/* Now this is the main loop of the typical bestline-based application.
* The call to bestline() will block as long as the user types something
* and presses enter.
*
* The typed string is returned as a malloc() allocated string by
* bestline, so the user needs to free() it. */
while((line = bestline("hello> ")) != NULL) {
/* Do something with the string. */
if (line[0] != '\0' && line[0] != '/') {
fputs("echo: '", stdout);
fputs(line, stdout);
fputs("'\n", stdout);
bestlineHistoryAdd(line); /* Add to the history. */
bestlineHistorySave("history.txt"); /* Save the history on disk. */
} else if (!strncmp(line, "/mask", 5)) {
bestlineMaskModeEnable();
} else if (!strncmp(line, "/unmask", 7)) {
bestlineMaskModeDisable();
} else if (line[0] == '/') {
fputs("Unreconized command: ", stdout);
fputs(line, stdout);
fputs("\n", stdout);
}
free(line);
}
return 0;
}

5
lib/edit_distance.cc/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
test
test.cc
Makefile
.cache
compile_commands.json

View File

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

View File

@ -0,0 +1,76 @@
// Copyright 2023 Robert Bendun <robert@bendun.cc>
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <algorithm>
#include <array>
#include <concepts>
#include <iterator>
#include <numeric>
#include <ranges>
#include <type_traits>
#include <vector>
template<std::random_access_iterator S, std::random_access_iterator T>
requires std::equality_comparable_with<
std::iter_value_t<S>,
std::iter_value_t<T>
>
constexpr int edit_distance(S s, unsigned m, T t, unsigned n)
{
std::array<std::vector<int>, 2> memo;
auto *v0 = &memo[0];
auto *v1 = &memo[1];
for (auto& v : memo) {
v.resize(n+1);
}
std::iota(v0->begin(), v0->end(), 0);
for (auto i = 0u; i < m; ++i) {
(*v1)[0] = i+1;
for (auto j = 0u; j < n; ++j) {
auto const deletion_cost = (*v0)[j+1] + 1;
auto const insertion_cost = (*v1)[j] + 1;
auto const substitution_cost = (*v0)[j] + (s[i] != t[j]);
(*v1)[j+1] = std::min({ deletion_cost, insertion_cost, substitution_cost });
}
std::swap(v0, v1);
}
return (*v0)[n];
}
template<
std::ranges::random_access_range Range1,
std::ranges::random_access_range Range2
>
requires std::equality_comparable_with<
std::ranges::range_value_t<Range1>,
std::ranges::range_value_t<Range2>
>
constexpr int edit_distance(Range1 const& range1, Range2 const& range2)
{
return edit_distance(
std::begin(range1), std::ranges::size(range1),
std::begin(range2), std::ranges::size(range2)
);
}

63
lib/replxx/LICENSE.md Normal file
View 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
View 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 */

View 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 */

View 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
View 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 */
/* --------------------------------------------------------------------- */

View 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 );
}
}

View 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
View 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
View 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
View 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
View 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

View 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
View 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
View 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
View 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_ ) );
}

File diff suppressed because it is too large Load Diff

View 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
View 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
}

View 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

View 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

View 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
View 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
View 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
View 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
View 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

View 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

474
musique/cmd.cc Normal file
View File

@ -0,0 +1,474 @@
#include <algorithm>
#include <array>
#include <chrono>
#include <edit_distance.hh>
#include <iomanip>
#include <iostream>
#include <limits>
#include <musique/cmd.hh>
#include <musique/common.hh>
#include <musique/errors.hh>
#include <musique/interpreter/builtin_function_documentation.hh>
#include <musique/pretty.hh>
#include <set>
#include <unordered_set>
#include <utility>
#include <variant>
// TODO: Command line parameters full documentation in other then man pages format. Maybe HTML generation?
#ifdef _WIN32
extern "C" {
#include <io.h>
}
#else
#include <unistd.h>
#endif
using Empty_Argument = void(*)();
using Requires_Argument = void(*)(std::string_view);
using Defines_Code = cmd::Run(*)(std::string_view);
using Parameter = std::variant<Empty_Argument, Requires_Argument, Defines_Code>;
using namespace cmd;
// from musique/main.cc:
extern bool enable_repl;
extern bool ast_only_mode;
static Defines_Code provide_function = [](std::string_view fname) -> cmd::Run {
return { .type = Run::Deffered_File, .argument = fname };
};
static Defines_Code provide_inline_code = [](std::string_view code) -> cmd::Run {
return { .type = Run::Argument, .argument = code };
};
static Defines_Code provide_file = [](std::string_view fname) -> cmd::Run {
return { .type = Run::File, .argument = fname };
};
static Requires_Argument show_docs = [](std::string_view builtin) {
if (auto maybe_docs = find_documentation_for_builtin(builtin); maybe_docs) {
std::cout << *maybe_docs << std::endl;
return;
}
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
std::cerr << " cannot find documentation for given builtin" << std::endl;
std::cerr << "Similar ones are:" << std::endl;
for (auto similar : similar_names_to_builtin(builtin)) {
std::cerr << " " << similar << '\n';
}
std::exit(1);
};
static Empty_Argument set_interactive_mode = [] { enable_repl = true; };
static Empty_Argument set_ast_only_mode = [] { ast_only_mode = true; };
static Empty_Argument print_version = [] { std::cout << Musique_Version << std::endl; };
static Empty_Argument print_help = usage;
[[noreturn]]
static void print_manpage();
struct Entry
{
std::string_view name;
Parameter handler;
bool internal = false;
void* handler_ptr() const
{
return std::visit([](auto p) { return reinterpret_cast<void*>(p); }, handler);
}
size_t arguments() const
{
return std::visit([]<typename R, typename ...A>(R(*)(A...)) { return sizeof...(A); }, handler);
}
};
// First entry for given action type should always be it's cannonical name
static auto all_parameters = std::array {
Entry { "run", provide_file },
Entry { "r", provide_file },
Entry { "exec", provide_file },
Entry { "load", provide_file },
Entry { "fun", provide_function },
Entry { "def", provide_function },
Entry { "f", provide_function },
Entry { "func", provide_function },
Entry { "function", provide_function },
Entry { "repl", set_interactive_mode },
Entry { "i", set_interactive_mode },
Entry { "interactive", set_interactive_mode },
Entry { "doc", show_docs },
Entry { "d", show_docs },
Entry { "docs", show_docs },
Entry { "man", print_manpage },
Entry { "inline", provide_inline_code },
Entry { "c", provide_inline_code },
Entry { "code", provide_inline_code },
Entry { "help", print_help },
Entry { "?", print_help },
Entry { "/?", print_help },
Entry { "h", print_help },
Entry { "version", print_version },
Entry { "v", print_version },
Entry {
.name = "ast",
.handler = set_ast_only_mode,
.internal = true,
},
};
struct Documentation_For_Handler_Entry
{
void *handler;
std::string_view short_documentation{};
std::string_view long_documentation{};
};
static auto documentation_for_handler = std::array {
Documentation_For_Handler_Entry {
.handler = reinterpret_cast<void*>(provide_function),
.short_documentation = "load file as function",
.long_documentation =
"Loads given file, placing it inside a function. The main use for this mechanism is\n"
"to delay execution of a file e.g. to play it using synchronization infrastructure.\n"
"Name of function is derived from file name, replacing special characters with underscores.\n"
"New name is reported when entering interactive mode."
},
Documentation_For_Handler_Entry {
.handler = reinterpret_cast<void*>(provide_file),
.short_documentation = "execute given file",
.long_documentation =
"Run provided Musique source file."
},
Documentation_For_Handler_Entry {
.handler = reinterpret_cast<void*>(set_interactive_mode),
.short_documentation = "enable interactive mode",
.long_documentation =
"Enables interactive mode. It's enabled by default when provided without arguments or\n"
"when all arguments are files loaded as functions."
},
Documentation_For_Handler_Entry {
.handler = reinterpret_cast<void*>(print_help),
.short_documentation = "print help",
.long_documentation =
"Prints short version of help, to provide version easy for quick lookup by the user."
},
Documentation_For_Handler_Entry {
.handler = reinterpret_cast<void*>(print_version),
.short_documentation = "print version information",
.long_documentation =
"Prints version of Musique, following Semantic Versioning.\n"
"It's either '<major>.<minor>.<patch>' for official releases or\n"
"'<major>.<minor>.<patch>-dev+gc<commit hash>' for self-build releases."
},
Documentation_For_Handler_Entry {
.handler = reinterpret_cast<void*>(show_docs),
.short_documentation = "print documentation for given builtin",
.long_documentation =
"Prints documentation for given builtin function (function predefined by language).\n"
"Documentation is in Markdown format and can be passed to render."
},
Documentation_For_Handler_Entry {
.handler = reinterpret_cast<void*>(provide_inline_code),
.short_documentation = "run code from an argument",
.long_documentation =
"Runs code passed as next argument. Same rules apply as for code inside a file."
},
Documentation_For_Handler_Entry {
.handler = reinterpret_cast<void*>(set_ast_only_mode),
.short_documentation = "don't run code, print AST of it",
.long_documentation =
"Parameter made for internal usage. Instead of executing provided code,\n"
"prints program syntax tree."
},
Documentation_For_Handler_Entry {
.handler = reinterpret_cast<void*>(print_manpage),
.short_documentation = "print man page source code to standard output",
.long_documentation =
"Prints Man page document to standard output of Musique full command line interface.\n"
"One can view it with 'musique man > /tmp/musique.1; man /tmp/musique.1'"
},
};
// Supported types of argument input:
// With arity = 0
// -i -j -k ≡ --i --j --k ≡ i j k
// With arity = 1
// -i 1 -j 2 ≡ --i 1 --j 2 ≡ i 1 j 2 ≡ --i=1 --j=2
// Arity ≥ 2 is not supported
std::optional<std::string_view> cmd::accept_commandline_argument(std::vector<cmd::Run> &runnables, std::span<char const*> &args)
{
if (args.empty()) {
return std::nullopt;
}
// This struct when function returns automatically ajust number of arguments used
struct Fix_Args_Array
{
std::string_view name() const { return m_name; }
std::optional<std::string_view> value() const
{
if (m_has_value) { m_success = m_value_consumed = true; return m_value; }
return std::nullopt;
}
void mark_success() { m_success = true; }
~Fix_Args_Array() { args = args.subspan(int(m_success) + (!m_packed * int(m_value_consumed))); }
std::span<char const*> &args;
std::string_view m_name = {};
std::string_view m_value = {};
bool m_has_value = false;
bool m_packed = false;
mutable bool m_success = false;
mutable bool m_value_consumed = false;
} state { .args = args };
std::string_view s = args.front();
if (s.starts_with("--")) s.remove_prefix(2);
if (s.starts_with('-')) s.remove_prefix(1);
state.m_name = s;
if (auto p = s.find('='); p != std::string_view::npos) {
state.m_name = s.substr(0, p);
state.m_value = s.substr(p+1);
state.m_has_value = true;
state.m_packed = true;
} else if (args.size() >= 2) {
state.m_has_value = true;
state.m_value = args[1];
}
for (auto const& p : all_parameters) {
if (p.name != state.name()) {
continue;
}
std::visit(Overloaded {
[&state](Empty_Argument const& h) {
state.mark_success();
h();
},
[&state, p](Requires_Argument const& h) {
auto arg = state.value();
if (!arg) {
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
std::cerr << " option " << std::quoted(p.name) << " requires an argument" << std::endl;
std::exit(1);
}
h(*arg);
},
[&state, &runnables, p](Defines_Code const& h) {
auto arg = state.value();
if (!arg) {
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
std::cerr << " option " << std::quoted(p.name) << " requires an argument" << std::endl;
std::exit(1);
}
runnables.push_back(h(*arg));
}
}, p.handler);
return std::nullopt;
}
return state.name();
}
Documentation_For_Handler_Entry find_documentation_for_handler(void *handler)
{
auto it = std::find_if(documentation_for_handler.begin(), documentation_for_handler.end(),
[=](Documentation_For_Handler_Entry const& e) { return e.handler == handler; });
ensure(it != documentation_for_handler.end(), "Parameter handler doesn't have matching documentation");
return *it;
}
Documentation_For_Handler_Entry find_documentation_for_parameter(std::string_view param)
{
auto entry = std::find_if(all_parameters.begin(), all_parameters.end(),
[=](auto const& e) { return e.name == param; });
ensure(entry != all_parameters.end(), "Cannot find parameter that maches given name");
return find_documentation_for_handler(entry->handler_ptr());
}
void cmd::print_close_matches(std::string_view arg)
{
auto minimum_distance = std::numeric_limits<int>::max();
std::array<typename decltype(all_parameters)::value_type, 3> closest;
std::partial_sort_copy(
all_parameters.begin(), all_parameters.end(),
closest.begin(), closest.end(),
[&minimum_distance, arg](auto const& lhs, auto const& rhs) {
auto const lhs_score = edit_distance(arg, lhs.name);
auto const rhs_score = edit_distance(arg, rhs.name);
minimum_distance = std::min({ minimum_distance, lhs_score, rhs_score });
return lhs_score < rhs_score;
}
);
std::vector<std::string> shown;
if (minimum_distance <= 3) {
for (auto const& p : closest) {
if (std::find(shown.begin(), shown.end(), std::string(p.name)) == shown.end()) {
shown.push_back(std::string(p.name));
}
}
}
if (shown.empty()) {
void *previous = nullptr;
std::cout << "Available subcommands are:\n";
for (auto const& p : all_parameters) {
auto handler_p = p.handler_ptr();
if (std::exchange(previous, handler_p) == handler_p || p.internal) {
continue;
}
std::cout << " " << p.name << " - " << find_documentation_for_handler(handler_p).short_documentation << '\n';
}
} else {
std::cout << "The most similar commands are:\n";
for (auto const& name : shown) {
std::cout << " " << name << " - " << find_documentation_for_parameter(name).short_documentation << '\n';
}
}
std::cout << "\nInvoke 'musique help' to read more about available commands\n";
}
static inline void iterate_over_documentation(
std::ostream& out,
std::string_view Documentation_For_Handler_Entry::* handler,
std::string_view prefix,
std::ostream&(*first)(std::ostream&, std::string_view name))
{
decltype(std::optional(all_parameters.begin())) previous = std::nullopt;
for (auto it = all_parameters.begin();; ++it) {
if (it != all_parameters.end() && it->internal)
continue;
if (it == all_parameters.end() || (previous && it->handler_ptr() != (*previous)->handler_ptr())) {
auto &e = **previous;
switch (e.arguments()) {
break; case 0: out << '\n';
break; case 1: out << " ARG\n";
break; default: unreachable();
}
out << prefix << find_documentation_for_handler(e.handler_ptr()).*handler << "\n\n";
}
if (it == all_parameters.end()) {
break;
}
if (previous && (**previous).handler_ptr() == it->handler_ptr()) {
out << ", " << it->name;
} else {
first(out, it->name);
}
previous = it;
}
}
void cmd::usage()
{
std::cerr << "usage: " << pretty::begin_bold << "musique" << pretty::end << " [subcommand]...\n";
std::cerr << " where available subcommands are:\n";
iterate_over_documentation(std::cerr, &Documentation_For_Handler_Entry::short_documentation, " ",
[](std::ostream& out, std::string_view name) -> std::ostream&
{
return out << " " << pretty::begin_bold << name << pretty::end;
});
std::exit(2);
}
void print_manpage()
{
auto const ymd = std::chrono::year_month_day(
std::chrono::floor<std::chrono::days>(
std::chrono::system_clock::now()
)
);
std::cout << ".TH MUSIQUE 1 "
<< int(ymd.year()) << '-'
<< std::setfill('0') << std::setw(2) << unsigned(ymd.month()) << '-'
<< std::setfill('0') << std::setw(2) << unsigned(ymd.day())
<< " Linux Linux\n";
std::cout << R"troff(.SH NAME
musique \- interactive, musical programming language
.SH SYNOPSIS
.B musique
[
SUBCOMMANDS
]
.SH DESCRIPTION
Musique is an interpreted, interactive, musical domain specific programming language
that allows for algorythmic music composition, live-coding and orchestra performing.
.SH SUBCOMMANDS
All subcommands can be expressed in three styles: -i arg -j -k
.I or
--i=arg --j --k
.I or
i arg j k
)troff";
iterate_over_documentation(std::cout, &Documentation_For_Handler_Entry::long_documentation, {},
[](std::ostream& out, std::string_view name) -> std::ostream&
{
return out << ".TP\n" << name;
});
std::cout << R"troff(.SH ENVIROMENT
.TP
NO_COLOR
This enviroment variable overrides standard Musique color behaviour.
When it's defined, it disables colors and ensures they are not enabled.
.SH FILES
.TP
History file
History file for interactive mode is kept in XDG_DATA_HOME (or similar on other operating systems).
.SH EXAMPLES
.TP
musique \-c "play (c5 + up 12)"
Plays all semitones in 5th octave
.TP
musique run examples/ode-to-joy.mq
Play Ode to Joy written as Musique source code in examples/ode-to-joy.mq
)troff";
std::exit(0);
}
bool cmd::is_tty()
{
#ifdef _WIN32
return _isatty(STDOUT_FILENO);
#else
return isatty(fileno(stdout));
#endif
}

39
musique/cmd.hh Normal file
View File

@ -0,0 +1,39 @@
#ifndef MUSIQUE_CMD_HH
#define MUSIQUE_CMD_HH
#include <optional>
#include <span>
#include <string_view>
#include <variant>
#include <vector>
namespace cmd
{
/// Describes all arguments that will be run
struct Run
{
enum Type
{
File,
Argument,
Deffered_File
} type;
std::string_view argument;
};
/// Accept and execute next command line argument with its parameters if it has any
std::optional<std::string_view> accept_commandline_argument(std::vector<cmd::Run> &runnables, std::span<char const*> &args);
/// Print all arguments that are similar to one provided
void print_close_matches(std::string_view arg);
/// Recognize if stdout is connected to terminal
bool is_tty();
[[noreturn]]
void usage();
}
#endif // MUSIQUE_CMD_HH

View File

@ -62,4 +62,11 @@ concept Three_Way_Comparable = requires (T const& lhs, T const& 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

View File

@ -0,0 +1,14 @@
#ifndef MUSIQUE_BUILTIN_FUNCTION_DOCUMENTATION_HH
#define MUSIQUE_BUILTIN_FUNCTION_DOCUMENTATION_HH
#include <optional>
#include <string_view>
#include <vector>
std::optional<std::string_view> find_documentation_for_builtin(std::string_view builtin_name);
/// Returns top 4 similar names to required
std::vector<std::string_view> similar_names_to_builtin(std::string_view builtin_name);
#endif

View File

@ -430,7 +430,9 @@ static Result<Value> builtin_par(Interpreter &interpreter, std::vector<Value> ar
for (auto const& note : chord->notes) {
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) {
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;
@ -548,12 +552,16 @@ static Result<Value> builtin_sim(Interpreter &interpreter, std::vector<Value> ar
for (auto const& instruction : schedule) {
auto const dur = ctx.length_to_duration({instruction.when});
if (start_time < dur) {
std::this_thread::sleep_for(dur - start_time);
interpreter.sleep(dur - start_time);
start_time = dur;
}
switch (instruction.action) {
break; case Instruction::On: interpreter.current_context->port->send_note_on(0, instruction.note, 127);
break; case Instruction::Off: interpreter.current_context->port->send_note_off(0, instruction.note, 127);
break; case Instruction::On:
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 });
}
}

View File

@ -6,6 +6,8 @@
#include <iostream>
#include <random>
#include <thread>
#include <condition_variable>
#include <mutex>
std::unordered_map<std::string, Intrinsic> Interpreter::operators {};
@ -53,6 +55,8 @@ Interpreter::~Interpreter()
Result<Value> Interpreter::eval(Ast &&ast)
{
handle_potential_interrupt();
switch (ast.type) {
case Ast::Type::Literal:
switch (ast.token.type) {
@ -248,8 +252,10 @@ std::optional<Error> Interpreter::play(Chord chord)
Try(ensure_midi_connection_available(*this, "play"));
auto &ctx = *current_context;
handle_potential_interrupt();
if (chord.notes.size() == 0) {
std::this_thread::sleep_for(ctx.length_to_duration(ctx.length));
sleep(ctx.length_to_duration(ctx.length));
return {};
}
@ -265,6 +271,7 @@ std::optional<Error> Interpreter::play(Chord chord)
for (auto const& note : chord.notes) {
if (note.base) {
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) {
if (max_time != Number(0)) {
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) {
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 {};
}
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)
{
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;
}
// 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{};
}
}

View File

@ -6,6 +6,13 @@
#include <musique/midi/midi.hh>
#include <musique/value/value.hh>
#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
struct Interpreter
@ -22,6 +29,8 @@ struct Interpreter
std::function<std::optional<Error>(Interpreter&, Value)> default_action;
std::multiset<std::pair<unsigned, unsigned>> active_notes;
Starter starter;
Interpreter();
@ -53,6 +62,18 @@ struct Interpreter
/// Dumps snapshot of interpreter into stream
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);

View File

@ -1,22 +1,30 @@
#include <charconv>
#include <csignal>
#include <cstdio>
#include <cstring>
#include <edit_distance.hh>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <span>
#include <thread>
#include <cstring>
#include <cstdio>
#include <musique/cmd.hh>
#include <musique/format.hh>
#include <musique/interpreter/builtin_function_documentation.hh>
#include <musique/interpreter/env.hh>
#include <musique/interpreter/interpreter.hh>
#include <musique/lexer/lines.hh>
#include <musique/midi/midi.hh>
#include <musique/parser/parser.hh>
#include <musique/platform.hh>
#include <musique/pretty.hh>
#include <musique/try.hh>
#include <musique/unicode.hh>
#include <musique/user_directory.hh>
#include <musique/value/block.hh>
#include <span>
#include <thread>
#include <unordered_set>
#include <replxx.hxx>
#ifdef _WIN32
extern "C" {
@ -24,73 +32,16 @@ extern "C" {
}
#else
#include <unistd.h>
extern "C" {
#include <bestline.h>
}
#endif
namespace fs = std::filesystem;
static bool quiet_mode = false;
static bool ast_only_mode = false;
static bool enable_repl = false;
bool ast_only_mode = false;
bool enable_repl = false;
static unsigned repl_line_number = 1;
#define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0)
/// Pop string from front of an array
template<typename T = std::string_view>
static T pop(std::span<char const*> &span)
{
auto element = span.front();
span = span.subspan(1);
if constexpr (std::is_same_v<T, std::string_view>) {
return element;
} else if constexpr (std::is_arithmetic_v<T>) {
T result;
auto end = element + std::strlen(element);
auto [ptr, ec] = std::from_chars(element, end, result);
if (ec != decltype(ec){}) {
std::cout << "Expected natural number as argument" << std::endl;
std::exit(1);
}
return result;
} else {
static_assert(always_false<T>, "Unsupported type for pop operation");
}
}
/// Print usage and exit
[[noreturn]] void usage()
{
std::cerr <<
"usage: musique <options> [filename]\n"
" where filename is path to file with Musique code that will be executed\n"
" where options are:\n"
" -c,--run CODE\n"
" executes given code\n"
" -I,--interactive,--repl\n"
" enables interactive mode even when another code was passed\n"
"\n"
" -f,--as-function FILENAME\n"
" deffer file execution and turn it into a file\n"
"\n"
" --ast\n"
" prints ast for given code\n"
"\n"
" -v,--version\n"
" prints Musique interpreter version\n"
"\n"
"Thanks to:\n"
" Sy Brand, https://sybrand.ink/, creator of tl::expected https://github.com/TartanLlama/expected\n"
" Justine Tunney, https://justinetunney.com, creator of bestline readline library https://github.com/jart/bestline\n"
" Gary P. Scavone, http://www.music.mcgill.ca/~gary/, creator of rtmidi https://github.com/thestk/rtmidi\n"
" Creators of ableton/link, https://github.com/Ableton/link\n"
;
std::exit(1);
}
void print_repl_help()
{
std::cout <<
@ -219,9 +170,15 @@ struct Runner
dump(ast);
return {};
}
try {
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 {};
}
};
@ -230,7 +187,7 @@ struct Runner
/// some of the strings are only views into source
std::vector<std::string> eternal_sources;
#ifndef _WIN32
#if 0
void completion(char const* buf, bestlineCompletions *lc)
{
std::string_view in{buf};
@ -245,15 +202,6 @@ void completion(char const* buf, bestlineCompletions *lc)
}
#endif
bool is_tty()
{
#ifdef _WIN32
return _isatty(STDOUT_FILENO);
#else
return isatty(fileno(stdout));
#endif
}
/// Handles commands inside REPL session (those starting with ':')
///
/// Returns if one of command matched
@ -352,84 +300,41 @@ static Result<bool> handle_repl_session_commands(std::string_view input, Runner
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)
static std::optional<Error> Main(std::span<char const*> args)
{
if (is_tty() && getenv("NO_COLOR") == nullptr) {
enable_repl = args.empty();
if (cmd::is_tty() && getenv("NO_COLOR") == nullptr) {
pretty::terminal_mode();
}
/// Describes all arguments that will be run
struct Run
{
enum Type
{
File,
Argument,
Deffered_File
} type;
std::string_view argument;
};
std::vector<cmd::Run> runnables;
std::vector<Run> runnables;
while (not args.empty()) {
std::string_view arg = pop(args);
if (arg == "-" || !arg.starts_with('-')) {
runnables.push_back({ .type = Run::File, .argument = std::move(arg) });
continue;
}
if (arg == "-c" || arg == "--run") {
if (args.empty()) {
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
std::exit(1);
}
runnables.push_back({ .type = Run::Argument, .argument = pop(args) });
continue;
}
if (arg == "-f" || arg == "--as-function") {
if (args.empty()) {
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
std::exit(1);
}
runnables.push_back({ .type = Run::Deffered_File, .argument = pop(args) });
continue;
}
if (arg == "--quiet" || arg == "-q") {
quiet_mode = true;
continue;
}
if (arg == "--ast") {
ast_only_mode = true;
continue;
}
if (arg == "--repl" || arg == "-I" || arg == "--interactive") {
enable_repl = true;
continue;
}
if (arg == "--version" || arg == "-v") {
std::cout << Musique_Version << std::endl;
return {};
}
if (arg == "-h" || arg == "--help") {
usage();
}
std::cerr << "musique: error: unrecognized command line option: " << arg << std::endl;
while (args.size()) if (auto failed = cmd::accept_commandline_argument(runnables, args)) {
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
std::cerr << " Failed to recognize parameter " << std::quoted(*failed) << std::endl;
cmd::print_close_matches(args.front());
std::exit(1);
}
Runner runner;
::runner = &runner;
std::signal(SIGINT, sigint_handler);
for (auto const& [type, argument] : runnables) {
if (type == Run::Argument) {
if (type == cmd::Run::Argument) {
Lines::the.add_line("<arguments>", argument, repl_line_number);
Try(runner.run(argument, "<arguments>"));
repl_line_number++;
@ -440,7 +345,8 @@ static std::optional<Error> Main(std::span<char const*> args)
eternal_sources.emplace_back(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
} else {
if (not fs::exists(path)) {
std::cerr << "musique: error: couldn't open file: " << path << std::endl;
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
std::cerr << " couldn't open file: " << path << std::endl;
std::exit(1);
}
std::ifstream source_file{fs::path(path)};
@ -448,63 +354,55 @@ static std::optional<Error> Main(std::span<char const*> args)
}
Lines::the.add_file(std::string(path), eternal_sources.back());
if (type == Run::File) {
if (type == cmd::Run::File) {
Try(runner.run(eternal_sources.back(), path));
} else {
Try(runner.deffered_file(eternal_sources.back(), argument));
}
}
enable_repl = enable_repl || std::all_of(runnables.begin(), runnables.end(),
[](Run const& run) {
return run.type == Run::Deffered_File;
});
enable_repl = enable_repl || (!runnables.empty() && std::all_of(runnables.begin(), runnables.end(),
[](cmd::Run const& run) { return run.type == cmd::Run::Deffered_File; }));
if (runnables.empty() || enable_repl) {
if (enable_repl) {
repl_line_number = 1;
enable_repl = true;
#ifndef _WIN32
bestlineSetCompletionCallback(completion);
#else
std::vector<std::string> repl_source_lines;
#endif
replxx::Replxx repl;
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 (;;) {
#ifndef _WIN32
char const* input_buffer = bestlineWithHistory("> ", "musique");
if (input_buffer == nullptr) {
char const* input = nullptr;
do input = repl.input("> "); while((input == nullptr) && (errno == EAGAIN));
if (input == nullptr) {
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
std::string_view raw = input_buffer;
std::string_view raw = input;
// Used to recognize REPL commands
std::string_view command = raw;
trim(command);
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;
}
repl.history_add(std::string(command));
repl.history_save(history_path);
if (command.starts_with(':')) {
command.remove_prefix(1);
if (!Try(handle_repl_session_commands(command, runner))) {
std::cerr << "musique: error: unrecognized REPL command '" << command << '\'' << std::endl;
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
std::cerr << " unrecognized REPL command '" << command << '\'' << std::endl;
}
continue;
}

24
musique/platform.hh Normal file
View 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

View File

@ -8,10 +8,11 @@ extern "C" {
namespace starters
{
static std::string_view Error;
static std::string_view Path;
static std::string_view Bold;
static std::string_view Comment;
static std::string_view End;
static std::string_view Error;
static std::string_view Path;
}
std::ostream& pretty::begin_error(std::ostream& os)
@ -29,6 +30,11 @@ std::ostream& pretty::begin_comment(std::ostream& os)
return os << starters::Comment;
}
std::ostream& pretty::begin_bold(std::ostream& os)
{
return os << starters::Bold;
}
std::ostream& pretty::end(std::ostream& os)
{
return os << starters::End;
@ -56,16 +62,18 @@ void pretty::terminal_mode()
#endif
starters::Error = "\x1b[31;1m";
starters::Path = "\x1b[34;1m";
starters::Bold = "\x1b[1m";
starters::Comment = "\x1b[30;1m";
starters::End = "\x1b[0m";
starters::Error = "\x1b[31;1m";
starters::Path = "\x1b[34;1m";
}
void pretty::no_color_mode()
{
starters::Error = {};
starters::Path = {};
starters::Bold = {};
starters::Comment = {};
starters::End = {};
starters::Error = {};
starters::Path = {};
}

View File

@ -15,6 +15,9 @@ namespace pretty
/// Mark start of printing a comment
std::ostream& begin_comment(std::ostream&);
/// Mark start of printing with bold face
std::ostream& begin_bold(std::ostream&);
/// Mark end of any above
std::ostream& end(std::ostream&);

92
musique/user_directory.cc Normal file
View 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
View 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

View File

@ -276,7 +276,7 @@
"exit_code": 0,
"stdin_lines": [],
"stdout_lines": [
"3/4",
"1/4",
"1/4",
"1",
"3/10"

36
scripts/build.mk Normal file → Executable file
View File

@ -1,24 +1,40 @@
Release_Obj=$(addprefix bin/$(os)/,$(Obj))
Release_Obj=$(addprefix bin/$(os)/,$(Obj)) bin/$(os)/builtin_function_documentation.o
bin/$(os)/bestline.o: lib/bestline/bestline.c lib/bestline/bestline.h
@echo "CC $@"
@$(CC) $< -c -O3 -o $@
# bin/$(os)/libreplxx.a:
# @CXX=$(CXX) os=$(os) scripts/build_replxx.sh
bin/$(os)/%.o: musique/%.cc
@echo "CXX $@"
@$(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 $@"
@$(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)
Debug_Obj=$(addprefix bin/$(os)/debug/,$(Obj))
bin/$(os)/debug/$(Target): $(Debug_Obj) bin/$(os)/debug/main.o bin/$(os)/rtmidi.o $(Bestline)
bin/$(os)/builtin_function_documentation.o: bin/$(os)/builtin_function_documentation.cc
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $(Debug_Obj) bin/$(os)/rtmidi.o $(Bestline) $(LDFLAGS) $(LDLIBS)
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
bin/$(os)/builtin_function_documentation.cc: musique/interpreter/builtin_functions.cc scripts/document-builtin.py
scripts/document-builtin.py -f cpp -o $@ musique/interpreter/builtin_functions.cc
Debug_Obj=$(addprefix bin/$(os)/debug/,$(Obj)) bin/$(os)/debug/builtin_function_documentation.o
bin/$(os)/debug/$(Target): $(Debug_Obj) bin/$(os)/debug/main.o bin/$(os)/rtmidi.o
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $(Debug_Obj) bin/$(os)/rtmidi.o $(shell CXX=$(CXX) os=$(os) scripts/build_replxx.sh) $(LDFLAGS) $(LDLIBS)
bin/$(os)/debug/%.o: musique/%.cc
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c
bin/$(os)/debug/builtin_function_documentation.o: bin/$(os)/builtin_function_documentation.cc
@echo "CXX $@"
@$(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
View 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

View File

@ -1,11 +1,12 @@
#!/usr/bin/env python3
import argparse
import dataclasses
import string
import re
import itertools
import typing
import json
import re
import string
import subprocess
import typing
MARKDOWN_CONVERTER = "lowdown -m 'shiftheadinglevelby=3'"
CPP_FUNC_IDENT_ALLOWLIST = string.ascii_letters + string.digits + "_"
@ -60,6 +61,38 @@ HTML_SUFFIX = """
</html>
"""
FIND_DOCUMENTATION_FOR_BUILTIN = """
std::optional<std::string_view> find_documentation_for_builtin(std::string_view builtin_name)
{
for (auto [name, doc] : names_to_documentation) {
if (builtin_name == name)
return doc;
}
return std::nullopt;
}
std::vector<std::string_view> similar_names_to_builtin(std::string_view builtin_name)
{
auto minimum_distance = std::numeric_limits<int>::max();
std::array<std::pair<std::string_view, std::string_view>, 4> closest;
std::partial_sort_copy(
names_to_documentation.begin(), names_to_documentation.end(),
closest.begin(), closest.end(),
[&minimum_distance, builtin_name](auto const& lhs, auto const& rhs) {
auto const lhs_score = edit_distance(builtin_name, lhs.first);
auto const rhs_score = edit_distance(builtin_name, rhs.first);
minimum_distance = std::min({ minimum_distance, lhs_score, rhs_score });
return lhs_score < rhs_score;
}
);
std::vector<std::string_view> result;
result.resize(4);
std::transform(closest.begin(), closest.end(), result.begin(), [](auto const& p) { return p.first; });
return result;
}
"""
def warning(*args, prefix: str | None = None):
if prefix is None:
@ -151,7 +184,9 @@ def builtins_from_file(source_path: str) -> typing.Generator[Builtin, None, None
yield builtin
def filter_builtins(builtins: list[Builtin]) -> typing.Generator[Builtin, None, None]:
def filter_builtins(
builtins: typing.Iterable[Builtin],
) -> typing.Generator[Builtin, None, None]:
for builtin in builtins:
if not builtin.documentation:
warning(f"builtin '{builtin.implementation}' doesn't have documentation")
@ -167,7 +202,7 @@ def filter_builtins(builtins: list[Builtin]) -> typing.Generator[Builtin, None,
def each_musique_name_occurs_once(
builtins: typing.Iterable[Builtin],
) -> typing.Generator[Builtin, None, None]:
names = {}
names: dict[str, str] = {}
for builtin in builtins:
for name in builtin.names:
if name in names:
@ -178,7 +213,7 @@ def each_musique_name_occurs_once(
yield builtin
def generate_html_document(builtins: list[Builtin], output_path: str):
def generate_html_document(builtins: typing.Iterable[Builtin], output_path: str):
with open(output_path, "w") as out:
out.write(HTML_PREFIX)
@ -218,7 +253,47 @@ def generate_html_document(builtins: list[Builtin], output_path: str):
out.write(HTML_SUFFIX)
def main(source_path: str, output_path: str):
def generate_cpp_documentation(builtins: typing.Iterable[Builtin], output_path: str):
# TODO Support markdown rendering and colors using `pretty` sublibrary
def documentation_str_var(builtin: Builtin):
return f"{builtin.implementation}_doc"
with open(output_path, "w") as out:
includes = [
"algorithm",
"array",
"edit_distance.hh",
"musique/interpreter/builtin_function_documentation.hh",
"vector",
]
for include in includes:
print(f"#include <{include}>", file=out)
# 1. Generate strings with documentation
for builtin in builtins:
# FIXME json.dumps will probably produce valid C++ strings for most cases,
# but can we ensure that will output valid strings for all cases?
print(
"static constexpr std::string_view %s = %s;"
% (documentation_str_var(builtin), json.dumps(builtin.documentation)),
file=out,
)
print("", file=out)
# 2. Generate array mapping from name to documentation variable
names_to_documentation = list(sorted((name, documentation_str_var(builtin)) for builtin in builtins for name in builtin.names))
print("static constexpr std::array<std::pair<std::string_view, std::string_view>, %d> names_to_documentation = {" % (len(names_to_documentation), ), file=out)
for name, doc in names_to_documentation:
print(" std::pair { std::string_view(%s), %s }," % (json.dumps(name), doc), file=out)
print("};", file=out);
# 3. Generate function that given builtin name results in documentation string
print(FIND_DOCUMENTATION_FOR_BUILTIN, file=out)
def main(source_path: str, output_path: str, format: typing.Literal["html", "cpp"]):
"Generates documentaiton from file source_path and saves in output_path"
builtins = builtins_from_file(source_path)
@ -226,7 +301,10 @@ def main(source_path: str, output_path: str):
builtins = each_musique_name_occurs_once(builtins)
builtins = sorted(list(builtins), key=lambda builtin: builtin.names[0])
if format == "md":
generate_html_document(builtins, output_path)
else:
generate_cpp_documentation(builtins, output_path)
if __name__ == "__main__":
@ -247,10 +325,21 @@ if __name__ == "__main__":
required=True,
help="path for standalone HTML file containing generated documentation",
)
parser.add_argument(
"-f",
"--format",
type=str,
default="md",
help="output format. One of {html, cpp} are allowed, where HTML yields standalone docs, and C++ mode yields integrated docs",
)
PROGRAM_NAME = parser.prog
args = parser.parse_args()
assert len(args.source) == 1
assert len(args.output) == 1
assert args.format in (
"html",
"cpp",
), "Only C++ and HTML output formats are supported"
main(source_path=args.source[0], output_path=args.output[0])
main(source_path=args.source[0], output_path=args.output[0], format=args.format)

View File

@ -2,5 +2,4 @@ CC=gcc
CXX=g++
CPPFLAGS:=$(CPPFLAGS) -D __LINUX_ALSA__ -D LINK_PLATFORM_LINUX
LDLIBS:=-lasound $(LDLIBS) -static-libgcc -static-libstdc++
Bestline=bin/$(os)/bestline.o
Target=musique

View File

@ -3,5 +3,4 @@ CXX=clang++
CPPFLAGS:=$(CPPFLAGS) -D __MACOSX_CORE__ -D LINK_PLATFORM_MACOSX
LDLIBS:=-framework CoreMIDI -framework CoreAudio -framework CoreFoundation $(LDLIBS)
Release_Obj=$(addprefix bin/,$(Obj))
Bestline=bin/$(os)/bestline.o
Target=musique

View File

@ -26,7 +26,7 @@ class TestCase:
def run(self, interpreter: str, source: str, cwd: str):
result = subprocess.run(
args=[interpreter, source, "-q"],
args=[interpreter, "run", source],
capture_output=True,
cwd=cwd,
text=True