REPL: Add clear-screen / auto-complete / basic hints and syntax highlighting (#316)

* swap to replxx from linenoise

* repl: Implement form auto-tab-completion

* repl: color coordinate the prompts

* repl: Add some basic syntax highlighting, bracket pairs and forms (all one color)

* repl: A more consistent starting screen for the repl

* repl: bug fix for auto-complete

* debug linux

* linting
This commit is contained in:
Tyler Wilding
2021-03-07 20:41:21 -08:00
committed by GitHub
parent 9074a35b9b
commit 8bba3d7fd7
58 changed files with 12141 additions and 2569 deletions
+116 -50
View File
@@ -11,7 +11,9 @@
#include "common/util/FileUtil.h"
#include "goalc/data_compiler/game_text.h"
#include "goalc/data_compiler/game_count.h"
#include "common/goos/ReplHistory.h"
#include "common/goos/ReplUtils.h"
#include <regex>
#include <stack>
/*!
* Exit the compiler. Disconnects the listener and tells the target to reset itself.
@@ -31,7 +33,7 @@ Val* Compiler::compile_exit(const goos::Object& form, const goos::Object& rest,
}
// flag for the REPL.
m_want_exit = true;
ReplHistory::repl_save_history();
m_repl->save_history();
return get_none();
}
@@ -198,52 +200,7 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
* Simple help / documentation command
*/
Val* Compiler::compile_repl_help(const goos::Object&, const goos::Object&, Env*) {
fmt::print(fmt::emphasis::bold, "\nREPL Controls:\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(e)\n");
fmt::print(" - Exit the compiler once the current REPL command is finished\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt [ip-address] [port-number])\n");
fmt::print(
" - Connect the listener to a running target. The IP address defaults to `127.0.0.1` and the "
"port to `8112`\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(r [ip-address] [port-number])\n");
fmt::print(
" - Attempt to reset the target and reconnect. After this, the target will have nothing "
"loaded.\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(:status)\n");
fmt::print(" - Send a ping-like message to the target. Requires the target to be connected\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(shutdown-target)\n");
fmt::print(" - If the target is connected, make it exit\n");
fmt::print(fmt::emphasis::bold, "\nCompiling & Building:\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(m \"filename\")\n");
fmt::print(" - Compile an OpenGOAL source file\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(ml \"filename\")\n");
fmt::print(" - Compile and Load an OpenGOAL source file\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(build-game)\n");
fmt::print(" - Loads and builds all game files and rebuilds DGOs\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(build-kernel)\n");
fmt::print(" - Similar to (build-game) but only the kernel files\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(blg)\n");
fmt::print(" - Performs a (build-game) and then loads all CGOs\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow),
"(build-dgos \"path/to/dgos/description/file\")\n");
fmt::print(
" - Builds all the DGO files described in the DGO description file. See "
"`goal_src/builds/dgos.txt` for an example.\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow),
"(asm-data-file tool-name \"file-name\")\n");
fmt::print(
" - Build a data file. The `tool-name` refers to which data building tool should be used.\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(build-data)\n");
fmt::print(" - Macro for rebuilding all data files\n");
fmt::print(fmt::emphasis::bold, "\nOther:\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::magenta), "(gs)\n");
fmt::print(" - Enter a GOOS REPL\n");
fmt::print(fmt::emphasis::bold | fg(fmt::color::magenta),
"(set-config! config-name config-value)\n");
fmt::print(" - Used to set compiler configuration\n");
m_repl.get()->print_help_message();
return get_none();
}
@@ -282,6 +239,13 @@ Val* Compiler::compile_listen_to_target(const goos::Object& form,
return get_none();
}
Val* Compiler::compile_repl_clear_screen(const goos::Object& form,
const goos::Object& rest,
Env* env) {
m_repl.get()->clear_screen();
return get_none();
}
/*!
* Send the target a command to reset, which totally resets the state of the target.
* Optionally takes a :shutdown command which causes the exec_runtime function of the target
@@ -321,7 +285,7 @@ Val* Compiler::compile_gs(const goos::Object& form, const goos::Object& rest, En
(void)env;
auto args = get_va(form, rest);
va_check(form, args, {}, {});
m_goos.execute_repl();
m_goos.execute_repl(*m_repl.get());
return get_none();
}
@@ -443,6 +407,108 @@ Val* Compiler::compile_get_info(const goos::Object& form, const goos::Object& re
return get_none();
}
Replxx::completions_t Compiler::find_symbols_by_prefix(std::string const& context,
int& contextLen,
std::vector<std::string> const& user_data) {
auto token = m_repl.get()->get_current_repl_token(context);
auto possible_forms = m_symbol_info.lookup_symbols_starting_with(token.first);
Replxx::completions_t completions;
for (auto& x : possible_forms) {
completions.push_back(token.second ? "(" + x : x);
}
return completions;
}
Replxx::hints_t Compiler::find_hints_by_prefix(std::string const& context,
int& contextLen,
Replxx::Color& color,
std::vector<std::string> const& user_data) {
auto token = m_repl.get()->get_current_repl_token(context);
auto possible_forms = m_symbol_info.lookup_symbols_starting_with(token.first);
Replxx::hints_t hints;
// Only show hints if there are <= 3 possibilities
if (possible_forms.size() <= 3) {
for (auto& x : possible_forms) {
hints.push_back(token.second ? "(" + x : x);
}
}
// set hint color to green if single match found
if (hints.size() == 1) {
color = Replxx::Color::GREEN;
}
return hints;
}
void Compiler::repl_coloring(
std::string const& context,
Replxx::colors_t& colors,
std::vector<std::pair<std::string, Replxx::Color>> const& regex_color) {
using cl = Replxx::Color;
// TODO - a proper circular queue would be cleaner to use
std::deque<cl> paren_colors = {cl::GREEN, cl::CYAN, cl::MAGENTA};
std::stack<std::pair<char, cl>> expression_stack;
std::pair<int, std::string> curr_symbol = {-1, ""};
for (std::string::size_type i = 0; i < context.size(); i++) {
char curr = context.at(i);
// We lookup every potential symbol and color it based on it's type
if (std::isspace(curr) || curr == ')') {
// Lookup the symbol, if its legit, color it
if (!curr_symbol.second.empty() && curr_symbol.second.at(0) == '(') {
curr_symbol.second.erase(0, 1);
curr_symbol.first++;
}
std::vector<SymbolInfo>* sym_match = m_symbol_info.lookup_exact_name(curr_symbol.second);
if (sym_match != nullptr && sym_match->size() == 1) {
SymbolInfo sym_info = sym_match->at(0);
for (int pos = curr_symbol.first; pos <= i; pos++) {
// TODO - currently just coloring all types brown/gold
// - would be nice to have a different color for globals, functions, etc
colors.at(pos) = cl::BROWN;
}
}
curr_symbol = {-1, ""};
} else {
if (curr_symbol.first == -1) {
curr_symbol.first = i;
}
curr_symbol.second += curr;
}
// Rainbow paren coloring and known-form coloring
if (curr == '(') {
cl color = paren_colors.front();
expression_stack.push({curr, color});
colors.at(i) = color;
paren_colors.pop_front();
paren_colors.push_back(color);
} else if (curr == ')') {
if (expression_stack.empty()) {
colors.at(i) = cl::RED;
} else {
auto& matching_paren = expression_stack.top();
expression_stack.pop();
if (matching_paren.first == '(') {
if (i == context.size() - 1 && !expression_stack.empty()) {
colors.at(i) = cl::RED;
} else {
colors.at(i) = matching_paren.second;
}
}
}
}
// Reset the color order
if (expression_stack.empty()) {
paren_colors = {cl::GREEN, cl::CYAN, cl::MAGENTA};
}
}
// TODO - general syntax highlighting with regexes (quotes, symbols, etc)
}
Val* Compiler::compile_autocomplete(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
@@ -460,4 +526,4 @@ Val* Compiler::compile_autocomplete(const goos::Object& form, const goos::Object
m_symbol_info.symbol_count(), time);
return get_none();
}
}