Compare commits
19 Commits
280d6bda3c
...
205868a868
Author | SHA1 | Date | |
---|---|---|---|
|
205868a868 | ||
|
1632ef74af | ||
|
ee9cd987e7 | ||
|
4ef724041b | ||
|
943b065626 | ||
|
719e8d4f26 | ||
|
92e5c3169c | ||
|
a2d8f28cd1 | ||
|
20a6779e2f | ||
|
c0f021e57f | ||
|
d351b49148 | ||
|
4dccde7830 | ||
|
ff58e6506d | ||
|
2d61896824 | ||
|
e26474a2d7 | ||
|
a7ed7a0d65 | ||
|
83ede4ef81 | ||
|
aaf6e6ec0c | ||
|
deabd1865a |
14
CHANGELOG.md
14
CHANGELOG.md
@ -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
|
||||
|
6
Makefile
6
Makefile
@ -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)))
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
@ -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);
|
@ -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
5
lib/edit_distance.cc/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
test
|
||||
test.cc
|
||||
Makefile
|
||||
.cache
|
||||
compile_commands.json
|
19
lib/edit_distance.cc/LICENSE
Normal file
19
lib/edit_distance.cc/LICENSE
Normal 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.
|
76
lib/edit_distance.cc/edit_distance.hh
Normal file
76
lib/edit_distance.cc/edit_distance.hh
Normal 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
63
lib/replxx/LICENSE.md
Normal file
@ -0,0 +1,63 @@
|
||||
Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org)
|
||||
Copyright (c) 2010, Salvatore Sanfilippo (antirez at gmail dot com)
|
||||
Copyright (c) 2010, Pieter Noordhuis (pcnoordhuis at gmail dot com)
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Redis nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
wcwidth.cpp
|
||||
===========
|
||||
|
||||
Markus Kuhn -- 2007-05-26 (Unicode 5.0)
|
||||
|
||||
Permission to use, copy, modify, and distribute this software
|
||||
for any purpose and without fee is hereby granted. The author
|
||||
disclaims all warranties with regard to this software.
|
||||
|
||||
|
||||
ConvertUTF.cpp
|
||||
==============
|
||||
|
||||
Copyright 2001-2004 Unicode, Inc.
|
||||
|
||||
Disclaimer
|
||||
|
||||
This source code is provided as is by Unicode, Inc. No claims are
|
||||
made as to fitness for any particular purpose. No warranties of any
|
||||
kind are expressed or implied. The recipient agrees to determine
|
||||
applicability of information provided. If this file has been
|
||||
purchased on magnetic or optical media from Unicode, Inc., the
|
||||
sole remedy for any claim will be exchange of defective media
|
||||
within 90 days of receipt.
|
||||
|
||||
Limitations on Rights to Redistribute This Code
|
||||
|
||||
Unicode, Inc. hereby grants the right to freely use the information
|
||||
supplied in this file in the creation of products supporting the
|
||||
Unicode Standard, and to make copies of this file in any form
|
||||
for internal or external distribution as long as this notice
|
||||
remains attached.
|
646
lib/replxx/include/replxx.h
Normal file
646
lib/replxx/include/replxx.h
Normal file
@ -0,0 +1,646 @@
|
||||
/* linenoise.h -- guerrilla line editing library against the idea that a
|
||||
* line editing lib needs to be 20,000 lines of C code.
|
||||
*
|
||||
* See linenoise.c for more information.
|
||||
*
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __REPLXX_H
|
||||
#define __REPLXX_H
|
||||
|
||||
#define REPLXX_VERSION "0.0.2"
|
||||
#define REPLXX_VERSION_MAJOR 0
|
||||
#define REPLXX_VERSION_MINOR 0
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* For use in Windows DLLs:
|
||||
*
|
||||
* If you are building replxx into a DLL,
|
||||
* unless you are using supplied CMake based build,
|
||||
* ensure that 'REPLXX_BUILDING_DLL' is defined when
|
||||
* building the DLL so that proper symbols are exported.
|
||||
*/
|
||||
#if defined( _WIN32 ) && ! defined( REPLXX_STATIC )
|
||||
# ifdef REPLXX_BUILDING_DLL
|
||||
# define REPLXX_IMPEXP __declspec( dllexport )
|
||||
# else
|
||||
# define REPLXX_IMPEXP __declspec( dllimport )
|
||||
# endif
|
||||
#else
|
||||
# define REPLXX_IMPEXP /**/
|
||||
#endif
|
||||
|
||||
/*! \brief Color definitions to use in highlighter callbacks.
|
||||
*/
|
||||
typedef enum {
|
||||
REPLXX_COLOR_BLACK = 0,
|
||||
REPLXX_COLOR_RED = 1,
|
||||
REPLXX_COLOR_GREEN = 2,
|
||||
REPLXX_COLOR_BROWN = 3,
|
||||
REPLXX_COLOR_BLUE = 4,
|
||||
REPLXX_COLOR_MAGENTA = 5,
|
||||
REPLXX_COLOR_CYAN = 6,
|
||||
REPLXX_COLOR_LIGHTGRAY = 7,
|
||||
REPLXX_COLOR_GRAY = 8,
|
||||
REPLXX_COLOR_BRIGHTRED = 9,
|
||||
REPLXX_COLOR_BRIGHTGREEN = 10,
|
||||
REPLXX_COLOR_YELLOW = 11,
|
||||
REPLXX_COLOR_BRIGHTBLUE = 12,
|
||||
REPLXX_COLOR_BRIGHTMAGENTA = 13,
|
||||
REPLXX_COLOR_BRIGHTCYAN = 14,
|
||||
REPLXX_COLOR_WHITE = 15,
|
||||
REPLXX_COLOR_DEFAULT = 1u << 16u
|
||||
} ReplxxColor;
|
||||
|
||||
enum { REPLXX_KEY_BASE = 0x0010ffff + 1 };
|
||||
enum { REPLXX_KEY_BASE_SHIFT = 0x01000000 };
|
||||
enum { REPLXX_KEY_BASE_CONTROL = 0x02000000 };
|
||||
enum { REPLXX_KEY_BASE_META = 0x04000000 };
|
||||
enum { REPLXX_KEY_ESCAPE = 27 };
|
||||
enum { REPLXX_KEY_PAGE_UP = REPLXX_KEY_BASE + 1 };
|
||||
enum { REPLXX_KEY_PAGE_DOWN = REPLXX_KEY_PAGE_UP + 1 };
|
||||
enum { REPLXX_KEY_DOWN = REPLXX_KEY_PAGE_DOWN + 1 };
|
||||
enum { REPLXX_KEY_UP = REPLXX_KEY_DOWN + 1 };
|
||||
enum { REPLXX_KEY_LEFT = REPLXX_KEY_UP + 1 };
|
||||
enum { REPLXX_KEY_RIGHT = REPLXX_KEY_LEFT + 1 };
|
||||
enum { REPLXX_KEY_HOME = REPLXX_KEY_RIGHT + 1 };
|
||||
enum { REPLXX_KEY_END = REPLXX_KEY_HOME + 1 };
|
||||
enum { REPLXX_KEY_DELETE = REPLXX_KEY_END + 1 };
|
||||
enum { REPLXX_KEY_INSERT = REPLXX_KEY_DELETE + 1 };
|
||||
enum { REPLXX_KEY_F1 = REPLXX_KEY_INSERT + 1 };
|
||||
enum { REPLXX_KEY_F2 = REPLXX_KEY_F1 + 1 };
|
||||
enum { REPLXX_KEY_F3 = REPLXX_KEY_F2 + 1 };
|
||||
enum { REPLXX_KEY_F4 = REPLXX_KEY_F3 + 1 };
|
||||
enum { REPLXX_KEY_F5 = REPLXX_KEY_F4 + 1 };
|
||||
enum { REPLXX_KEY_F6 = REPLXX_KEY_F5 + 1 };
|
||||
enum { REPLXX_KEY_F7 = REPLXX_KEY_F6 + 1 };
|
||||
enum { REPLXX_KEY_F8 = REPLXX_KEY_F7 + 1 };
|
||||
enum { REPLXX_KEY_F9 = REPLXX_KEY_F8 + 1 };
|
||||
enum { REPLXX_KEY_F10 = REPLXX_KEY_F9 + 1 };
|
||||
enum { REPLXX_KEY_F11 = REPLXX_KEY_F10 + 1 };
|
||||
enum { REPLXX_KEY_F12 = REPLXX_KEY_F11 + 1 };
|
||||
enum { REPLXX_KEY_F13 = REPLXX_KEY_F12 + 1 };
|
||||
enum { REPLXX_KEY_F14 = REPLXX_KEY_F13 + 1 };
|
||||
enum { REPLXX_KEY_F15 = REPLXX_KEY_F14 + 1 };
|
||||
enum { REPLXX_KEY_F16 = REPLXX_KEY_F15 + 1 };
|
||||
enum { REPLXX_KEY_F17 = REPLXX_KEY_F16 + 1 };
|
||||
enum { REPLXX_KEY_F18 = REPLXX_KEY_F17 + 1 };
|
||||
enum { REPLXX_KEY_F19 = REPLXX_KEY_F18 + 1 };
|
||||
enum { REPLXX_KEY_F20 = REPLXX_KEY_F19 + 1 };
|
||||
enum { REPLXX_KEY_F21 = REPLXX_KEY_F20 + 1 };
|
||||
enum { REPLXX_KEY_F22 = REPLXX_KEY_F21 + 1 };
|
||||
enum { REPLXX_KEY_F23 = REPLXX_KEY_F22 + 1 };
|
||||
enum { REPLXX_KEY_F24 = REPLXX_KEY_F23 + 1 };
|
||||
enum { REPLXX_KEY_MOUSE = REPLXX_KEY_F24 + 1 };
|
||||
enum { REPLXX_KEY_PASTE_START = REPLXX_KEY_MOUSE + 1 };
|
||||
enum { REPLXX_KEY_PASTE_FINISH = REPLXX_KEY_PASTE_START + 1 };
|
||||
|
||||
#define REPLXX_KEY_SHIFT( key ) ( ( key ) | REPLXX_KEY_BASE_SHIFT )
|
||||
#define REPLXX_KEY_CONTROL( key ) ( ( key ) | REPLXX_KEY_BASE_CONTROL )
|
||||
#define REPLXX_KEY_META( key ) ( ( key ) | REPLXX_KEY_BASE_META )
|
||||
|
||||
enum { REPLXX_KEY_BACKSPACE = REPLXX_KEY_CONTROL( 'H' ) };
|
||||
enum { REPLXX_KEY_TAB = REPLXX_KEY_CONTROL( 'I' ) };
|
||||
enum { REPLXX_KEY_ENTER = REPLXX_KEY_CONTROL( 'M' ) };
|
||||
enum { REPLXX_KEY_ABORT = REPLXX_KEY_META( REPLXX_KEY_CONTROL( 'M' ) ) };
|
||||
|
||||
/*! \brief List of built-in actions that act upon user input.
|
||||
*/
|
||||
typedef enum {
|
||||
REPLXX_ACTION_INSERT_CHARACTER,
|
||||
REPLXX_ACTION_NEW_LINE,
|
||||
REPLXX_ACTION_DELETE_CHARACTER_UNDER_CURSOR,
|
||||
REPLXX_ACTION_DELETE_CHARACTER_LEFT_OF_CURSOR,
|
||||
REPLXX_ACTION_KILL_TO_END_OF_LINE,
|
||||
REPLXX_ACTION_KILL_TO_BEGINING_OF_LINE,
|
||||
REPLXX_ACTION_KILL_TO_END_OF_WORD,
|
||||
REPLXX_ACTION_KILL_TO_BEGINING_OF_WORD,
|
||||
REPLXX_ACTION_KILL_TO_END_OF_SUBWORD,
|
||||
REPLXX_ACTION_KILL_TO_BEGINING_OF_SUBWORD,
|
||||
REPLXX_ACTION_KILL_TO_WHITESPACE_ON_LEFT,
|
||||
REPLXX_ACTION_YANK,
|
||||
REPLXX_ACTION_YANK_CYCLE,
|
||||
REPLXX_ACTION_YANK_LAST_ARG,
|
||||
REPLXX_ACTION_MOVE_CURSOR_TO_BEGINING_OF_LINE,
|
||||
REPLXX_ACTION_MOVE_CURSOR_TO_END_OF_LINE,
|
||||
REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_LEFT,
|
||||
REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_RIGHT,
|
||||
REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_LEFT,
|
||||
REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_RIGHT,
|
||||
REPLXX_ACTION_MOVE_CURSOR_LEFT,
|
||||
REPLXX_ACTION_MOVE_CURSOR_RIGHT,
|
||||
REPLXX_ACTION_LINE_NEXT,
|
||||
REPLXX_ACTION_LINE_PREVIOUS,
|
||||
REPLXX_ACTION_HISTORY_MOVE_NEXT,
|
||||
REPLXX_ACTION_HISTORY_MOVE_PREVIOUS,
|
||||
REPLXX_ACTION_HISTORY_FIRST,
|
||||
REPLXX_ACTION_HISTORY_LAST,
|
||||
REPLXX_ACTION_HISTORY_RESTORE,
|
||||
REPLXX_ACTION_HISTORY_RESTORE_CURRENT,
|
||||
REPLXX_ACTION_HISTORY_INCREMENTAL_SEARCH,
|
||||
REPLXX_ACTION_HISTORY_SEEDED_INCREMENTAL_SEARCH,
|
||||
REPLXX_ACTION_HISTORY_COMMON_PREFIX_SEARCH,
|
||||
REPLXX_ACTION_HINT_NEXT,
|
||||
REPLXX_ACTION_HINT_PREVIOUS,
|
||||
REPLXX_ACTION_CAPITALIZE_WORD,
|
||||
REPLXX_ACTION_LOWERCASE_WORD,
|
||||
REPLXX_ACTION_UPPERCASE_WORD,
|
||||
REPLXX_ACTION_CAPITALIZE_SUBWORD,
|
||||
REPLXX_ACTION_LOWERCASE_SUBWORD,
|
||||
REPLXX_ACTION_UPPERCASE_SUBWORD,
|
||||
REPLXX_ACTION_TRANSPOSE_CHARACTERS,
|
||||
REPLXX_ACTION_TOGGLE_OVERWRITE_MODE,
|
||||
#ifndef _WIN32
|
||||
REPLXX_ACTION_VERBATIM_INSERT,
|
||||
REPLXX_ACTION_SUSPEND,
|
||||
#endif
|
||||
REPLXX_ACTION_BRACKETED_PASTE,
|
||||
REPLXX_ACTION_CLEAR_SCREEN,
|
||||
REPLXX_ACTION_CLEAR_SELF,
|
||||
REPLXX_ACTION_REPAINT,
|
||||
REPLXX_ACTION_COMPLETE_LINE,
|
||||
REPLXX_ACTION_COMPLETE_NEXT,
|
||||
REPLXX_ACTION_COMPLETE_PREVIOUS,
|
||||
REPLXX_ACTION_COMMIT_LINE,
|
||||
REPLXX_ACTION_ABORT_LINE,
|
||||
REPLXX_ACTION_SEND_EOF
|
||||
} ReplxxAction;
|
||||
|
||||
/*! \brief Possible results of key-press handler actions.
|
||||
*/
|
||||
typedef enum {
|
||||
REPLXX_ACTION_RESULT_CONTINUE, /*!< Continue processing user input. */
|
||||
REPLXX_ACTION_RESULT_RETURN, /*!< Return user input entered so far. */
|
||||
REPLXX_ACTION_RESULT_BAIL /*!< Stop processing user input, returns nullptr from the \e input() call. */
|
||||
} ReplxxActionResult;
|
||||
|
||||
typedef struct ReplxxStateTag {
|
||||
char const* text;
|
||||
int cursorPosition;
|
||||
} ReplxxState;
|
||||
|
||||
typedef struct Replxx Replxx;
|
||||
typedef struct ReplxxHistoryScan ReplxxHistoryScan;
|
||||
typedef struct ReplxxHistoryEntryTag {
|
||||
char const* timestamp;
|
||||
char const* text;
|
||||
} ReplxxHistoryEntry;
|
||||
|
||||
/*! \brief Create Replxx library resource holder.
|
||||
*
|
||||
* Use replxx_end() to free resources acquired with this function.
|
||||
*
|
||||
* \return Replxx library resource holder.
|
||||
*/
|
||||
REPLXX_IMPEXP Replxx* replxx_init( void );
|
||||
|
||||
/*! \brief Cleanup resources used by Replxx library.
|
||||
*
|
||||
* \param replxx - a Replxx library resource holder.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_end( Replxx* replxx );
|
||||
|
||||
/*! \brief Line modification callback type definition.
|
||||
*
|
||||
* User can observe and modify line contents (and cursor position)
|
||||
* in response to changes to both introduced by the user through
|
||||
* normal interactions.
|
||||
*
|
||||
* When callback returns Replxx updates current line content
|
||||
* and current cursor position to the ones updated by the callback.
|
||||
*
|
||||
* \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far.
|
||||
* \param cursorPosition[in,out] - a R/W reference to current cursor position.
|
||||
* \param userData - pointer to opaque user data block.
|
||||
*/
|
||||
typedef void (replxx_modify_callback_t)(char** input, int* contextLen, void* userData);
|
||||
|
||||
/*! \brief Register modify callback.
|
||||
*
|
||||
* \param fn - user defined callback function.
|
||||
* \param userData - pointer to opaque user data block to be passed into each invocation of the callback.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_modify_callback( Replxx*, replxx_modify_callback_t* fn, void* userData );
|
||||
|
||||
/*! \brief Highlighter callback type definition.
|
||||
*
|
||||
* If user want to have colorful input she must simply install highlighter callback.
|
||||
* The callback would be invoked by the library after each change to the input done by
|
||||
* the user. After callback returns library uses data from colors buffer to colorize
|
||||
* displayed user input.
|
||||
*
|
||||
* \e size of \e colors buffer is equal to number of code points in user \e input
|
||||
* which will be different from simple `strlen( input )`!
|
||||
*
|
||||
* \param input - an UTF-8 encoded input entered by the user so far.
|
||||
* \param colors - output buffer for color information.
|
||||
* \param size - size of output buffer for color information.
|
||||
* \param userData - pointer to opaque user data block.
|
||||
*/
|
||||
typedef void (replxx_highlighter_callback_t)(char const* input, ReplxxColor* colors, int size, void* userData);
|
||||
|
||||
/*! \brief Register highlighter callback.
|
||||
*
|
||||
* \param fn - user defined callback function.
|
||||
* \param userData - pointer to opaque user data block to be passed into each invocation of the callback.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_highlighter_callback( Replxx*, replxx_highlighter_callback_t* fn, void* userData );
|
||||
|
||||
typedef struct replxx_completions replxx_completions;
|
||||
|
||||
/*! \brief Completions callback type definition.
|
||||
*
|
||||
* \e contextLen is counted in Unicode code points (not in bytes!).
|
||||
*
|
||||
* For user input:
|
||||
* if ( obj.me
|
||||
*
|
||||
* input == "if ( obj.me"
|
||||
* contextLen == 2 (depending on \e replxx_set_word_break_characters())
|
||||
*
|
||||
* Client application is free to update \e contextLen to be 6 (or any other non-negative
|
||||
* number not greater than the number of code points in input) if it makes better sense
|
||||
* for given client application semantics.
|
||||
*
|
||||
* \param input - UTF-8 encoded input entered by the user until current cursor position.
|
||||
* \param completions - pointer to opaque list of user completions.
|
||||
* \param contextLen[in,out] - length of the additional context to provide while displaying completions.
|
||||
* \param userData - pointer to opaque user data block.
|
||||
*/
|
||||
typedef void(replxx_completion_callback_t)(const char* input, replxx_completions* completions, int* contextLen, void* userData);
|
||||
|
||||
/*! \brief Register completion callback.
|
||||
*
|
||||
* \param fn - user defined callback function.
|
||||
* \param userData - pointer to opaque user data block to be passed into each invocation of the callback.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_completion_callback( Replxx*, replxx_completion_callback_t* fn, void* userData );
|
||||
|
||||
/*! \brief Add another possible completion for current user input.
|
||||
*
|
||||
* \param completions - pointer to opaque list of user completions.
|
||||
* \param str - UTF-8 encoded completion string.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_add_completion( replxx_completions* completions, const char* str );
|
||||
|
||||
/*! \brief Add another possible completion for current user input.
|
||||
*
|
||||
* \param completions - pointer to opaque list of user completions.
|
||||
* \param str - UTF-8 encoded completion string.
|
||||
* \param color - a color for the completion.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_add_color_completion( replxx_completions* completions, const char* str, ReplxxColor color );
|
||||
|
||||
typedef struct replxx_hints replxx_hints;
|
||||
|
||||
/*! \brief Hints callback type definition.
|
||||
*
|
||||
* \e contextLen is counted in Unicode code points (not in bytes!).
|
||||
*
|
||||
* For user input:
|
||||
* if ( obj.me
|
||||
*
|
||||
* input == "if ( obj.me"
|
||||
* contextLen == 2 (depending on \e replxx_set_word_break_characters())
|
||||
*
|
||||
* Client application is free to update \e contextLen to be 6 (or any other non-negative
|
||||
* number not greater than the number of code points in input) if it makes better sense
|
||||
* for given client application semantics.
|
||||
*
|
||||
* \param input - UTF-8 encoded input entered by the user until current cursor position.
|
||||
* \param hints - pointer to opaque list of possible hints.
|
||||
* \param contextLen[in,out] - length of the additional context to provide while displaying hints.
|
||||
* \param color - a color used for displaying hints.
|
||||
* \param userData - pointer to opaque user data block.
|
||||
*/
|
||||
typedef void(replxx_hint_callback_t)(const char* input, replxx_hints* hints, int* contextLen, ReplxxColor* color, void* userData);
|
||||
|
||||
/*! \brief Register hints callback.
|
||||
*
|
||||
* \param fn - user defined callback function.
|
||||
* \param userData - pointer to opaque user data block to be passed into each invocation of the callback.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_hint_callback( Replxx*, replxx_hint_callback_t* fn, void* userData );
|
||||
|
||||
/*! \brief Key press handler type definition.
|
||||
*
|
||||
* \param code - the key code replxx got from terminal.
|
||||
* \return Decision on how should input() behave after this key handler returns.
|
||||
*/
|
||||
typedef ReplxxActionResult (key_press_handler_t)( int code, void* userData );
|
||||
|
||||
/*! \brief Add another possible hint for current user input.
|
||||
*
|
||||
* \param hints - pointer to opaque list of hints.
|
||||
* \param str - UTF-8 encoded hint string.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_add_hint( replxx_hints* hints, const char* str );
|
||||
|
||||
/*! \brief Read line of user input.
|
||||
*
|
||||
* Returned pointer is managed by the library and is not to be freed in the client.
|
||||
*
|
||||
* \param prompt - prompt to be displayed before getting user input.
|
||||
* \return An UTF-8 encoded input given by the user (or nullptr on EOF).
|
||||
*/
|
||||
REPLXX_IMPEXP char const* replxx_input( Replxx*, const char* prompt );
|
||||
|
||||
/*! \brief Get current state data.
|
||||
*
|
||||
* This call is intended to be used in handlers.
|
||||
*
|
||||
* \param state - buffer for current state of the model.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_get_state( Replxx*, ReplxxState* state );
|
||||
|
||||
/*! \brief Set new state data.
|
||||
*
|
||||
* This call is intended to be used in handlers.
|
||||
*
|
||||
* \param state - new state of the model.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_state( Replxx*, ReplxxState* state );
|
||||
|
||||
/*! \brief Enable/disable case insensitive history search and completion.
|
||||
*
|
||||
* \param val - if set to non-zero then history search and completion will be case insensitive.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_ignore_case( Replxx*, int val );
|
||||
|
||||
/*! \brief Print formatted string to standard output.
|
||||
*
|
||||
* This function ensures proper handling of ANSI escape sequences
|
||||
* contained in printed data, which is especially useful on Windows
|
||||
* since Unixes handle them correctly out of the box.
|
||||
*
|
||||
* \param fmt - printf style format.
|
||||
*/
|
||||
REPLXX_IMPEXP int replxx_print( Replxx*, char const* fmt, ... );
|
||||
|
||||
/*! \brief Prints a char array with the given length to standard output.
|
||||
*
|
||||
* \copydetails print
|
||||
*
|
||||
* \param str - The char array to print.
|
||||
* \param length - The length of the array.
|
||||
*/
|
||||
REPLXX_IMPEXP int replxx_write( Replxx*, char const* str, int length );
|
||||
|
||||
/*! \brief Asynchronously change the prompt while replxx_input() call is in efect.
|
||||
*
|
||||
* Can be used to change the prompt from callbacks or other threads.
|
||||
*
|
||||
* \param prompt - The prompt string to change to.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_prompt( Replxx*, const char* prompt );
|
||||
|
||||
/*! \brief Schedule an emulated key press event.
|
||||
*
|
||||
* \param code - key press code to be emulated.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_emulate_key_press( Replxx*, int unsigned code );
|
||||
|
||||
/*! \brief Invoke built-in action handler.
|
||||
*
|
||||
* \pre This function can be called only from key-press handler.
|
||||
*
|
||||
* \param action - a built-in action to invoke.
|
||||
* \param code - a supplementary key-code to consume by built-in action handler.
|
||||
* \return The action result informing the replxx what shall happen next.
|
||||
*/
|
||||
REPLXX_IMPEXP ReplxxActionResult replxx_invoke( Replxx*, ReplxxAction action, int unsigned code );
|
||||
|
||||
/*! \brief Bind user defined action to handle given key-press event.
|
||||
*
|
||||
* \param code - handle this key-press event with following handler.
|
||||
* \param handler - use this handler to handle key-press event.
|
||||
* \param userData - supplementary user data passed to invoked handlers.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_bind_key( Replxx*, int code, key_press_handler_t handler, void* userData );
|
||||
|
||||
/*! \brief Bind internal `replxx` action (by name) to handle given key-press event.
|
||||
*
|
||||
* Action names are the same as unique part of names of ReplxxAction enumerations
|
||||
* but in lower case, e.g.: an action for recalling previous history line
|
||||
* is \e REPLXX_ACTION_LINE_PREVIOUS so action name to be used in this
|
||||
* interface for the same effect is "line_previous".
|
||||
*
|
||||
* \param code - handle this key-press event with following handler.
|
||||
* \param actionName - name of internal action to be invoked on key press.
|
||||
* \return -1 if invalid action name was used, 0 otherwise.
|
||||
*/
|
||||
int replxx_bind_key_internal( Replxx*, int code, char const* actionName );
|
||||
|
||||
REPLXX_IMPEXP void replxx_set_preload_buffer( Replxx*, const char* preloadText );
|
||||
|
||||
REPLXX_IMPEXP void replxx_history_add( Replxx*, const char* line );
|
||||
REPLXX_IMPEXP int replxx_history_size( Replxx* );
|
||||
|
||||
/*! \brief Set set of word break characters.
|
||||
*
|
||||
* This setting influences word based cursor movement and line editing capabilities.
|
||||
*
|
||||
* \param wordBreakers - 7-bit ASCII set of word breaking characters.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_word_break_characters( Replxx*, char const* wordBreakers );
|
||||
|
||||
/*! \brief How many completions should trigger pagination.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_completion_count_cutoff( Replxx*, int count );
|
||||
|
||||
/*! \brief Set maximum number of displayed hint rows.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_max_hint_rows( Replxx*, int count );
|
||||
|
||||
/*! \brief Set a delay before hint are shown after user stopped typing..
|
||||
*
|
||||
* \param milliseconds - a number of milliseconds to wait before showing hints.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_hint_delay( Replxx*, int milliseconds );
|
||||
|
||||
/*! \brief Set tab completion behavior.
|
||||
*
|
||||
* \param val - use double tab to invoke completions (if != 0).
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_double_tab_completion( Replxx*, int val );
|
||||
|
||||
/*! \brief Set tab completion behavior.
|
||||
*
|
||||
* \param val - invoke completion even if user input is empty (if != 0).
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_complete_on_empty( Replxx*, int val );
|
||||
|
||||
/*! \brief Set tab completion behavior.
|
||||
*
|
||||
* \param val - beep if completion is ambiguous (if != 0).
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_beep_on_ambiguous_completion( Replxx*, int val );
|
||||
|
||||
/*! \brief Set complete next/complete previous behavior.
|
||||
*
|
||||
* COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations,
|
||||
* in case when a partial completion is possible complete only partial part (`false` setting)
|
||||
* or complete first proposed completion fully (`true` setting).
|
||||
* The default is to complete fully (a `true` setting - complete immediately).
|
||||
*
|
||||
* \param val - complete immediately.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_immediate_completion( Replxx*, int val );
|
||||
|
||||
/*! \brief Set history duplicate entries behaviour.
|
||||
*
|
||||
* \param val - should history contain only unique entries?
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_unique_history( Replxx*, int val );
|
||||
|
||||
/*! \brief Disable output coloring.
|
||||
*
|
||||
* \param val - if set to non-zero disable output colors.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_no_color( Replxx*, int val );
|
||||
|
||||
/*! \brief Enable/disable (prompt width) indent for multiline entry.
|
||||
*
|
||||
* \param val - if set to non-zero then multiline indent will be enabled.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_indent_multiline( Replxx*, int val );
|
||||
|
||||
/*! \brief Set maximum number of entries in history list.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_set_max_history_size( Replxx*, int len );
|
||||
REPLXX_IMPEXP ReplxxHistoryScan* replxx_history_scan_start( Replxx* );
|
||||
REPLXX_IMPEXP void replxx_history_scan_stop( Replxx*, ReplxxHistoryScan* );
|
||||
REPLXX_IMPEXP int replxx_history_scan_next( Replxx*, ReplxxHistoryScan*, ReplxxHistoryEntry* );
|
||||
|
||||
/*! \brief Synchronize REPL's history with given file.
|
||||
*
|
||||
* Synchronizing means loading existing history from given file,
|
||||
* merging it with current history sorted by timestamps,
|
||||
* saving merged version to given file,
|
||||
* keeping merged version as current REPL's history.
|
||||
*
|
||||
* This call is an equivalent of calling:
|
||||
* replxx_history_save( rx, "some-file" );
|
||||
* replxx_history_load( rx, "some-file" );
|
||||
*
|
||||
* \param filename - a path to the file with which REPL's current history should be synchronized.
|
||||
* \return 0 iff history file was successfully created, -1 otherwise.
|
||||
*/
|
||||
REPLXX_IMPEXP int replxx_history_sync( Replxx*, const char* filename );
|
||||
|
||||
/*! \brief Save REPL's history into given file.
|
||||
*
|
||||
* Saving means loading existing history from given file,
|
||||
* merging it with current history sorted by timestamps,
|
||||
* saving merged version to given file,
|
||||
* keeping original (NOT merged) version as current REPL's history.
|
||||
*
|
||||
* \param filename - a path to the file where REPL's history should be saved.
|
||||
* \return 0 iff history file was successfully created, -1 otherwise.
|
||||
*/
|
||||
REPLXX_IMPEXP int replxx_history_save( Replxx*, const char* filename );
|
||||
|
||||
/*! \brief Load REPL's history from given file.
|
||||
*
|
||||
* \param filename - a path to the file which contains REPL's history that should be loaded.
|
||||
* \return 0 iff history file was successfully opened, -1 otherwise.
|
||||
*/
|
||||
REPLXX_IMPEXP int replxx_history_load( Replxx*, const char* filename );
|
||||
|
||||
/*! \brief Clear REPL's in-memory history.
|
||||
*/
|
||||
REPLXX_IMPEXP void replxx_history_clear( Replxx* );
|
||||
REPLXX_IMPEXP void replxx_clear_screen( Replxx* );
|
||||
#ifdef __REPLXX_DEBUG__
|
||||
void replxx_debug_dump_print_codes(void);
|
||||
#endif
|
||||
/* the following is extension to the original linenoise API */
|
||||
REPLXX_IMPEXP int replxx_install_window_change_handler( Replxx* );
|
||||
REPLXX_IMPEXP void replxx_enable_bracketed_paste( Replxx* );
|
||||
REPLXX_IMPEXP void replxx_disable_bracketed_paste( Replxx* );
|
||||
|
||||
/*! \brief Combine two color definitions to get encompassing color definition.
|
||||
*
|
||||
* To be used only for combining foreground and background colors.
|
||||
*
|
||||
* \param color1 - first input color.
|
||||
* \param color2 - second input color.
|
||||
* \return A new color definition that represent combined input colors.
|
||||
*/
|
||||
ReplxxColor replxx_color_combine( ReplxxColor color1, ReplxxColor color2 );
|
||||
|
||||
/*! \brief Transform foreground color definition into a background color definition.
|
||||
*
|
||||
* \param color - an input foreground color definition.
|
||||
* \return A background color definition that is a transformed input \e color.
|
||||
*/
|
||||
ReplxxColor replxx_color_bg( ReplxxColor color );
|
||||
|
||||
/*! \brief Add `bold` attribute to color definition.
|
||||
*
|
||||
* \param color - an input color definition.
|
||||
* \return A new color definition with bold attribute set.
|
||||
*/
|
||||
ReplxxColor replxx_color_bold( ReplxxColor color );
|
||||
|
||||
/*! \brief Add `underline` attribute to color definition.
|
||||
*
|
||||
* \param color - an input color definition.
|
||||
* \return A new color definition with underline attribute set.
|
||||
*/
|
||||
ReplxxColor replxx_color_underline( ReplxxColor color );
|
||||
|
||||
/*! \brief Create a new grayscale color of given brightness level.
|
||||
*
|
||||
* \param level - a brightness level for new color, must be between 0 (darkest) and 23 (brightest).
|
||||
* \return A new grayscale color of a given brightest \e level.
|
||||
*/
|
||||
ReplxxColor replxx_color_grayscale( int level );
|
||||
|
||||
/*! \brief Create a new color in 6×6×6 RGB color space from base component levels.
|
||||
*
|
||||
* \param red - a red (of RGB) component level, must be 0 and 5.
|
||||
* \param green - a green (of RGB) component level, must be 0 and 5.
|
||||
* \param blue - a blue (of RGB) component level, must be 0 and 5.
|
||||
* \return A new color in 6×6×6 RGB color space.
|
||||
*/
|
||||
ReplxxColor replxx_color_rgb666( int red, int green, int blue );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __REPLXX_H */
|
||||
|
710
lib/replxx/include/replxx.hxx
Normal file
710
lib/replxx/include/replxx.hxx
Normal file
@ -0,0 +1,710 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org)
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef HAVE_REPLXX_HXX_INCLUDED
|
||||
#define HAVE_REPLXX_HXX_INCLUDED 1
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
|
||||
/*
|
||||
* For use in Windows DLLs:
|
||||
*
|
||||
* If you are building replxx into a DLL,
|
||||
* unless you are using supplied CMake based build,
|
||||
* ensure that 'REPLXX_BUILDING_DLL' is defined when
|
||||
* building the DLL so that proper symbols are exported.
|
||||
*/
|
||||
#if defined( _WIN32 ) && ! defined( REPLXX_STATIC )
|
||||
# ifdef REPLXX_BUILDING_DLL
|
||||
# define REPLXX_IMPEXP __declspec( dllexport )
|
||||
# else
|
||||
# define REPLXX_IMPEXP __declspec( dllimport )
|
||||
# endif
|
||||
#else
|
||||
# define REPLXX_IMPEXP /**/
|
||||
#endif
|
||||
|
||||
#ifdef ERROR
|
||||
enum { ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 = ERROR };
|
||||
#undef ERROR
|
||||
enum { ERROR = ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 };
|
||||
#endif
|
||||
#ifdef ABORT
|
||||
enum { ABORT_8D12A2CA7E5A64036D7251A3EDA51A38 = ABORT };
|
||||
#undef ABORT
|
||||
enum { ABORT = ABORT_8D12A2CA7E5A64036D7251A3EDA51A38 };
|
||||
#endif
|
||||
#ifdef DELETE
|
||||
enum { DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E = DELETE };
|
||||
#undef DELETE
|
||||
enum { DELETE = DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E };
|
||||
#endif
|
||||
|
||||
namespace replxx {
|
||||
|
||||
class REPLXX_IMPEXP Replxx {
|
||||
public:
|
||||
enum class Color : int {
|
||||
BLACK = 0,
|
||||
RED = 1,
|
||||
GREEN = 2,
|
||||
BROWN = 3,
|
||||
BLUE = 4,
|
||||
MAGENTA = 5,
|
||||
CYAN = 6,
|
||||
LIGHTGRAY = 7,
|
||||
GRAY = 8,
|
||||
BRIGHTRED = 9,
|
||||
BRIGHTGREEN = 10,
|
||||
YELLOW = 11,
|
||||
BRIGHTBLUE = 12,
|
||||
BRIGHTMAGENTA = 13,
|
||||
BRIGHTCYAN = 14,
|
||||
WHITE = 15,
|
||||
DEFAULT = 1u << 16u
|
||||
};
|
||||
struct KEY {
|
||||
static char32_t const BASE = 0x0010ffff + 1;
|
||||
static char32_t const BASE_SHIFT = 0x01000000;
|
||||
static char32_t const BASE_CONTROL = 0x02000000;
|
||||
static char32_t const BASE_META = 0x04000000;
|
||||
static char32_t const ESCAPE = 27;
|
||||
static char32_t const PAGE_UP = BASE + 1;
|
||||
static char32_t const PAGE_DOWN = PAGE_UP + 1;
|
||||
static char32_t const DOWN = PAGE_DOWN + 1;
|
||||
static char32_t const UP = DOWN + 1;
|
||||
static char32_t const LEFT = UP + 1;
|
||||
static char32_t const RIGHT = LEFT + 1;
|
||||
static char32_t const HOME = RIGHT + 1;
|
||||
static char32_t const END = HOME + 1;
|
||||
static char32_t const DELETE = END + 1;
|
||||
static char32_t const INSERT = DELETE + 1;
|
||||
static char32_t const F1 = INSERT + 1;
|
||||
static char32_t const F2 = F1 + 1;
|
||||
static char32_t const F3 = F2 + 1;
|
||||
static char32_t const F4 = F3 + 1;
|
||||
static char32_t const F5 = F4 + 1;
|
||||
static char32_t const F6 = F5 + 1;
|
||||
static char32_t const F7 = F6 + 1;
|
||||
static char32_t const F8 = F7 + 1;
|
||||
static char32_t const F9 = F8 + 1;
|
||||
static char32_t const F10 = F9 + 1;
|
||||
static char32_t const F11 = F10 + 1;
|
||||
static char32_t const F12 = F11 + 1;
|
||||
static char32_t const F13 = F12 + 1;
|
||||
static char32_t const F14 = F13 + 1;
|
||||
static char32_t const F15 = F14 + 1;
|
||||
static char32_t const F16 = F15 + 1;
|
||||
static char32_t const F17 = F16 + 1;
|
||||
static char32_t const F18 = F17 + 1;
|
||||
static char32_t const F19 = F18 + 1;
|
||||
static char32_t const F20 = F19 + 1;
|
||||
static char32_t const F21 = F20 + 1;
|
||||
static char32_t const F22 = F21 + 1;
|
||||
static char32_t const F23 = F22 + 1;
|
||||
static char32_t const F24 = F23 + 1;
|
||||
static char32_t const MOUSE = F24 + 1;
|
||||
static char32_t const PASTE_START = MOUSE + 1;
|
||||
static char32_t const PASTE_FINISH = PASTE_START + 1;
|
||||
static constexpr char32_t shift( char32_t key_ ) {
|
||||
return ( key_ | BASE_SHIFT );
|
||||
}
|
||||
static constexpr char32_t control( char32_t key_ ) {
|
||||
return ( key_ | BASE_CONTROL );
|
||||
}
|
||||
static constexpr char32_t meta( char32_t key_ ) {
|
||||
return ( key_ | BASE_META );
|
||||
}
|
||||
static char32_t const BACKSPACE = 'H' | BASE_CONTROL;
|
||||
static char32_t const TAB = 'I' | BASE_CONTROL;
|
||||
static char32_t const ENTER = 'M' | BASE_CONTROL;
|
||||
static char32_t const ABORT = 'C' | BASE_CONTROL | BASE_META;
|
||||
};
|
||||
/*! \brief List of built-in actions that act upon user input.
|
||||
*/
|
||||
enum class ACTION {
|
||||
INSERT_CHARACTER,
|
||||
NEW_LINE,
|
||||
DELETE_CHARACTER_UNDER_CURSOR,
|
||||
DELETE_CHARACTER_LEFT_OF_CURSOR,
|
||||
KILL_TO_END_OF_LINE,
|
||||
KILL_TO_BEGINING_OF_LINE,
|
||||
KILL_TO_END_OF_WORD,
|
||||
KILL_TO_BEGINING_OF_WORD,
|
||||
KILL_TO_END_OF_SUBWORD,
|
||||
KILL_TO_BEGINING_OF_SUBWORD,
|
||||
KILL_TO_WHITESPACE_ON_LEFT,
|
||||
YANK,
|
||||
YANK_CYCLE,
|
||||
YANK_LAST_ARG,
|
||||
MOVE_CURSOR_TO_BEGINING_OF_LINE,
|
||||
MOVE_CURSOR_TO_END_OF_LINE,
|
||||
MOVE_CURSOR_ONE_WORD_LEFT,
|
||||
MOVE_CURSOR_ONE_WORD_RIGHT,
|
||||
MOVE_CURSOR_ONE_SUBWORD_LEFT,
|
||||
MOVE_CURSOR_ONE_SUBWORD_RIGHT,
|
||||
MOVE_CURSOR_LEFT,
|
||||
MOVE_CURSOR_RIGHT,
|
||||
LINE_NEXT,
|
||||
LINE_PREVIOUS,
|
||||
HISTORY_NEXT,
|
||||
HISTORY_PREVIOUS,
|
||||
HISTORY_FIRST,
|
||||
HISTORY_LAST,
|
||||
HISTORY_RESTORE,
|
||||
HISTORY_RESTORE_CURRENT,
|
||||
HISTORY_INCREMENTAL_SEARCH,
|
||||
HISTORY_SEEDED_INCREMENTAL_SEARCH,
|
||||
HISTORY_COMMON_PREFIX_SEARCH,
|
||||
HINT_NEXT,
|
||||
HINT_PREVIOUS,
|
||||
CAPITALIZE_WORD,
|
||||
LOWERCASE_WORD,
|
||||
UPPERCASE_WORD,
|
||||
CAPITALIZE_SUBWORD,
|
||||
LOWERCASE_SUBWORD,
|
||||
UPPERCASE_SUBWORD,
|
||||
TRANSPOSE_CHARACTERS,
|
||||
TOGGLE_OVERWRITE_MODE,
|
||||
#ifndef _WIN32
|
||||
VERBATIM_INSERT,
|
||||
SUSPEND,
|
||||
#endif
|
||||
BRACKETED_PASTE,
|
||||
CLEAR_SCREEN,
|
||||
CLEAR_SELF,
|
||||
REPAINT,
|
||||
COMPLETE_LINE,
|
||||
COMPLETE_NEXT,
|
||||
COMPLETE_PREVIOUS,
|
||||
COMMIT_LINE,
|
||||
ABORT_LINE,
|
||||
SEND_EOF
|
||||
};
|
||||
/*! \brief Possible results of key-press handler actions.
|
||||
*/
|
||||
enum class ACTION_RESULT {
|
||||
CONTINUE, /*!< Continue processing user input. */
|
||||
RETURN, /*!< Return user input entered so far. */
|
||||
BAIL /*!< Stop processing user input, returns nullptr from the \e input() call. */
|
||||
};
|
||||
typedef std::vector<Color> colors_t;
|
||||
class Completion {
|
||||
std::string _text;
|
||||
Color _color;
|
||||
public:
|
||||
Completion( char const* text_ )
|
||||
: _text( text_ )
|
||||
, _color( Color::DEFAULT ) {
|
||||
}
|
||||
Completion( std::string const& text_ )
|
||||
: _text( text_ )
|
||||
, _color( Color::DEFAULT ) {
|
||||
}
|
||||
Completion( std::string const& text_, Color color_ )
|
||||
: _text( text_ )
|
||||
, _color( color_ ) {
|
||||
}
|
||||
std::string const& text( void ) const {
|
||||
return ( _text );
|
||||
}
|
||||
Color color( void ) const {
|
||||
return ( _color );
|
||||
}
|
||||
};
|
||||
typedef std::vector<Completion> completions_t;
|
||||
class HistoryEntry {
|
||||
std::string _timestamp;
|
||||
std::string _text;
|
||||
public:
|
||||
HistoryEntry( std::string const& timestamp_, std::string const& text_ )
|
||||
: _timestamp( timestamp_ )
|
||||
, _text( text_ ) {
|
||||
}
|
||||
std::string const& timestamp( void ) const {
|
||||
return ( _timestamp );
|
||||
}
|
||||
std::string const& text( void ) const {
|
||||
return ( _text );
|
||||
}
|
||||
};
|
||||
class HistoryScanImpl;
|
||||
class HistoryScan {
|
||||
public:
|
||||
typedef std::unique_ptr<HistoryScanImpl, void (*)( HistoryScanImpl* )> impl_t;
|
||||
private:
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4251)
|
||||
#endif
|
||||
impl_t _impl;
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
public:
|
||||
HistoryScan( impl_t );
|
||||
HistoryScan( HistoryScan&& ) = default;
|
||||
HistoryScan& operator = ( HistoryScan&& ) = default;
|
||||
bool next( void );
|
||||
HistoryEntry const& get( void ) const;
|
||||
private:
|
||||
HistoryScan( HistoryScan const& ) = delete;
|
||||
HistoryScan& operator = ( HistoryScan const& ) = delete;
|
||||
};
|
||||
typedef std::vector<std::string> hints_t;
|
||||
|
||||
/*! \brief Line modification callback type definition.
|
||||
*
|
||||
* User can observe and modify line contents (and cursor position)
|
||||
* in response to changes to both introduced by the user through
|
||||
* normal interactions.
|
||||
*
|
||||
* When callback returns Replxx updates current line content
|
||||
* and current cursor position to the ones updated by the callback.
|
||||
*
|
||||
* \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far.
|
||||
* \param cursorPosition[in,out] - a R/W reference to current cursor position.
|
||||
*/
|
||||
typedef std::function<void ( std::string& line, int& cursorPosition )> modify_callback_t;
|
||||
|
||||
/*! \brief Completions callback type definition.
|
||||
*
|
||||
* \e contextLen is counted in Unicode code points (not in bytes!).
|
||||
*
|
||||
* For user input:
|
||||
* if ( obj.me
|
||||
*
|
||||
* input == "if ( obj.me"
|
||||
* contextLen == 2 (depending on \e set_word_break_characters())
|
||||
*
|
||||
* Client application is free to update \e contextLen to be 6 (or any other non-negative
|
||||
* number not greater than the number of code points in input) if it makes better sense
|
||||
* for given client application semantics.
|
||||
*
|
||||
* \param input - UTF-8 encoded input entered by the user until current cursor position.
|
||||
* \param[in,out] contextLen - length of the additional context to provide while displaying completions.
|
||||
* \return A list of user completions.
|
||||
*/
|
||||
typedef std::function<completions_t ( std::string const& input, int& contextLen )> completion_callback_t;
|
||||
|
||||
/*! \brief Highlighter callback type definition.
|
||||
*
|
||||
* If user want to have colorful input she must simply install highlighter callback.
|
||||
* The callback would be invoked by the library after each change to the input done by
|
||||
* the user. After callback returns library uses data from colors buffer to colorize
|
||||
* displayed user input.
|
||||
*
|
||||
* Size of \e colors buffer is equal to number of code points in user \e input
|
||||
* which will be different from simple `input.length()`!
|
||||
*
|
||||
* \param input - an UTF-8 encoded input entered by the user so far.
|
||||
* \param colors - output buffer for color information.
|
||||
*/
|
||||
typedef std::function<void ( std::string const& input, colors_t& colors )> highlighter_callback_t;
|
||||
|
||||
/*! \brief Hints callback type definition.
|
||||
*
|
||||
* \e contextLen is counted in Unicode code points (not in bytes!).
|
||||
*
|
||||
* For user input:
|
||||
* if ( obj.me
|
||||
*
|
||||
* input == "if ( obj.me"
|
||||
* contextLen == 2 (depending on \e set_word_break_characters())
|
||||
*
|
||||
* Client application is free to update \e contextLen to be 6 (or any other non-negative
|
||||
* number not greater than the number of code points in input) if it makes better sense
|
||||
* for given client application semantics.
|
||||
*
|
||||
* \param input - UTF-8 encoded input entered by the user until current cursor position.
|
||||
* \param contextLen[in,out] - length of the additional context to provide while displaying hints.
|
||||
* \param color - a color used for displaying hints.
|
||||
* \return A list of possible hints.
|
||||
*/
|
||||
typedef std::function<hints_t ( std::string const& input, int& contextLen, Color& color )> hint_callback_t;
|
||||
|
||||
/*! \brief Key press handler type definition.
|
||||
*
|
||||
* \param code - the key code replxx got from terminal.
|
||||
* \return Decision on how should input() behave after this key handler returns.
|
||||
*/
|
||||
typedef std::function<ACTION_RESULT ( char32_t code )> key_press_handler_t;
|
||||
|
||||
struct State {
|
||||
char const* _text;
|
||||
int _cursorPosition;
|
||||
State( char const* text_, int cursorPosition_ = -1 )
|
||||
: _text( text_ )
|
||||
, _cursorPosition( cursorPosition_ ) {
|
||||
}
|
||||
State( State const& ) = default;
|
||||
State& operator = ( State const& ) = default;
|
||||
char const* text( void ) const {
|
||||
return ( _text );
|
||||
}
|
||||
int cursor_position( void ) const {
|
||||
return ( _cursorPosition );
|
||||
}
|
||||
};
|
||||
|
||||
class ReplxxImpl;
|
||||
private:
|
||||
typedef std::unique_ptr<ReplxxImpl, void (*)( ReplxxImpl* )> impl_t;
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4251)
|
||||
#endif
|
||||
impl_t _impl;
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
public:
|
||||
Replxx( void );
|
||||
Replxx( Replxx&& ) = default;
|
||||
Replxx& operator = ( Replxx&& ) = default;
|
||||
|
||||
/*! \brief Register modify callback.
|
||||
*
|
||||
* \param fn - user defined callback function.
|
||||
*/
|
||||
void set_modify_callback( modify_callback_t const& fn );
|
||||
|
||||
/*! \brief Register completion callback.
|
||||
*
|
||||
* \param fn - user defined callback function.
|
||||
*/
|
||||
void set_completion_callback( completion_callback_t const& fn );
|
||||
|
||||
/*! \brief Register highlighter callback.
|
||||
*
|
||||
* \param fn - user defined callback function.
|
||||
*/
|
||||
void set_highlighter_callback( highlighter_callback_t const& fn );
|
||||
|
||||
/*! \brief Register hints callback.
|
||||
*
|
||||
* \param fn - user defined callback function.
|
||||
*/
|
||||
void set_hint_callback( hint_callback_t const& fn );
|
||||
|
||||
/*! \brief Read line of user input.
|
||||
*
|
||||
* Returned pointer is managed by the library and is not to be freed in the client.
|
||||
*
|
||||
* \param prompt - prompt to be displayed before getting user input.
|
||||
* \return An UTF-8 encoded input given by the user (or nullptr on EOF).
|
||||
*/
|
||||
char const* input( std::string const& prompt );
|
||||
|
||||
/*! \brief Get current state data.
|
||||
*
|
||||
* This call is intended to be used in handlers.
|
||||
*
|
||||
* \return Current state of the model.
|
||||
*/
|
||||
State get_state( void ) const;
|
||||
|
||||
/*! \brief Set new state data.
|
||||
*
|
||||
* This call is intended to be used in handlers.
|
||||
*
|
||||
* \param state - new state of the model.
|
||||
*/
|
||||
void set_state( State const& state );
|
||||
|
||||
/*! \brief Enable/disable case insensitive history search and completion.
|
||||
*
|
||||
* \param val - if set to non-zero then history search and completion will be case insensitive.
|
||||
*/
|
||||
void set_ignore_case( bool val );
|
||||
|
||||
/*! \brief Print formatted string to standard output.
|
||||
*
|
||||
* This function ensures proper handling of ANSI escape sequences
|
||||
* contained in printed data, which is especially useful on Windows
|
||||
* since Unixes handle them correctly out of the box.
|
||||
*
|
||||
* \param fmt - printf style format.
|
||||
*/
|
||||
void print( char const* fmt, ... );
|
||||
|
||||
/*! \brief Prints a char array with the given length to standard output.
|
||||
*
|
||||
* \copydetails print
|
||||
*
|
||||
* \param str - The char array to print.
|
||||
* \param length - The length of the array.
|
||||
*/
|
||||
void write( char const* str, int length );
|
||||
|
||||
/*! \brief Asynchronously change the prompt while replxx_input() call is in efect.
|
||||
*
|
||||
* Can be used to change the prompt from callbacks or other threads.
|
||||
*
|
||||
* \param prompt - The prompt string to change to.
|
||||
*/
|
||||
void set_prompt( std::string prompt );
|
||||
|
||||
/*! \brief Schedule an emulated key press event.
|
||||
*
|
||||
* \param code - key press code to be emulated.
|
||||
*/
|
||||
void emulate_key_press( char32_t code );
|
||||
|
||||
/*! \brief Invoke built-in action handler.
|
||||
*
|
||||
* \pre This method can be called only from key-press handler.
|
||||
*
|
||||
* \param action - a built-in action to invoke.
|
||||
* \param code - a supplementary key-code to consume by built-in action handler.
|
||||
* \return The action result informing the replxx what shall happen next.
|
||||
*/
|
||||
ACTION_RESULT invoke( ACTION action, char32_t code );
|
||||
|
||||
/*! \brief Bind user defined action to handle given key-press event.
|
||||
*
|
||||
* \param code - handle this key-press event with following handler.
|
||||
* \param handle - use this handler to handle key-press event.
|
||||
*/
|
||||
void bind_key( char32_t code, key_press_handler_t handler );
|
||||
|
||||
/*! \brief Bind internal `replxx` action (by name) to handle given key-press event.
|
||||
*
|
||||
* Action names are the same as names of Replxx::ACTION enumerations
|
||||
* but in lower case, e.g.: an action for recalling previous history line
|
||||
* is \e Replxx::ACTION::LINE_PREVIOUS so action name to be used in this
|
||||
* interface for the same effect is "line_previous".
|
||||
*
|
||||
* \param code - handle this key-press event with following handler.
|
||||
* \param actionName - name of internal action to be invoked on key press.
|
||||
*/
|
||||
void bind_key_internal( char32_t code, char const* actionName );
|
||||
|
||||
void history_add( std::string const& line );
|
||||
|
||||
/*! \brief Synchronize REPL's history with given file.
|
||||
*
|
||||
* Synchronizing means loading existing history from given file,
|
||||
* merging it with current history sorted by timestamps,
|
||||
* saving merged version to given file,
|
||||
* keeping merged version as current REPL's history.
|
||||
*
|
||||
* This call is an equivalent of calling:
|
||||
* history_save( "some-file" );
|
||||
* history_load( "some-file" );
|
||||
*
|
||||
* \param filename - a path to the file with which REPL's current history should be synchronized.
|
||||
* \return True iff history file was successfully created.
|
||||
*/
|
||||
bool history_sync( std::string const& filename );
|
||||
|
||||
/*! \brief Save REPL's history into given file.
|
||||
*
|
||||
* Saving means loading existing history from given file,
|
||||
* merging it with current history sorted by timestamps,
|
||||
* saving merged version to given file,
|
||||
* keeping original (NOT merged) version as current REPL's history.
|
||||
*
|
||||
* \param filename - a path to the file where REPL's history should be saved.
|
||||
* \return True iff history file was successfully created.
|
||||
*/
|
||||
bool history_save( std::string const& filename );
|
||||
|
||||
/*!
|
||||
* \copydoc history_save
|
||||
*/
|
||||
void history_save( std::ostream& out );
|
||||
|
||||
/*! \brief Load REPL's history from given file.
|
||||
*
|
||||
* \param filename - a path to the file which contains REPL's history that should be loaded.
|
||||
* \return True iff history file was successfully opened.
|
||||
*/
|
||||
bool history_load( std::string const& filename );
|
||||
|
||||
/*!
|
||||
* \copydoc history_load
|
||||
*/
|
||||
void history_load( std::istream& in );
|
||||
|
||||
/*! \brief Clear REPL's in-memory history.
|
||||
*/
|
||||
void history_clear( void );
|
||||
int history_size( void ) const;
|
||||
HistoryScan history_scan( void ) const;
|
||||
|
||||
void set_preload_buffer( std::string const& preloadText );
|
||||
|
||||
/*! \brief Set set of word break characters.
|
||||
*
|
||||
* This setting influences word based cursor movement and line editing capabilities.
|
||||
*
|
||||
* \param wordBreakers - 7-bit ASCII set of word breaking characters.
|
||||
*/
|
||||
void set_word_break_characters( char const* wordBreakers );
|
||||
|
||||
/*! \brief How many completions should trigger pagination.
|
||||
*/
|
||||
void set_completion_count_cutoff( int count );
|
||||
|
||||
/*! \brief Set maximum number of displayed hint rows.
|
||||
*/
|
||||
void set_max_hint_rows( int count );
|
||||
|
||||
/*! \brief Set a delay before hint are shown after user stopped typing..
|
||||
*
|
||||
* \param milliseconds - a number of milliseconds to wait before showing hints.
|
||||
*/
|
||||
void set_hint_delay( int milliseconds );
|
||||
|
||||
/*! \brief Set tab completion behavior.
|
||||
*
|
||||
* \param val - use double tab to invoke completions.
|
||||
*/
|
||||
void set_double_tab_completion( bool val );
|
||||
|
||||
/*! \brief Set tab completion behavior.
|
||||
*
|
||||
* \param val - invoke completion even if user input is empty.
|
||||
*/
|
||||
void set_complete_on_empty( bool val );
|
||||
|
||||
/*! \brief Set tab completion behavior.
|
||||
*
|
||||
* \param val - beep if completion is ambiguous.
|
||||
*/
|
||||
void set_beep_on_ambiguous_completion( bool val );
|
||||
|
||||
/*! \brief Set complete next/complete previous behavior.
|
||||
*
|
||||
* COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations,
|
||||
* in case when a partial completion is possible complete only partial part (`false` setting)
|
||||
* or complete first proposed completion fully (`true` setting).
|
||||
* The default is to complete fully (a `true` setting - complete immediately).
|
||||
*
|
||||
* \param val - complete immediately.
|
||||
*/
|
||||
void set_immediate_completion( bool val );
|
||||
|
||||
/*! \brief Set history duplicate entries behaviour.
|
||||
*
|
||||
* \param val - should history contain only unique entries?
|
||||
*/
|
||||
void set_unique_history( bool val );
|
||||
|
||||
/*! \brief Disable output coloring.
|
||||
*
|
||||
* \param val - if set to non-zero disable output colors.
|
||||
*/
|
||||
void set_no_color( bool val );
|
||||
|
||||
/*! \brief Enable/disable (prompt width) indent for multiline entry.
|
||||
*
|
||||
* \param val - if set to true then multiline indent will be enabled.
|
||||
*/
|
||||
void set_indent_multiline( bool val );
|
||||
|
||||
/*! \brief Set maximum number of entries in history list.
|
||||
*/
|
||||
void set_max_history_size( int len );
|
||||
void clear_screen( void );
|
||||
int install_window_change_handler( void );
|
||||
void enable_bracketed_paste( void );
|
||||
void disable_bracketed_paste( void );
|
||||
|
||||
private:
|
||||
Replxx( Replxx const& ) = delete;
|
||||
Replxx& operator = ( Replxx const& ) = delete;
|
||||
};
|
||||
|
||||
/*! \brief Color definition related helper function.
|
||||
*
|
||||
* To be used to leverage 256 color terminal capabilities.
|
||||
*/
|
||||
namespace color {
|
||||
|
||||
/*! \brief Combine two color definitions to get encompassing color definition.
|
||||
*
|
||||
* To be used only for combining foreground and background colors.
|
||||
*
|
||||
* \param color1 - first input color.
|
||||
* \param color2 - second input color.
|
||||
* \return A new color definition that represent combined input colors.
|
||||
*/
|
||||
Replxx::Color operator | ( Replxx::Color color1, Replxx::Color color2 );
|
||||
|
||||
/*! \brief Transform foreground color definition into a background color definition.
|
||||
*
|
||||
* \param color - an input foreground color definition.
|
||||
* \return A background color definition that is a transformed input \e color.
|
||||
*/
|
||||
Replxx::Color bg( Replxx::Color color );
|
||||
|
||||
/*! \brief Add `bold` attribute to color definition.
|
||||
*
|
||||
* \param color - an input color definition.
|
||||
* \return A new color definition with bold attribute set.
|
||||
*/
|
||||
Replxx::Color bold( Replxx::Color color );
|
||||
|
||||
/*! \brief Add `underline` attribute to color definition.
|
||||
*
|
||||
* \param color - an input color definition.
|
||||
* \return A new color definition with underline attribute set.
|
||||
*/
|
||||
Replxx::Color underline( Replxx::Color color );
|
||||
|
||||
/*! \brief Create a new grayscale color of given brightness level.
|
||||
*
|
||||
* \param level - a brightness level for new color, must be between 0 (darkest) and 23 (brightest).
|
||||
* \return A new grayscale color of a given brightest \e level.
|
||||
*/
|
||||
Replxx::Color grayscale( int level );
|
||||
|
||||
/*! \brief Create a new color in 6×6×6 RGB color space from base component levels.
|
||||
*
|
||||
* \param red - a red (of RGB) component level, must be 0 and 5.
|
||||
* \param green - a green (of RGB) component level, must be 0 and 5.
|
||||
* \param blue - a blue (of RGB) component level, must be 0 and 5.
|
||||
* \return A new color in 6×6×6 RGB color space.
|
||||
*/
|
||||
Replxx::Color rgb666( int red, int green, int blue );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* HAVE_REPLXX_HXX_INCLUDED */
|
||||
|
271
lib/replxx/src/ConvertUTF.cpp
Normal file
271
lib/replxx/src/ConvertUTF.cpp
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright 2001-2004 Unicode, Inc.
|
||||
*
|
||||
* Disclaimer
|
||||
*
|
||||
* This source code is provided as is by Unicode, Inc. No claims are
|
||||
* made as to fitness for any particular purpose. No warranties of any
|
||||
* kind are expressed or implied. The recipient agrees to determine
|
||||
* applicability of information provided. If this file has been
|
||||
* purchased on magnetic or optical media from Unicode, Inc., the
|
||||
* sole remedy for any claim will be exchange of defective media
|
||||
* within 90 days of receipt.
|
||||
*
|
||||
* Limitations on Rights to Redistribute This Code
|
||||
*
|
||||
* Unicode, Inc. hereby grants the right to freely use the information
|
||||
* supplied in this file in the creation of products supporting the
|
||||
* Unicode Standard, and to make copies of this file in any form
|
||||
* for internal or external distribution as long as this notice
|
||||
* remains attached.
|
||||
*/
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
|
||||
Conversions between UTF32, UTF-16, and UTF-8. Source code file.
|
||||
Author: Mark E. Davis, 1994.
|
||||
Rev History: Rick McGowan, fixes & updates May 2001.
|
||||
Sept 2001: fixed const & error conditions per
|
||||
mods suggested by S. Parent & A. Lillich.
|
||||
June 2002: Tim Dodd added detection and handling of incomplete
|
||||
source sequences, enhanced error detection, added casts
|
||||
to eliminate compiler warnings.
|
||||
July 2003: slight mods to back out aggressive FFFE detection.
|
||||
Jan 2004: updated switches in from-UTF8 conversions.
|
||||
Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions.
|
||||
|
||||
See the header file "ConvertUTF.h" for complete documentation.
|
||||
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
#include "ConvertUTF.h"
|
||||
#ifdef CVTUTF_DEBUG
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
namespace replxx {
|
||||
|
||||
#define UNI_SUR_HIGH_START (UTF32)0xD800
|
||||
#define UNI_SUR_LOW_END (UTF32)0xDFFF
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Index into the table below with the first byte of a UTF-8 sequence to
|
||||
* get the number of trailing bytes that are supposed to follow it.
|
||||
* Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
|
||||
* left as-is for anyone who may want to do such conversion, which was
|
||||
* allowed in earlier algorithms.
|
||||
*/
|
||||
static const char trailingBytesForUTF8[256] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
|
||||
};
|
||||
|
||||
/*
|
||||
* Magic values subtracted from a buffer value during UTF8 conversion.
|
||||
* This table contains as many values as there might be trailing bytes
|
||||
* in a UTF-8 sequence.
|
||||
*/
|
||||
static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL,
|
||||
0x03C82080UL, 0xFA082080UL, 0x82082080UL };
|
||||
|
||||
/*
|
||||
* Once the bits are split out into bytes of UTF-8, this is a mask OR-ed
|
||||
* into the first byte, depending on how many bytes follow. There are
|
||||
* as many entries in this table as there are UTF-8 sequence types.
|
||||
* (I.e., one byte sequence, two byte... etc.). Remember that sequencs
|
||||
* for *legal* UTF-8 will be 4 or fewer bytes total.
|
||||
*/
|
||||
static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
/* The interface converts a whole buffer to avoid function-call overhead.
|
||||
* Constants have been gathered. Loops & conditionals have been removed as
|
||||
* much as possible for efficiency, in favor of drop-through switches.
|
||||
* (See "Note A" at the bottom of the file for equivalent code.)
|
||||
* If your compiler supports it, the "isLegalUTF8" call can be turned
|
||||
* into an inline function.
|
||||
*/
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Utility routine to tell whether a sequence of bytes is legal UTF-8.
|
||||
* This must be called with the length pre-determined by the first byte.
|
||||
* If not calling this from ConvertUTF8to*, then the length can be set by:
|
||||
* length = trailingBytesForUTF8[*source]+1;
|
||||
* and the sequence is illegal right away if there aren't that many bytes
|
||||
* available.
|
||||
* If presented with a length > 4, this returns false. The Unicode
|
||||
* definition of UTF-8 goes up to 4-byte sequences.
|
||||
*/
|
||||
|
||||
static bool isLegalUTF8(const UTF8 *source, int length) {
|
||||
UTF8 a;
|
||||
const UTF8 *srcptr = source+length;
|
||||
switch (length) {
|
||||
default: return false;
|
||||
/* Everything else falls through when "true"... */
|
||||
case 4: { if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; } /* fall through */
|
||||
case 3: { if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; } /* fall through */
|
||||
case 2: {
|
||||
if ((a = (*--srcptr)) > 0xBF) return false;
|
||||
|
||||
switch (*source) {
|
||||
/* no fall-through in this inner switch */
|
||||
case 0xE0: if (a < 0xA0) return false; break;
|
||||
case 0xED: if (a > 0x9F) return false; break;
|
||||
case 0xF0: if (a < 0x90) return false; break;
|
||||
case 0xF4: if (a > 0x8F) return false; break;
|
||||
default: if (a < 0x80) return false;
|
||||
}
|
||||
} /* fall through */
|
||||
case 1: { if (*source >= 0x80 && *source < 0xC2) return false; } /* fall through */
|
||||
}
|
||||
if (*source > 0xF4) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
ConversionResult ConvertUTF32toUTF8 (
|
||||
const UTF32** sourceStart, const UTF32* sourceEnd,
|
||||
UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) {
|
||||
ConversionResult result = conversionOK;
|
||||
const UTF32* source = *sourceStart;
|
||||
UTF8* target = *targetStart;
|
||||
while (source < sourceEnd) {
|
||||
UTF32 ch;
|
||||
unsigned short bytesToWrite = 0;
|
||||
const UTF32 byteMask = 0xBF;
|
||||
const UTF32 byteMark = 0x80;
|
||||
ch = *source++;
|
||||
if (flags == strictConversion ) {
|
||||
/* UTF-16 surrogate values are illegal in UTF-32 */
|
||||
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
|
||||
--source; /* return to the illegal value itself */
|
||||
result = sourceIllegal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Figure out how many bytes the result will require. Turn any
|
||||
* illegally large UTF32 things (> Plane 17) into replacement chars.
|
||||
*/
|
||||
if (ch < (UTF32)0x80) { bytesToWrite = 1;
|
||||
} else if (ch < (UTF32)0x800) { bytesToWrite = 2;
|
||||
} else if (ch < (UTF32)0x10000) { bytesToWrite = 3;
|
||||
} else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4;
|
||||
} else { bytesToWrite = 3;
|
||||
ch = UNI_REPLACEMENT_CHAR;
|
||||
result = sourceIllegal;
|
||||
}
|
||||
|
||||
target += bytesToWrite;
|
||||
if (target > targetEnd) {
|
||||
--source; /* Back up source pointer! */
|
||||
target -= bytesToWrite; result = targetExhausted; break;
|
||||
}
|
||||
switch (bytesToWrite) { /* note: everything falls through. */
|
||||
case 4: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */
|
||||
case 3: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */
|
||||
case 2: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */
|
||||
case 1: { *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); } /* fall through */
|
||||
}
|
||||
target += bytesToWrite;
|
||||
}
|
||||
*sourceStart = source;
|
||||
*targetStart = target;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
ConversionResult ConvertUTF8toUTF32 (
|
||||
const UTF8** sourceStart, const UTF8* sourceEnd,
|
||||
UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) {
|
||||
ConversionResult result = conversionOK;
|
||||
const UTF8* source = *sourceStart;
|
||||
UTF32* target = *targetStart;
|
||||
while (source < sourceEnd) {
|
||||
UTF32 ch = 0;
|
||||
unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
|
||||
if (source + extraBytesToRead >= sourceEnd) {
|
||||
result = sourceExhausted; break;
|
||||
}
|
||||
/* Do this check whether lenient or strict */
|
||||
if (! isLegalUTF8(source, extraBytesToRead+1)) {
|
||||
result = sourceIllegal;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* The cases all fall through. See "Note A" below.
|
||||
*/
|
||||
switch (extraBytesToRead) {
|
||||
case 5: { ch += *source++; ch <<= 6; } /* fall through */
|
||||
case 4: { ch += *source++; ch <<= 6; } /* fall through */
|
||||
case 3: { ch += *source++; ch <<= 6; } /* fall through */
|
||||
case 2: { ch += *source++; ch <<= 6; } /* fall through */
|
||||
case 1: { ch += *source++; ch <<= 6; } /* fall through */
|
||||
case 0: { ch += *source++; } /* fall through */
|
||||
}
|
||||
ch -= offsetsFromUTF8[extraBytesToRead];
|
||||
|
||||
if (target >= targetEnd) {
|
||||
source -= (extraBytesToRead+1); /* Back up the source pointer! */
|
||||
result = targetExhausted; break;
|
||||
}
|
||||
if (ch <= UNI_MAX_LEGAL_UTF32) {
|
||||
/*
|
||||
* UTF-16 surrogate values are illegal in UTF-32, and anything
|
||||
* over Plane 17 (> 0x10FFFF) is illegal.
|
||||
*/
|
||||
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
|
||||
if (flags == strictConversion) {
|
||||
source -= (extraBytesToRead+1); /* return to the illegal value itself */
|
||||
result = sourceIllegal;
|
||||
break;
|
||||
} else {
|
||||
*target++ = UNI_REPLACEMENT_CHAR;
|
||||
}
|
||||
} else {
|
||||
*target++ = ch;
|
||||
}
|
||||
} else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */
|
||||
result = sourceIllegal;
|
||||
*target++ = UNI_REPLACEMENT_CHAR;
|
||||
}
|
||||
}
|
||||
*sourceStart = source;
|
||||
*targetStart = target;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
|
||||
Note A.
|
||||
The fall-through switches in UTF-8 reading code save a
|
||||
temp variable, some decrements & conditionals. The switches
|
||||
are equivalent to the following loop:
|
||||
{
|
||||
int tmpBytesToRead = extraBytesToRead+1;
|
||||
do {
|
||||
ch += *source++;
|
||||
--tmpBytesToRead;
|
||||
if (tmpBytesToRead) ch <<= 6;
|
||||
} while (tmpBytesToRead > 0);
|
||||
}
|
||||
In UTF-8 writing code, the switches on "bytesToWrite" are
|
||||
similarly unrolled loops.
|
||||
|
||||
--------------------------------------------------------------------- */
|
139
lib/replxx/src/ConvertUTF.h
Normal file
139
lib/replxx/src/ConvertUTF.h
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2001-2004 Unicode, Inc.
|
||||
*
|
||||
* Disclaimer
|
||||
*
|
||||
* This source code is provided as is by Unicode, Inc. No claims are
|
||||
* made as to fitness for any particular purpose. No warranties of any
|
||||
* kind are expressed or implied. The recipient agrees to determine
|
||||
* applicability of information provided. If this file has been
|
||||
* purchased on magnetic or optical media from Unicode, Inc., the
|
||||
* sole remedy for any claim will be exchange of defective media
|
||||
* within 90 days of receipt.
|
||||
*
|
||||
* Limitations on Rights to Redistribute This Code
|
||||
*
|
||||
* Unicode, Inc. hereby grants the right to freely use the information
|
||||
* supplied in this file in the creation of products supporting the
|
||||
* Unicode Standard, and to make copies of this file in any form
|
||||
* for internal or external distribution as long as this notice
|
||||
* remains attached.
|
||||
*/
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
|
||||
Conversions between UTF32, UTF-16, and UTF-8. Header file.
|
||||
|
||||
Several funtions are included here, forming a complete set of
|
||||
conversions between the three formats. UTF-7 is not included
|
||||
here, but is handled in a separate source file.
|
||||
|
||||
Each of these routines takes pointers to input buffers and output
|
||||
buffers. The input buffers are const.
|
||||
|
||||
Each routine converts the text between *sourceStart and sourceEnd,
|
||||
putting the result into the buffer between *targetStart and
|
||||
targetEnd. Note: the end pointers are *after* the last item: e.g.
|
||||
*(sourceEnd - 1) is the last item.
|
||||
|
||||
The return result indicates whether the conversion was successful,
|
||||
and if not, whether the problem was in the source or target buffers.
|
||||
(Only the first encountered problem is indicated.)
|
||||
|
||||
After the conversion, *sourceStart and *targetStart are both
|
||||
updated to point to the end of last text successfully converted in
|
||||
the respective buffers.
|
||||
|
||||
Input parameters:
|
||||
sourceStart - pointer to a pointer to the source buffer.
|
||||
The contents of this are modified on return so that
|
||||
it points at the next thing to be converted.
|
||||
targetStart - similarly, pointer to pointer to the target buffer.
|
||||
sourceEnd, targetEnd - respectively pointers to the ends of the
|
||||
two buffers, for overflow checking only.
|
||||
|
||||
These conversion functions take a ConversionFlags argument. When this
|
||||
flag is set to strict, both irregular sequences and isolated surrogates
|
||||
will cause an error. When the flag is set to lenient, both irregular
|
||||
sequences and isolated surrogates are converted.
|
||||
|
||||
Whether the flag is strict or lenient, all illegal sequences will cause
|
||||
an error return. This includes sequences such as: <F4 90 80 80>, <C0 80>,
|
||||
or <A0> in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code
|
||||
must check for illegal sequences.
|
||||
|
||||
When the flag is set to lenient, characters over 0x10FFFF are converted
|
||||
to the replacement character; otherwise (when the flag is set to strict)
|
||||
they constitute an error.
|
||||
|
||||
Output parameters:
|
||||
The value "sourceIllegal" is returned from some routines if the input
|
||||
sequence is malformed. When "sourceIllegal" is returned, the source
|
||||
value will point to the illegal value that caused the problem. E.g.,
|
||||
in UTF-8 when a sequence is malformed, it points to the start of the
|
||||
malformed sequence.
|
||||
|
||||
Author: Mark E. Davis, 1994.
|
||||
Rev History: Rick McGowan, fixes & updates May 2001.
|
||||
Fixes & updates, Sept 2001.
|
||||
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
The following 4 definitions are compiler-specific.
|
||||
The C standard does not guarantee that wchar_t has at least
|
||||
16 bits, so wchar_t is no less portable than unsigned short!
|
||||
All should be unsigned values to avoid sign extension during
|
||||
bit mask & shift operations.
|
||||
------------------------------------------------------------------------ */
|
||||
|
||||
#ifndef REPLXX_CONVERT_UTF8_H_INCLUDED
|
||||
#define REPLXX_CONVERT_UTF8_H_INCLUDED 1
|
||||
|
||||
#if 0
|
||||
typedef unsigned long UTF32; /* at least 32 bits */
|
||||
typedef unsigned short UTF16; /* at least 16 bits */
|
||||
typedef unsigned char UTF8; /* typically 8 bits */
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
namespace replxx {
|
||||
|
||||
typedef uint32_t UTF32;
|
||||
typedef uint16_t UTF16;
|
||||
typedef uint8_t UTF8;
|
||||
|
||||
/* Some fundamental constants */
|
||||
#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
|
||||
#define UNI_MAX_BMP (UTF32)0x0000FFFF
|
||||
#define UNI_MAX_UTF16 (UTF32)0x0010FFFF
|
||||
#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF
|
||||
#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF
|
||||
|
||||
typedef enum {
|
||||
conversionOK, /* conversion successful */
|
||||
sourceExhausted, /* partial character in source, but hit end */
|
||||
targetExhausted, /* insuff. room in target for conversion */
|
||||
sourceIllegal /* source sequence is illegal/malformed */
|
||||
} ConversionResult;
|
||||
|
||||
typedef enum {
|
||||
strictConversion = 0,
|
||||
lenientConversion
|
||||
} ConversionFlags;
|
||||
|
||||
ConversionResult ConvertUTF8toUTF32 (
|
||||
const UTF8** sourceStart, const UTF8* sourceEnd,
|
||||
UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags);
|
||||
|
||||
ConversionResult ConvertUTF32toUTF8 (
|
||||
const UTF32** sourceStart, const UTF32* sourceEnd,
|
||||
UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags);
|
||||
|
||||
}
|
||||
|
||||
#endif /* REPLXX_CONVERT_UTF8_H_INCLUDED */
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
108
lib/replxx/src/conversion.cxx
Normal file
108
lib/replxx/src/conversion.cxx
Normal file
@ -0,0 +1,108 @@
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
#include <locale.h>
|
||||
|
||||
#include "conversion.hxx"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define strdup _strdup
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace replxx {
|
||||
|
||||
namespace locale {
|
||||
|
||||
void to_lower( std::string& s_ ) {
|
||||
transform( s_.begin(), s_.end(), s_.begin(), static_cast<int(*)(int)>( &tolower ) );
|
||||
}
|
||||
|
||||
bool is_8bit_encoding( void ) {
|
||||
bool is8BitEncoding( false );
|
||||
string origLC( setlocale( LC_CTYPE, nullptr ) );
|
||||
string lc( origLC );
|
||||
to_lower( lc );
|
||||
if ( lc == "c" ) {
|
||||
setlocale( LC_CTYPE, "" );
|
||||
}
|
||||
lc = setlocale( LC_CTYPE, nullptr );
|
||||
setlocale( LC_CTYPE, origLC.c_str() );
|
||||
to_lower( lc );
|
||||
if ( lc.find( "8859" ) != std::string::npos ) {
|
||||
is8BitEncoding = true;
|
||||
}
|
||||
return ( is8BitEncoding );
|
||||
}
|
||||
|
||||
bool is8BitEncoding( is_8bit_encoding() );
|
||||
|
||||
}
|
||||
|
||||
ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char* src) {
|
||||
ConversionResult res = ConversionResult::conversionOK;
|
||||
if ( ! locale::is8BitEncoding ) {
|
||||
const UTF8* sourceStart = reinterpret_cast<const UTF8*>(src);
|
||||
const UTF8* sourceEnd = sourceStart + strlen(src);
|
||||
UTF32* targetStart = reinterpret_cast<UTF32*>(dst);
|
||||
UTF32* targetEnd = targetStart + dstSize;
|
||||
|
||||
res = ConvertUTF8toUTF32(
|
||||
&sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion);
|
||||
|
||||
if (res == conversionOK) {
|
||||
dstCount = static_cast<int>( targetStart - reinterpret_cast<UTF32*>( dst ) );
|
||||
|
||||
if (dstCount < dstSize) {
|
||||
*targetStart = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( dstCount = 0; ( dstCount < dstSize ) && src[dstCount]; ++ dstCount ) {
|
||||
dst[dstCount] = src[dstCount];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char8_t* src) {
|
||||
return copyString8to32(
|
||||
dst, dstSize, dstCount, reinterpret_cast<const char*>(src)
|
||||
);
|
||||
}
|
||||
|
||||
int copyString32to8( char* dst, int dstSize, const char32_t* src, int srcSize ) {
|
||||
int resCount( 0 );
|
||||
if ( ! locale::is8BitEncoding ) {
|
||||
const UTF32* sourceStart = reinterpret_cast<const UTF32*>(src);
|
||||
const UTF32* sourceEnd = sourceStart + srcSize;
|
||||
UTF8* targetStart = reinterpret_cast<UTF8*>(dst);
|
||||
UTF8* targetEnd = targetStart + dstSize;
|
||||
|
||||
ConversionResult res = ConvertUTF32toUTF8(
|
||||
&sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion
|
||||
);
|
||||
|
||||
if ( res == conversionOK ) {
|
||||
resCount = static_cast<int>( targetStart - reinterpret_cast<UTF8*>( dst ) );
|
||||
if ( resCount < dstSize ) {
|
||||
*targetStart = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int i( 0 );
|
||||
for ( i = 0; ( i < dstSize ) && ( i < srcSize ) && src[i]; ++ i ) {
|
||||
dst[i] = static_cast<char>( src[i] );
|
||||
}
|
||||
resCount = i;
|
||||
if ( i < dstSize ) {
|
||||
dst[i] = 0;
|
||||
}
|
||||
}
|
||||
return ( resCount );
|
||||
}
|
||||
|
||||
}
|
||||
|
30
lib/replxx/src/conversion.hxx
Normal file
30
lib/replxx/src/conversion.hxx
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef REPLXX_CONVERSION_HXX_INCLUDED
|
||||
#define REPLXX_CONVERSION_HXX_INCLUDED 1
|
||||
|
||||
#include "ConvertUTF.h"
|
||||
|
||||
#ifdef __has_include
|
||||
#if __has_include( <version> )
|
||||
#include <version>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if ! ( defined( __cpp_lib_char8_t ) || ( defined( __clang_major__ ) && ( __clang_major__ >= 8 ) && ( __cplusplus > 201703L ) ) )
|
||||
namespace replxx {
|
||||
typedef unsigned char char8_t;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace replxx {
|
||||
|
||||
ConversionResult copyString8to32( char32_t* dst, int dstSize, int& dstCount, char const* src );
|
||||
ConversionResult copyString8to32( char32_t* dst, int dstSize, int& dstCount, char8_t const* src );
|
||||
int copyString32to8( char* dst, int dstSize, char32_t const* src, int srcSize );
|
||||
|
||||
namespace locale {
|
||||
extern bool is8BitEncoding;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
890
lib/replxx/src/escape.cxx
Normal file
890
lib/replxx/src/escape.cxx
Normal file
@ -0,0 +1,890 @@
|
||||
#include "escape.hxx"
|
||||
#include "terminal.hxx"
|
||||
#include "replxx.hxx"
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
namespace replxx {
|
||||
|
||||
namespace EscapeSequenceProcessing { // move these out of global namespace
|
||||
|
||||
// This chunk of code does parsing of the escape sequences sent by various Linux
|
||||
// terminals.
|
||||
//
|
||||
// It handles arrow keys, Home, End and Delete keys by interpreting the
|
||||
// sequences sent by
|
||||
// gnome terminal, xterm, rxvt, konsole, aterm and yakuake including the Alt and
|
||||
// Ctrl key
|
||||
// combinations that are understood by replxx.
|
||||
//
|
||||
// The parsing uses tables, a bunch of intermediate dispatch routines and a
|
||||
// doDispatch
|
||||
// loop that reads the tables and sends control to "deeper" routines to continue
|
||||
// the
|
||||
// parsing. The starting call to doDispatch( c, initialDispatch ) will
|
||||
// eventually return
|
||||
// either a character (with optional CTRL and META bits set), or -1 if parsing
|
||||
// fails, or
|
||||
// zero if an attempt to read from the keyboard fails.
|
||||
//
|
||||
// This is rather sloppy escape sequence processing, since we're not paying
|
||||
// attention to what the
|
||||
// actual TERM is set to and are processing all key sequences for all terminals,
|
||||
// but it works with
|
||||
// the most common keystrokes on the most common terminals. It's intricate, but
|
||||
// the nested 'if'
|
||||
// statements required to do it directly would be worse. This way has the
|
||||
// advantage of allowing
|
||||
// changes and extensions without having to touch a lot of code.
|
||||
|
||||
|
||||
static char32_t thisKeyMetaCtrl = 0; // holds pre-set Meta and/or Ctrl modifiers
|
||||
|
||||
// This dispatch routine is given a dispatch table and then farms work out to
|
||||
// routines
|
||||
// listed in the table based on the character it is called with. The dispatch
|
||||
// routines can
|
||||
// read more input characters to decide what should eventually be returned.
|
||||
// Eventually,
|
||||
// a called routine returns either a character or -1 to indicate parsing
|
||||
// failure.
|
||||
//
|
||||
char32_t doDispatch(char32_t c, CharacterDispatch& dispatchTable) {
|
||||
for (unsigned int i = 0; i < dispatchTable.len; ++i) {
|
||||
if (static_cast<unsigned char>(dispatchTable.chars[i]) == c) {
|
||||
return dispatchTable.dispatch[i](c);
|
||||
}
|
||||
}
|
||||
return dispatchTable.dispatch[dispatchTable.len](c);
|
||||
}
|
||||
|
||||
// Final dispatch routines -- return something
|
||||
//
|
||||
static char32_t normalKeyRoutine(char32_t c) { return thisKeyMetaCtrl | c; }
|
||||
static char32_t upArrowKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::UP;;
|
||||
}
|
||||
static char32_t downArrowKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::DOWN;
|
||||
}
|
||||
static char32_t rightArrowKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::RIGHT;
|
||||
}
|
||||
static char32_t leftArrowKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::LEFT;
|
||||
}
|
||||
static char32_t homeKeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::HOME; }
|
||||
static char32_t endKeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::END; }
|
||||
static char32_t shiftTabRoutine(char32_t) { return Replxx::KEY::BASE_SHIFT | Replxx::KEY::TAB; }
|
||||
static char32_t f1KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F1; }
|
||||
static char32_t f2KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F2; }
|
||||
static char32_t f3KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F3; }
|
||||
static char32_t f4KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F4; }
|
||||
static char32_t f5KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F5; }
|
||||
static char32_t f6KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F6; }
|
||||
static char32_t f7KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F7; }
|
||||
static char32_t f8KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F8; }
|
||||
static char32_t f9KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F9; }
|
||||
static char32_t f10KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F10; }
|
||||
static char32_t f11KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F11; }
|
||||
static char32_t f12KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F12; }
|
||||
static char32_t pageUpKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::PAGE_UP;
|
||||
}
|
||||
static char32_t pageDownKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::PAGE_DOWN;
|
||||
}
|
||||
static char32_t deleteCharRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::BACKSPACE;
|
||||
} // key labeled Backspace
|
||||
static char32_t insertKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::INSERT;
|
||||
} // key labeled Delete
|
||||
static char32_t deleteKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::DELETE;
|
||||
} // key labeled Delete
|
||||
static char32_t ctrlUpArrowKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::UP;
|
||||
}
|
||||
static char32_t ctrlDownArrowKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::DOWN;
|
||||
}
|
||||
static char32_t ctrlRightArrowKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::RIGHT;
|
||||
}
|
||||
static char32_t ctrlLeftArrowKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::LEFT;
|
||||
}
|
||||
static char32_t bracketPasteStartKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::PASTE_START;
|
||||
}
|
||||
static char32_t bracketPasteFinishKeyRoutine(char32_t) {
|
||||
return thisKeyMetaCtrl | Replxx::KEY::PASTE_FINISH;
|
||||
}
|
||||
static char32_t escFailureRoutine(char32_t) {
|
||||
beep();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Handle ESC [ 1 ; 2 or 3 (or 5) <more stuff> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escLeftBracket1Semicolon2or3or5Routines[] = {
|
||||
upArrowKeyRoutine,
|
||||
downArrowKeyRoutine,
|
||||
rightArrowKeyRoutine,
|
||||
leftArrowKeyRoutine,
|
||||
homeKeyRoutine,
|
||||
endKeyRoutine,
|
||||
f1KeyRoutine,
|
||||
f2KeyRoutine,
|
||||
f3KeyRoutine,
|
||||
f4KeyRoutine,
|
||||
escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket1Semicolon2or3or5Dispatch = {
|
||||
10, "ABCDHFPQRS", escLeftBracket1Semicolon2or3or5Routines
|
||||
};
|
||||
|
||||
// Handle ESC [ 1 ; <more stuff> escape sequences
|
||||
//
|
||||
static char32_t escLeftBracket1Semicolon2Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
|
||||
return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch);
|
||||
}
|
||||
static char32_t escLeftBracket1Semicolon3Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_META;
|
||||
return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch);
|
||||
}
|
||||
static char32_t escLeftBracket1Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch);
|
||||
}
|
||||
static CharacterDispatchRoutine escLeftBracket1SemicolonRoutines[] = {
|
||||
escLeftBracket1Semicolon2Routine,
|
||||
escLeftBracket1Semicolon3Routine,
|
||||
escLeftBracket1Semicolon5Routine,
|
||||
escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket1SemicolonDispatch = {
|
||||
3, "235", escLeftBracket1SemicolonRoutines
|
||||
};
|
||||
|
||||
// Handle ESC [ 1 ; <more stuff> escape sequences
|
||||
//
|
||||
static char32_t escLeftBracket1SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket1SemicolonDispatch);
|
||||
}
|
||||
|
||||
// (S)-F5
|
||||
static CharacterDispatchRoutine escLeftBracket15Semicolon2Routines[] = {
|
||||
f5KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket15Semicolon2Dispatch = {
|
||||
1, "~", escLeftBracket15Semicolon2Routines
|
||||
};
|
||||
static char32_t escLeftBracket15Semicolon2Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
|
||||
return doDispatch(c, escLeftBracket15Semicolon2Dispatch);
|
||||
}
|
||||
|
||||
// (C)-F5
|
||||
static CharacterDispatchRoutine escLeftBracket15Semicolon5Routines[] = {
|
||||
f5KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket15Semicolon5Dispatch = {
|
||||
1, "~", escLeftBracket15Semicolon5Routines
|
||||
};
|
||||
static char32_t escLeftBracket15Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket15Semicolon5Dispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket15SemicolonRoutines[] = {
|
||||
escLeftBracket15Semicolon2Routine, escLeftBracket15Semicolon5Routine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket15SemicolonDispatch = {
|
||||
2, "25", escLeftBracket15SemicolonRoutines
|
||||
};
|
||||
static char32_t escLeftBracket15SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket15SemicolonDispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket15Routines[] = {
|
||||
f5KeyRoutine, escLeftBracket15SemicolonRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket15Dispatch = {
|
||||
2, "~;", escLeftBracket15Routines
|
||||
};
|
||||
static char32_t escLeftBracket15Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket15Dispatch);
|
||||
}
|
||||
|
||||
// (S)-F6
|
||||
static CharacterDispatchRoutine escLeftBracket17Semicolon2Routines[] = {
|
||||
f6KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket17Semicolon2Dispatch = {
|
||||
1, "~", escLeftBracket17Semicolon2Routines
|
||||
};
|
||||
static char32_t escLeftBracket17Semicolon2Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
|
||||
return doDispatch(c, escLeftBracket17Semicolon2Dispatch);
|
||||
}
|
||||
|
||||
// (C)-F6
|
||||
static CharacterDispatchRoutine escLeftBracket17Semicolon5Routines[] = {
|
||||
f6KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket17Semicolon5Dispatch = {
|
||||
1, "~", escLeftBracket17Semicolon5Routines
|
||||
};
|
||||
static char32_t escLeftBracket17Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket17Semicolon5Dispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket17SemicolonRoutines[] = {
|
||||
escLeftBracket17Semicolon2Routine, escLeftBracket17Semicolon5Routine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket17SemicolonDispatch = {
|
||||
2, "25", escLeftBracket17SemicolonRoutines
|
||||
};
|
||||
static char32_t escLeftBracket17SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket17SemicolonDispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket17Routines[] = {
|
||||
f6KeyRoutine, escLeftBracket17SemicolonRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket17Dispatch = {
|
||||
2, "~;", escLeftBracket17Routines
|
||||
};
|
||||
static char32_t escLeftBracket17Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket17Dispatch);
|
||||
}
|
||||
|
||||
// (S)-F7
|
||||
static CharacterDispatchRoutine escLeftBracket18Semicolon2Routines[] = {
|
||||
f7KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket18Semicolon2Dispatch = {
|
||||
1, "~", escLeftBracket18Semicolon2Routines
|
||||
};
|
||||
static char32_t escLeftBracket18Semicolon2Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
|
||||
return doDispatch(c, escLeftBracket18Semicolon2Dispatch);
|
||||
}
|
||||
|
||||
// (C)-F7
|
||||
static CharacterDispatchRoutine escLeftBracket18Semicolon5Routines[] = {
|
||||
f7KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket18Semicolon5Dispatch = {
|
||||
1, "~", escLeftBracket18Semicolon5Routines
|
||||
};
|
||||
static char32_t escLeftBracket18Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket18Semicolon5Dispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket18SemicolonRoutines[] = {
|
||||
escLeftBracket18Semicolon2Routine, escLeftBracket18Semicolon5Routine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket18SemicolonDispatch = {
|
||||
2, "25", escLeftBracket18SemicolonRoutines
|
||||
};
|
||||
static char32_t escLeftBracket18SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket18SemicolonDispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket18Routines[] = {
|
||||
f7KeyRoutine, escLeftBracket18SemicolonRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket18Dispatch = {
|
||||
2, "~;", escLeftBracket18Routines
|
||||
};
|
||||
static char32_t escLeftBracket18Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket18Dispatch);
|
||||
}
|
||||
|
||||
// (S)-F8
|
||||
static CharacterDispatchRoutine escLeftBracket19Semicolon2Routines[] = {
|
||||
f8KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket19Semicolon2Dispatch = {
|
||||
1, "~", escLeftBracket19Semicolon2Routines
|
||||
};
|
||||
static char32_t escLeftBracket19Semicolon2Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
|
||||
return doDispatch(c, escLeftBracket19Semicolon2Dispatch);
|
||||
}
|
||||
|
||||
// (C)-F8
|
||||
static CharacterDispatchRoutine escLeftBracket19Semicolon5Routines[] = {
|
||||
f8KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket19Semicolon5Dispatch = {
|
||||
1, "~", escLeftBracket19Semicolon5Routines
|
||||
};
|
||||
static char32_t escLeftBracket19Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket19Semicolon5Dispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket19SemicolonRoutines[] = {
|
||||
escLeftBracket19Semicolon2Routine, escLeftBracket19Semicolon5Routine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket19SemicolonDispatch = {
|
||||
2, "25", escLeftBracket19SemicolonRoutines
|
||||
};
|
||||
static char32_t escLeftBracket19SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket19SemicolonDispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket19Routines[] = {
|
||||
f8KeyRoutine, escLeftBracket19SemicolonRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket19Dispatch = {
|
||||
2, "~;", escLeftBracket19Routines
|
||||
};
|
||||
static char32_t escLeftBracket19Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket19Dispatch);
|
||||
}
|
||||
|
||||
// Handle ESC [ 1 <more stuff> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escLeftBracket1Routines[] = {
|
||||
homeKeyRoutine, escLeftBracket1SemicolonRoutine,
|
||||
escLeftBracket15Routine,
|
||||
escLeftBracket17Routine,
|
||||
escLeftBracket18Routine,
|
||||
escLeftBracket19Routine,
|
||||
escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket1Dispatch = {
|
||||
6, "~;5789", escLeftBracket1Routines
|
||||
};
|
||||
|
||||
// Handle ESC [ 2 <more stuff> escape sequences
|
||||
//
|
||||
|
||||
// (S)-F9
|
||||
static CharacterDispatchRoutine escLeftBracket20Semicolon2Routines[] = {
|
||||
f9KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket20Semicolon2Dispatch = {
|
||||
1, "~", escLeftBracket20Semicolon2Routines
|
||||
};
|
||||
static char32_t escLeftBracket20Semicolon2Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
|
||||
return doDispatch(c, escLeftBracket20Semicolon2Dispatch);
|
||||
}
|
||||
|
||||
// (C)-F9
|
||||
static CharacterDispatchRoutine escLeftBracket20Semicolon5Routines[] = {
|
||||
f9KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket20Semicolon5Dispatch = {
|
||||
1, "~", escLeftBracket20Semicolon5Routines
|
||||
};
|
||||
static char32_t escLeftBracket20Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket20Semicolon5Dispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket20SemicolonRoutines[] = {
|
||||
escLeftBracket20Semicolon2Routine, escLeftBracket20Semicolon5Routine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket20SemicolonDispatch = {
|
||||
2, "25", escLeftBracket20SemicolonRoutines
|
||||
};
|
||||
static char32_t escLeftBracket20SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket20SemicolonDispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket200Routines[] = {
|
||||
bracketPasteStartKeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket200Dispatch = {
|
||||
1, "~", escLeftBracket200Routines
|
||||
};
|
||||
static char32_t escLeftBracket200Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket200Dispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket201Routines[] = {
|
||||
bracketPasteFinishKeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket201Dispatch = {
|
||||
1, "~", escLeftBracket201Routines
|
||||
};
|
||||
static char32_t escLeftBracket201Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket201Dispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket20Routines[] = {
|
||||
f9KeyRoutine, escLeftBracket20SemicolonRoutine, escLeftBracket200Routine, escLeftBracket201Routine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket20Dispatch = {
|
||||
4, "~;01", escLeftBracket20Routines
|
||||
};
|
||||
static char32_t escLeftBracket20Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket20Dispatch);
|
||||
}
|
||||
|
||||
// (S)-F10
|
||||
static CharacterDispatchRoutine escLeftBracket21Semicolon2Routines[] = {
|
||||
f10KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket21Semicolon2Dispatch = {
|
||||
1, "~", escLeftBracket21Semicolon2Routines
|
||||
};
|
||||
static char32_t escLeftBracket21Semicolon2Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
|
||||
return doDispatch(c, escLeftBracket21Semicolon2Dispatch);
|
||||
}
|
||||
|
||||
// (C)-F10
|
||||
static CharacterDispatchRoutine escLeftBracket21Semicolon5Routines[] = {
|
||||
f10KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket21Semicolon5Dispatch = {
|
||||
1, "~", escLeftBracket21Semicolon5Routines
|
||||
};
|
||||
static char32_t escLeftBracket21Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket21Semicolon5Dispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket21SemicolonRoutines[] = {
|
||||
escLeftBracket21Semicolon2Routine, escLeftBracket21Semicolon5Routine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket21SemicolonDispatch = {
|
||||
2, "25", escLeftBracket21SemicolonRoutines
|
||||
};
|
||||
static char32_t escLeftBracket21SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket21SemicolonDispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket21Routines[] = {
|
||||
f10KeyRoutine, escLeftBracket21SemicolonRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket21Dispatch = {
|
||||
2, "~;", escLeftBracket21Routines
|
||||
};
|
||||
static char32_t escLeftBracket21Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket21Dispatch);
|
||||
}
|
||||
|
||||
// (S)-F11
|
||||
static CharacterDispatchRoutine escLeftBracket23Semicolon2Routines[] = {
|
||||
f11KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket23Semicolon2Dispatch = {
|
||||
1, "~", escLeftBracket23Semicolon2Routines
|
||||
};
|
||||
static char32_t escLeftBracket23Semicolon2Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
|
||||
return doDispatch(c, escLeftBracket23Semicolon2Dispatch);
|
||||
}
|
||||
|
||||
// (C)-F11
|
||||
static CharacterDispatchRoutine escLeftBracket23Semicolon5Routines[] = {
|
||||
f11KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket23Semicolon5Dispatch = {
|
||||
1, "~", escLeftBracket23Semicolon5Routines
|
||||
};
|
||||
static char32_t escLeftBracket23Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket23Semicolon5Dispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket23SemicolonRoutines[] = {
|
||||
escLeftBracket23Semicolon2Routine, escLeftBracket23Semicolon5Routine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket23SemicolonDispatch = {
|
||||
2, "25", escLeftBracket23SemicolonRoutines
|
||||
};
|
||||
static char32_t escLeftBracket23SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket23SemicolonDispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket23Routines[] = {
|
||||
f11KeyRoutine, escLeftBracket23SemicolonRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket23Dispatch = {
|
||||
2, "~;", escLeftBracket23Routines
|
||||
};
|
||||
static char32_t escLeftBracket23Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket23Dispatch);
|
||||
}
|
||||
|
||||
// (S)-F12
|
||||
static CharacterDispatchRoutine escLeftBracket24Semicolon2Routines[] = {
|
||||
f12KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket24Semicolon2Dispatch = {
|
||||
1, "~", escLeftBracket24Semicolon2Routines
|
||||
};
|
||||
static char32_t escLeftBracket24Semicolon2Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
|
||||
return doDispatch(c, escLeftBracket24Semicolon2Dispatch);
|
||||
}
|
||||
|
||||
// (C)-F12
|
||||
static CharacterDispatchRoutine escLeftBracket24Semicolon5Routines[] = {
|
||||
f12KeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket24Semicolon5Dispatch = {
|
||||
1, "~", escLeftBracket24Semicolon5Routines
|
||||
};
|
||||
static char32_t escLeftBracket24Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket24Semicolon5Dispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket24SemicolonRoutines[] = {
|
||||
escLeftBracket24Semicolon2Routine, escLeftBracket24Semicolon5Routine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket24SemicolonDispatch = {
|
||||
2, "25", escLeftBracket24SemicolonRoutines
|
||||
};
|
||||
static char32_t escLeftBracket24SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket24SemicolonDispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket24Routines[] = {
|
||||
f12KeyRoutine, escLeftBracket24SemicolonRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket24Dispatch = {
|
||||
2, "~;", escLeftBracket24Routines
|
||||
};
|
||||
static char32_t escLeftBracket24Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket24Dispatch);
|
||||
}
|
||||
|
||||
// Handle ESC [ 2 <more stuff> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escLeftBracket2Routines[] = {
|
||||
insertKeyRoutine,
|
||||
escLeftBracket20Routine,
|
||||
escLeftBracket21Routine,
|
||||
escLeftBracket23Routine,
|
||||
escLeftBracket24Routine,
|
||||
escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket2Dispatch = {
|
||||
5, "~0134", escLeftBracket2Routines
|
||||
};
|
||||
|
||||
// Handle ESC [ 3 <more stuff> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escLeftBracket3Routines[] = {
|
||||
deleteKeyRoutine, escFailureRoutine
|
||||
};
|
||||
|
||||
static CharacterDispatch escLeftBracket3Dispatch = {
|
||||
1, "~", escLeftBracket3Routines
|
||||
};
|
||||
|
||||
// Handle ESC [ 4 <more stuff> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escLeftBracket4Routines[] = {
|
||||
endKeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket4Dispatch = {
|
||||
1, "~", escLeftBracket4Routines
|
||||
};
|
||||
|
||||
// Handle ESC [ 5 <more stuff> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escLeftBracket5Semicolon5Routines[] = {
|
||||
pageUpKeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket5Semicolon5Dispatch = {
|
||||
1, "~", escLeftBracket5Semicolon5Routines
|
||||
};
|
||||
static char32_t escLeftBracket5Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket5Semicolon5Dispatch);
|
||||
}
|
||||
static CharacterDispatchRoutine escLeftBracket5SemicolonRoutines[] = {
|
||||
escLeftBracket5Semicolon5Routine,
|
||||
escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket5SemicolonDispatch = {
|
||||
1, "5", escLeftBracket5SemicolonRoutines
|
||||
};
|
||||
static char32_t escLeftBracket5SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket5SemicolonDispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket5Routines[] = {
|
||||
pageUpKeyRoutine, escLeftBracket5SemicolonRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket5Dispatch = {
|
||||
2, "~;", escLeftBracket5Routines
|
||||
};
|
||||
|
||||
// Handle ESC [ 6 <more stuff> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escLeftBracket6Semicolon5Routines[] = {
|
||||
pageDownKeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket6Semicolon5Dispatch = {
|
||||
1, "~", escLeftBracket6Semicolon5Routines
|
||||
};
|
||||
static char32_t escLeftBracket6Semicolon5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
|
||||
return doDispatch(c, escLeftBracket6Semicolon5Dispatch);
|
||||
}
|
||||
static CharacterDispatchRoutine escLeftBracket6SemicolonRoutines[] = {
|
||||
escLeftBracket6Semicolon5Routine,
|
||||
escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket6SemicolonDispatch = {
|
||||
1, "5", escLeftBracket6SemicolonRoutines
|
||||
};
|
||||
static char32_t escLeftBracket6SemicolonRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket6SemicolonDispatch);
|
||||
}
|
||||
|
||||
static CharacterDispatchRoutine escLeftBracket6Routines[] = {
|
||||
pageDownKeyRoutine, escLeftBracket6SemicolonRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket6Dispatch = {
|
||||
2, "~;", escLeftBracket6Routines
|
||||
};
|
||||
|
||||
// Handle ESC [ 7 <more stuff> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escLeftBracket7Routines[] = {
|
||||
homeKeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket7Dispatch = {
|
||||
1, "~", escLeftBracket7Routines
|
||||
};
|
||||
|
||||
// Handle ESC [ 8 <more stuff> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escLeftBracket8Routines[] = {
|
||||
endKeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracket8Dispatch = {
|
||||
1, "~", escLeftBracket8Routines
|
||||
};
|
||||
|
||||
// Handle ESC [ <digit> escape sequences
|
||||
//
|
||||
static char32_t escLeftBracket0Routine(char32_t c) {
|
||||
return escFailureRoutine(c);
|
||||
}
|
||||
static char32_t escLeftBracket1Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket1Dispatch);
|
||||
}
|
||||
static char32_t escLeftBracket2Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket2Dispatch);
|
||||
}
|
||||
static char32_t escLeftBracket3Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket3Dispatch);
|
||||
}
|
||||
static char32_t escLeftBracket4Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket4Dispatch);
|
||||
}
|
||||
static char32_t escLeftBracket5Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket5Dispatch);
|
||||
}
|
||||
static char32_t escLeftBracket6Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket6Dispatch);
|
||||
}
|
||||
static char32_t escLeftBracket7Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket7Dispatch);
|
||||
}
|
||||
static char32_t escLeftBracket8Routine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracket8Dispatch);
|
||||
}
|
||||
static char32_t escLeftBracket9Routine(char32_t c) {
|
||||
return escFailureRoutine(c);
|
||||
}
|
||||
|
||||
// Handle ESC [ <more stuff> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escLeftBracketRoutines[] = {
|
||||
upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine,
|
||||
leftArrowKeyRoutine, homeKeyRoutine, endKeyRoutine,
|
||||
shiftTabRoutine,
|
||||
escLeftBracket0Routine, escLeftBracket1Routine, escLeftBracket2Routine,
|
||||
escLeftBracket3Routine, escLeftBracket4Routine, escLeftBracket5Routine,
|
||||
escLeftBracket6Routine, escLeftBracket7Routine, escLeftBracket8Routine,
|
||||
escLeftBracket9Routine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escLeftBracketDispatch = {17, "ABCDHFZ0123456789",
|
||||
escLeftBracketRoutines};
|
||||
|
||||
// Handle ESC O <char> escape sequences
|
||||
//
|
||||
static CharacterDispatchRoutine escORoutines[] = {
|
||||
upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine,
|
||||
leftArrowKeyRoutine, homeKeyRoutine, endKeyRoutine,
|
||||
f1KeyRoutine, f2KeyRoutine, f3KeyRoutine,
|
||||
f4KeyRoutine,
|
||||
ctrlUpArrowKeyRoutine, ctrlDownArrowKeyRoutine, ctrlRightArrowKeyRoutine,
|
||||
ctrlLeftArrowKeyRoutine, escFailureRoutine
|
||||
};
|
||||
static CharacterDispatch escODispatch = {14, "ABCDHFPQRSabcd", escORoutines};
|
||||
|
||||
// Initial ESC dispatch -- could be a Meta prefix or the start of an escape
|
||||
// sequence
|
||||
//
|
||||
static char32_t escLeftBracketRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escLeftBracketDispatch);
|
||||
}
|
||||
static char32_t escORoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escODispatch);
|
||||
}
|
||||
static char32_t setMetaRoutine(char32_t c); // need forward reference
|
||||
static CharacterDispatchRoutine escRoutines[] = {
|
||||
escLeftBracketRoutine, escORoutine, setMetaRoutine
|
||||
};
|
||||
static CharacterDispatch escDispatch = {2, "[O", escRoutines};
|
||||
|
||||
// Initial dispatch -- we are not in the middle of anything yet
|
||||
//
|
||||
static char32_t escRoutine(char32_t c) {
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escDispatch);
|
||||
}
|
||||
static CharacterDispatchRoutine initialRoutines[] = {
|
||||
escRoutine, deleteCharRoutine, normalKeyRoutine
|
||||
};
|
||||
static CharacterDispatch initialDispatch = {2, "\x1B\x7F", initialRoutines};
|
||||
|
||||
// Special handling for the ESC key because it does double duty
|
||||
//
|
||||
static char32_t setMetaRoutine(char32_t c) {
|
||||
thisKeyMetaCtrl = Replxx::KEY::BASE_META;
|
||||
if (c == 0x1B) { // another ESC, stay in ESC processing mode
|
||||
c = read_unicode_character();
|
||||
if (c == 0) return 0;
|
||||
return doDispatch(c, escDispatch);
|
||||
}
|
||||
return doDispatch(c, initialDispatch);
|
||||
}
|
||||
|
||||
char32_t doDispatch(char32_t c) {
|
||||
EscapeSequenceProcessing::thisKeyMetaCtrl = 0; // no modifiers yet at initialDispatch
|
||||
return doDispatch(c, initialDispatch);
|
||||
}
|
||||
|
||||
} // namespace EscapeSequenceProcessing // move these out of global namespace
|
||||
|
||||
}
|
||||
|
||||
#endif /* #ifndef _WIN32 */
|
||||
|
37
lib/replxx/src/escape.hxx
Normal file
37
lib/replxx/src/escape.hxx
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef REPLXX_ESCAPE_HXX_INCLUDED
|
||||
#define REPLXX_ESCAPE_HXX_INCLUDED 1
|
||||
|
||||
namespace replxx {
|
||||
|
||||
namespace EscapeSequenceProcessing {
|
||||
|
||||
// This is a typedef for the routine called by doDispatch(). It takes the
|
||||
// current character
|
||||
// as input, does any required processing including reading more characters and
|
||||
// calling other
|
||||
// dispatch routines, then eventually returns the final (possibly extended or
|
||||
// special) character.
|
||||
//
|
||||
typedef char32_t (*CharacterDispatchRoutine)(char32_t);
|
||||
|
||||
// This structure is used by doDispatch() to hold a list of characters to test
|
||||
// for and
|
||||
// a list of routines to call if the character matches. The dispatch routine
|
||||
// list is one
|
||||
// longer than the character list; the final entry is used if no character
|
||||
// matches.
|
||||
//
|
||||
struct CharacterDispatch {
|
||||
unsigned int len; // length of the chars list
|
||||
const char* chars; // chars to test
|
||||
CharacterDispatchRoutine* dispatch; // array of routines to call
|
||||
};
|
||||
|
||||
char32_t doDispatch(char32_t c);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
428
lib/replxx/src/history.cxx
Normal file
428
lib/replxx/src/history.cxx
Normal file
@ -0,0 +1,428 @@
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <ostream>
|
||||
#include <istream>
|
||||
#include <cstring>
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#include "replxx.hxx"
|
||||
#include "history.hxx"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace replxx {
|
||||
|
||||
namespace {
|
||||
void delete_ReplxxHistoryScanImpl( Replxx::HistoryScanImpl* impl_ ) {
|
||||
delete impl_;
|
||||
}
|
||||
static int const ETB = 0x17;
|
||||
}
|
||||
|
||||
static int const REPLXX_DEFAULT_HISTORY_MAX_LEN( 1000 );
|
||||
|
||||
Replxx::HistoryScan::HistoryScan( impl_t impl_ )
|
||||
: _impl( std::move( impl_ ) ) {
|
||||
}
|
||||
|
||||
bool Replxx::HistoryScan::next( void ) {
|
||||
return ( _impl->next() );
|
||||
}
|
||||
|
||||
Replxx::HistoryScanImpl::HistoryScanImpl( History::entries_t const& entries_ )
|
||||
: _entries( entries_ )
|
||||
, _it( _entries.end() )
|
||||
, _utf8Cache()
|
||||
, _entryCache( std::string(), std::string() )
|
||||
, _cacheValid( false ) {
|
||||
}
|
||||
|
||||
Replxx::HistoryEntry const& Replxx::HistoryScan::get( void ) const {
|
||||
return ( _impl->get() );
|
||||
}
|
||||
|
||||
bool Replxx::HistoryScanImpl::next( void ) {
|
||||
if ( _it == _entries.end() ) {
|
||||
_it = _entries.begin();
|
||||
} else {
|
||||
++ _it;
|
||||
}
|
||||
_cacheValid = false;
|
||||
return ( _it != _entries.end() );
|
||||
}
|
||||
|
||||
Replxx::HistoryEntry const& Replxx::HistoryScanImpl::get( void ) const {
|
||||
if ( _cacheValid ) {
|
||||
return ( _entryCache );
|
||||
}
|
||||
_utf8Cache.assign( _it->text() );
|
||||
_entryCache = Replxx::HistoryEntry( _it->timestamp(), _utf8Cache.get() );
|
||||
_cacheValid = true;
|
||||
return ( _entryCache );
|
||||
}
|
||||
|
||||
Replxx::HistoryScan::impl_t History::scan( void ) const {
|
||||
return ( Replxx::HistoryScan::impl_t( new Replxx::HistoryScanImpl( _entries ), delete_ReplxxHistoryScanImpl ) );
|
||||
}
|
||||
|
||||
History::History( void )
|
||||
: _entries()
|
||||
, _maxSize( REPLXX_DEFAULT_HISTORY_MAX_LEN )
|
||||
, _current( _entries.begin() )
|
||||
, _yankPos( _entries.end() )
|
||||
, _previous( _entries.begin() )
|
||||
, _recallMostRecent( false )
|
||||
, _unique( true ) {
|
||||
}
|
||||
|
||||
void History::add( UnicodeString const& line, std::string const& when ) {
|
||||
if ( _maxSize <= 0 ) {
|
||||
return;
|
||||
}
|
||||
if ( ! _entries.empty() && ( line == _entries.back().text() ) ) {
|
||||
_entries.back() = Entry( now_ms_str(), line );
|
||||
return;
|
||||
}
|
||||
remove_duplicate( line );
|
||||
trim_to_max_size();
|
||||
_entries.emplace_back( when, line );
|
||||
_locations.insert( make_pair( line, last() ) );
|
||||
if ( _current == _entries.end() ) {
|
||||
_current = last();
|
||||
}
|
||||
_yankPos = _entries.end();
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
class FileLock {
|
||||
std::string _path;
|
||||
int _lockFd;
|
||||
public:
|
||||
FileLock( std::string const& name_ )
|
||||
: _path( name_ + ".lock" )
|
||||
, _lockFd( ::open( _path.c_str(), O_CREAT | O_RDWR, 0600 ) ) {
|
||||
static_cast<void>( ::lockf( _lockFd, F_LOCK, 0 ) == 0 );
|
||||
}
|
||||
~FileLock( void ) {
|
||||
static_cast<void>( ::lockf( _lockFd, F_ULOCK, 0 ) == 0 );
|
||||
::close( _lockFd );
|
||||
::unlink( _path.c_str() );
|
||||
return;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
bool History::save( std::string const& filename, bool sync_ ) {
|
||||
#ifndef _WIN32
|
||||
mode_t old_umask = umask( S_IXUSR | S_IRWXG | S_IRWXO );
|
||||
FileLock fileLock( filename );
|
||||
#endif
|
||||
entries_t entries;
|
||||
locations_t locations;
|
||||
if ( ! sync_ ) {
|
||||
entries.swap( _entries );
|
||||
locations.swap( _locations );
|
||||
_entries = entries;
|
||||
reset_iters();
|
||||
}
|
||||
/* scope for ifstream object auto-close */ {
|
||||
ifstream histFile( filename );
|
||||
if ( histFile ) {
|
||||
do_load( histFile );
|
||||
}
|
||||
}
|
||||
sort();
|
||||
remove_duplicates();
|
||||
trim_to_max_size();
|
||||
ofstream histFile( filename );
|
||||
if ( ! histFile ) {
|
||||
return ( false );
|
||||
}
|
||||
#ifndef _WIN32
|
||||
umask( old_umask );
|
||||
chmod( filename.c_str(), S_IRUSR | S_IWUSR );
|
||||
#endif
|
||||
save( histFile );
|
||||
if ( ! sync_ ) {
|
||||
_entries = std::move( entries );
|
||||
_locations = std::move( locations );
|
||||
}
|
||||
reset_iters();
|
||||
return ( true );
|
||||
}
|
||||
|
||||
void History::save( std::ostream& histFile ) {
|
||||
Utf8String utf8;
|
||||
UnicodeString us;
|
||||
for ( Entry& h : _entries ) {
|
||||
h.reset_scratch();
|
||||
if ( ! h.text().is_empty() ) {
|
||||
us.assign( h.text() );
|
||||
std::replace( us.begin(), us.end(), char32_t( '\n' ), char32_t( ETB ) );
|
||||
utf8.assign( us );
|
||||
histFile << "### " << h.timestamp() << "\n" << utf8.get() << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool is_timestamp( std::string const& s ) {
|
||||
static char const TIMESTAMP_PATTERN[] = "### dddd-dd-dd dd:dd:dd.ddd";
|
||||
static int const TIMESTAMP_LENGTH( sizeof ( TIMESTAMP_PATTERN ) - 1 );
|
||||
if ( s.length() != TIMESTAMP_LENGTH ) {
|
||||
return ( false );
|
||||
}
|
||||
for ( int i( 0 ); i < TIMESTAMP_LENGTH; ++ i ) {
|
||||
if ( TIMESTAMP_PATTERN[i] == 'd' ) {
|
||||
if ( ! isdigit( s[i] ) ) {
|
||||
return ( false );
|
||||
}
|
||||
} else if ( s[i] != TIMESTAMP_PATTERN[i] ) {
|
||||
return ( false );
|
||||
}
|
||||
}
|
||||
return ( true );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void History::do_load( std::istream& histFile ) {
|
||||
string line;
|
||||
string when( "0000-00-00 00:00:00.000" );
|
||||
UnicodeString us;
|
||||
while ( getline( histFile, line ).good() ) {
|
||||
string::size_type eol( line.find_first_of( "\r\n" ) );
|
||||
if ( eol != string::npos ) {
|
||||
line.erase( eol );
|
||||
}
|
||||
if ( is_timestamp( line ) ) {
|
||||
when.assign( line, 4, std::string::npos );
|
||||
continue;
|
||||
}
|
||||
if ( ! line.empty() ) {
|
||||
us.assign( line );
|
||||
std::replace( us.begin(), us.end(), char32_t( ETB ), char32_t( '\n' ) );
|
||||
_entries.emplace_back( when, us );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool History::load( std::string const& filename ) {
|
||||
ifstream histFile( filename );
|
||||
if ( ! histFile ) {
|
||||
clear();
|
||||
return false;
|
||||
}
|
||||
load(histFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
void History::load( std::istream& histFile ) {
|
||||
clear();
|
||||
do_load( histFile );
|
||||
sort();
|
||||
remove_duplicates();
|
||||
trim_to_max_size();
|
||||
_previous = _current = last();
|
||||
_yankPos = _entries.end();
|
||||
}
|
||||
|
||||
void History::sort( void ) {
|
||||
typedef std::vector<Entry> sortable_entries_t;
|
||||
_locations.clear();
|
||||
sortable_entries_t sortableEntries( _entries.begin(), _entries.end() );
|
||||
std::stable_sort( sortableEntries.begin(), sortableEntries.end() );
|
||||
_entries.clear();
|
||||
_entries.insert( _entries.begin(), sortableEntries.begin(), sortableEntries.end() );
|
||||
}
|
||||
|
||||
void History::clear( void ) {
|
||||
_locations.clear();
|
||||
_entries.clear();
|
||||
_current = _entries.begin();
|
||||
_recallMostRecent = false;
|
||||
}
|
||||
|
||||
void History::set_max_size( int size_ ) {
|
||||
if ( size_ >= 0 ) {
|
||||
_maxSize = size_;
|
||||
trim_to_max_size();
|
||||
}
|
||||
}
|
||||
|
||||
void History::reset_yank_iterator( void ) {
|
||||
_yankPos = _entries.end();
|
||||
}
|
||||
|
||||
bool History::next_yank_position( void ) {
|
||||
bool resetYankSize( false );
|
||||
if ( _yankPos == _entries.end() ) {
|
||||
resetYankSize = true;
|
||||
}
|
||||
if ( ( _yankPos != _entries.begin() ) && ( _yankPos != _entries.end() ) ) {
|
||||
-- _yankPos;
|
||||
} else {
|
||||
_yankPos = moved( _entries.end(), -2 );
|
||||
}
|
||||
return ( resetYankSize );
|
||||
}
|
||||
|
||||
bool History::move( bool up_ ) {
|
||||
bool doRecall( _recallMostRecent && ! up_ );
|
||||
if ( doRecall ) {
|
||||
_current = _previous; // emulate Windows down-arrow
|
||||
}
|
||||
_recallMostRecent = false;
|
||||
return ( doRecall || move( _current, up_ ? -1 : 1 ) );
|
||||
}
|
||||
|
||||
void History::jump( bool start_, bool reset_ ) {
|
||||
if ( start_ ) {
|
||||
_current = _entries.begin();
|
||||
} else {
|
||||
_current = last();
|
||||
}
|
||||
if ( reset_ ) {
|
||||
_recallMostRecent = false;
|
||||
}
|
||||
}
|
||||
|
||||
void History::save_pos( void ) {
|
||||
_previous = _current;
|
||||
}
|
||||
|
||||
void History::restore_pos( void ) {
|
||||
_current = _previous;
|
||||
}
|
||||
|
||||
bool History::common_prefix_search( UnicodeString const& prefix_, int prefixSize_, bool back_, bool ignoreCase ) {
|
||||
int step( back_ ? -1 : 1 );
|
||||
entries_t::iterator it( moved( _current, step, true ) );
|
||||
bool lowerCaseContext( std::none_of( prefix_.begin(), prefix_.end(), []( char32_t x ) { return iswupper( static_cast<wint_t>( x ) ); } ) );
|
||||
while ( it != _current ) {
|
||||
if ( it->text().starts_with( prefix_.begin(), prefix_.begin() + prefixSize_, ignoreCase && lowerCaseContext ? case_insensitive_equal : case_sensitive_equal ) ) {
|
||||
_current = it;
|
||||
commit_index();
|
||||
return ( true );
|
||||
}
|
||||
move( it, step, true );
|
||||
}
|
||||
return ( false );
|
||||
}
|
||||
|
||||
bool History::move( entries_t::iterator& it_, int by_, bool wrapped_ ) {
|
||||
if ( by_ > 0 ) {
|
||||
for ( int i( 0 ); i < by_; ++ i ) {
|
||||
++ it_;
|
||||
if ( it_ != _entries.end() ) {
|
||||
} else if ( wrapped_ ) {
|
||||
it_ = _entries.begin();
|
||||
} else {
|
||||
-- it_;
|
||||
return ( false );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( int i( 0 ); i > by_; -- i ) {
|
||||
if ( it_ != _entries.begin() ) {
|
||||
-- it_;
|
||||
} else if ( wrapped_ ) {
|
||||
it_ = last();
|
||||
} else {
|
||||
return ( false );
|
||||
}
|
||||
}
|
||||
}
|
||||
return ( true );
|
||||
}
|
||||
|
||||
History::entries_t::iterator History::moved( entries_t::iterator it_, int by_, bool wrapped_ ) {
|
||||
move( it_, by_, wrapped_ );
|
||||
return ( it_ );
|
||||
}
|
||||
|
||||
void History::erase( entries_t::iterator it_ ) {
|
||||
bool invalidated( it_ == _current );
|
||||
_locations.erase( it_->text() );
|
||||
it_ = _entries.erase( it_ );
|
||||
if ( invalidated ) {
|
||||
_current = it_;
|
||||
}
|
||||
if ( ( _current == _entries.end() ) && ! _entries.empty() ) {
|
||||
-- _current;
|
||||
}
|
||||
_yankPos = _entries.end();
|
||||
_previous = _current;
|
||||
}
|
||||
|
||||
void History::trim_to_max_size( void ) {
|
||||
while ( size() > _maxSize ) {
|
||||
erase( _entries.begin() );
|
||||
}
|
||||
}
|
||||
|
||||
void History::remove_duplicate( UnicodeString const& line_ ) {
|
||||
if ( ! _unique ) {
|
||||
return;
|
||||
}
|
||||
locations_t::iterator it( _locations.find( line_ ) );
|
||||
if ( it == _locations.end() ) {
|
||||
return;
|
||||
}
|
||||
erase( it->second );
|
||||
}
|
||||
|
||||
void History::remove_duplicates( void ) {
|
||||
if ( ! _unique ) {
|
||||
return;
|
||||
}
|
||||
_locations.clear();
|
||||
typedef std::pair<locations_t::iterator, bool> locations_insertion_result_t;
|
||||
for ( entries_t::iterator it( _entries.begin() ), end( _entries.end() ); it != end; ++ it ) {
|
||||
it->reset_scratch();
|
||||
locations_insertion_result_t locationsInsertionResult( _locations.insert( make_pair( it->text(), it ) ) );
|
||||
if ( ! locationsInsertionResult.second ) {
|
||||
_entries.erase( locationsInsertionResult.first->second );
|
||||
locationsInsertionResult.first->second = it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void History::update_last( UnicodeString const& line_ ) {
|
||||
if ( _unique ) {
|
||||
_locations.erase( _entries.back().text() );
|
||||
remove_duplicate( line_ );
|
||||
_locations.insert( make_pair( line_, last() ) );
|
||||
}
|
||||
_entries.back() = Entry( now_ms_str(), line_ );
|
||||
}
|
||||
|
||||
void History::drop_last( void ) {
|
||||
reset_current_scratch();
|
||||
erase( last() );
|
||||
}
|
||||
|
||||
bool History::is_last( void ) {
|
||||
return ( _current == last() );
|
||||
}
|
||||
|
||||
History::entries_t::iterator History::last( void ) {
|
||||
return ( moved( _entries.end(), -1 ) );
|
||||
}
|
||||
|
||||
void History::reset_iters( void ) {
|
||||
_previous = _current = last();
|
||||
_yankPos = _entries.end();
|
||||
}
|
||||
|
||||
}
|
||||
|
162
lib/replxx/src/history.hxx
Normal file
162
lib/replxx/src/history.hxx
Normal file
@ -0,0 +1,162 @@
|
||||
#ifndef REPLXX_HISTORY_HXX_INCLUDED
|
||||
#define REPLXX_HISTORY_HXX_INCLUDED 1
|
||||
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "unicodestring.hxx"
|
||||
#include "utf8string.hxx"
|
||||
#include "conversion.hxx"
|
||||
#include "util.hxx"
|
||||
|
||||
namespace std {
|
||||
template<>
|
||||
struct hash<replxx::UnicodeString> {
|
||||
std::size_t operator()( replxx::UnicodeString const& us_ ) const {
|
||||
std::size_t h( 0 );
|
||||
char32_t const* p( us_.get() );
|
||||
char32_t const* e( p + us_.length() );
|
||||
while ( p != e ) {
|
||||
h *= 31;
|
||||
h += *p;
|
||||
++ p;
|
||||
}
|
||||
return ( h );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace replxx {
|
||||
|
||||
class History {
|
||||
public:
|
||||
class Entry {
|
||||
std::string _timestamp;
|
||||
UnicodeString _text;
|
||||
UnicodeString _scratch;
|
||||
public:
|
||||
Entry( std::string const& timestamp_, UnicodeString const& text_ )
|
||||
: _timestamp( timestamp_ )
|
||||
, _text( text_ )
|
||||
, _scratch( text_ ) {
|
||||
}
|
||||
std::string const& timestamp( void ) const {
|
||||
return ( _timestamp );
|
||||
}
|
||||
UnicodeString const& text( void ) const {
|
||||
return ( _scratch );
|
||||
}
|
||||
void set_scratch( UnicodeString const& s ) {
|
||||
_scratch = s;
|
||||
}
|
||||
void reset_scratch( void ) {
|
||||
_scratch = _text;
|
||||
}
|
||||
bool operator < ( Entry const& other_ ) const {
|
||||
return ( _timestamp < other_._timestamp );
|
||||
}
|
||||
};
|
||||
typedef std::list<Entry> entries_t;
|
||||
typedef std::unordered_map<UnicodeString, entries_t::iterator> locations_t;
|
||||
private:
|
||||
entries_t _entries;
|
||||
locations_t _locations;
|
||||
int _maxSize;
|
||||
entries_t::iterator _current;
|
||||
entries_t::const_iterator _yankPos;
|
||||
/*
|
||||
* _previous and _recallMostRecent are used to allow
|
||||
* HISTORY_NEXT action (a down-arrow key) to have a special meaning
|
||||
* if invoked after a line from history was accepted without
|
||||
* any modification.
|
||||
* Special meaning is: a down arrow shall jump to the line one
|
||||
* after previously accepted from history.
|
||||
*/
|
||||
entries_t::iterator _previous;
|
||||
bool _recallMostRecent;
|
||||
bool _unique;
|
||||
public:
|
||||
History( void );
|
||||
void add( UnicodeString const& line, std::string const& when = now_ms_str() );
|
||||
bool save( std::string const& filename, bool );
|
||||
void save( std::ostream& histFile );
|
||||
bool load( std::string const& filename );
|
||||
void load( std::istream& histFile );
|
||||
void clear( void );
|
||||
void set_max_size( int len );
|
||||
void set_unique( bool unique_ ) {
|
||||
_unique = unique_;
|
||||
remove_duplicates();
|
||||
}
|
||||
void reset_yank_iterator();
|
||||
bool next_yank_position( void );
|
||||
void reset_recall_most_recent( void ) {
|
||||
_recallMostRecent = false;
|
||||
}
|
||||
void commit_index( void ) {
|
||||
_previous = _current;
|
||||
_recallMostRecent = true;
|
||||
}
|
||||
bool is_empty( void ) const {
|
||||
return ( _entries.empty() );
|
||||
}
|
||||
void update_last( UnicodeString const& );
|
||||
void drop_last( void );
|
||||
bool is_last( void );
|
||||
bool move( bool );
|
||||
void set_current_scratch( UnicodeString const& s ) {
|
||||
_current->set_scratch( s );
|
||||
}
|
||||
void reset_scratches( void ) {
|
||||
for ( Entry& entry : _entries ) {
|
||||
entry.reset_scratch();
|
||||
}
|
||||
}
|
||||
void reset_current_scratch( void ) {
|
||||
_current->reset_scratch();
|
||||
}
|
||||
UnicodeString const& current( void ) const {
|
||||
return ( _current->text() );
|
||||
}
|
||||
UnicodeString const& yank_line( void ) const {
|
||||
return ( _yankPos->text() );
|
||||
}
|
||||
void jump( bool, bool = true );
|
||||
bool common_prefix_search( UnicodeString const&, int, bool, bool );
|
||||
int size( void ) const {
|
||||
return ( static_cast<int>( _entries.size() ) );
|
||||
}
|
||||
Replxx::HistoryScan::impl_t scan( void ) const;
|
||||
void save_pos( void );
|
||||
void restore_pos( void );
|
||||
private:
|
||||
History( History const& ) = delete;
|
||||
History& operator = ( History const& ) = delete;
|
||||
bool move( entries_t::iterator&, int, bool = false );
|
||||
entries_t::iterator moved( entries_t::iterator, int, bool = false );
|
||||
void erase( entries_t::iterator );
|
||||
void trim_to_max_size( void );
|
||||
void remove_duplicate( UnicodeString const& );
|
||||
void remove_duplicates( void );
|
||||
void do_load( std::istream& );
|
||||
entries_t::iterator last( void );
|
||||
void sort( void );
|
||||
void reset_iters( void );
|
||||
};
|
||||
|
||||
class Replxx::HistoryScanImpl {
|
||||
History::entries_t const& _entries;
|
||||
History::entries_t::const_iterator _it;
|
||||
mutable Utf8String _utf8Cache;
|
||||
mutable Replxx::HistoryEntry _entryCache;
|
||||
mutable bool _cacheValid;
|
||||
public:
|
||||
HistoryScanImpl( History::entries_t const& );
|
||||
bool next( void );
|
||||
Replxx::HistoryEntry const& get( void ) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
78
lib/replxx/src/killring.hxx
Normal file
78
lib/replxx/src/killring.hxx
Normal file
@ -0,0 +1,78 @@
|
||||
#ifndef REPLXX_KILLRING_HXX_INCLUDED
|
||||
#define REPLXX_KILLRING_HXX_INCLUDED 1
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "unicodestring.hxx"
|
||||
|
||||
namespace replxx {
|
||||
|
||||
class KillRing {
|
||||
static const int capacity = 10;
|
||||
int size;
|
||||
int index;
|
||||
char indexToSlot[10];
|
||||
std::vector<UnicodeString> theRing;
|
||||
|
||||
public:
|
||||
enum action { actionOther, actionKill, actionYank };
|
||||
action lastAction;
|
||||
|
||||
KillRing()
|
||||
: size(0)
|
||||
, index(0)
|
||||
, lastAction(actionOther) {
|
||||
theRing.reserve(capacity);
|
||||
}
|
||||
|
||||
void kill(const char32_t* text, int textLen, bool forward) {
|
||||
if (textLen == 0) {
|
||||
return;
|
||||
}
|
||||
UnicodeString killedText(text, textLen);
|
||||
if (lastAction == actionKill && size > 0) {
|
||||
int slot = indexToSlot[0];
|
||||
int currentLen = static_cast<int>(theRing[slot].length());
|
||||
UnicodeString temp;
|
||||
if ( forward ) {
|
||||
temp.append( theRing[slot].get(), currentLen ).append( killedText.get(), textLen );
|
||||
} else {
|
||||
temp.append( killedText.get(), textLen ).append( theRing[slot].get(), currentLen );
|
||||
}
|
||||
theRing[slot] = temp;
|
||||
} else {
|
||||
if (size < capacity) {
|
||||
if (size > 0) {
|
||||
memmove(&indexToSlot[1], &indexToSlot[0], size);
|
||||
}
|
||||
indexToSlot[0] = size;
|
||||
size++;
|
||||
theRing.push_back(killedText);
|
||||
} else {
|
||||
int slot = indexToSlot[capacity - 1];
|
||||
theRing[slot] = killedText;
|
||||
memmove(&indexToSlot[1], &indexToSlot[0], capacity - 1);
|
||||
indexToSlot[0] = slot;
|
||||
}
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeString* yank() { return (size > 0) ? &theRing[indexToSlot[index]] : 0; }
|
||||
|
||||
UnicodeString* yankPop() {
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
++index;
|
||||
if (index == size) {
|
||||
index = 0;
|
||||
}
|
||||
return &theRing[indexToSlot[index]];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
90
lib/replxx/src/prompt.cxx
Normal file
90
lib/replxx/src/prompt.cxx
Normal file
@ -0,0 +1,90 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <conio.h>
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#if _MSC_VER < 1900 && defined (_MSC_VER)
|
||||
#define snprintf _snprintf // Microsoft headers use underscores in some names
|
||||
#endif
|
||||
#define strcasecmp _stricmp
|
||||
#define strdup _strdup
|
||||
#define write _write
|
||||
#define STDIN_FILENO 0
|
||||
|
||||
#else /* _WIN32 */
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#include "prompt.hxx"
|
||||
#include "util.hxx"
|
||||
|
||||
namespace replxx {
|
||||
|
||||
Prompt::Prompt( Terminal& terminal_ )
|
||||
: _extraLines( 0 )
|
||||
, _lastLinePosition( 0 )
|
||||
, _cursorRowOffset( 0 )
|
||||
, _screenColumns( 0 )
|
||||
, _terminal( terminal_ ) {
|
||||
}
|
||||
|
||||
void Prompt::write() {
|
||||
_terminal.write32( _text.get(), _text.length() );
|
||||
}
|
||||
|
||||
void Prompt::update_screen_columns( void ) {
|
||||
_screenColumns = _terminal.get_screen_columns();
|
||||
}
|
||||
|
||||
void Prompt::set_text( UnicodeString const& text_ ) {
|
||||
_text = text_;
|
||||
update_state();
|
||||
}
|
||||
|
||||
void Prompt::update_state() {
|
||||
_cursorRowOffset -= _extraLines;
|
||||
_extraLines = 0;
|
||||
_lastLinePosition = 0;
|
||||
_screenColumns = 0;
|
||||
update_screen_columns();
|
||||
// strip control characters from the prompt -- we do allow newline
|
||||
UnicodeString::const_iterator in( _text.begin() );
|
||||
|
||||
int x = 0;
|
||||
int renderedSize( 0 );
|
||||
_characterCount = virtual_render( _text.get(), _text.length(), x, _extraLines, _screenColumns, 0, _text.get(), &renderedSize );
|
||||
_lastLinePosition = _characterCount - x;
|
||||
_text.erase( renderedSize, _text.length() - renderedSize );
|
||||
|
||||
_cursorRowOffset += _extraLines;
|
||||
}
|
||||
|
||||
int Prompt::indentation() const {
|
||||
return _characterCount - _lastLinePosition;
|
||||
}
|
||||
|
||||
// Used with DynamicPrompt (history search)
|
||||
//
|
||||
const UnicodeString forwardSearchBasePrompt("(i-search)`");
|
||||
const UnicodeString reverseSearchBasePrompt("(reverse-i-search)`");
|
||||
const UnicodeString endSearchBasePrompt("': ");
|
||||
|
||||
DynamicPrompt::DynamicPrompt( Terminal& terminal_, int initialDirection )
|
||||
: Prompt( terminal_ )
|
||||
, _searchText()
|
||||
, _direction( initialDirection ) {
|
||||
updateSearchPrompt();
|
||||
}
|
||||
|
||||
void DynamicPrompt::updateSearchPrompt(void) {
|
||||
update_screen_columns();
|
||||
const UnicodeString* basePrompt =
|
||||
(_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt;
|
||||
_text.assign( *basePrompt ).append( _searchText ).append( endSearchBasePrompt );
|
||||
update_state();
|
||||
}
|
||||
|
||||
}
|
||||
|
46
lib/replxx/src/prompt.hxx
Normal file
46
lib/replxx/src/prompt.hxx
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef REPLXX_PROMPT_HXX_INCLUDED
|
||||
#define REPLXX_PROMPT_HXX_INCLUDED 1
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "unicodestring.hxx"
|
||||
#include "terminal.hxx"
|
||||
|
||||
namespace replxx {
|
||||
|
||||
class Prompt { // a convenience struct for grouping prompt info
|
||||
public:
|
||||
UnicodeString _text; // our copy of the prompt text, edited
|
||||
int _characterCount{0}; // visible characters in _text
|
||||
int _extraLines{0}; // extra lines (beyond 1) occupied by prompt
|
||||
int _lastLinePosition{0}; // index into _text where last line begins
|
||||
int _cursorRowOffset{0}; // where the cursor is relative to the start of the prompt
|
||||
|
||||
private:
|
||||
int _screenColumns{0}; // width of screen in columns [cache]
|
||||
Terminal& _terminal;
|
||||
public:
|
||||
Prompt( Terminal& );
|
||||
void set_text( UnicodeString const& textPtr );
|
||||
void update_state();
|
||||
void update_screen_columns( void );
|
||||
int screen_columns() const {
|
||||
return ( _screenColumns );
|
||||
}
|
||||
void write();
|
||||
int indentation() const;
|
||||
};
|
||||
|
||||
// changing prompt for "(reverse-i-search)`text':" etc.
|
||||
//
|
||||
struct DynamicPrompt : public Prompt {
|
||||
UnicodeString _searchText; // text we are searching for
|
||||
int _direction; // current search _direction, 1=forward, -1=reverse
|
||||
|
||||
DynamicPrompt( Terminal&, int initialDirection );
|
||||
void updateSearchPrompt(void);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
743
lib/replxx/src/replxx.cxx
Normal file
743
lib/replxx/src/replxx.cxx
Normal file
@ -0,0 +1,743 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org)
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* line editing lib needs to be 20,000 lines of C code.
|
||||
*
|
||||
* You can find the latest source code at:
|
||||
*
|
||||
* http://github.com/antirez/linenoise
|
||||
*
|
||||
* Does a number of crazy assumptions that happen to be true in 99.9999% of
|
||||
* the 2010 UNIX computers around.
|
||||
*
|
||||
* References:
|
||||
* - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
* - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
|
||||
*
|
||||
* Todo list:
|
||||
* - Switch to gets() if $TERM is something we can't support.
|
||||
* - Filter bogus Ctrl+<char> combinations.
|
||||
* - Win32 support
|
||||
*
|
||||
* Bloat:
|
||||
* - Completion?
|
||||
* - History search like Ctrl+r in readline?
|
||||
*
|
||||
* List of escape sequences used by this program, we do everything just
|
||||
* with three sequences. In order to be so cheap we may have some
|
||||
* flickering effect with some slow terminal, but the lesser sequences
|
||||
* the more compatible.
|
||||
*
|
||||
* CHA (Cursor Horizontal Absolute)
|
||||
* Sequence: ESC [ n G
|
||||
* Effect: moves cursor to column n (1 based)
|
||||
*
|
||||
* EL (Erase Line)
|
||||
* Sequence: ESC [ n K
|
||||
* Effect: if n is 0 or missing, clear from cursor to end of line
|
||||
* Effect: if n is 1, clear from beginning of line to cursor
|
||||
* Effect: if n is 2, clear entire line
|
||||
*
|
||||
* CUF (Cursor Forward)
|
||||
* Sequence: ESC [ n C
|
||||
* Effect: moves cursor forward of n chars
|
||||
*
|
||||
* The following are used to clear the screen: ESC [ H ESC [ 2 J
|
||||
* This is actually composed of two sequences:
|
||||
*
|
||||
* cursorhome
|
||||
* Sequence: ESC [ H
|
||||
* Effect: moves the cursor to upper left corner
|
||||
*
|
||||
* ED2 (Clear entire screen)
|
||||
* Sequence: ESC [ 2 J
|
||||
* Effect: clear the whole screen
|
||||
*
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdarg>
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <io.h>
|
||||
#define STDIN_FILENO 0
|
||||
|
||||
#else /* _WIN32 */
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#include "replxx.h"
|
||||
#include "replxx.hxx"
|
||||
#include "replxx_impl.hxx"
|
||||
#include "history.hxx"
|
||||
|
||||
static_assert(
|
||||
static_cast<int>( replxx::Replxx::ACTION::SEND_EOF ) == static_cast<int>( REPLXX_ACTION_SEND_EOF ),
|
||||
"C and C++ `ACTION` APIs are missaligned!"
|
||||
);
|
||||
|
||||
static_assert(
|
||||
static_cast<int>( replxx::Replxx::KEY::PASTE_FINISH ) == static_cast<int>( REPLXX_KEY_PASTE_FINISH ),
|
||||
"C and C++ `KEY` APIs are missaligned!"
|
||||
);
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
using namespace replxx;
|
||||
|
||||
namespace replxx {
|
||||
|
||||
namespace {
|
||||
void delete_ReplxxImpl( Replxx::ReplxxImpl* impl_ ) {
|
||||
delete impl_;
|
||||
}
|
||||
}
|
||||
|
||||
Replxx::Replxx( void )
|
||||
: _impl( new Replxx::ReplxxImpl( nullptr, nullptr, nullptr ), delete_ReplxxImpl ) {
|
||||
}
|
||||
|
||||
void Replxx::set_completion_callback( completion_callback_t const& fn ) {
|
||||
_impl->set_completion_callback( fn );
|
||||
}
|
||||
|
||||
void Replxx::set_modify_callback( modify_callback_t const& fn ) {
|
||||
_impl->set_modify_callback( fn );
|
||||
}
|
||||
|
||||
void Replxx::set_highlighter_callback( highlighter_callback_t const& fn ) {
|
||||
_impl->set_highlighter_callback( fn );
|
||||
}
|
||||
|
||||
void Replxx::set_hint_callback( hint_callback_t const& fn ) {
|
||||
_impl->set_hint_callback( fn );
|
||||
}
|
||||
|
||||
char const* Replxx::input( std::string const& prompt ) {
|
||||
return ( _impl->input( prompt ) );
|
||||
}
|
||||
|
||||
void Replxx::history_add( std::string const& line ) {
|
||||
_impl->history_add( line );
|
||||
}
|
||||
|
||||
bool Replxx::history_sync( std::string const& filename ) {
|
||||
return ( _impl->history_sync( filename ) );
|
||||
}
|
||||
|
||||
bool Replxx::history_save( std::string const& filename ) {
|
||||
return ( _impl->history_save( filename ) );
|
||||
}
|
||||
|
||||
void Replxx::history_save( std::ostream& out ) {
|
||||
_impl->history_save( out );
|
||||
}
|
||||
|
||||
bool Replxx::history_load( std::string const& filename ) {
|
||||
return ( _impl->history_load( filename ) );
|
||||
}
|
||||
|
||||
void Replxx::history_load( std::istream& in ) {
|
||||
_impl->history_load( in );
|
||||
}
|
||||
|
||||
void Replxx::history_clear( void ) {
|
||||
_impl->history_clear();
|
||||
}
|
||||
|
||||
int Replxx::history_size( void ) const {
|
||||
return ( _impl->history_size() );
|
||||
}
|
||||
|
||||
Replxx::HistoryScan Replxx::history_scan( void ) const {
|
||||
return ( _impl->history_scan() );
|
||||
}
|
||||
|
||||
void Replxx::set_preload_buffer( std::string const& preloadText ) {
|
||||
_impl->set_preload_buffer( preloadText );
|
||||
}
|
||||
|
||||
void Replxx::set_word_break_characters( char const* wordBreakers ) {
|
||||
_impl->set_word_break_characters( wordBreakers );
|
||||
}
|
||||
|
||||
void Replxx::set_max_hint_rows( int count ) {
|
||||
_impl->set_max_hint_rows( count );
|
||||
}
|
||||
|
||||
void Replxx::set_hint_delay( int milliseconds ) {
|
||||
_impl->set_hint_delay( milliseconds );
|
||||
}
|
||||
|
||||
void Replxx::set_completion_count_cutoff( int count ) {
|
||||
_impl->set_completion_count_cutoff( count );
|
||||
}
|
||||
|
||||
void Replxx::set_double_tab_completion( bool val ) {
|
||||
_impl->set_double_tab_completion( val );
|
||||
}
|
||||
|
||||
void Replxx::set_complete_on_empty( bool val ) {
|
||||
_impl->set_complete_on_empty( val );
|
||||
}
|
||||
|
||||
void Replxx::set_beep_on_ambiguous_completion( bool val ) {
|
||||
_impl->set_beep_on_ambiguous_completion( val );
|
||||
}
|
||||
|
||||
void Replxx::set_immediate_completion( bool val ) {
|
||||
_impl->set_immediate_completion( val );
|
||||
}
|
||||
|
||||
void Replxx::set_unique_history( bool val ) {
|
||||
_impl->set_unique_history( val );
|
||||
}
|
||||
|
||||
void Replxx::set_no_color( bool val ) {
|
||||
_impl->set_no_color( val );
|
||||
}
|
||||
|
||||
void Replxx::set_indent_multiline( bool val ) {
|
||||
_impl->set_indent_multiline( val );
|
||||
}
|
||||
|
||||
void Replxx::set_max_history_size( int len ) {
|
||||
_impl->set_max_history_size( len );
|
||||
}
|
||||
|
||||
void Replxx::clear_screen( void ) {
|
||||
_impl->clear_screen( 0 );
|
||||
}
|
||||
|
||||
void Replxx::emulate_key_press( char32_t keyPress_ ) {
|
||||
_impl->emulate_key_press( keyPress_ );
|
||||
}
|
||||
|
||||
Replxx::ACTION_RESULT Replxx::invoke( ACTION action_, char32_t keyPress_ ) {
|
||||
return ( _impl->invoke( action_, keyPress_ ) );
|
||||
}
|
||||
|
||||
void Replxx::bind_key( char32_t keyPress_, key_press_handler_t handler_ ) {
|
||||
_impl->bind_key( keyPress_, handler_ );
|
||||
}
|
||||
|
||||
void Replxx::bind_key_internal( char32_t keyPress_, char const* actionName_ ) {
|
||||
_impl->bind_key_internal( keyPress_, actionName_ );
|
||||
}
|
||||
|
||||
Replxx::State Replxx::get_state( void ) const {
|
||||
return ( _impl->get_state() );
|
||||
}
|
||||
|
||||
void Replxx::set_state( Replxx::State const& state_ ) {
|
||||
_impl->set_state( state_ );
|
||||
}
|
||||
|
||||
void Replxx::set_ignore_case( bool val ) {
|
||||
_impl->set_ignore_case( val );
|
||||
}
|
||||
|
||||
int Replxx::install_window_change_handler( void ) {
|
||||
return ( _impl->install_window_change_handler() );
|
||||
}
|
||||
|
||||
void Replxx::enable_bracketed_paste( void ) {
|
||||
_impl->enable_bracketed_paste();
|
||||
}
|
||||
|
||||
void Replxx::disable_bracketed_paste( void ) {
|
||||
_impl->disable_bracketed_paste();
|
||||
}
|
||||
|
||||
void Replxx::print( char const* format_, ... ) {
|
||||
::std::va_list ap;
|
||||
va_start( ap, format_ );
|
||||
int size = static_cast<int>( vsnprintf( nullptr, 0, format_, ap ) );
|
||||
va_end( ap );
|
||||
va_start( ap, format_ );
|
||||
unique_ptr<char[]> buf( new char[size + 1] );
|
||||
vsnprintf( buf.get(), static_cast<size_t>( size + 1 ), format_, ap );
|
||||
va_end( ap );
|
||||
return ( _impl->print( buf.get(), size ) );
|
||||
}
|
||||
|
||||
void Replxx::write( char const* str, int length ) {
|
||||
return ( _impl->print( str, length ) );
|
||||
}
|
||||
|
||||
void Replxx::set_prompt( std::string prompt ) {
|
||||
return ( _impl->set_prompt( std::move( prompt ) ) );
|
||||
}
|
||||
|
||||
namespace color {
|
||||
|
||||
Replxx::Color operator | ( Replxx::Color color1_, Replxx::Color color2_ ) {
|
||||
return static_cast<Replxx::Color>( static_cast<int unsigned>( color1_ ) | static_cast<int unsigned>( color2_ ) );
|
||||
}
|
||||
|
||||
Replxx::Color bg( Replxx::Color color_ ) {
|
||||
return static_cast<Replxx::Color>( ( ( static_cast<int unsigned>( color_ ) & 0xFFu ) << 8 ) | color::BACKGROUND_COLOR_SET );
|
||||
}
|
||||
|
||||
Replxx::Color bold( Replxx::Color color_ ) {
|
||||
return static_cast<Replxx::Color>( static_cast<int unsigned>( color_ ) | color::BOLD );
|
||||
}
|
||||
|
||||
Replxx::Color underline( Replxx::Color color_ ) {
|
||||
return static_cast<Replxx::Color>( static_cast<int unsigned>( color_ ) | color::UNDERLINE );
|
||||
}
|
||||
|
||||
Replxx::Color grayscale( int level_ ) {
|
||||
assert( ( level_ >= 0 ) && ( level_ < 24 ) );
|
||||
return static_cast<Replxx::Color>( abs( level_ ) % 24 + static_cast<int unsigned>( color::GRAYSCALE ) );
|
||||
}
|
||||
|
||||
Replxx::Color rgb666( int red_, int green_, int blue_ ) {
|
||||
assert( ( red_ >= 0 ) && ( red_ < 6 ) && ( green_ >= 0 ) && ( green_ < 6 ) && ( blue_ >= 0 ) && ( blue_ < 6 ) );
|
||||
return static_cast<Replxx::Color>(
|
||||
( abs( red_ ) % 6 ) * 36
|
||||
+ ( abs( green_ ) % 6 ) * 6
|
||||
+ ( abs( blue_ ) % 6 )
|
||||
+ static_cast<int unsigned>( color::RGB666 )
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
::Replxx* replxx_init() {
|
||||
typedef ::Replxx* replxx_data_t;
|
||||
return ( reinterpret_cast<replxx_data_t>( new replxx::Replxx::ReplxxImpl( nullptr, nullptr, nullptr ) ) );
|
||||
}
|
||||
|
||||
void replxx_end( ::Replxx* replxx_ ) {
|
||||
delete reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ );
|
||||
}
|
||||
|
||||
void replxx_clear_screen( ::Replxx* replxx_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->clear_screen( 0 );
|
||||
}
|
||||
|
||||
void replxx_emulate_key_press( ::Replxx* replxx_, int unsigned keyPress_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->emulate_key_press( keyPress_ );
|
||||
}
|
||||
|
||||
ReplxxActionResult replxx_invoke( ::Replxx* replxx_, ReplxxAction action_, int unsigned keyPress_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
return ( static_cast<ReplxxActionResult>( replxx->invoke( static_cast<replxx::Replxx::ACTION>( action_ ), keyPress_ ) ) );
|
||||
}
|
||||
|
||||
replxx::Replxx::ACTION_RESULT key_press_handler_forwarder( key_press_handler_t handler_, char32_t code_, void* userData_ ) {
|
||||
return ( static_cast<replxx::Replxx::ACTION_RESULT>( handler_( code_, userData_ ) ) );
|
||||
}
|
||||
|
||||
void replxx_bind_key( ::Replxx* replxx_, int code_, key_press_handler_t handler_, void* userData_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->bind_key( code_, std::bind( key_press_handler_forwarder, handler_, _1, userData_ ) );
|
||||
}
|
||||
|
||||
int replxx_bind_key_internal( ::Replxx* replxx_, int code_, char const* actionName_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
try {
|
||||
replxx->bind_key_internal( code_, actionName_ );
|
||||
} catch ( ... ) {
|
||||
return ( -1 );
|
||||
}
|
||||
return ( 0 );
|
||||
}
|
||||
|
||||
void replxx_get_state( ::Replxx* replxx_, ReplxxState* state ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx::Replxx::State s( replxx->get_state() );
|
||||
state->text = s.text();
|
||||
state->cursorPosition = s.cursor_position();
|
||||
}
|
||||
|
||||
void replxx_set_state( ::Replxx* replxx_, ReplxxState* state ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_state( replxx::Replxx::State( state->text, state->cursorPosition ) );
|
||||
}
|
||||
|
||||
void replxx_set_ignore_case( ::Replxx* replxx_, int val ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_ignore_case( val );
|
||||
}
|
||||
|
||||
/**
|
||||
* replxx_set_preload_buffer provides text to be inserted into the command buffer
|
||||
*
|
||||
* the provided text will be processed to be usable and will be used to preload
|
||||
* the input buffer on the next call to replxx_input()
|
||||
*
|
||||
* @param preloadText text to begin with on the next call to replxx_input()
|
||||
*/
|
||||
void replxx_set_preload_buffer(::Replxx* replxx_, const char* preloadText) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_preload_buffer( preloadText ? preloadText : "" );
|
||||
}
|
||||
|
||||
/**
|
||||
* replxx_input is a readline replacement.
|
||||
*
|
||||
* call it with a prompt to display and it will return a line of input from the
|
||||
* user
|
||||
*
|
||||
* @param prompt text of prompt to display to the user
|
||||
* @return the returned string is managed by replxx library
|
||||
* and it must NOT be freed in the client.
|
||||
*/
|
||||
char const* replxx_input( ::Replxx* replxx_, const char* prompt ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
return ( replxx->input( prompt ) );
|
||||
}
|
||||
|
||||
int replxx_print( ::Replxx* replxx_, char const* format_, ... ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
::std::va_list ap;
|
||||
va_start( ap, format_ );
|
||||
int size = static_cast<int>( vsnprintf( nullptr, 0, format_, ap ) );
|
||||
va_end( ap );
|
||||
va_start( ap, format_ );
|
||||
unique_ptr<char[]> buf( new char[size + 1] );
|
||||
vsnprintf( buf.get(), static_cast<size_t>( size + 1 ), format_, ap );
|
||||
va_end( ap );
|
||||
try {
|
||||
replxx->print( buf.get(), size );
|
||||
} catch ( ... ) {
|
||||
return ( -1 );
|
||||
}
|
||||
return ( size );
|
||||
}
|
||||
|
||||
int replxx_write( ::Replxx* replxx_, char const* str, int length ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
try {
|
||||
replxx->print( str, length );
|
||||
} catch ( ... ) {
|
||||
return ( -1 );
|
||||
}
|
||||
return static_cast<int>( length );
|
||||
}
|
||||
|
||||
void replxx_set_prompt( ::Replxx* replxx_, const char* prompt ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_prompt( prompt );
|
||||
}
|
||||
|
||||
struct replxx_completions {
|
||||
replxx::Replxx::completions_t data;
|
||||
};
|
||||
|
||||
struct replxx_hints {
|
||||
replxx::Replxx::hints_t data;
|
||||
};
|
||||
|
||||
void modify_fwd( replxx_modify_callback_t fn, std::string& line_, int& cursorPosition_, void* userData_ ) {
|
||||
#ifdef _WIN32
|
||||
#define strdup _strdup
|
||||
#endif
|
||||
char* s( strdup( line_.c_str() ) );
|
||||
#undef strdup
|
||||
fn( &s, &cursorPosition_, userData_ );
|
||||
line_ = s;
|
||||
free( s );
|
||||
return;
|
||||
}
|
||||
|
||||
void replxx_set_modify_callback(::Replxx* replxx_, replxx_modify_callback_t* fn, void* userData) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_modify_callback( std::bind( &modify_fwd, fn, _1, _2, userData ) );
|
||||
}
|
||||
|
||||
replxx::Replxx::completions_t completions_fwd( replxx_completion_callback_t fn, std::string const& input_, int& contextLen_, void* userData ) {
|
||||
replxx_completions completions;
|
||||
fn( input_.c_str(), &completions, &contextLen_, userData );
|
||||
return ( completions.data );
|
||||
}
|
||||
|
||||
/* Register a callback function to be called for tab-completion. */
|
||||
void replxx_set_completion_callback(::Replxx* replxx_, replxx_completion_callback_t* fn, void* userData) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_completion_callback( std::bind( &completions_fwd, fn, _1, _2, userData ) );
|
||||
}
|
||||
|
||||
void highlighter_fwd( replxx_highlighter_callback_t fn, std::string const& input, replxx::Replxx::colors_t& colors, void* userData ) {
|
||||
std::vector<ReplxxColor> colorsTmp( colors.size() );
|
||||
std::transform(
|
||||
colors.begin(),
|
||||
colors.end(),
|
||||
colorsTmp.begin(),
|
||||
[]( replxx::Replxx::Color c ) {
|
||||
return ( static_cast<ReplxxColor>( c ) );
|
||||
}
|
||||
);
|
||||
fn( input.c_str(), colorsTmp.data(), static_cast<int>( colors.size() ), userData );
|
||||
std::transform(
|
||||
colorsTmp.begin(),
|
||||
colorsTmp.end(),
|
||||
colors.begin(),
|
||||
[]( ReplxxColor c ) {
|
||||
return ( static_cast<replxx::Replxx::Color>( c ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void replxx_set_highlighter_callback( ::Replxx* replxx_, replxx_highlighter_callback_t* fn, void* userData ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_highlighter_callback( std::bind( &highlighter_fwd, fn, _1, _2, userData ) );
|
||||
}
|
||||
|
||||
replxx::Replxx::hints_t hints_fwd( replxx_hint_callback_t fn, std::string const& input_, int& contextLen_, replxx::Replxx::Color& color_, void* userData ) {
|
||||
replxx_hints hints;
|
||||
ReplxxColor c( static_cast<ReplxxColor>( color_ ) );
|
||||
fn( input_.c_str(), &hints, &contextLen_, &c, userData );
|
||||
return ( hints.data );
|
||||
}
|
||||
|
||||
void replxx_set_hint_callback( ::Replxx* replxx_, replxx_hint_callback_t* fn, void* userData ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_hint_callback( std::bind( &hints_fwd, fn, _1, _2, _3, userData ) );
|
||||
}
|
||||
|
||||
void replxx_add_hint(replxx_hints* lh, const char* str) {
|
||||
lh->data.emplace_back(str);
|
||||
}
|
||||
|
||||
void replxx_add_completion( replxx_completions* lc, const char* str ) {
|
||||
lc->data.emplace_back( str );
|
||||
}
|
||||
|
||||
void replxx_add_color_completion( replxx_completions* lc, const char* str, ReplxxColor color ) {
|
||||
lc->data.emplace_back( str, static_cast<replxx::Replxx::Color>( color ) );
|
||||
}
|
||||
|
||||
void replxx_history_add( ::Replxx* replxx_, const char* line ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->history_add( line );
|
||||
}
|
||||
|
||||
void replxx_set_max_history_size( ::Replxx* replxx_, int len ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_max_history_size( len );
|
||||
}
|
||||
|
||||
void replxx_set_max_hint_rows( ::Replxx* replxx_, int count ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_max_hint_rows( count );
|
||||
}
|
||||
|
||||
void replxx_set_hint_delay( ::Replxx* replxx_, int milliseconds ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_hint_delay( milliseconds );
|
||||
}
|
||||
|
||||
void replxx_set_completion_count_cutoff( ::Replxx* replxx_, int count ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_completion_count_cutoff( count );
|
||||
}
|
||||
|
||||
void replxx_set_word_break_characters( ::Replxx* replxx_, char const* breakChars_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_word_break_characters( breakChars_ );
|
||||
}
|
||||
|
||||
void replxx_set_double_tab_completion( ::Replxx* replxx_, int val ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_double_tab_completion( val ? true : false );
|
||||
}
|
||||
|
||||
void replxx_set_complete_on_empty( ::Replxx* replxx_, int val ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_complete_on_empty( val ? true : false );
|
||||
}
|
||||
|
||||
void replxx_set_no_color( ::Replxx* replxx_, int val ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_no_color( val ? true : false );
|
||||
}
|
||||
|
||||
void replxx_set_indent_multiline( ::Replxx* replxx_, int val ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_indent_multiline( val ? true : false );
|
||||
}
|
||||
|
||||
void replxx_set_beep_on_ambiguous_completion( ::Replxx* replxx_, int val ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_beep_on_ambiguous_completion( val ? true : false );
|
||||
}
|
||||
|
||||
void replxx_set_immediate_completion( ::Replxx* replxx_, int val ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_immediate_completion( val ? true : false );
|
||||
}
|
||||
|
||||
void replxx_set_unique_history( ::Replxx* replxx_, int val ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->set_unique_history( val ? true : false );
|
||||
}
|
||||
|
||||
void replxx_enable_bracketed_paste( ::Replxx* replxx_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->enable_bracketed_paste();
|
||||
}
|
||||
|
||||
void replxx_disable_bracketed_paste( ::Replxx* replxx_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->disable_bracketed_paste();
|
||||
}
|
||||
|
||||
ReplxxHistoryScan* replxx_history_scan_start( ::Replxx* replxx_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
return ( reinterpret_cast<ReplxxHistoryScan*>( replxx->history_scan().release() ) );
|
||||
}
|
||||
|
||||
void replxx_history_scan_stop( ::Replxx*, ReplxxHistoryScan* historyScan_ ) {
|
||||
delete reinterpret_cast<replxx::Replxx::HistoryScanImpl*>( historyScan_ );
|
||||
}
|
||||
|
||||
int replxx_history_scan_next( ::Replxx*, ReplxxHistoryScan* historyScan_, ReplxxHistoryEntry* historyEntry_ ) {
|
||||
replxx::Replxx::HistoryScanImpl* historyScan( reinterpret_cast<replxx::Replxx::HistoryScanImpl*>( historyScan_ ) );
|
||||
bool hasNext( historyScan->next() );
|
||||
if ( hasNext ) {
|
||||
replxx::Replxx::HistoryEntry const& historyEntry( historyScan->get() );
|
||||
historyEntry_->timestamp = historyEntry.timestamp().c_str();
|
||||
historyEntry_->text = historyEntry.text().c_str();
|
||||
}
|
||||
return ( hasNext ? 0 : -1 );
|
||||
}
|
||||
|
||||
/* Save the history in the specified file. On success 0 is returned
|
||||
* otherwise -1 is returned. */
|
||||
int replxx_history_sync( ::Replxx* replxx_, const char* filename ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
return ( replxx->history_sync( filename ) ? 0 : -1 );
|
||||
}
|
||||
|
||||
/* Save the history in the specified file. On success 0 is returned
|
||||
* otherwise -1 is returned. */
|
||||
int replxx_history_save( ::Replxx* replxx_, const char* filename ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
return ( replxx->history_save( filename ) ? 0 : -1 );
|
||||
}
|
||||
|
||||
/* Load the history from the specified file. If the file does not exist
|
||||
* zero is returned and no operation is performed.
|
||||
*
|
||||
* If the file exists and the operation succeeded 0 is returned, otherwise
|
||||
* on error -1 is returned. */
|
||||
int replxx_history_load( ::Replxx* replxx_, const char* filename ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
return ( replxx->history_load( filename ) ? 0 : -1 );
|
||||
}
|
||||
|
||||
void replxx_history_clear( ::Replxx* replxx_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
replxx->history_clear();
|
||||
}
|
||||
|
||||
int replxx_history_size( ::Replxx* replxx_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
return ( replxx->history_size() );
|
||||
}
|
||||
|
||||
/* This special mode is used by replxx in order to print scan codes
|
||||
* on screen for debugging / development purposes. It is implemented
|
||||
* by the replxx-c-api-example program using the --keycodes option. */
|
||||
#ifdef __REPLXX_DEBUG__
|
||||
void replxx_debug_dump_print_codes(void) {
|
||||
char quit[4];
|
||||
|
||||
printf(
|
||||
"replxx key codes debugging mode.\n"
|
||||
"Press keys to see scan codes. Type 'quit' at any time to exit.\n");
|
||||
if (enableRawMode() == -1) return;
|
||||
memset(quit, ' ', 4);
|
||||
while (1) {
|
||||
char c;
|
||||
int nread;
|
||||
|
||||
#if _WIN32
|
||||
nread = _read(STDIN_FILENO, &c, 1);
|
||||
#else
|
||||
nread = read(STDIN_FILENO, &c, 1);
|
||||
#endif
|
||||
if (nread <= 0) continue;
|
||||
memmove(quit, quit + 1, sizeof(quit) - 1); /* shift string to left. */
|
||||
quit[sizeof(quit) - 1] = c; /* Insert current char on the right. */
|
||||
if (memcmp(quit, "quit", sizeof(quit)) == 0) break;
|
||||
|
||||
printf("'%c' %02x (%d) (type quit to exit)\n", isprint(c) ? c : '?', (int)c,
|
||||
(int)c);
|
||||
printf("\r"); /* Go left edge manually, we are in raw mode. */
|
||||
fflush(stdout);
|
||||
}
|
||||
disableRawMode();
|
||||
}
|
||||
#endif // __REPLXX_DEBUG__
|
||||
|
||||
int replxx_install_window_change_handler( ::Replxx* replxx_ ) {
|
||||
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
|
||||
return ( replxx->install_window_change_handler() );
|
||||
}
|
||||
|
||||
using namespace replxx::color;
|
||||
ReplxxColor replxx_color_combine( ReplxxColor color1_, ReplxxColor color2_ ) {
|
||||
return static_cast<ReplxxColor>( static_cast<replxx::Replxx::Color>( color1_ ) | static_cast<replxx::Replxx::Color>( color2_ ) );
|
||||
}
|
||||
|
||||
ReplxxColor replxx_color_bg( ReplxxColor color_ ) {
|
||||
return static_cast<ReplxxColor>( color::bg( static_cast<replxx::Replxx::Color>( color_ ) ) );
|
||||
}
|
||||
|
||||
ReplxxColor replxx_color_bold( ReplxxColor color_ ) {
|
||||
return static_cast<ReplxxColor>( color::bold( static_cast<replxx::Replxx::Color>( color_ ) ) );
|
||||
}
|
||||
|
||||
ReplxxColor replxx_color_underline( ReplxxColor color_ ) {
|
||||
return static_cast<ReplxxColor>( color::underline( static_cast<replxx::Replxx::Color>( color_ ) ) );
|
||||
}
|
||||
|
||||
ReplxxColor replxx_color_grayscale( int level_ ) {
|
||||
return static_cast<ReplxxColor>( color::grayscale( level_ ) );
|
||||
}
|
||||
|
||||
ReplxxColor replxx_color_rgb666( int r_, int g_, int b_ ) {
|
||||
return static_cast<ReplxxColor>( color::rgb666( r_, g_, b_ ) );
|
||||
}
|
||||
|
2577
lib/replxx/src/replxx_impl.cxx
Normal file
2577
lib/replxx/src/replxx_impl.cxx
Normal file
File diff suppressed because it is too large
Load Diff
304
lib/replxx/src/replxx_impl.hxx
Normal file
304
lib/replxx/src/replxx_impl.hxx
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org)
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef HAVE_REPLXX_REPLXX_IMPL_HXX_INCLUDED
|
||||
#define HAVE_REPLXX_REPLXX_IMPL_HXX_INCLUDED 1
|
||||
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <iosfwd>
|
||||
|
||||
#include "replxx.hxx"
|
||||
#include "history.hxx"
|
||||
#include "killring.hxx"
|
||||
#include "utf8string.hxx"
|
||||
#include "prompt.hxx"
|
||||
|
||||
namespace replxx {
|
||||
|
||||
class Replxx::ReplxxImpl {
|
||||
public:
|
||||
class Completion {
|
||||
UnicodeString _text;
|
||||
Replxx::Color _color;
|
||||
public:
|
||||
Completion( UnicodeString const& text_, Replxx::Color color_ )
|
||||
: _text( text_ )
|
||||
, _color( color_ ) {
|
||||
}
|
||||
Completion( Replxx::Completion const& completion_ )
|
||||
: _text( completion_.text() )
|
||||
, _color( completion_.color() ) {
|
||||
}
|
||||
Completion( Completion const& ) = default;
|
||||
Completion& operator = ( Completion const& ) = default;
|
||||
Completion( Completion&& ) = default;
|
||||
Completion& operator = ( Completion&& ) = default;
|
||||
UnicodeString const& text( void ) const {
|
||||
return ( _text );
|
||||
}
|
||||
Replxx::Color color( void ) const {
|
||||
return ( _color );
|
||||
}
|
||||
};
|
||||
typedef std::vector<Completion> completions_t;
|
||||
typedef std::vector<UnicodeString> data_t;
|
||||
typedef std::vector<UnicodeString> hints_t;
|
||||
typedef std::unique_ptr<char[]> utf8_buffer_t;
|
||||
typedef std::unique_ptr<char32_t[]> input_buffer_t;
|
||||
typedef std::vector<char32_t> display_t;
|
||||
typedef std::deque<char32_t> key_presses_t;
|
||||
typedef std::deque<std::string> messages_t;
|
||||
enum class HINT_ACTION {
|
||||
REGENERATE,
|
||||
REPAINT,
|
||||
TRIM,
|
||||
SKIP
|
||||
};
|
||||
typedef std::unordered_map<std::string, Replxx::key_press_handler_t> named_actions_t;
|
||||
typedef Replxx::ACTION_RESULT ( ReplxxImpl::* key_press_handler_raw_t )( char32_t );
|
||||
typedef std::unordered_map<int, Replxx::key_press_handler_t> key_press_handlers_t;
|
||||
private:
|
||||
typedef int long long unsigned action_trait_t;
|
||||
static action_trait_t const NOOP = 0;
|
||||
static action_trait_t const WANT_REFRESH = 1;
|
||||
static action_trait_t const MOVE_CURSOR = 2;
|
||||
static action_trait_t const RESET_KILL_ACTION = 4;
|
||||
static action_trait_t const SET_KILL_ACTION = 8;
|
||||
static action_trait_t const DONT_RESET_PREFIX = 16;
|
||||
static action_trait_t const DONT_RESET_COMPLETIONS = 32;
|
||||
static action_trait_t const HISTORY_RECALL_MOST_RECENT = 64;
|
||||
static action_trait_t const DONT_RESET_HIST_YANK_INDEX = 128;
|
||||
private:
|
||||
mutable Utf8String _utf8Buffer;
|
||||
UnicodeString _data;
|
||||
int _pos; // character position in buffer ( 0 <= _pos <= _data[_line].length() )
|
||||
display_t _display;
|
||||
int _displayInputLength;
|
||||
UnicodeString _hint;
|
||||
int _prefix; // prefix length used in common prefix search
|
||||
int _hintSelection; // Currently selected hint.
|
||||
History _history;
|
||||
KillRing _killRing;
|
||||
int long long _lastRefreshTime;
|
||||
bool _refreshSkipped;
|
||||
int _lastYankSize;
|
||||
int _maxHintRows;
|
||||
int _hintDelay;
|
||||
std::string _wordBreakChars;
|
||||
std::string _subwordBreakChars;
|
||||
int _completionCountCutoff;
|
||||
bool _overwrite;
|
||||
bool _doubleTabCompletion;
|
||||
bool _completeOnEmpty;
|
||||
bool _beepOnAmbiguousCompletion;
|
||||
bool _immediateCompletion;
|
||||
bool _bracketedPaste;
|
||||
bool _noColor;
|
||||
bool _indentMultiline;
|
||||
named_actions_t _namedActions;
|
||||
key_press_handlers_t _keyPressHandlers;
|
||||
Terminal _terminal;
|
||||
std::thread::id _currentThread;
|
||||
Prompt _prompt;
|
||||
Replxx::modify_callback_t _modifyCallback;
|
||||
Replxx::completion_callback_t _completionCallback;
|
||||
Replxx::highlighter_callback_t _highlighterCallback;
|
||||
Replxx::hint_callback_t _hintCallback;
|
||||
key_presses_t _keyPresses;
|
||||
messages_t _messages;
|
||||
std::string _asyncPrompt;
|
||||
bool _updatePrompt;
|
||||
completions_t _completions;
|
||||
int _completionContextLength;
|
||||
int _completionSelection;
|
||||
std::string _preloadedBuffer; // used with set_preload_buffer
|
||||
std::string _errorMessage;
|
||||
UnicodeString _previousSearchText; // remembered across invocations of replxx_input()
|
||||
bool _modifiedState;
|
||||
Replxx::Color _hintColor;
|
||||
hints_t _hintsCache;
|
||||
int _hintContextLenght;
|
||||
Utf8String _hintSeed;
|
||||
bool _hasNewlines;
|
||||
int _oldPos;
|
||||
bool _moveCursor;
|
||||
bool _ignoreCase;
|
||||
mutable std::mutex _mutex;
|
||||
public:
|
||||
ReplxxImpl( FILE*, FILE*, FILE* );
|
||||
virtual ~ReplxxImpl( void );
|
||||
void set_modify_callback( Replxx::modify_callback_t const& fn );
|
||||
void set_completion_callback( Replxx::completion_callback_t const& fn );
|
||||
void set_highlighter_callback( Replxx::highlighter_callback_t const& fn );
|
||||
void set_hint_callback( Replxx::hint_callback_t const& fn );
|
||||
char const* input( std::string const& prompt );
|
||||
void history_add( std::string const& line );
|
||||
bool history_sync( std::string const& filename );
|
||||
bool history_save( std::string const& filename );
|
||||
void history_save( std::ostream& out );
|
||||
bool history_load( std::string const& filename );
|
||||
void history_load( std::istream& in );
|
||||
void history_clear( void );
|
||||
Replxx::HistoryScan::impl_t history_scan( void ) const;
|
||||
int history_size( void ) const;
|
||||
void set_preload_buffer(std::string const& preloadText);
|
||||
void set_word_break_characters( char const* wordBreakers );
|
||||
void set_subword_break_characters( char const* subwordBreakers );
|
||||
void set_max_hint_rows( int count );
|
||||
void set_hint_delay( int milliseconds );
|
||||
void set_double_tab_completion( bool val );
|
||||
void set_complete_on_empty( bool val );
|
||||
void set_beep_on_ambiguous_completion( bool val );
|
||||
void set_immediate_completion( bool val );
|
||||
void set_unique_history( bool );
|
||||
void set_no_color( bool val );
|
||||
void set_indent_multiline( bool val );
|
||||
void set_max_history_size( int len );
|
||||
void set_completion_count_cutoff( int len );
|
||||
int install_window_change_handler( void );
|
||||
void enable_bracketed_paste( void );
|
||||
void disable_bracketed_paste( void );
|
||||
void print( char const*, int );
|
||||
void set_prompt( std::string prompt );
|
||||
Replxx::ACTION_RESULT clear_screen( char32_t );
|
||||
void emulate_key_press( char32_t );
|
||||
Replxx::ACTION_RESULT invoke( Replxx::ACTION, char32_t );
|
||||
void bind_key( char32_t, Replxx::key_press_handler_t );
|
||||
void bind_key_internal( char32_t, char const* );
|
||||
Replxx::State get_state( void ) const;
|
||||
void set_state( Replxx::State const& );
|
||||
void set_ignore_case( bool val );
|
||||
private:
|
||||
ReplxxImpl( ReplxxImpl const& ) = delete;
|
||||
ReplxxImpl& operator = ( ReplxxImpl const& ) = delete;
|
||||
private:
|
||||
void preload_puffer( char const* preloadText );
|
||||
int get_input_line( void );
|
||||
Replxx::ACTION_RESULT action( action_trait_t, key_press_handler_raw_t const&, char32_t );
|
||||
Replxx::ACTION_RESULT insert_character( char32_t );
|
||||
Replxx::ACTION_RESULT new_line( char32_t );
|
||||
Replxx::ACTION_RESULT go_to_begining_of_line( char32_t );
|
||||
Replxx::ACTION_RESULT go_to_end_of_line( char32_t );
|
||||
Replxx::ACTION_RESULT move_one_char_left( char32_t );
|
||||
Replxx::ACTION_RESULT move_one_char_right( char32_t );
|
||||
template <bool subword>
|
||||
Replxx::ACTION_RESULT move_one_word_left( char32_t );
|
||||
template <bool subword>
|
||||
Replxx::ACTION_RESULT move_one_word_right( char32_t );
|
||||
template <bool subword>
|
||||
Replxx::ACTION_RESULT kill_word_to_left( char32_t );
|
||||
template <bool subword>
|
||||
Replxx::ACTION_RESULT kill_word_to_right( char32_t );
|
||||
Replxx::ACTION_RESULT kill_to_whitespace_to_left( char32_t );
|
||||
Replxx::ACTION_RESULT kill_to_begining_of_line( char32_t );
|
||||
Replxx::ACTION_RESULT kill_to_end_of_line( char32_t );
|
||||
Replxx::ACTION_RESULT yank( char32_t );
|
||||
Replxx::ACTION_RESULT yank_cycle( char32_t );
|
||||
Replxx::ACTION_RESULT yank_last_arg( char32_t );
|
||||
template <bool subword>
|
||||
Replxx::ACTION_RESULT capitalize_word( char32_t );
|
||||
template <bool subword>
|
||||
Replxx::ACTION_RESULT lowercase_word( char32_t );
|
||||
template <bool subword>
|
||||
Replxx::ACTION_RESULT uppercase_word( char32_t );
|
||||
Replxx::ACTION_RESULT transpose_characters( char32_t );
|
||||
Replxx::ACTION_RESULT abort_line( char32_t );
|
||||
Replxx::ACTION_RESULT send_eof( char32_t );
|
||||
Replxx::ACTION_RESULT delete_character( char32_t );
|
||||
Replxx::ACTION_RESULT backspace_character( char32_t );
|
||||
Replxx::ACTION_RESULT commit_line( char32_t );
|
||||
Replxx::ACTION_RESULT line_next( char32_t );
|
||||
Replxx::ACTION_RESULT line_previous( char32_t );
|
||||
Replxx::ACTION_RESULT history_next( char32_t );
|
||||
Replxx::ACTION_RESULT history_previous( char32_t );
|
||||
Replxx::ACTION_RESULT history_move( bool );
|
||||
Replxx::ACTION_RESULT history_first( char32_t );
|
||||
Replxx::ACTION_RESULT history_last( char32_t );
|
||||
Replxx::ACTION_RESULT history_restore( char32_t );
|
||||
Replxx::ACTION_RESULT history_restore_current( char32_t );
|
||||
Replxx::ACTION_RESULT history_jump( bool );
|
||||
Replxx::ACTION_RESULT hint_next( char32_t );
|
||||
Replxx::ACTION_RESULT hint_previous( char32_t );
|
||||
Replxx::ACTION_RESULT hint_move( bool );
|
||||
Replxx::ACTION_RESULT toggle_overwrite_mode( char32_t );
|
||||
#ifndef _WIN32
|
||||
Replxx::ACTION_RESULT verbatim_insert( char32_t );
|
||||
Replxx::ACTION_RESULT suspend( char32_t );
|
||||
#endif
|
||||
Replxx::ACTION_RESULT complete_line( char32_t );
|
||||
Replxx::ACTION_RESULT complete_next( char32_t );
|
||||
Replxx::ACTION_RESULT complete_previous( char32_t );
|
||||
Replxx::ACTION_RESULT complete( bool );
|
||||
Replxx::ACTION_RESULT incremental_history_search( char32_t startChar );
|
||||
Replxx::ACTION_RESULT common_prefix_search( char32_t startChar );
|
||||
Replxx::ACTION_RESULT bracketed_paste( char32_t startChar );
|
||||
char32_t read_char( HINT_ACTION = HINT_ACTION::SKIP );
|
||||
char const* read_from_stdin( void );
|
||||
char32_t do_complete_line( bool );
|
||||
void call_modify_callback( void );
|
||||
completions_t call_completer( std::string const& input, int& ) const;
|
||||
hints_t call_hinter( std::string const& input, int&, Replxx::Color& color ) const;
|
||||
void refresh_line( HINT_ACTION = HINT_ACTION::REGENERATE );
|
||||
void move_cursor( void );
|
||||
void indent( void );
|
||||
int virtual_render( char32_t const*, int, int&, int&, Prompt const* = nullptr );
|
||||
void render( char32_t );
|
||||
void render( HINT_ACTION );
|
||||
void handle_hints( HINT_ACTION );
|
||||
void set_color( Replxx::Color );
|
||||
int context_length( void );
|
||||
int prev_newline_position( int ) const;
|
||||
int next_newline_position( int ) const;
|
||||
int pos_in_line( void ) const;
|
||||
void clear( void );
|
||||
void repaint( void );
|
||||
template <bool subword>
|
||||
bool is_word_break_character( char32_t ) const;
|
||||
void dynamic_refresh(Prompt& oldPrompt, Prompt& newPrompt, char32_t* buf32, int len, int pos);
|
||||
char const* finalize_input( char const* );
|
||||
void clear_self_to_end_of_screen( Prompt const* = nullptr );
|
||||
typedef struct {
|
||||
int index;
|
||||
bool error;
|
||||
} paren_info_t;
|
||||
paren_info_t matching_paren( void );
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
781
lib/replxx/src/terminal.cxx
Normal file
781
lib/replxx/src/terminal.cxx
Normal file
@ -0,0 +1,781 @@
|
||||
#include <memory>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <conio.h>
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#define isatty _isatty
|
||||
#define strcasecmp _stricmp
|
||||
#define strdup _strdup
|
||||
#define write _write
|
||||
#define STDIN_FILENO 0
|
||||
|
||||
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
|
||||
#endif
|
||||
|
||||
#include "windows.hxx"
|
||||
|
||||
#else /* _WIN32 */
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#include "terminal.hxx"
|
||||
#include "conversion.hxx"
|
||||
#include "escape.hxx"
|
||||
#include "replxx.hxx"
|
||||
#include "util.hxx"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace replxx {
|
||||
|
||||
namespace tty {
|
||||
|
||||
bool is_a_tty( int fd_ ) {
|
||||
bool aTTY( isatty( fd_ ) != 0 );
|
||||
#ifdef _WIN32
|
||||
do {
|
||||
if ( aTTY ) {
|
||||
break;
|
||||
}
|
||||
HANDLE h( (HANDLE)_get_osfhandle( fd_ ) );
|
||||
if ( h == INVALID_HANDLE_VALUE ) {
|
||||
break;
|
||||
}
|
||||
DWORD st( 0 );
|
||||
if ( ! GetConsoleMode( h, &st ) ) {
|
||||
break;
|
||||
}
|
||||
aTTY = true;
|
||||
} while ( false );
|
||||
#endif
|
||||
return ( aTTY );
|
||||
}
|
||||
|
||||
bool in( is_a_tty( 0 ) );
|
||||
bool out( is_a_tty( 1 ) );
|
||||
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
Terminal* _terminal_ = nullptr;
|
||||
static void WindowSizeChanged( int ) {
|
||||
if ( ! _terminal_ ) {
|
||||
return;
|
||||
}
|
||||
_terminal_->notify_event( Terminal::EVENT_TYPE::RESIZE );
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Terminal::Terminal( void )
|
||||
#ifdef _WIN32
|
||||
: _consoleOut( INVALID_HANDLE_VALUE )
|
||||
, _consoleIn( INVALID_HANDLE_VALUE )
|
||||
, _origOutMode()
|
||||
, _origInMode()
|
||||
, _oldDisplayAttribute()
|
||||
, _inputCodePage( GetConsoleCP() )
|
||||
, _outputCodePage( GetConsoleOutputCP() )
|
||||
, _interrupt( INVALID_HANDLE_VALUE )
|
||||
, _events()
|
||||
, _empty()
|
||||
#else
|
||||
: _origTermios()
|
||||
, _rawModeTermios()
|
||||
, _interrupt()
|
||||
#endif
|
||||
, _rawMode( false )
|
||||
, _utf8() {
|
||||
#ifdef _WIN32
|
||||
_interrupt = CreateEvent( nullptr, true, false, TEXT( "replxx_interrupt_event" ) );
|
||||
#else
|
||||
static_cast<void>( ::pipe( _interrupt ) == 0 );
|
||||
#endif
|
||||
}
|
||||
|
||||
Terminal::~Terminal( void ) {
|
||||
if ( _rawMode ) {
|
||||
disable_raw_mode();
|
||||
}
|
||||
#ifdef _WIN32
|
||||
CloseHandle( _interrupt );
|
||||
#else
|
||||
static_cast<void>( ::close( _interrupt[0] ) == 0 );
|
||||
static_cast<void>( ::close( _interrupt[1] ) == 0 );
|
||||
#endif
|
||||
}
|
||||
|
||||
void Terminal::write32( char32_t const* text32, int len32 ) {
|
||||
_utf8.assign( text32, len32 );
|
||||
write8( _utf8.get(), _utf8.size() );
|
||||
return;
|
||||
}
|
||||
|
||||
void Terminal::write8( char const* data_, int size_ ) {
|
||||
#ifdef _WIN32
|
||||
bool temporarilyEnabled( false );
|
||||
if ( _consoleOut == INVALID_HANDLE_VALUE ) {
|
||||
enable_out();
|
||||
temporarilyEnabled = true;
|
||||
}
|
||||
int nWritten( win_write( _consoleOut, _autoEscape, data_, size_ ) );
|
||||
if ( temporarilyEnabled ) {
|
||||
disable_out();
|
||||
}
|
||||
#else
|
||||
int nWritten( write( 1, data_, size_ ) );
|
||||
#endif
|
||||
if ( nWritten != size_ ) {
|
||||
throw std::runtime_error( "write failed" );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int Terminal::get_screen_columns( void ) {
|
||||
int cols( 0 );
|
||||
#ifdef _WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO inf;
|
||||
GetConsoleScreenBufferInfo( _consoleOut, &inf );
|
||||
cols = inf.dwSize.X;
|
||||
#else
|
||||
struct winsize ws;
|
||||
cols = ( ioctl( 1, TIOCGWINSZ, &ws ) == -1 ) ? 80 : ws.ws_col;
|
||||
#endif
|
||||
// cols is 0 in certain circumstances like inside debugger, which creates
|
||||
// further issues
|
||||
return ( cols > 0 ) ? cols : 80;
|
||||
}
|
||||
|
||||
int Terminal::get_screen_rows( void ) {
|
||||
int rows;
|
||||
#ifdef _WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO inf;
|
||||
GetConsoleScreenBufferInfo( _consoleOut, &inf );
|
||||
rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top;
|
||||
#else
|
||||
struct winsize ws;
|
||||
rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row;
|
||||
#endif
|
||||
return (rows > 0) ? rows : 24;
|
||||
}
|
||||
|
||||
namespace {
|
||||
inline int notty( void ) {
|
||||
errno = ENOTTY;
|
||||
return ( -1 );
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::enable_out( void ) {
|
||||
#ifdef _WIN32
|
||||
SetConsoleOutputCP( 65001 );
|
||||
_consoleOut = GetStdHandle( STD_OUTPUT_HANDLE );
|
||||
GetConsoleMode( _consoleOut, &_origOutMode );
|
||||
_autoEscape = SetConsoleMode( _consoleOut, _origOutMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Terminal::disable_out( void ) {
|
||||
#ifdef _WIN32
|
||||
SetConsoleMode( _consoleOut, _origOutMode );
|
||||
SetConsoleOutputCP( _outputCodePage );
|
||||
_consoleOut = INVALID_HANDLE_VALUE;
|
||||
_autoEscape = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Terminal::enable_bracketed_paste( void ) {
|
||||
static char const BRACK_PASTE_INIT[] = "\033[?2004h";
|
||||
write8( BRACK_PASTE_INIT, sizeof ( BRACK_PASTE_INIT ) - 1 );
|
||||
}
|
||||
|
||||
void Terminal::disable_bracketed_paste( void ) {
|
||||
static char const BRACK_PASTE_DISABLE[] = "\033[?2004l";
|
||||
write8( BRACK_PASTE_DISABLE, sizeof ( BRACK_PASTE_DISABLE ) - 1 );
|
||||
}
|
||||
|
||||
int Terminal::enable_raw_mode( void ) {
|
||||
if ( _rawMode ) {
|
||||
return ( 0 );
|
||||
}
|
||||
#ifdef _WIN32
|
||||
_consoleIn = GetStdHandle( STD_INPUT_HANDLE );
|
||||
GetConsoleMode( _consoleIn, &_origInMode );
|
||||
#else
|
||||
|
||||
if ( ! tty::in ) {
|
||||
return ( notty() );
|
||||
}
|
||||
if ( tcgetattr( 0, &_origTermios ) == -1 ) {
|
||||
return ( notty() );
|
||||
}
|
||||
|
||||
_rawModeTermios = _origTermios; /* modify the original mode */
|
||||
/* input modes: no break, no CR to NL, no parity check, no strip char,
|
||||
* no start/stop output control. */
|
||||
_rawModeTermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||
/* output modes - disable post processing */
|
||||
// this is wrong, we don't want _rawModeTermios output, it turns newlines into straight
|
||||
// linefeeds
|
||||
// _rawModeTermios.c_oflag &= ~(OPOST);
|
||||
/* control modes - set 8 bit chars */
|
||||
_rawModeTermios.c_cflag |= (CS8);
|
||||
/* local modes - echoing off, canonical off, no extended functions,
|
||||
* no signal chars (^Z,^C) */
|
||||
_rawModeTermios.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||
/* control chars - set return condition: min number of bytes and timer.
|
||||
* We want read to return every single byte, without timeout. */
|
||||
_rawModeTermios.c_cc[VMIN] = 1;
|
||||
_rawModeTermios.c_cc[VTIME] = 0; /* 1 byte, no timer */
|
||||
|
||||
#endif
|
||||
|
||||
_rawMode = true;
|
||||
if ( reset_raw_mode() < 0 ) {
|
||||
_rawMode = false;
|
||||
return ( notty() );
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
_terminal_ = this;
|
||||
#endif
|
||||
return ( 0 );
|
||||
}
|
||||
|
||||
int Terminal::reset_raw_mode( void ) {
|
||||
if ( ! _rawMode ) {
|
||||
return ( -1 );
|
||||
}
|
||||
#ifdef _WIN32
|
||||
SetConsoleMode(
|
||||
_consoleIn,
|
||||
( _origInMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT ) ) | ENABLE_QUICK_EDIT_MODE
|
||||
);
|
||||
SetConsoleCP( 65001 );
|
||||
enable_out();
|
||||
return ( 0 );
|
||||
#else
|
||||
/* put terminal in raw mode after flushing */
|
||||
return ( tcsetattr( 0, TCSADRAIN, &_rawModeTermios ) );
|
||||
#endif
|
||||
}
|
||||
|
||||
void Terminal::disable_raw_mode(void) {
|
||||
if ( ! _rawMode ) {
|
||||
return;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
disable_out();
|
||||
SetConsoleMode( _consoleIn, _origInMode );
|
||||
SetConsoleCP( _inputCodePage );
|
||||
_consoleIn = INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
_terminal_ = nullptr;
|
||||
if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
_rawMode = false;
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
/**
|
||||
* Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode
|
||||
* (char32_t) character it encodes
|
||||
*
|
||||
* @return char32_t Unicode character
|
||||
*/
|
||||
char32_t read_unicode_character(void) {
|
||||
static char8_t utf8String[5];
|
||||
static size_t utf8Count = 0;
|
||||
while (true) {
|
||||
char8_t c;
|
||||
|
||||
/* Continue reading if interrupted by signal. */
|
||||
ssize_t nread;
|
||||
do {
|
||||
nread = read( STDIN_FILENO, &c, 1 );
|
||||
} while ((nread == -1) && (errno == EINTR));
|
||||
|
||||
if (nread <= 0) return 0;
|
||||
if (c <= 0x7F || locale::is8BitEncoding) { // short circuit ASCII
|
||||
utf8Count = 0;
|
||||
return c;
|
||||
} else if (utf8Count < sizeof(utf8String) - 1) {
|
||||
utf8String[utf8Count++] = c;
|
||||
utf8String[utf8Count] = 0;
|
||||
char32_t unicodeChar[2];
|
||||
int ucharCount( 0 );
|
||||
ConversionResult res = copyString8to32(unicodeChar, 2, ucharCount, utf8String);
|
||||
if (res == conversionOK && ucharCount) {
|
||||
utf8Count = 0;
|
||||
return unicodeChar[0];
|
||||
}
|
||||
} else {
|
||||
utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // #ifndef _WIN32
|
||||
|
||||
void beep() {
|
||||
fprintf(stderr, "\x7"); // ctrl-G == bell/beep
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
// replxx_read_char -- read a keystroke or keychord from the keyboard, and translate it
|
||||
// into an encoded "keystroke". When convenient, extended keys are translated into their
|
||||
// simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B.
|
||||
//
|
||||
// A return value of zero means "no input available", and a return value of -1
|
||||
// means "invalid key".
|
||||
//
|
||||
char32_t Terminal::read_char( void ) {
|
||||
char32_t c( 0 );
|
||||
#ifdef _WIN32
|
||||
INPUT_RECORD rec;
|
||||
DWORD count;
|
||||
char32_t modifierKeys = 0;
|
||||
bool escSeen = false;
|
||||
int highSurrogate( 0 );
|
||||
while (true) {
|
||||
ReadConsoleInputW( _consoleIn, &rec, 1, &count );
|
||||
#if __REPLXX_DEBUG__ // helper for debugging keystrokes, display info in the debug "Output"
|
||||
// window in the debugger
|
||||
{
|
||||
if ( rec.EventType == KEY_EVENT ) {
|
||||
//if ( rec.Event.KeyEvent.uChar.UnicodeChar ) {
|
||||
char buf[1024];
|
||||
sprintf(
|
||||
buf,
|
||||
"Unicode character 0x%04X, repeat count %d, virtual keycode 0x%04X, "
|
||||
"virtual scancode 0x%04X, key %s%s%s%s%s\n",
|
||||
rec.Event.KeyEvent.uChar.UnicodeChar,
|
||||
rec.Event.KeyEvent.wRepeatCount,
|
||||
rec.Event.KeyEvent.wVirtualKeyCode,
|
||||
rec.Event.KeyEvent.wVirtualScanCode,
|
||||
rec.Event.KeyEvent.bKeyDown ? "down" : "up",
|
||||
(rec.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) ? " L-Ctrl" : "",
|
||||
(rec.Event.KeyEvent.dwControlKeyState & RIGHT_CTRL_PRESSED) ? " R-Ctrl" : "",
|
||||
(rec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) ? " L-Alt" : "",
|
||||
(rec.Event.KeyEvent.dwControlKeyState & RIGHT_ALT_PRESSED) ? " R-Alt" : ""
|
||||
);
|
||||
OutputDebugStringA( buf );
|
||||
//}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if ( rec.EventType != KEY_EVENT ) {
|
||||
continue;
|
||||
}
|
||||
// Windows provides for entry of characters that are not on your keyboard by sending the
|
||||
// Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == Alt key) ...
|
||||
// accept these characters, otherwise only process characters on "key down"
|
||||
if ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) ) {
|
||||
continue;
|
||||
}
|
||||
modifierKeys = 0;
|
||||
// AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't treat this
|
||||
// combination as either CTRL or META we just turn off those two bits, so it is still
|
||||
// possible to combine CTRL and/or META with an AltGr key by using right-Ctrl and/or
|
||||
// left-Alt
|
||||
DWORD const AltGr( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED );
|
||||
if ( ( rec.Event.KeyEvent.dwControlKeyState & AltGr ) == AltGr ) {
|
||||
rec.Event.KeyEvent.dwControlKeyState &= ~( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED );
|
||||
}
|
||||
if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED ) ) {
|
||||
modifierKeys |= Replxx::KEY::BASE_CONTROL;
|
||||
}
|
||||
if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) {
|
||||
modifierKeys |= Replxx::KEY::BASE_META;
|
||||
}
|
||||
if ( escSeen ) {
|
||||
modifierKeys |= Replxx::KEY::BASE_META;
|
||||
}
|
||||
int key( rec.Event.KeyEvent.uChar.UnicodeChar );
|
||||
if ( key == 0 ) {
|
||||
if ( rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED ) {
|
||||
modifierKeys |= Replxx::KEY::BASE_SHIFT;
|
||||
}
|
||||
switch ( rec.Event.KeyEvent.wVirtualKeyCode ) {
|
||||
case VK_LEFT:
|
||||
return modifierKeys | Replxx::KEY::LEFT;
|
||||
case VK_RIGHT:
|
||||
return modifierKeys | Replxx::KEY::RIGHT;
|
||||
case VK_UP:
|
||||
return modifierKeys | Replxx::KEY::UP;
|
||||
case VK_DOWN:
|
||||
return modifierKeys | Replxx::KEY::DOWN;
|
||||
case VK_DELETE:
|
||||
return modifierKeys | Replxx::KEY::DELETE;
|
||||
case VK_HOME:
|
||||
return modifierKeys | Replxx::KEY::HOME;
|
||||
case VK_END:
|
||||
return modifierKeys | Replxx::KEY::END;
|
||||
case VK_PRIOR:
|
||||
return modifierKeys | Replxx::KEY::PAGE_UP;
|
||||
case VK_NEXT:
|
||||
return modifierKeys | Replxx::KEY::PAGE_DOWN;
|
||||
case VK_F1:
|
||||
return modifierKeys | Replxx::KEY::F1;
|
||||
case VK_F2:
|
||||
return modifierKeys | Replxx::KEY::F2;
|
||||
case VK_F3:
|
||||
return modifierKeys | Replxx::KEY::F3;
|
||||
case VK_F4:
|
||||
return modifierKeys | Replxx::KEY::F4;
|
||||
case VK_F5:
|
||||
return modifierKeys | Replxx::KEY::F5;
|
||||
case VK_F6:
|
||||
return modifierKeys | Replxx::KEY::F6;
|
||||
case VK_F7:
|
||||
return modifierKeys | Replxx::KEY::F7;
|
||||
case VK_F8:
|
||||
return modifierKeys | Replxx::KEY::F8;
|
||||
case VK_F9:
|
||||
return modifierKeys | Replxx::KEY::F9;
|
||||
case VK_F10:
|
||||
return modifierKeys | Replxx::KEY::F10;
|
||||
case VK_F11:
|
||||
return modifierKeys | Replxx::KEY::F11;
|
||||
case VK_F12:
|
||||
return modifierKeys | Replxx::KEY::F12;
|
||||
default:
|
||||
continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them
|
||||
}
|
||||
} else if ( key == Replxx::KEY::ESCAPE ) { // ESC, set flag for later
|
||||
escSeen = true;
|
||||
continue;
|
||||
} else if ( ( key >= 0xD800 ) && ( key <= 0xDBFF ) ) {
|
||||
highSurrogate = key - 0xD800;
|
||||
continue;
|
||||
} else {
|
||||
if ( ( key == 13 ) && ( rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED ) ) {
|
||||
key = 10;
|
||||
}
|
||||
// we got a real character, return it
|
||||
if ( ( key >= 0xDC00 ) && ( key <= 0xDFFF ) ) {
|
||||
key -= 0xDC00;
|
||||
key |= ( highSurrogate << 10 );
|
||||
key += 0x10000;
|
||||
}
|
||||
if ( is_control_code( key ) ) {
|
||||
key = control_to_human( key );
|
||||
modifierKeys |= Replxx::KEY::BASE_CONTROL;
|
||||
}
|
||||
key |= modifierKeys;
|
||||
highSurrogate = 0;
|
||||
c = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
c = read_unicode_character();
|
||||
if (c == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard
|
||||
// debugging mode
|
||||
// where we print out decimal and decoded values for whatever the "terminal"
|
||||
// program
|
||||
// gives us on different keystrokes. Hit ctrl-C to exit this mode.
|
||||
//
|
||||
#ifdef __REPLXX_DEBUG__
|
||||
if (c == ctrlChar('^')) { // ctrl-^, special debug mode, prints all keys hit,
|
||||
// ctrl-C to get out
|
||||
printf(
|
||||
"\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit "
|
||||
"this mode\n");
|
||||
while (true) {
|
||||
unsigned char keys[10];
|
||||
int ret = read(0, keys, 10);
|
||||
|
||||
if (ret <= 0) {
|
||||
printf("\nret: %d\n", ret);
|
||||
}
|
||||
for (int i = 0; i < ret; ++i) {
|
||||
char32_t key = static_cast<char32_t>(keys[i]);
|
||||
char* friendlyTextPtr;
|
||||
char friendlyTextBuf[10];
|
||||
const char* prefixText = (key < 0x80) ? "" : "0x80+";
|
||||
char32_t keyCopy = (key < 0x80) ? key : key - 0x80;
|
||||
if (keyCopy >= '!' && keyCopy <= '~') { // printable
|
||||
friendlyTextBuf[0] = '\'';
|
||||
friendlyTextBuf[1] = keyCopy;
|
||||
friendlyTextBuf[2] = '\'';
|
||||
friendlyTextBuf[3] = 0;
|
||||
friendlyTextPtr = friendlyTextBuf;
|
||||
} else if (keyCopy == ' ') {
|
||||
friendlyTextPtr = const_cast<char*>("space");
|
||||
} else if (keyCopy == 27) {
|
||||
friendlyTextPtr = const_cast<char*>("ESC");
|
||||
} else if (keyCopy == 0) {
|
||||
friendlyTextPtr = const_cast<char*>("NUL");
|
||||
} else if (keyCopy == 127) {
|
||||
friendlyTextPtr = const_cast<char*>("DEL");
|
||||
} else {
|
||||
friendlyTextBuf[0] = '^';
|
||||
friendlyTextBuf[1] = control_to_human( keyCopy );
|
||||
friendlyTextBuf[2] = 0;
|
||||
friendlyTextPtr = friendlyTextBuf;
|
||||
}
|
||||
printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr);
|
||||
}
|
||||
printf("\x1b[1G\n"); // go to first column of new line
|
||||
|
||||
// drop out of this loop on ctrl-C
|
||||
if (keys[0] == ctrlChar('C')) {
|
||||
printf("Leaving keyboard debugging mode (on ctrl-C)\n");
|
||||
fflush(stdout);
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // __REPLXX_DEBUG__
|
||||
|
||||
c = EscapeSequenceProcessing::doDispatch(c);
|
||||
if ( is_control_code( c ) ) {
|
||||
c = Replxx::KEY::control( control_to_human( c ) );
|
||||
}
|
||||
#endif // #_WIN32
|
||||
return ( c );
|
||||
}
|
||||
|
||||
Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) {
|
||||
#ifdef _WIN32
|
||||
std::array<HANDLE, 2> handles = { _consoleIn, _interrupt };
|
||||
while ( true ) {
|
||||
DWORD event( WaitForMultipleObjects( static_cast<DWORD>( handles.size() ), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) );
|
||||
switch ( event ) {
|
||||
case ( WAIT_OBJECT_0 + 0 ): {
|
||||
// peek events that will be skipped
|
||||
INPUT_RECORD rec;
|
||||
DWORD count;
|
||||
PeekConsoleInputW( _consoleIn, &rec, 1, &count );
|
||||
|
||||
if (
|
||||
( rec.EventType != KEY_EVENT )
|
||||
|| ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) )
|
||||
) {
|
||||
// read the event to unsignal the handle
|
||||
ReadConsoleInputW( _consoleIn, &rec, 1, &count );
|
||||
continue;
|
||||
} else if ( rec.EventType == KEY_EVENT ) {
|
||||
int key( rec.Event.KeyEvent.uChar.UnicodeChar );
|
||||
if ( key == 0 ) {
|
||||
switch ( rec.Event.KeyEvent.wVirtualKeyCode ) {
|
||||
case VK_LEFT:
|
||||
case VK_RIGHT:
|
||||
case VK_UP:
|
||||
case VK_DOWN:
|
||||
case VK_DELETE:
|
||||
case VK_HOME:
|
||||
case VK_END:
|
||||
case VK_PRIOR:
|
||||
case VK_NEXT:
|
||||
case VK_F1:
|
||||
case VK_F2:
|
||||
case VK_F3:
|
||||
case VK_F4:
|
||||
case VK_F5:
|
||||
case VK_F6:
|
||||
case VK_F7:
|
||||
case VK_F8:
|
||||
case VK_F9:
|
||||
case VK_F10:
|
||||
case VK_F11:
|
||||
case VK_F12:
|
||||
break;
|
||||
default:
|
||||
ReadConsoleInputW( _consoleIn, &rec, 1, &count );
|
||||
continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ( EVENT_TYPE::KEY_PRESS );
|
||||
}
|
||||
case ( WAIT_OBJECT_0 + 1 ): {
|
||||
ResetEvent( _interrupt );
|
||||
if ( _events.empty() ) {
|
||||
continue;
|
||||
}
|
||||
EVENT_TYPE eventType( _events.front() );
|
||||
_events.pop_front();
|
||||
return ( eventType );
|
||||
}
|
||||
case ( WAIT_TIMEOUT ): {
|
||||
return ( EVENT_TYPE::TIMEOUT );
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
fd_set fdSet;
|
||||
int nfds( max( _interrupt[0], _interrupt[1] ) + 1 );
|
||||
while ( true ) {
|
||||
FD_ZERO( &fdSet );
|
||||
FD_SET( 0, &fdSet );
|
||||
FD_SET( _interrupt[0], &fdSet );
|
||||
timeval tv{ timeout_ / 1000, static_cast<suseconds_t>( ( timeout_ % 1000 ) * 1000 ) };
|
||||
int err( select( nfds, &fdSet, nullptr, nullptr, timeout_ > 0 ? &tv : nullptr ) );
|
||||
if ( ( err == -1 ) && ( errno == EINTR ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( err == 0 ) {
|
||||
return ( EVENT_TYPE::TIMEOUT );
|
||||
}
|
||||
if ( FD_ISSET( _interrupt[0], &fdSet ) ) {
|
||||
char data( 0 );
|
||||
static_cast<void>( read( _interrupt[0], &data, 1 ) == 1 );
|
||||
if ( data == 'k' ) {
|
||||
return ( EVENT_TYPE::KEY_PRESS );
|
||||
}
|
||||
if ( data == 'm' ) {
|
||||
return ( EVENT_TYPE::MESSAGE );
|
||||
}
|
||||
if ( data == 'r' ) {
|
||||
return ( EVENT_TYPE::RESIZE );
|
||||
}
|
||||
}
|
||||
if ( FD_ISSET( 0, &fdSet ) ) {
|
||||
return ( EVENT_TYPE::KEY_PRESS );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Terminal::notify_event( EVENT_TYPE eventType_ ) {
|
||||
#ifdef _WIN32
|
||||
_events.push_back( eventType_ );
|
||||
SetEvent( _interrupt );
|
||||
#else
|
||||
char data( ( eventType_ == EVENT_TYPE::KEY_PRESS ) ? 'k' : ( eventType_ == EVENT_TYPE::MESSAGE ? 'm' : 'r' ) );
|
||||
static_cast<void>( write( _interrupt[1], &data, 1 ) == 1 );
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the screen ONLY (no redisplay of anything)
|
||||
*/
|
||||
void Terminal::clear_screen( CLEAR_SCREEN clearScreen_ ) {
|
||||
#ifdef _WIN32
|
||||
if ( _autoEscape ) {
|
||||
#endif
|
||||
if ( clearScreen_ == CLEAR_SCREEN::WHOLE ) {
|
||||
char const clearCode[] = "\033c\033[H\033[2J\033[0m";
|
||||
static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
|
||||
} else {
|
||||
char const clearCode[] = "\033[J";
|
||||
static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
|
||||
}
|
||||
return;
|
||||
#ifdef _WIN32
|
||||
}
|
||||
COORD coord = { 0, 0 };
|
||||
CONSOLE_SCREEN_BUFFER_INFO inf;
|
||||
HANDLE consoleOut( _consoleOut != INVALID_HANDLE_VALUE ? _consoleOut : GetStdHandle( STD_OUTPUT_HANDLE ) );
|
||||
GetConsoleScreenBufferInfo( consoleOut, &inf );
|
||||
if ( clearScreen_ == CLEAR_SCREEN::TO_END ) {
|
||||
coord = inf.dwCursorPosition;
|
||||
DWORD nWritten( 0 );
|
||||
SHORT height( inf.srWindow.Bottom - inf.srWindow.Top );
|
||||
DWORD yPos( inf.dwCursorPosition.Y - inf.srWindow.Top );
|
||||
DWORD toWrite( ( height + 1 - yPos ) * inf.dwSize.X - inf.dwCursorPosition.X );
|
||||
// FillConsoleOutputCharacterA( consoleOut, ' ', toWrite, coord, &nWritten );
|
||||
_empty.resize( toWrite - 1, ' ' );
|
||||
WriteConsoleA( consoleOut, _empty.data(), toWrite - 1, &nWritten, nullptr );
|
||||
} else {
|
||||
COORD scrollTarget = { 0, static_cast<SHORT>( -inf.dwSize.Y ) };
|
||||
CHAR_INFO fill{ TEXT( ' ' ), inf.wAttributes };
|
||||
SMALL_RECT scrollRect = { 0, 0, inf.dwSize.X, inf.dwSize.Y };
|
||||
ScrollConsoleScreenBuffer( consoleOut, &scrollRect, nullptr, scrollTarget, &fill );
|
||||
}
|
||||
SetConsoleCursorPosition( consoleOut, coord );
|
||||
#endif
|
||||
}
|
||||
|
||||
void Terminal::jump_cursor( int xPos_, int yOffset_ ) {
|
||||
#ifdef _WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO inf;
|
||||
GetConsoleScreenBufferInfo( _consoleOut, &inf );
|
||||
inf.dwCursorPosition.X = xPos_;
|
||||
inf.dwCursorPosition.Y += yOffset_;
|
||||
SetConsoleCursorPosition( _consoleOut, inf.dwCursorPosition );
|
||||
#else
|
||||
char seq[64];
|
||||
if ( yOffset_ != 0 ) { // move the cursor up as required
|
||||
snprintf( seq, sizeof seq, "\033[%d%c", abs( yOffset_ ), yOffset_ > 0 ? 'B' : 'A' );
|
||||
write8( seq, strlen( seq ) );
|
||||
}
|
||||
// position at the end of the prompt, clear to end of screen
|
||||
snprintf(
|
||||
seq, sizeof seq, "\033[%dG",
|
||||
xPos_ + 1 /* 1-based on VT100 */
|
||||
);
|
||||
write8( seq, strlen( seq ) );
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void Terminal::set_cursor_visible( bool visible_ ) {
|
||||
CONSOLE_CURSOR_INFO cursorInfo;
|
||||
GetConsoleCursorInfo( _consoleOut, &cursorInfo );
|
||||
cursorInfo.bVisible = visible_;
|
||||
SetConsoleCursorInfo( _consoleOut, &cursorInfo );
|
||||
return;
|
||||
}
|
||||
#else
|
||||
void Terminal::set_cursor_visible( bool ) {}
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
int Terminal::read_verbatim( char32_t* buffer_, int size_ ) {
|
||||
int len( 0 );
|
||||
buffer_[len ++] = read_unicode_character();
|
||||
int statusFlags( ::fcntl( STDIN_FILENO, F_GETFL, 0 ) );
|
||||
::fcntl( STDIN_FILENO, F_SETFL, statusFlags | O_NONBLOCK );
|
||||
while ( len < size_ ) {
|
||||
char32_t c( read_unicode_character() );
|
||||
if ( c == 0 ) {
|
||||
break;
|
||||
}
|
||||
buffer_[len ++] = c;
|
||||
}
|
||||
::fcntl( STDIN_FILENO, F_SETFL, statusFlags );
|
||||
return ( len );
|
||||
}
|
||||
|
||||
int Terminal::install_window_change_handler( void ) {
|
||||
struct sigaction sa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
sa.sa_handler = &WindowSizeChanged;
|
||||
|
||||
if (sigaction(SIGWINCH, &sa, nullptr) == -1) {
|
||||
return errno;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
96
lib/replxx/src/terminal.hxx
Normal file
96
lib/replxx/src/terminal.hxx
Normal file
@ -0,0 +1,96 @@
|
||||
#ifndef REPLXX_IO_HXX_INCLUDED
|
||||
#define REPLXX_IO_HXX_INCLUDED 1
|
||||
|
||||
#include <deque>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <vector>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <termios.h>
|
||||
#endif
|
||||
|
||||
#include "utf8string.hxx"
|
||||
|
||||
namespace replxx {
|
||||
|
||||
class Terminal {
|
||||
public:
|
||||
enum class EVENT_TYPE {
|
||||
KEY_PRESS,
|
||||
MESSAGE,
|
||||
TIMEOUT,
|
||||
RESIZE
|
||||
};
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
HANDLE _consoleOut;
|
||||
HANDLE _consoleIn;
|
||||
DWORD _origOutMode;
|
||||
DWORD _origInMode;
|
||||
bool _autoEscape;
|
||||
WORD _oldDisplayAttribute;
|
||||
UINT const _inputCodePage;
|
||||
UINT const _outputCodePage;
|
||||
HANDLE _interrupt;
|
||||
typedef std::deque<EVENT_TYPE> events_t;
|
||||
events_t _events;
|
||||
std::vector<char> _empty;
|
||||
#else
|
||||
struct termios _origTermios; /* in order to restore at exit */
|
||||
struct termios _rawModeTermios; /* in order to reset raw mode after callbacks */
|
||||
int _interrupt[2];
|
||||
#endif
|
||||
bool _rawMode; /* for destructor to check if restore is needed */
|
||||
Utf8String _utf8;
|
||||
public:
|
||||
enum class CLEAR_SCREEN {
|
||||
WHOLE,
|
||||
TO_END
|
||||
};
|
||||
public:
|
||||
Terminal( void );
|
||||
~Terminal( void );
|
||||
void write32( char32_t const*, int );
|
||||
void write8( char const*, int );
|
||||
int get_screen_columns(void);
|
||||
int get_screen_rows(void);
|
||||
void enable_bracketed_paste( void );
|
||||
void disable_bracketed_paste( void );
|
||||
int enable_raw_mode(void);
|
||||
int reset_raw_mode(void);
|
||||
void disable_raw_mode(void);
|
||||
char32_t read_char(void);
|
||||
void clear_screen( CLEAR_SCREEN );
|
||||
EVENT_TYPE wait_for_input( int long = 0 );
|
||||
void notify_event( EVENT_TYPE );
|
||||
void jump_cursor( int, int );
|
||||
void set_cursor_visible( bool );
|
||||
#ifndef _WIN32
|
||||
int read_verbatim( char32_t*, int );
|
||||
int install_window_change_handler( void );
|
||||
#endif
|
||||
private:
|
||||
void enable_out( void );
|
||||
void disable_out( void );
|
||||
private:
|
||||
Terminal( Terminal const& ) = delete;
|
||||
Terminal& operator = ( Terminal const& ) = delete;
|
||||
Terminal( Terminal&& ) = delete;
|
||||
Terminal& operator = ( Terminal&& ) = delete;
|
||||
};
|
||||
|
||||
void beep();
|
||||
char32_t read_unicode_character(void);
|
||||
|
||||
namespace tty {
|
||||
|
||||
extern bool in;
|
||||
extern bool out;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
228
lib/replxx/src/unicodestring.hxx
Normal file
228
lib/replxx/src/unicodestring.hxx
Normal file
@ -0,0 +1,228 @@
|
||||
#ifndef REPLXX_UNICODESTRING_HXX_INCLUDED
|
||||
#define REPLXX_UNICODESTRING_HXX_INCLUDED
|
||||
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <cwctype>
|
||||
#include <cassert>
|
||||
|
||||
#include "conversion.hxx"
|
||||
|
||||
namespace replxx {
|
||||
|
||||
inline bool case_sensitive_equal( char32_t l, char32_t r ) {
|
||||
return l == r;
|
||||
}
|
||||
|
||||
inline bool case_insensitive_equal( char32_t l, char32_t r ) {
|
||||
return towlower( static_cast<wint_t>( l ) ) == towlower( static_cast<wint_t>( r ) );
|
||||
}
|
||||
|
||||
class UnicodeString {
|
||||
public:
|
||||
typedef std::vector<char32_t> data_buffer_t;
|
||||
typedef data_buffer_t::const_iterator const_iterator;
|
||||
typedef data_buffer_t::iterator iterator;
|
||||
private:
|
||||
data_buffer_t _data;
|
||||
public:
|
||||
UnicodeString()
|
||||
: _data() {
|
||||
}
|
||||
|
||||
explicit UnicodeString( std::string const& src )
|
||||
: _data() {
|
||||
assign( src );
|
||||
}
|
||||
|
||||
explicit UnicodeString( UnicodeString const& other, int offset, int len = -1 )
|
||||
: _data() {
|
||||
_data.insert(
|
||||
_data.end(),
|
||||
other._data.begin() + offset,
|
||||
len > 0 ? other._data.begin() + offset + len : other._data.end()
|
||||
);
|
||||
}
|
||||
|
||||
explicit UnicodeString( char const* src )
|
||||
: _data() {
|
||||
assign( src );
|
||||
}
|
||||
|
||||
explicit UnicodeString( char8_t const* src )
|
||||
: UnicodeString( reinterpret_cast<const char*>( src ) ) {
|
||||
}
|
||||
|
||||
explicit UnicodeString( char32_t const* src )
|
||||
: _data() {
|
||||
int len( 0 );
|
||||
while ( src[len] != 0 ) {
|
||||
++ len;
|
||||
}
|
||||
_data.assign( src, src + len );
|
||||
}
|
||||
|
||||
explicit UnicodeString( char32_t const* src, int len )
|
||||
: _data() {
|
||||
_data.assign( src, src + len );
|
||||
}
|
||||
|
||||
explicit UnicodeString( int len )
|
||||
: _data() {
|
||||
_data.resize( len );
|
||||
}
|
||||
|
||||
UnicodeString& assign( std::string const& str_ ) {
|
||||
_data.resize( static_cast<int>( str_.length() ) );
|
||||
int len( 0 );
|
||||
copyString8to32( _data.data(), static_cast<int>( str_.length() ), len, str_.c_str() );
|
||||
_data.resize( len );
|
||||
return *this;
|
||||
}
|
||||
|
||||
UnicodeString& assign( char const* str_ ) {
|
||||
int byteCount( static_cast<int>( strlen( str_ ) ) );
|
||||
_data.resize( byteCount );
|
||||
int len( 0 );
|
||||
copyString8to32( _data.data(), byteCount, len, str_ );
|
||||
_data.resize( len );
|
||||
return *this;
|
||||
}
|
||||
|
||||
UnicodeString& assign( UnicodeString const& other_ ) {
|
||||
_data = other_._data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit UnicodeString( UnicodeString const& ) = default;
|
||||
UnicodeString& operator = ( UnicodeString const& ) = default;
|
||||
UnicodeString( UnicodeString&& ) = default;
|
||||
UnicodeString& operator = ( UnicodeString&& ) = default;
|
||||
bool operator == ( UnicodeString const& other_ ) const {
|
||||
return ( _data == other_._data );
|
||||
}
|
||||
|
||||
bool operator != ( UnicodeString const& other_ ) const {
|
||||
return ( _data != other_._data );
|
||||
}
|
||||
|
||||
bool operator < ( UnicodeString const& other_ ) const {
|
||||
return std::lexicographical_compare(begin(), end(), other_.begin(), other_.end());
|
||||
}
|
||||
|
||||
UnicodeString& append( UnicodeString const& other ) {
|
||||
_data.insert( _data.end(), other._data.begin(), other._data.end() );
|
||||
return *this;
|
||||
}
|
||||
|
||||
void push_back( char32_t c_ ) {
|
||||
_data.push_back( c_ );
|
||||
}
|
||||
|
||||
UnicodeString& append( char32_t const* src, int len ) {
|
||||
_data.insert( _data.end(), src, src + len );
|
||||
return *this;
|
||||
}
|
||||
|
||||
UnicodeString& insert( int pos_, UnicodeString const& str_, int offset_, int len_ ) {
|
||||
_data.insert( _data.begin() + pos_, str_._data.begin() + offset_, str_._data.begin() + offset_ + len_ );
|
||||
return *this;
|
||||
}
|
||||
|
||||
UnicodeString& insert( int pos_, char32_t c_ ) {
|
||||
_data.insert( _data.begin() + pos_, c_ );
|
||||
return *this;
|
||||
}
|
||||
|
||||
UnicodeString& erase( int pos_ ) {
|
||||
_data.erase( _data.begin() + pos_ );
|
||||
return *this;
|
||||
}
|
||||
|
||||
UnicodeString& erase( int pos_, int len_ ) {
|
||||
_data.erase( _data.begin() + pos_, _data.begin() + pos_ + len_ );
|
||||
return *this;
|
||||
}
|
||||
|
||||
char32_t const* get() const {
|
||||
return _data.data();
|
||||
}
|
||||
|
||||
char32_t* get() {
|
||||
return _data.data();
|
||||
}
|
||||
|
||||
int length() const {
|
||||
return static_cast<int>( _data.size() );
|
||||
}
|
||||
|
||||
void clear( void ) {
|
||||
_data.clear();
|
||||
}
|
||||
|
||||
const char32_t& operator[]( int pos ) const {
|
||||
assert( ( pos >= 0 ) && ( pos < static_cast<int>( _data.size() ) ) );
|
||||
return _data[pos];
|
||||
}
|
||||
|
||||
char32_t& operator[]( int pos ) {
|
||||
assert( ( pos >= 0 ) && ( pos < static_cast<int>( _data.size() ) ) );
|
||||
return _data[pos];
|
||||
}
|
||||
|
||||
bool starts_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const {
|
||||
return (
|
||||
( std::distance( first_, last_ ) <= length() )
|
||||
&& ( std::equal( first_, last_, _data.begin() ) )
|
||||
);
|
||||
}
|
||||
|
||||
template <class BinaryPredicate>
|
||||
bool starts_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_, BinaryPredicate&& pred ) const {
|
||||
return (
|
||||
( std::distance( first_, last_ ) <= length() )
|
||||
&& ( std::equal( first_, last_, _data.begin(), std::forward<BinaryPredicate>( pred ) ) )
|
||||
);
|
||||
}
|
||||
|
||||
bool ends_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const {
|
||||
int len( static_cast<int>( std::distance( first_, last_ ) ) );
|
||||
return (
|
||||
( len <= length() )
|
||||
&& ( std::equal( first_, last_, _data.end() - len ) )
|
||||
);
|
||||
}
|
||||
|
||||
bool is_empty( void ) const {
|
||||
return ( _data.size() == 0 );
|
||||
}
|
||||
|
||||
void swap( UnicodeString& other_ ) {
|
||||
_data.swap( other_._data );
|
||||
}
|
||||
|
||||
const_iterator begin( void ) const {
|
||||
return ( _data.begin() );
|
||||
}
|
||||
|
||||
const_iterator end( void ) const {
|
||||
return ( _data.end() );
|
||||
}
|
||||
|
||||
iterator begin( void ) {
|
||||
return ( _data.begin() );
|
||||
}
|
||||
|
||||
iterator end( void ) {
|
||||
return ( _data.end() );
|
||||
}
|
||||
|
||||
char32_t back( void ) const {
|
||||
return ( _data.back() );
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
100
lib/replxx/src/utf8string.hxx
Normal file
100
lib/replxx/src/utf8string.hxx
Normal file
@ -0,0 +1,100 @@
|
||||
#ifndef REPLXX_UTF8STRING_HXX_INCLUDED
|
||||
#define REPLXX_UTF8STRING_HXX_INCLUDED
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "unicodestring.hxx"
|
||||
|
||||
namespace replxx {
|
||||
|
||||
class Utf8String {
|
||||
private:
|
||||
typedef std::unique_ptr<char[]> buffer_t;
|
||||
buffer_t _data;
|
||||
int _bufSize;
|
||||
int _len;
|
||||
public:
|
||||
Utf8String( void )
|
||||
: _data()
|
||||
, _bufSize( 0 )
|
||||
, _len( 0 ) {
|
||||
}
|
||||
explicit Utf8String( UnicodeString const& src )
|
||||
: _data()
|
||||
, _bufSize( 0 )
|
||||
, _len( 0 ) {
|
||||
assign( src, src.length() );
|
||||
}
|
||||
|
||||
Utf8String( UnicodeString const& src_, int len_ )
|
||||
: _data()
|
||||
, _bufSize( 0 )
|
||||
, _len( 0 ) {
|
||||
assign( src_, len_ );
|
||||
}
|
||||
|
||||
void assign( UnicodeString const& str_ ) {
|
||||
assign( str_, str_.length() );
|
||||
}
|
||||
|
||||
void assign( UnicodeString const& str_, int len_ ) {
|
||||
assign( str_.get(), len_ );
|
||||
}
|
||||
|
||||
void assign( char32_t const* str_, int len_ ) {
|
||||
int len( len_ * 4 );
|
||||
realloc( len );
|
||||
_len = copyString32to8( _data.get(), len, str_, len_ );
|
||||
}
|
||||
|
||||
void assign( std::string const& str_ ) {
|
||||
realloc( static_cast<int>( str_.length() ) );
|
||||
strncpy( _data.get(), str_.c_str(), str_.length() );
|
||||
_len = static_cast<int>( str_.length() );
|
||||
}
|
||||
|
||||
void assign( Utf8String const& other_ ) {
|
||||
realloc( other_._len );
|
||||
strncpy( _data.get(), other_._data.get(), other_._len );
|
||||
_len = other_._len;
|
||||
}
|
||||
|
||||
char const* get() const {
|
||||
return _data.get();
|
||||
}
|
||||
|
||||
int size( void ) const {
|
||||
return ( _len );
|
||||
}
|
||||
|
||||
bool operator != ( Utf8String const& other_ ) {
|
||||
return (
|
||||
( other_._len != _len )
|
||||
|| (
|
||||
( _len != 0 )
|
||||
&& ( memcmp( other_._data.get(), _data.get(), _len ) != 0 )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
void realloc( int reqLen ) {
|
||||
if ( ( reqLen + 1 ) > _bufSize ) {
|
||||
_bufSize = 1;
|
||||
while ( ( reqLen + 1 ) > _bufSize ) {
|
||||
_bufSize *= 2;
|
||||
}
|
||||
_data.reset( new char[_bufSize] );
|
||||
memset( _data.get(), 0, _bufSize );
|
||||
}
|
||||
_data[reqLen] = 0;
|
||||
return;
|
||||
}
|
||||
Utf8String(const Utf8String&) = delete;
|
||||
Utf8String& operator=(const Utf8String&) = delete;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
179
lib/replxx/src/util.cxx
Normal file
179
lib/replxx/src/util.cxx
Normal file
@ -0,0 +1,179 @@
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <wctype.h>
|
||||
|
||||
#include "util.hxx"
|
||||
#include "terminal.hxx"
|
||||
|
||||
#undef min
|
||||
|
||||
namespace replxx {
|
||||
|
||||
int mk_wcwidth( char32_t );
|
||||
|
||||
int virtual_render( char32_t const* display_, int size_, int& x_, int& y_, int screenColumns_, int promptLen_, char32_t* rendered_, int* renderedSize_ ) {
|
||||
char32_t* out( rendered_ );
|
||||
int visibleCount( 0 );
|
||||
auto render = [&rendered_, &renderedSize_, &out, &visibleCount]( char32_t c_, bool visible_, bool renderAttributes_ = true ) {
|
||||
if ( rendered_ && renderedSize_ && renderAttributes_ ) {
|
||||
*out = c_;
|
||||
++ out;
|
||||
if ( visible_ ) {
|
||||
++ visibleCount;
|
||||
}
|
||||
}
|
||||
};
|
||||
bool wrapped( false );
|
||||
auto advance_cursor = [&x_, &y_, &screenColumns_, &wrapped]( int by_ = 1 ) {
|
||||
wrapped = false;
|
||||
x_ += by_;
|
||||
if ( x_ >= screenColumns_ ) {
|
||||
x_ = 0;
|
||||
++ y_;
|
||||
wrapped = true;
|
||||
}
|
||||
};
|
||||
bool const renderAttributes( !!tty::out );
|
||||
int pos( 0 );
|
||||
while ( pos < size_ ) {
|
||||
char32_t c( display_[pos] );
|
||||
if ( ( c == '\n' ) || ( c == '\r' ) ) {
|
||||
render( c, true );
|
||||
if ( ( c == '\n' ) && ! wrapped ) {
|
||||
++ y_;
|
||||
}
|
||||
x_ = promptLen_;
|
||||
++ pos;
|
||||
continue;
|
||||
}
|
||||
if ( c == '\b' ) {
|
||||
render( c, true );
|
||||
-- x_;
|
||||
if ( x_ < 0 ) {
|
||||
x_ = screenColumns_ - 1;
|
||||
-- y_;
|
||||
}
|
||||
++ pos;
|
||||
continue;
|
||||
}
|
||||
if ( c == '\033' ) {
|
||||
render( c, false, renderAttributes );
|
||||
++ pos;
|
||||
if ( pos >= size_ ) {
|
||||
advance_cursor( 2 );
|
||||
continue;
|
||||
}
|
||||
c = display_[pos];
|
||||
if ( c != '[' ) {
|
||||
advance_cursor( 2 );
|
||||
continue;
|
||||
}
|
||||
render( c, false, renderAttributes );
|
||||
++ pos;
|
||||
if ( pos >= size_ ) {
|
||||
advance_cursor( 3 );
|
||||
continue;
|
||||
}
|
||||
int codeLen( 0 );
|
||||
while ( pos < size_ ) {
|
||||
c = display_[pos];
|
||||
if ( ( c != ';' ) && ( ( c < '0' ) || ( c > '9' ) ) ) {
|
||||
break;
|
||||
}
|
||||
render( c, false, renderAttributes );
|
||||
++ codeLen;
|
||||
++ pos;
|
||||
}
|
||||
if ( pos >= size_ ) {
|
||||
continue;
|
||||
}
|
||||
c = display_[pos];
|
||||
if ( c != 'm' ) {
|
||||
advance_cursor( 3 + codeLen );
|
||||
continue;
|
||||
}
|
||||
render( c, false, renderAttributes );
|
||||
++ pos;
|
||||
continue;
|
||||
}
|
||||
if ( is_control_code( c ) ) {
|
||||
render( c, true );
|
||||
advance_cursor( 2 );
|
||||
++ pos;
|
||||
continue;
|
||||
}
|
||||
int wcw( mk_wcwidth( c ) );
|
||||
if ( wcw < 0 ) {
|
||||
break;
|
||||
}
|
||||
render( c, true );
|
||||
advance_cursor( wcw );
|
||||
++ pos;
|
||||
}
|
||||
if ( rendered_ && renderedSize_ ) {
|
||||
*renderedSize_ = out - rendered_;
|
||||
}
|
||||
return ( visibleCount );
|
||||
}
|
||||
|
||||
char const* ansi_color( Replxx::Color color_ ) {
|
||||
int unsigned code( static_cast<int unsigned>( color_ ) );
|
||||
int unsigned fg( code & 0xFFu );
|
||||
int unsigned bg( ( code >> 8 ) & 0xFFu );
|
||||
char const* bold( ( code & color::BOLD ) != 0 ? ";1" : "" );
|
||||
char const* underline = ( ( code & color::UNDERLINE ) != 0 ? ";4" : "" );
|
||||
static int const MAX_COLOR_CODE_SIZE( 32 );
|
||||
static char colorBuffer[MAX_COLOR_CODE_SIZE];
|
||||
int pos( 0 );
|
||||
if ( ( code & static_cast<int unsigned>( Replxx::Color::DEFAULT ) ) != 0 ) {
|
||||
pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, "\033[0%s%sm", underline, bold );
|
||||
} else if ( fg <= static_cast<int unsigned>( Replxx::Color::LIGHTGRAY ) ) {
|
||||
pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, "\033[0;22;3%d%s%sm", fg, underline, bold );
|
||||
} else if ( fg <= static_cast<int unsigned>( Replxx::Color::WHITE ) ) {
|
||||
#ifdef _WIN32
|
||||
static bool const has256colorDefault( true );
|
||||
#else
|
||||
static bool const has256colorDefault( false );
|
||||
#endif
|
||||
static char const* TERM( getenv( "TERM" ) );
|
||||
static bool const has256color( TERM ? ( strstr( TERM, "256" ) != nullptr ) : has256colorDefault );
|
||||
static char const* ansiEscapeCodeTemplate = has256color ? "\033[0;9%d%s%sm" : "\033[0;1;3%d%s%sm";
|
||||
pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, ansiEscapeCodeTemplate, fg - static_cast<int>( Replxx::Color::GRAY ), underline, bold );
|
||||
} else {
|
||||
pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, "\033[0;38;5;%d%s%sm", fg, underline, bold );
|
||||
}
|
||||
if ( ( code & color::BACKGROUND_COLOR_SET ) == 0 ) {
|
||||
return colorBuffer;
|
||||
}
|
||||
if ( bg <= static_cast<int unsigned>( Replxx::Color::WHITE ) ) {
|
||||
if ( bg <= static_cast<int unsigned>( Replxx::Color::LIGHTGRAY ) ) {
|
||||
snprintf( colorBuffer + pos, MAX_COLOR_CODE_SIZE - pos, "\033[4%dm", bg );
|
||||
} else {
|
||||
snprintf( colorBuffer + pos, MAX_COLOR_CODE_SIZE - pos, "\033[10%dm", bg - static_cast<int>( Replxx::Color::GRAY ) );
|
||||
}
|
||||
} else {
|
||||
snprintf( colorBuffer + pos, MAX_COLOR_CODE_SIZE - pos, "\033[48;5;%dm", bg );
|
||||
}
|
||||
return colorBuffer;
|
||||
}
|
||||
|
||||
std::string now_ms_str( void ) {
|
||||
std::chrono::milliseconds ms( std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ) );
|
||||
time_t t( ms.count() / 1000 );
|
||||
tm broken;
|
||||
#ifdef _WIN32
|
||||
#define localtime_r( t, b ) localtime_s( ( b ), ( t ) )
|
||||
#endif
|
||||
localtime_r( &t, &broken );
|
||||
#undef localtime_r
|
||||
static int const BUFF_SIZE( 32 );
|
||||
char str[BUFF_SIZE];
|
||||
strftime( str, BUFF_SIZE, "%Y-%m-%d %H:%M:%S.", &broken );
|
||||
snprintf( str + sizeof ( "YYYY-mm-dd HH:MM:SS" ), 5, "%03d", static_cast<int>( ms.count() % 1000 ) );
|
||||
return ( str );
|
||||
}
|
||||
|
||||
}
|
||||
|
32
lib/replxx/src/util.hxx
Normal file
32
lib/replxx/src/util.hxx
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef REPLXX_UTIL_HXX_INCLUDED
|
||||
#define REPLXX_UTIL_HXX_INCLUDED 1
|
||||
|
||||
#include "replxx.hxx"
|
||||
|
||||
namespace replxx {
|
||||
|
||||
namespace color {
|
||||
static int unsigned const RGB666 = 16u;
|
||||
static int unsigned const GRAYSCALE = 232u;
|
||||
static int unsigned const BOLD = 1u << 17u;
|
||||
static int unsigned const UNDERLINE = 1u << 18u;
|
||||
static int unsigned const BACKGROUND_COLOR_SET = 1u << 19u;
|
||||
}
|
||||
|
||||
inline bool is_control_code(char32_t testChar) {
|
||||
return (testChar < ' ') || // C0 controls
|
||||
(testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls
|
||||
}
|
||||
|
||||
inline char32_t control_to_human( char32_t key ) {
|
||||
return ( key < 27 ? ( key + 0x40 ) : ( key + 0x18 ) );
|
||||
}
|
||||
|
||||
int virtual_render( char32_t const*, int, int&, int&, int, int, char32_t* = nullptr, int* = nullptr );
|
||||
char const* ansi_color( Replxx::Color );
|
||||
std::string now_ms_str( void );
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
296
lib/replxx/src/wcwidth.cpp
Normal file
296
lib/replxx/src/wcwidth.cpp
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* This is an implementation of wcwidth() and wcswidth() (defined in
|
||||
* IEEE Std 1002.1-2001) for Unicode.
|
||||
*
|
||||
* http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
|
||||
* http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
|
||||
*
|
||||
* In fixed-width output devices, Latin characters all occupy a single
|
||||
* "cell" position of equal width, whereas ideographic CJK characters
|
||||
* occupy two such cells. Interoperability between terminal-line
|
||||
* applications and (teletype-style) character terminals using the
|
||||
* UTF-8 encoding requires agreement on which character should advance
|
||||
* the cursor by how many cell positions. No established formal
|
||||
* standards exist at present on which Unicode character shall occupy
|
||||
* how many cell positions on character terminals. These routines are
|
||||
* a first attempt of defining such behavior based on simple rules
|
||||
* applied to data provided by the Unicode Consortium.
|
||||
*
|
||||
* For some graphical characters, the Unicode standard explicitly
|
||||
* defines a character-cell width via the definition of the East Asian
|
||||
* FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
|
||||
* In all these cases, there is no ambiguity about which width a
|
||||
* terminal shall use. For characters in the East Asian Ambiguous (A)
|
||||
* class, the width choice depends purely on a preference of backward
|
||||
* compatibility with either historic CJK or Western practice.
|
||||
* Choosing single-width for these characters is easy to justify as
|
||||
* the appropriate long-term solution, as the CJK practice of
|
||||
* displaying these characters as double-width comes from historic
|
||||
* implementation simplicity (8-bit encoded characters were displayed
|
||||
* single-width and 16-bit ones double-width, even for Greek,
|
||||
* Cyrillic, etc.) and not any typographic considerations.
|
||||
*
|
||||
* Much less clear is the choice of width for the Not East Asian
|
||||
* (Neutral) class. Existing practice does not dictate a width for any
|
||||
* of these characters. It would nevertheless make sense
|
||||
* typographically to allocate two character cells to characters such
|
||||
* as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
|
||||
* represented adequately with a single-width glyph. The following
|
||||
* routines at present merely assign a single-cell width to all
|
||||
* neutral characters, in the interest of simplicity. This is not
|
||||
* entirely satisfactory and should be reconsidered before
|
||||
* establishing a formal standard in this area. At the moment, the
|
||||
* decision which Not East Asian (Neutral) characters should be
|
||||
* represented by double-width glyphs cannot yet be answered by
|
||||
* applying a simple rule from the Unicode database content. Setting
|
||||
* up a proper standard for the behavior of UTF-8 character terminals
|
||||
* will require a careful analysis not only of each Unicode character,
|
||||
* but also of each presentation form, something the author of these
|
||||
* routines has avoided to do so far.
|
||||
*
|
||||
* http://www.unicode.org/unicode/reports/tr11/
|
||||
*
|
||||
* Markus Kuhn -- 2007-05-26 (Unicode 5.0)
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software
|
||||
* for any purpose and without fee is hereby granted. The author
|
||||
* disclaims all warranties with regard to this software.
|
||||
*
|
||||
* Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
|
||||
*/
|
||||
|
||||
#include <wchar.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace replxx {
|
||||
|
||||
struct interval {
|
||||
char32_t first;
|
||||
char32_t last;
|
||||
};
|
||||
|
||||
/* auxiliary function for binary search in interval table */
|
||||
static int bisearch(char32_t ucs, const struct interval *table, int max) {
|
||||
int min = 0;
|
||||
int mid;
|
||||
|
||||
if (ucs < table[0].first || ucs > table[max].last)
|
||||
return 0;
|
||||
while (max >= min) {
|
||||
mid = (min + max) / 2;
|
||||
if (ucs > table[mid].last)
|
||||
min = mid + 1;
|
||||
else if (ucs < table[mid].first)
|
||||
max = mid - 1;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* The following two functions define the column width of an ISO 10646
|
||||
* character as follows:
|
||||
*
|
||||
* - The null character (U+0000) has a column width of 0.
|
||||
*
|
||||
* - Other C0/C1 control characters and DEL will lead to a return
|
||||
* value of -1.
|
||||
*
|
||||
* - Non-spacing and enclosing combining characters (general
|
||||
* category code Mn or Me in the Unicode database) have a
|
||||
* column width of 0.
|
||||
*
|
||||
* - SOFT HYPHEN (U+00AD) has a column width of 1.
|
||||
*
|
||||
* - Other format characters (general category code Cf in the Unicode
|
||||
* database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
|
||||
*
|
||||
* - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
|
||||
* have a column width of 0.
|
||||
*
|
||||
* - Spacing characters in the East Asian Wide (W) or East Asian
|
||||
* Full-width (F) category as defined in Unicode Technical
|
||||
* Report #11 have a column width of 2.
|
||||
*
|
||||
* - All remaining characters (including all printable
|
||||
* ISO 8859-1 and WGL4 characters, Unicode control characters,
|
||||
* etc.) have a column width of 1.
|
||||
*
|
||||
* This implementation assumes that wchar_t characters are encoded
|
||||
* in ISO 10646.
|
||||
*/
|
||||
|
||||
int mk_is_wide_char(char32_t ucs) {
|
||||
static const struct interval wide[] = {
|
||||
{0x1100, 0x115f}, {0x231a, 0x231b}, {0x2329, 0x232a},
|
||||
{0x23e9, 0x23ec}, {0x23f0, 0x23f0}, {0x23f3, 0x23f3},
|
||||
{0x25fd, 0x25fe}, {0x2614, 0x2615}, {0x2648, 0x2653},
|
||||
{0x267f, 0x267f}, {0x2693, 0x2693}, {0x26a1, 0x26a1},
|
||||
{0x26aa, 0x26ab}, {0x26bd, 0x26be}, {0x26c4, 0x26c5},
|
||||
{0x26ce, 0x26ce}, {0x26d4, 0x26d4}, {0x26ea, 0x26ea},
|
||||
{0x26f2, 0x26f3}, {0x26f5, 0x26f5}, {0x26fa, 0x26fa},
|
||||
{0x26fd, 0x26fd}, {0x2705, 0x2705}, {0x270a, 0x270b},
|
||||
{0x2728, 0x2728}, {0x274c, 0x274c}, {0x274e, 0x274e},
|
||||
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
|
||||
{0x27b0, 0x27b0}, {0x27bf, 0x27bf}, {0x2b1b, 0x2b1c},
|
||||
{0x2b50, 0x2b50}, {0x2b55, 0x2b55}, {0x2e80, 0x2fdf},
|
||||
{0x2ff0, 0x303e}, {0x3040, 0x3247}, {0x3250, 0x4dbf},
|
||||
{0x4e00, 0xa4cf}, {0xa960, 0xa97f}, {0xac00, 0xd7a3},
|
||||
{0xf900, 0xfaff}, {0xfe10, 0xfe19}, {0xfe30, 0xfe6f},
|
||||
{0xff01, 0xff60}, {0xffe0, 0xffe6}, {0x16fe0, 0x16fe1},
|
||||
{0x17000, 0x18aff}, {0x1b000, 0x1b12f}, {0x1b170, 0x1b2ff},
|
||||
{0x1f004, 0x1f004}, {0x1f0cf, 0x1f0cf}, {0x1f18e, 0x1f18e},
|
||||
{0x1f191, 0x1f19a}, {0x1f200, 0x1f202}, {0x1f210, 0x1f23b},
|
||||
{0x1f240, 0x1f248}, {0x1f250, 0x1f251}, {0x1f260, 0x1f265},
|
||||
{0x1f300, 0x1f320}, {0x1f32d, 0x1f335}, {0x1f337, 0x1f37c},
|
||||
{0x1f37e, 0x1f393}, {0x1f3a0, 0x1f3ca}, {0x1f3cf, 0x1f3d3},
|
||||
{0x1f3e0, 0x1f3f0}, {0x1f3f4, 0x1f3f4}, {0x1f3f8, 0x1f43e},
|
||||
{0x1f440, 0x1f440}, {0x1f442, 0x1f4fc}, {0x1f4ff, 0x1f53d},
|
||||
{0x1f54b, 0x1f54e}, {0x1f550, 0x1f567}, {0x1f57a, 0x1f57a},
|
||||
{0x1f595, 0x1f596}, {0x1f5a4, 0x1f5a4}, {0x1f5fb, 0x1f64f},
|
||||
{0x1f680, 0x1f6c5}, {0x1f6cc, 0x1f6cc}, {0x1f6d0, 0x1f6d2},
|
||||
{0x1f6eb, 0x1f6ec}, {0x1f6f4, 0x1f6f8}, {0x1f910, 0x1f93e},
|
||||
{0x1f940, 0x1f94c}, {0x1f950, 0x1f96b}, {0x1f980, 0x1f997},
|
||||
{0x1f9c0, 0x1f9c0}, {0x1f9d0, 0x1f9e6}, {0x20000, 0x2fffd},
|
||||
{0x30000, 0x3fffd},
|
||||
};
|
||||
|
||||
if ( bisearch(ucs, wide, sizeof(wide) / sizeof(struct interval) - 1) ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mk_wcwidth(char32_t ucs) {
|
||||
/* sorted list of non-overlapping intervals of non-spacing characters */
|
||||
/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
|
||||
static const struct interval combining[] = {
|
||||
{0x00ad, 0x00ad}, {0x0300, 0x036f}, {0x0483, 0x0489},
|
||||
{0x0591, 0x05bd}, {0x05bf, 0x05bf}, {0x05c1, 0x05c2},
|
||||
{0x05c4, 0x05c5}, {0x05c7, 0x05c7}, {0x0610, 0x061a},
|
||||
{0x061c, 0x061c}, {0x064b, 0x065f}, {0x0670, 0x0670},
|
||||
{0x06d6, 0x06dc}, {0x06df, 0x06e4}, {0x06e7, 0x06e8},
|
||||
{0x06ea, 0x06ed}, {0x0711, 0x0711}, {0x0730, 0x074a},
|
||||
{0x07a6, 0x07b0}, {0x07eb, 0x07f3}, {0x0816, 0x0819},
|
||||
{0x081b, 0x0823}, {0x0825, 0x0827}, {0x0829, 0x082d},
|
||||
{0x0859, 0x085b}, {0x08d4, 0x08e1}, {0x08e3, 0x0902},
|
||||
{0x093a, 0x093a}, {0x093c, 0x093c}, {0x0941, 0x0948},
|
||||
{0x094d, 0x094d}, {0x0951, 0x0957}, {0x0962, 0x0963},
|
||||
{0x0981, 0x0981}, {0x09bc, 0x09bc}, {0x09c1, 0x09c4},
|
||||
{0x09cd, 0x09cd}, {0x09e2, 0x09e3}, {0x0a01, 0x0a02},
|
||||
{0x0a3c, 0x0a3c}, {0x0a41, 0x0a42}, {0x0a47, 0x0a48},
|
||||
{0x0a4b, 0x0a4d}, {0x0a51, 0x0a51}, {0x0a70, 0x0a71},
|
||||
{0x0a75, 0x0a75}, {0x0a81, 0x0a82}, {0x0abc, 0x0abc},
|
||||
{0x0ac1, 0x0ac5}, {0x0ac7, 0x0ac8}, {0x0acd, 0x0acd},
|
||||
{0x0ae2, 0x0ae3}, {0x0afa, 0x0aff}, {0x0b01, 0x0b01},
|
||||
{0x0b3c, 0x0b3c}, {0x0b3f, 0x0b3f}, {0x0b41, 0x0b44},
|
||||
{0x0b4d, 0x0b4d}, {0x0b56, 0x0b56}, {0x0b62, 0x0b63},
|
||||
{0x0b82, 0x0b82}, {0x0bc0, 0x0bc0}, {0x0bcd, 0x0bcd},
|
||||
{0x0c00, 0x0c00}, {0x0c3e, 0x0c40}, {0x0c46, 0x0c48},
|
||||
{0x0c4a, 0x0c4d}, {0x0c55, 0x0c56}, {0x0c62, 0x0c63},
|
||||
{0x0c81, 0x0c81}, {0x0cbc, 0x0cbc}, {0x0cbf, 0x0cbf},
|
||||
{0x0cc6, 0x0cc6}, {0x0ccc, 0x0ccd}, {0x0ce2, 0x0ce3},
|
||||
{0x0d00, 0x0d01}, {0x0d3b, 0x0d3c}, {0x0d41, 0x0d44},
|
||||
{0x0d4d, 0x0d4d}, {0x0d62, 0x0d63}, {0x0dca, 0x0dca},
|
||||
{0x0dd2, 0x0dd4}, {0x0dd6, 0x0dd6}, {0x0e31, 0x0e31},
|
||||
{0x0e34, 0x0e3a}, {0x0e47, 0x0e4e}, {0x0eb1, 0x0eb1},
|
||||
{0x0eb4, 0x0eb9}, {0x0ebb, 0x0ebc}, {0x0ec8, 0x0ecd},
|
||||
{0x0f18, 0x0f19}, {0x0f35, 0x0f35}, {0x0f37, 0x0f37},
|
||||
{0x0f39, 0x0f39}, {0x0f71, 0x0f7e}, {0x0f80, 0x0f84},
|
||||
{0x0f86, 0x0f87}, {0x0f8d, 0x0f97}, {0x0f99, 0x0fbc},
|
||||
{0x0fc6, 0x0fc6}, {0x102d, 0x1030}, {0x1032, 0x1037},
|
||||
{0x1039, 0x103a}, {0x103d, 0x103e}, {0x1058, 0x1059},
|
||||
{0x105e, 0x1060}, {0x1071, 0x1074}, {0x1082, 0x1082},
|
||||
{0x1085, 0x1086}, {0x108d, 0x108d}, {0x109d, 0x109d},
|
||||
{0x1160, 0x11ff}, {0x135d, 0x135f}, {0x1712, 0x1714},
|
||||
{0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773},
|
||||
{0x17b4, 0x17b5}, {0x17b7, 0x17bd}, {0x17c6, 0x17c6},
|
||||
{0x17c9, 0x17d3}, {0x17dd, 0x17dd}, {0x180b, 0x180e},
|
||||
{0x1885, 0x1886}, {0x18a9, 0x18a9}, {0x1920, 0x1922},
|
||||
{0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193b},
|
||||
{0x1a17, 0x1a18}, {0x1a1b, 0x1a1b}, {0x1a56, 0x1a56},
|
||||
{0x1a58, 0x1a5e}, {0x1a60, 0x1a60}, {0x1a62, 0x1a62},
|
||||
{0x1a65, 0x1a6c}, {0x1a73, 0x1a7c}, {0x1a7f, 0x1a7f},
|
||||
{0x1ab0, 0x1abe}, {0x1b00, 0x1b03}, {0x1b34, 0x1b34},
|
||||
{0x1b36, 0x1b3a}, {0x1b3c, 0x1b3c}, {0x1b42, 0x1b42},
|
||||
{0x1b6b, 0x1b73}, {0x1b80, 0x1b81}, {0x1ba2, 0x1ba5},
|
||||
{0x1ba8, 0x1ba9}, {0x1bab, 0x1bad}, {0x1be6, 0x1be6},
|
||||
{0x1be8, 0x1be9}, {0x1bed, 0x1bed}, {0x1bef, 0x1bf1},
|
||||
{0x1c2c, 0x1c33}, {0x1c36, 0x1c37}, {0x1cd0, 0x1cd2},
|
||||
{0x1cd4, 0x1ce0}, {0x1ce2, 0x1ce8}, {0x1ced, 0x1ced},
|
||||
{0x1cf4, 0x1cf4}, {0x1cf8, 0x1cf9}, {0x1dc0, 0x1df9},
|
||||
{0x1dfb, 0x1dff}, {0x200b, 0x200f}, {0x202a, 0x202e},
|
||||
{0x2060, 0x2064}, {0x2066, 0x206f}, {0x20d0, 0x20f0},
|
||||
{0x2cef, 0x2cf1}, {0x2d7f, 0x2d7f}, {0x2de0, 0x2dff},
|
||||
{0x302a, 0x302d}, {0x3099, 0x309a}, {0xa66f, 0xa672},
|
||||
{0xa674, 0xa67d}, {0xa69e, 0xa69f}, {0xa6f0, 0xa6f1},
|
||||
{0xa802, 0xa802}, {0xa806, 0xa806}, {0xa80b, 0xa80b},
|
||||
{0xa825, 0xa826}, {0xa8c4, 0xa8c5}, {0xa8e0, 0xa8f1},
|
||||
{0xa926, 0xa92d}, {0xa947, 0xa951}, {0xa980, 0xa982},
|
||||
{0xa9b3, 0xa9b3}, {0xa9b6, 0xa9b9}, {0xa9bc, 0xa9bc},
|
||||
{0xa9e5, 0xa9e5}, {0xaa29, 0xaa2e}, {0xaa31, 0xaa32},
|
||||
{0xaa35, 0xaa36}, {0xaa43, 0xaa43}, {0xaa4c, 0xaa4c},
|
||||
{0xaa7c, 0xaa7c}, {0xaab0, 0xaab0}, {0xaab2, 0xaab4},
|
||||
{0xaab7, 0xaab8}, {0xaabe, 0xaabf}, {0xaac1, 0xaac1},
|
||||
{0xaaec, 0xaaed}, {0xaaf6, 0xaaf6}, {0xabe5, 0xabe5},
|
||||
{0xabe8, 0xabe8}, {0xabed, 0xabed}, {0xfb1e, 0xfb1e},
|
||||
{0xfe00, 0xfe0f}, {0xfe20, 0xfe2f}, {0xfeff, 0xfeff},
|
||||
{0xfff9, 0xfffb}, {0x101fd, 0x101fd}, {0x102e0, 0x102e0},
|
||||
{0x10376, 0x1037a}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06},
|
||||
{0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f},
|
||||
{0x10ae5, 0x10ae6}, {0x11001, 0x11001}, {0x11038, 0x11046},
|
||||
{0x1107f, 0x11081}, {0x110b3, 0x110b6}, {0x110b9, 0x110ba},
|
||||
{0x11100, 0x11102}, {0x11127, 0x1112b}, {0x1112d, 0x11134},
|
||||
{0x11173, 0x11173}, {0x11180, 0x11181}, {0x111b6, 0x111be},
|
||||
{0x111ca, 0x111cc}, {0x1122f, 0x11231}, {0x11234, 0x11234},
|
||||
{0x11236, 0x11237}, {0x1123e, 0x1123e}, {0x112df, 0x112df},
|
||||
{0x112e3, 0x112ea}, {0x11300, 0x11301}, {0x1133c, 0x1133c},
|
||||
{0x11340, 0x11340}, {0x11366, 0x1136c}, {0x11370, 0x11374},
|
||||
{0x11438, 0x1143f}, {0x11442, 0x11444}, {0x11446, 0x11446},
|
||||
{0x114b3, 0x114b8}, {0x114ba, 0x114ba}, {0x114bf, 0x114c0},
|
||||
{0x114c2, 0x114c3}, {0x115b2, 0x115b5}, {0x115bc, 0x115bd},
|
||||
{0x115bf, 0x115c0}, {0x115dc, 0x115dd}, {0x11633, 0x1163a},
|
||||
{0x1163d, 0x1163d}, {0x1163f, 0x11640}, {0x116ab, 0x116ab},
|
||||
{0x116ad, 0x116ad}, {0x116b0, 0x116b5}, {0x116b7, 0x116b7},
|
||||
{0x1171d, 0x1171f}, {0x11722, 0x11725}, {0x11727, 0x1172b},
|
||||
{0x11a01, 0x11a06}, {0x11a09, 0x11a0a}, {0x11a33, 0x11a38},
|
||||
{0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a56},
|
||||
{0x11a59, 0x11a5b}, {0x11a8a, 0x11a96}, {0x11a98, 0x11a99},
|
||||
{0x11c30, 0x11c36}, {0x11c38, 0x11c3d}, {0x11c3f, 0x11c3f},
|
||||
{0x11c92, 0x11ca7}, {0x11caa, 0x11cb0}, {0x11cb2, 0x11cb3},
|
||||
{0x11cb5, 0x11cb6}, {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a},
|
||||
{0x11d3c, 0x11d3d}, {0x11d3f, 0x11d45}, {0x11d47, 0x11d47},
|
||||
{0x16af0, 0x16af4}, {0x16b30, 0x16b36}, {0x16f8f, 0x16f92},
|
||||
{0x1bc9d, 0x1bc9e}, {0x1bca0, 0x1bca3}, {0x1d167, 0x1d169},
|
||||
{0x1d173, 0x1d182}, {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad},
|
||||
{0x1d242, 0x1d244}, {0x1da00, 0x1da36}, {0x1da3b, 0x1da6c},
|
||||
{0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f},
|
||||
{0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018},
|
||||
{0x1e01b, 0x1e021}, {0x1e023, 0x1e024}, {0x1e026, 0x1e02a},
|
||||
{0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a}, {0xe0001, 0xe0001},
|
||||
{0xe0020, 0xe007f}, {0xe0100, 0xe01ef},
|
||||
};
|
||||
|
||||
/* test for 8-bit control characters */
|
||||
if ( ucs == 0 ) {
|
||||
return 0;
|
||||
}
|
||||
if ( ( ucs < 32 ) || ( ( ucs >= 0x7f ) && ( ucs < 0xa0 ) ) ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* binary search in table of non-spacing characters */
|
||||
if ( bisearch( ucs, combining, sizeof( combining ) / sizeof( struct interval ) - 1 ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* if we arrive here, ucs is not a combining or C0/C1 control character */
|
||||
return ( mk_is_wide_char( ucs ) ? 2 : 1 );
|
||||
}
|
||||
|
||||
}
|
||||
|
144
lib/replxx/src/windows.cxx
Normal file
144
lib/replxx/src/windows.cxx
Normal file
@ -0,0 +1,144 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "windows.hxx"
|
||||
#include "conversion.hxx"
|
||||
#include "terminal.hxx"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace replxx {
|
||||
|
||||
WinAttributes WIN_ATTR;
|
||||
|
||||
template<typename T>
|
||||
T* HandleEsc(HANDLE out_, T* p, T* end) {
|
||||
if (*p == '[') {
|
||||
int code = 0;
|
||||
|
||||
int thisBackground( WIN_ATTR._defaultBackground );
|
||||
for (++p; p < end; ++p) {
|
||||
char32_t c = *p;
|
||||
|
||||
if ('0' <= c && c <= '9') {
|
||||
code = code * 10 + (c - '0');
|
||||
} else if (c == 'm' || c == ';') {
|
||||
switch (code) {
|
||||
case 0:
|
||||
WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute;
|
||||
WIN_ATTR._consoleColor = WIN_ATTR._defaultColor | thisBackground;
|
||||
break;
|
||||
case 1: // BOLD
|
||||
case 5: // BLINK
|
||||
WIN_ATTR._consoleAttribute = (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY;
|
||||
break;
|
||||
case 22:
|
||||
WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute;
|
||||
break;
|
||||
case 30:
|
||||
case 90:
|
||||
WIN_ATTR._consoleColor = thisBackground;
|
||||
break;
|
||||
case 31:
|
||||
case 91:
|
||||
WIN_ATTR._consoleColor = FOREGROUND_RED | thisBackground;
|
||||
break;
|
||||
case 32:
|
||||
case 92:
|
||||
WIN_ATTR._consoleColor = FOREGROUND_GREEN | thisBackground;
|
||||
break;
|
||||
case 33:
|
||||
case 93:
|
||||
WIN_ATTR._consoleColor = FOREGROUND_RED | FOREGROUND_GREEN | thisBackground;
|
||||
break;
|
||||
case 34:
|
||||
case 94:
|
||||
WIN_ATTR._consoleColor = FOREGROUND_BLUE | thisBackground;
|
||||
break;
|
||||
case 35:
|
||||
case 95:
|
||||
WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_RED | thisBackground;
|
||||
break;
|
||||
case 36:
|
||||
case 96:
|
||||
WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_GREEN | thisBackground;
|
||||
break;
|
||||
case 37:
|
||||
case 97:
|
||||
WIN_ATTR._consoleColor = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | thisBackground;
|
||||
break;
|
||||
case 101:
|
||||
thisBackground = BACKGROUND_RED;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( ( code >= 90 ) && ( code <= 97 ) ) {
|
||||
WIN_ATTR._consoleAttribute = (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY;
|
||||
}
|
||||
|
||||
code = 0;
|
||||
}
|
||||
|
||||
if (*p == 'm') {
|
||||
++p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
++p;
|
||||
}
|
||||
|
||||
SetConsoleTextAttribute(
|
||||
out_,
|
||||
WIN_ATTR._consoleAttribute | WIN_ATTR._consoleColor
|
||||
);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
int win_write( HANDLE out_, bool autoEscape_, char const* str_, int size_ ) {
|
||||
int count( 0 );
|
||||
if ( tty::out ) {
|
||||
DWORD nWritten( 0 );
|
||||
if ( autoEscape_ ) {
|
||||
WriteConsoleA( out_, str_, size_, &nWritten, nullptr );
|
||||
count = nWritten;
|
||||
} else {
|
||||
char const* s( str_ );
|
||||
char const* e( str_ + size_ );
|
||||
while ( str_ < e ) {
|
||||
if ( *str_ == 27 ) {
|
||||
if ( s < str_ ) {
|
||||
int toWrite( static_cast<int>( str_ - s ) );
|
||||
WriteConsoleA( out_, s, static_cast<DWORD>( toWrite ), &nWritten, nullptr );
|
||||
count += nWritten;
|
||||
if ( static_cast<int>( nWritten ) != toWrite ) {
|
||||
s = str_ = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s = HandleEsc( out_, str_ + 1, e );
|
||||
int escaped( static_cast<int>( s - str_ ) );
|
||||
count += escaped;
|
||||
str_ = s;
|
||||
} else {
|
||||
++ str_;
|
||||
}
|
||||
}
|
||||
|
||||
if ( s < str_ ) {
|
||||
WriteConsoleA( out_, s, static_cast<DWORD>( str_ - s ), &nWritten, nullptr );
|
||||
count += nWritten;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
count = _write( 1, str_, size_ );
|
||||
}
|
||||
return ( count );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
44
lib/replxx/src/windows.hxx
Normal file
44
lib/replxx/src/windows.hxx
Normal file
@ -0,0 +1,44 @@
|
||||
#ifndef REPLXX_WINDOWS_HXX_INCLUDED
|
||||
#define REPLXX_WINDOWS_HXX_INCLUDED 1
|
||||
|
||||
#include <conio.h>
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
|
||||
namespace replxx {
|
||||
|
||||
static const int FOREGROUND_WHITE =
|
||||
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
||||
static const int BACKGROUND_WHITE =
|
||||
BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
|
||||
static const int INTENSITY = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
|
||||
|
||||
class WinAttributes {
|
||||
public:
|
||||
WinAttributes() {
|
||||
CONSOLE_SCREEN_BUFFER_INFO info;
|
||||
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
|
||||
_defaultAttribute = info.wAttributes & INTENSITY;
|
||||
_defaultColor = info.wAttributes & FOREGROUND_WHITE;
|
||||
_defaultBackground = info.wAttributes & BACKGROUND_WHITE;
|
||||
|
||||
_consoleAttribute = _defaultAttribute;
|
||||
_consoleColor = _defaultColor | _defaultBackground;
|
||||
}
|
||||
|
||||
public:
|
||||
int _defaultAttribute;
|
||||
int _defaultColor;
|
||||
int _defaultBackground;
|
||||
|
||||
int _consoleAttribute;
|
||||
int _consoleColor;
|
||||
};
|
||||
|
||||
int win_write( HANDLE, bool, char const*, int );
|
||||
|
||||
extern WinAttributes WIN_ATTR;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
474
musique/cmd.cc
Normal file
474
musique/cmd.cc
Normal 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
39
musique/cmd.hh
Normal 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
|
||||
|
@ -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
|
||||
|
14
musique/interpreter/builtin_function_documentation.hh
Normal file
14
musique/interpreter/builtin_function_documentation.hh
Normal 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
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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{};
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
242
musique/main.cc
242
musique/main.cc
@ -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
24
musique/platform.hh
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef MUSIQUE_PLATFORM_HH
|
||||
#define MUSIQUE_PLATFORM_HH
|
||||
|
||||
namespace platform
|
||||
{
|
||||
enum class Operating_System
|
||||
{
|
||||
MacOS,
|
||||
Unix,
|
||||
Windows,
|
||||
};
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
||||
static constexpr Operating_System os = Operating_System::Windows;
|
||||
#elif defined(__APPLE__)
|
||||
static constexpr Operating_System os = Operating_System::MacOS;
|
||||
#elif defined(__linux__) || defined(__unix__)
|
||||
static constexpr Operating_System os = Operating_System::Unix;
|
||||
#else
|
||||
#error "Unknown platform"
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // MUSIQUE_PLATFORM_HH
|
@ -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 = {};
|
||||
}
|
||||
|
@ -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
92
musique/user_directory.cc
Normal file
@ -0,0 +1,92 @@
|
||||
#include <musique/user_directory.hh>
|
||||
#include <musique/errors.hh>
|
||||
#include <musique/platform.hh>
|
||||
|
||||
static std::filesystem::path home()
|
||||
{
|
||||
if constexpr (platform::os == platform::Operating_System::Windows) {
|
||||
if (auto home = std::getenv("USERPROFILE")) return home;
|
||||
|
||||
if (auto drive = std::getenv("HOMEDRIVE")) {
|
||||
if (auto path = std::getenv("HOMEPATH")) {
|
||||
return std::string(drive) + path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (auto home = std::getenv("HOME")) {
|
||||
return home;
|
||||
}
|
||||
}
|
||||
|
||||
unreachable();
|
||||
}
|
||||
|
||||
std::filesystem::path user_directory::data_home()
|
||||
{
|
||||
std::filesystem::path path;
|
||||
|
||||
static_assert(one_of(platform::os,
|
||||
platform::Operating_System::Unix,
|
||||
platform::Operating_System::Windows,
|
||||
platform::Operating_System::MacOS
|
||||
));
|
||||
|
||||
if constexpr (platform::os == platform::Operating_System::Unix) {
|
||||
if (auto data = std::getenv("XDG_DATA_HOME")) {
|
||||
path = data;
|
||||
} else {
|
||||
path = home() / ".local" / "share";
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (platform::os == platform::Operating_System::Windows) {
|
||||
if (auto data = std::getenv("LOCALAPPDATA")) {
|
||||
path = data;
|
||||
} else {
|
||||
path = home() / "AppData" / "Local";
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (platform::os == platform::Operating_System::MacOS) {
|
||||
path = home() / "Library";
|
||||
}
|
||||
|
||||
path /= "musique";
|
||||
std::filesystem::create_directories(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
std::filesystem::path user_directory::config_home()
|
||||
{
|
||||
std::filesystem::path path;
|
||||
|
||||
static_assert(one_of(platform::os,
|
||||
platform::Operating_System::Unix,
|
||||
platform::Operating_System::Windows,
|
||||
platform::Operating_System::MacOS
|
||||
));
|
||||
|
||||
if constexpr (platform::os == platform::Operating_System::Unix) {
|
||||
if (auto data = std::getenv("XDG_CONFIG_HOME")) {
|
||||
path = data;
|
||||
} else {
|
||||
path = home() / ".config";
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (platform::os == platform::Operating_System::Windows) {
|
||||
if (auto data = std::getenv("LOCALAPPDATA")) {
|
||||
path = data;
|
||||
} else {
|
||||
path = home() / "AppData" / "Local";
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (platform::os == platform::Operating_System::MacOS) {
|
||||
path = home() / "Library" / "Preferences";
|
||||
}
|
||||
|
||||
path /= "musique";
|
||||
std::filesystem::create_directories(path);
|
||||
return path;
|
||||
}
|
15
musique/user_directory.hh
Normal file
15
musique/user_directory.hh
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef MUSIQUE_USER_DIRECTORY_HH
|
||||
#define MUSIQUE_USER_DIRECTORY_HH
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace user_directory
|
||||
{
|
||||
/// Returns system-specific user directory for data; same as XDG_DATA_HOME
|
||||
std::filesystem::path data_home();
|
||||
|
||||
/// Returns system-specific user directory for config; same as XDG_CONFIG_HOME
|
||||
std::filesystem::path config_home();
|
||||
}
|
||||
|
||||
#endif // MUSIQUE_USER_DIRECTORY_HH
|
@ -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
36
scripts/build.mk
Normal file → Executable 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
20
scripts/build_replxx.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
prefix="lib/replxx/src/"
|
||||
|
||||
mkdir -p "bin/${os}/replxx"
|
||||
|
||||
objects=""
|
||||
|
||||
find lib/replxx/src/ -name '*.cxx' -o -name '*.cpp' | while read -r src
|
||||
do
|
||||
dst="${src##"$prefix"}"
|
||||
dst="bin/${os}/replxx/${dst%%.*}.o"
|
||||
if [ ! -f "$dst" ]; then
|
||||
"${CXX}" -Ilib/replxx/src/ -Ilib/replxx/include/ -c -o "$dst" "$src" -std=c++20 -O3 -DREPLXX_STATIC
|
||||
fi
|
||||
echo "${dst}"
|
||||
done
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user