mirror of
https://github.com/open-goal/jak-project
synced 2026-09-09 12:14:10 -04:00
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:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user