GNU Readline replace with bestline
This commit is contained in:
parent
e6aed50fe9
commit
7d0f732a57
14
Makefile
14
Makefile
@ -1,6 +1,6 @@
|
|||||||
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
|
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
|
||||||
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result
|
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result
|
||||||
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Ilib/midi/include -Isrc/
|
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Ilib/midi/include -Isrc/ -Ilib/bestline/
|
||||||
RELEASE_FLAGS=-O3
|
RELEASE_FLAGS=-O3
|
||||||
DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined
|
DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined
|
||||||
CXX=g++
|
CXX=g++
|
||||||
@ -49,18 +49,22 @@ bin/%.o: src/%.cc src/*.hh
|
|||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
@$(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 $@"
|
@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 $@"
|
@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
|
bin/debug/%.o: src/%.cc src/*.hh
|
||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
@$(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
|
unit-tests: bin/unit-tests
|
||||||
./$<
|
./$<
|
||||||
|
|
||||||
|
30
lib/bestline/LICENSE
Normal file
30
lib/bestline/LICENSE
Normal 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
3578
lib/bestline/bestline.c
Normal file
File diff suppressed because it is too large
Load Diff
39
lib/bestline/bestline.h
Normal file
39
lib/bestline/bestline.h
Normal 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
79
lib/bestline/example.c
Normal 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;
|
||||||
|
}
|
38
src/main.cc
38
src/main.cc
@ -7,8 +7,10 @@
|
|||||||
|
|
||||||
#include <musique.hh>
|
#include <musique.hh>
|
||||||
#include <midi.hh>
|
#include <midi.hh>
|
||||||
#include <readline/readline.h>
|
|
||||||
#include <readline/history.h>
|
extern "C" {
|
||||||
|
#include <bestline.h>
|
||||||
|
}
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
@ -66,6 +68,8 @@ static void trim(std::string_view &s)
|
|||||||
/// Runs interpreter on given source code
|
/// Runs interpreter on given source code
|
||||||
struct Runner
|
struct Runner
|
||||||
{
|
{
|
||||||
|
static inline Runner *the;
|
||||||
|
|
||||||
midi::ALSA alsa;
|
midi::ALSA alsa;
|
||||||
Interpreter interpreter;
|
Interpreter interpreter;
|
||||||
|
|
||||||
@ -73,6 +77,9 @@ struct Runner
|
|||||||
Runner(std::string port)
|
Runner(std::string port)
|
||||||
: alsa("musique")
|
: alsa("musique")
|
||||||
{
|
{
|
||||||
|
assert(the == nullptr, "Only one instance of runner is supported");
|
||||||
|
the = this;
|
||||||
|
|
||||||
alsa.init_sequencer();
|
alsa.init_sequencer();
|
||||||
alsa.connect(port);
|
alsa.connect(port);
|
||||||
interpreter.midi_connection = &alsa;
|
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
|
/// Run given source
|
||||||
Result<void> run(std::string_view source, std::string_view filename, bool output = false)
|
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
|
/// some of the strings are only views into source
|
||||||
std::vector<std::string> eternal_sources;
|
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)
|
/// Fancy main that supports Result forwarding on error (Try macro)
|
||||||
static Result<void> Main(std::span<char const*> args)
|
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) {
|
if (runnables.empty() || enable_repl) {
|
||||||
enable_repl = true;
|
enable_repl = true;
|
||||||
|
bestlineSetCompletionCallback(completion);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
char *input_buffer = readline("> ");
|
char *input_buffer = bestlineWithHistory("> ", "musique");
|
||||||
if (input_buffer == nullptr) {
|
if (input_buffer == nullptr) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -208,8 +234,6 @@ static Result<void> Main(std::span<char const*> args)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
add_history(input_buffer);
|
|
||||||
|
|
||||||
if (command.starts_with(':')) {
|
if (command.starts_with(':')) {
|
||||||
command.remove_prefix(1);
|
command.remove_prefix(1);
|
||||||
if (command == "exit") { break; }
|
if (command == "exit") { break; }
|
||||||
@ -234,10 +258,6 @@ static Result<void> Main(std::span<char const*> args)
|
|||||||
|
|
||||||
int main(int argc, char const** argv)
|
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 args = std::span(argv, argc).subspan(1);
|
||||||
auto const result = Main(args);
|
auto const result = Main(args);
|
||||||
if (not result.has_value()) {
|
if (not result.has_value()) {
|
||||||
|
Loading…
Reference in New Issue
Block a user