GNU Readline replace with bestline

This commit is contained in:
Robert Bendun 2022-06-11 19:57:56 +02:00
parent e6aed50fe9
commit 7d0f732a57
6 changed files with 3764 additions and 14 deletions

View File

@ -1,6 +1,6 @@
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Ilib/midi/include -Isrc/
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Ilib/midi/include -Isrc/ -Ilib/bestline/
RELEASE_FLAGS=-O3
DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined
CXX=g++
@ -49,18 +49,22 @@ bin/%.o: src/%.cc src/*.hh
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
bin/musique: $(Release_Obj) bin/main.o src/*.hh lib/midi/libmidi-alsa.a
bin/musique: $(Release_Obj) bin/main.o bin/bestline.o src/*.hh lib/midi/libmidi-alsa.a
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/main.o $(LDFLAGS) $(LDLIBS)
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/bestline.o bin/main.o $(LDFLAGS) $(LDLIBS)
bin/debug/musique: $(Debug_Obj) bin/debug/main.o src/*.hh
bin/debug/musique: $(Debug_Obj) bin/debug/main.o bin/bestline.o src/*.hh
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $(Debug_Obj) bin/debug/main.o $(LDFLAGS) $(LDLIBS)
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $(Debug_Obj) bin/bestline.o bin/debug/main.o $(LDFLAGS) $(LDLIBS)
bin/debug/%.o: src/%.cc src/*.hh
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c
bin/bestline.o: lib/bestline/bestline.c lib/bestline/bestline.h
@echo "CC $@"
@$(CC) $< -c -O3 -o $@
unit-tests: bin/unit-tests
./$<

30
lib/bestline/LICENSE Normal file
View File

@ -0,0 +1,30 @@
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.

3578
lib/bestline/bestline.c Normal file

File diff suppressed because it is too large Load Diff

39
lib/bestline/bestline.h Normal file
View File

@ -0,0 +1,39 @@
#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);

79
lib/bestline/example.c Normal file
View File

@ -0,0 +1,79 @@
#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;
}

View File

@ -7,8 +7,10 @@
#include <musique.hh>
#include <midi.hh>
#include <readline/readline.h>
#include <readline/history.h>
extern "C" {
#include <bestline.h>
}
namespace fs = std::filesystem;
@ -66,6 +68,8 @@ static void trim(std::string_view &s)
/// Runs interpreter on given source code
struct Runner
{
static inline Runner *the;
midi::ALSA alsa;
Interpreter interpreter;
@ -73,6 +77,9 @@ struct Runner
Runner(std::string port)
: alsa("musique")
{
assert(the == nullptr, "Only one instance of runner is supported");
the = this;
alsa.init_sequencer();
alsa.connect(port);
interpreter.midi_connection = &alsa;
@ -88,6 +95,11 @@ struct Runner
});
}
Runner(Runner const&) = delete;
Runner(Runner &&) = delete;
Runner& operator=(Runner const&) = delete;
Runner& operator=(Runner &&) = delete;
/// Run given source
Result<void> run(std::string_view source, std::string_view filename, bool output = false)
{
@ -108,6 +120,19 @@ struct Runner
/// some of the strings are only views into source
std::vector<std::string> eternal_sources;
void completion(char const* buf, bestlineCompletions *lc)
{
std::string_view in{buf};
for (auto scope = Runner::the->interpreter.env.get(); scope != nullptr; scope = scope->parent.get()) {
for (auto const& [name, _] : scope->variables) {
if (name.starts_with(in)) {
bestlineAddCompletion(lc, name.c_str());
}
}
}
}
/// Fancy main that supports Result forwarding on error (Try macro)
static Result<void> Main(std::span<char const*> args)
{
@ -189,8 +214,9 @@ static Result<void> Main(std::span<char const*> args)
if (runnables.empty() || enable_repl) {
enable_repl = true;
bestlineSetCompletionCallback(completion);
for (;;) {
char *input_buffer = readline("> ");
char *input_buffer = bestlineWithHistory("> ", "musique");
if (input_buffer == nullptr) {
break;
}
@ -208,8 +234,6 @@ static Result<void> Main(std::span<char const*> args)
continue;
}
add_history(input_buffer);
if (command.starts_with(':')) {
command.remove_prefix(1);
if (command == "exit") { break; }
@ -234,10 +258,6 @@ static Result<void> Main(std::span<char const*> args)
int main(int argc, char const** argv)
{
rl_readline_name = *argv;
// Set editing mode to Vim-like
rl_editing_mode = 0;
auto const args = std::span(argv, argc).subspan(1);
auto const result = Main(args);
if (not result.has_value()) {