Refactor app services and stabilize refresh/UI flows
- Add refresh scheduler and network refresh service boundaries for typed refresh results, ordered RPC collectors, applicators, and price parsing. - Add daemon lifecycle and wallet security workflow helpers while preserving App-owned command RPC, decrypt, cancellation, and UI handoff behavior. - Split balance, console, mining, amount formatting, and async task logic into focused modules with expanded Phase 4 test coverage. - Fix market price loading by triggering price refresh immediately, avoiding queue-pressure drops, tracking loading/error state, and adding translations. - Polish send, explorer, peers, settings, theme/schema, and related tab UI. - Replace checked-in generated language headers with build-generated resources. - Document the cleanup audit, UI static-state guidance, and architecture updates.
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
// tab completion, daemon log display, and color-coded output.
|
||||
|
||||
#include "console_tab.h"
|
||||
#include "console_command_reference.h"
|
||||
#include "console_input_model.h"
|
||||
#include "console_output_model.h"
|
||||
#include "console_tab_helpers.h"
|
||||
#include "../material/colors.h"
|
||||
#include "../material/type.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
@@ -213,13 +217,18 @@ void ConsoleTab::render(daemon::EmbeddedDaemon* daemon, rpc::RPCClient* rpc, rpc
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Output area (scrollable) — glass panel background
|
||||
float frameH = ImGui::GetFrameHeightWithSpacing();
|
||||
float itemSp = ImGui::GetStyle().ItemSpacing.y;
|
||||
float input_height = (Layout::spacingSm() + itemSp) // Dummy(0,sm) + spacing
|
||||
+ frameH + Layout::spacingSm() + Layout::spacingXs() + schema::UI().drawElement("tabs.console", "input-cursor-offset").size; // input glass panel + cursor offset
|
||||
float outputH = ImGui::GetContentRegionAvail().y - input_height;
|
||||
float availHeight = ImGui::GetContentRegionAvail().y;
|
||||
if (outputH < std::max(schema::UI().drawElement("tabs.console", "output-min-height").size, availHeight * schema::UI().drawElement("tabs.console", "output-min-height-ratio").size)) outputH = std::max(schema::UI().drawElement("tabs.console", "output-min-height").size, availHeight * schema::UI().drawElement("tabs.console", "output-min-height-ratio").size);
|
||||
float input_height = ComputeConsoleInputHeight(
|
||||
ImGui::GetFrameHeightWithSpacing(),
|
||||
ImGui::GetStyle().ItemSpacing.y,
|
||||
Layout::spacingSm(),
|
||||
Layout::spacingXs(),
|
||||
schema::UI().drawElement("tabs.console", "input-cursor-offset").size);
|
||||
float outputH = ComputeConsoleOutputHeight(
|
||||
availHeight,
|
||||
input_height,
|
||||
schema::UI().drawElement("tabs.console", "output-min-height").size,
|
||||
schema::UI().drawElement("tabs.console", "output-min-height-ratio").size);
|
||||
|
||||
ImDrawList* dlOut = ImGui::GetWindowDrawList();
|
||||
ImVec2 outPanelMin = ImGui::GetCursorScreenPos();
|
||||
@@ -600,28 +609,14 @@ void ConsoleTab::renderOutput()
|
||||
output_scroll_y_ = ImGui::GetScrollY();
|
||||
|
||||
// Build filtered line index list BEFORE mouse handling (so screenToTextPos works)
|
||||
std::string filter_str(filter_text_);
|
||||
bool has_text_filter = !filter_str.empty();
|
||||
bool hide_daemon = !s_daemon_messages_enabled;
|
||||
bool errors_only = s_errors_only_enabled;
|
||||
bool has_filter = has_text_filter || hide_daemon || errors_only;
|
||||
ConsoleOutputFilter outputFilter{filter_text_, s_daemon_messages_enabled,
|
||||
s_errors_only_enabled, COLOR_DAEMON, COLOR_ERROR};
|
||||
bool has_text_filter = !outputFilter.text.empty();
|
||||
bool has_filter = has_text_filter || !outputFilter.daemonMessagesEnabled || outputFilter.errorsOnly;
|
||||
visible_indices_.clear();
|
||||
if (has_filter) {
|
||||
std::string filter_lower;
|
||||
if (has_text_filter) {
|
||||
filter_lower = filter_str;
|
||||
std::transform(filter_lower.begin(), filter_lower.end(), filter_lower.begin(), ::tolower);
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(lines_.size()); i++) {
|
||||
// Skip daemon lines when daemon toggle is off
|
||||
if (hide_daemon && lines_[i].color == COLOR_DAEMON) continue;
|
||||
// When errors-only is enabled, skip non-error lines
|
||||
if (errors_only && lines_[i].color != COLOR_ERROR) continue;
|
||||
if (has_text_filter) {
|
||||
std::string lower = lines_[i].text;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
|
||||
if (lower.find(filter_lower) == std::string::npos) continue;
|
||||
}
|
||||
if (!consoleLinePassesFilter(lines_[i].text, lines_[i].color, outputFilter)) continue;
|
||||
visible_indices_.push_back(i);
|
||||
}
|
||||
} else {
|
||||
@@ -636,8 +631,7 @@ void ConsoleTab::renderOutput()
|
||||
// Each segment records which bytes of the source text appear on that visual
|
||||
// row, so hit-testing and selection highlight can map screen positions to
|
||||
// exact character offsets.
|
||||
float wrap_width = ImGui::GetContentRegionAvail().x - padX * 2;
|
||||
if (wrap_width < 50.0f) wrap_width = 50.0f;
|
||||
float wrap_width = ClampConsoleWrapWidth(ImGui::GetContentRegionAvail().x, padX);
|
||||
|
||||
ImFont* font = ImGui::GetFont();
|
||||
float fontSize = ImGui::GetFontSize();
|
||||
@@ -1169,106 +1163,37 @@ void ConsoleTab::renderInput(rpc::RPCClient* rpc, rpc::RPCWorker* worker)
|
||||
if (console->command_history_.empty()) return 0;
|
||||
|
||||
int prev_index = console->history_index_;
|
||||
|
||||
if (data->EventKey == ImGuiKey_UpArrow) {
|
||||
if (console->history_index_ < 0) {
|
||||
console->history_index_ = static_cast<int>(console->command_history_.size()) - 1;
|
||||
} else if (console->history_index_ > 0) {
|
||||
console->history_index_--;
|
||||
}
|
||||
} else if (data->EventKey == ImGuiKey_DownArrow) {
|
||||
if (console->history_index_ >= 0) {
|
||||
console->history_index_++;
|
||||
if (console->history_index_ >= static_cast<int>(console->command_history_.size())) {
|
||||
console->history_index_ = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
console->history_index_ = NavigateConsoleHistoryIndex(
|
||||
console->history_index_,
|
||||
console->command_history_.size(),
|
||||
data->EventKey == ImGuiKey_UpArrow);
|
||||
|
||||
if (prev_index != console->history_index_) {
|
||||
const char* history_str = (console->history_index_ >= 0)
|
||||
? console->command_history_[console->history_index_].c_str()
|
||||
: "";
|
||||
std::string history = ConsoleHistoryEntry(console->command_history_, console->history_index_);
|
||||
data->DeleteChars(0, data->BufTextLen);
|
||||
data->InsertChars(0, history_str);
|
||||
data->InsertChars(0, history.c_str());
|
||||
}
|
||||
}
|
||||
else if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion) {
|
||||
// Tab completion for common RPC commands
|
||||
static const char* commands[] = {
|
||||
// Control
|
||||
"help", "getinfo", "stop",
|
||||
// Network
|
||||
"getnetworkinfo", "getpeerinfo", "getconnectioncount",
|
||||
"addnode", "setban", "listbanned", "clearbanned", "ping",
|
||||
// Blockchain
|
||||
"getblockchaininfo", "getblockcount", "getbestblockhash",
|
||||
"getblock", "getblockhash", "getblockheader", "getdifficulty",
|
||||
"getrawmempool", "gettxout", "coinsupply", "getchaintips",
|
||||
// Mining
|
||||
"getmininginfo", "setgenerate", "getgenerate",
|
||||
"getnetworkhashps", "getblocksubsidy",
|
||||
// Wallet
|
||||
"getbalance", "z_gettotalbalance", "z_getbalances",
|
||||
"getnewaddress", "z_getnewaddress",
|
||||
"listaddresses", "z_listaddresses",
|
||||
"sendtoaddress", "z_sendmany",
|
||||
"listtransactions", "listunspent", "z_listunspent",
|
||||
"z_getoperationstatus", "z_getoperationresult",
|
||||
"getwalletinfo", "backupwallet",
|
||||
"dumpprivkey", "importprivkey",
|
||||
"z_exportkey", "z_importkey",
|
||||
"signmessage", "settxfee",
|
||||
// Raw Transactions
|
||||
"createrawtransaction", "decoderawtransaction",
|
||||
"getrawtransaction", "sendrawtransaction", "signrawtransaction",
|
||||
// Utility
|
||||
"validateaddress", "z_validateaddress", "estimatefee",
|
||||
// Built-in
|
||||
"clear"
|
||||
};
|
||||
|
||||
std::string input(data->Buf);
|
||||
if (!input.empty()) {
|
||||
// Collect all matches
|
||||
std::vector<const char*> matches;
|
||||
for (const char* cmd : commands) {
|
||||
if (strncmp(cmd, input.c_str(), input.length()) == 0) {
|
||||
matches.push_back(cmd);
|
||||
}
|
||||
}
|
||||
auto completion = CompleteConsoleCommand(input);
|
||||
|
||||
if (matches.size() == 1) {
|
||||
if (completion.matches.size() == 1) {
|
||||
// Single match — complete it
|
||||
data->DeleteChars(0, data->BufTextLen);
|
||||
data->InsertChars(0, matches[0]);
|
||||
} else if (matches.size() > 1) {
|
||||
data->InsertChars(0, completion.matches.front().c_str());
|
||||
} else if (completion.matches.size() > 1) {
|
||||
// Multiple matches — show list in console and complete common prefix
|
||||
console->addLine(TR("console_completions"), ConsoleTab::COLOR_INFO);
|
||||
std::string line = " ";
|
||||
for (size_t m = 0; m < matches.size(); m++) {
|
||||
if (m > 0) line += " ";
|
||||
line += matches[m];
|
||||
if (line.length() > 60) {
|
||||
console->addLine(line, ConsoleTab::COLOR_RESULT);
|
||||
line = " ";
|
||||
}
|
||||
}
|
||||
if (line.length() > 2) {
|
||||
for (const auto& line : FormatConsoleCompletionLines(completion.matches)) {
|
||||
console->addLine(line, ConsoleTab::COLOR_RESULT);
|
||||
}
|
||||
|
||||
// Complete to longest common prefix
|
||||
std::string prefix = matches[0];
|
||||
for (size_t m = 1; m < matches.size(); m++) {
|
||||
size_t len = 0;
|
||||
while (len < prefix.length() && len < strlen(matches[m]) &&
|
||||
prefix[len] == matches[m][len]) len++;
|
||||
prefix = prefix.substr(0, len);
|
||||
}
|
||||
if (prefix.length() > input.length()) {
|
||||
if (completion.commonPrefix.length() > input.length()) {
|
||||
data->DeleteChars(0, data->BufTextLen);
|
||||
data->InsertChars(0, prefix.c_str());
|
||||
data->InsertChars(0, completion.commonPrefix.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1312,117 +1237,7 @@ void ConsoleTab::renderCommandsPopup()
|
||||
ImGui::InputTextWithHint("##CmdSearch", TR("console_search_commands"), cmdFilter, sizeof(cmdFilter));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// Command entries
|
||||
struct CmdEntry { const char* name; const char* desc; const char* params; };
|
||||
|
||||
static const CmdEntry controlCmds[] = {
|
||||
{"help", "List all commands, or get help for a specified command", "[\"command\"]"},
|
||||
{"getinfo", "Get general info about the node", ""},
|
||||
{"stop", "Stop the daemon", ""},
|
||||
};
|
||||
static const CmdEntry networkCmds[] = {
|
||||
{"getnetworkinfo", "Return P2P network state info", ""},
|
||||
{"getpeerinfo", "Get data about each connected peer", ""},
|
||||
{"getconnectioncount", "Get number of peer connections", ""},
|
||||
{"getnettotals", "Get network traffic statistics", ""},
|
||||
{"addnode", "Add, remove, or connect to a node", "\"node\" \"add|remove|onetry\""},
|
||||
{"setban", "Add or remove an IP/subnet from the ban list", "\"ip\" \"add|remove\" [bantime] [absolute]"},
|
||||
{"listbanned", "List all banned IPs/subnets", ""},
|
||||
{"clearbanned", "Clear all banned IPs", ""},
|
||||
{"ping", "Ping all peers to measure round-trip time", ""},
|
||||
};
|
||||
static const CmdEntry blockchainCmds[] = {
|
||||
{"getblockchaininfo", "Get current blockchain state", ""},
|
||||
{"getblockcount", "Get number of blocks in longest chain", ""},
|
||||
{"getbestblockhash", "Get hash of the tip block", ""},
|
||||
{"getblock", "Get block data for a given hash or height", "\"hash|height\" [verbosity]"},
|
||||
{"getblockhash", "Get block hash at a given height", "height"},
|
||||
{"getblockheader", "Get block header for a given hash", "\"hash\" [verbose]"},
|
||||
{"getdifficulty", "Get proof-of-work difficulty", ""},
|
||||
{"getrawmempool", "Get all txids in mempool", "[verbose]"},
|
||||
{"getmempoolinfo", "Get mempool state info", ""},
|
||||
{"gettxout", "Get details about an unspent output", "\"txid\" n [includemempool]"},
|
||||
{"coinsupply", "Get coin supply information", "[height]"},
|
||||
{"getchaintips", "Get all known chain tips", ""},
|
||||
{"getchaintxstats", "Get chain transaction statistics", "[nblocks] [\"blockhash\"]"},
|
||||
{"verifychain", "Verify the blockchain database", "[checklevel] [numblocks]"},
|
||||
{"kvsearch", "Search the blockchain key-value store", "\"key\""},
|
||||
{"kvupdate", "Update a key-value pair on-chain", "\"key\" \"value\" days"},
|
||||
};
|
||||
static const CmdEntry miningCmds[] = {
|
||||
{"getmininginfo", "Get mining-related information", ""},
|
||||
{"setgenerate", "Turn mining on or off (true/false [threads])", "generate [genproclimit]"},
|
||||
{"getgenerate", "Check if the node is mining", ""},
|
||||
{"getnetworkhashps", "Get estimated network hash rate", "[blocks] [height]"},
|
||||
{"getblocksubsidy", "Get block reward at a given height", "[height]"},
|
||||
{"getblocktemplate", "Get block template for mining", "[\"jsonrequestobject\"]"},
|
||||
{"submitblock", "Submit a mined block to the network", "\"hexdata\""},
|
||||
};
|
||||
static const CmdEntry walletCmds[] = {
|
||||
{"getbalance", "Get wallet transparent balance", "[\"account\"] [minconf]"},
|
||||
{"z_gettotalbalance", "Get total transparent + shielded balance", "[minconf]"},
|
||||
{"z_getbalances", "Get all balances (transparent + shielded)", ""},
|
||||
{"getnewaddress", "Generate a new transparent address", ""},
|
||||
{"z_getnewaddress", "Generate a new shielded address", "[\"type\"]"},
|
||||
{"listaddresses", "List all transparent addresses", ""},
|
||||
{"z_listaddresses", "List all z-addresses", ""},
|
||||
{"sendtoaddress", "Send to a specific address", "\"address\" amount"},
|
||||
{"z_sendmany", "Send to multiple z/t-addresses with shielded support", "\"fromaddress\" [{\"address\":\"...\",\"amount\":...}]"},
|
||||
{"z_shieldcoinbase", "Shield transparent coinbase funds to a z-address", "\"fromaddress\" \"tozaddress\" [fee] [limit]"},
|
||||
{"z_mergetoaddress", "Merge multiple UTXOs/notes to one address", "[\"fromaddress\",...] \"toaddress\" [fee] [limit]"},
|
||||
{"listtransactions", "List recent wallet transactions", "[\"account\"] [count] [from]"},
|
||||
{"listunspent", "List unspent transaction outputs", "[minconf] [maxconf]"},
|
||||
{"z_listunspent", "List unspent shielded notes", "[minconf] [maxconf]"},
|
||||
{"z_getoperationstatus", "Get status of async z operations", "[\"operationid\",...]"},
|
||||
{"z_getoperationresult", "Get result of completed z operations", "[\"operationid\",...]"},
|
||||
{"z_listoperationids", "List all async z operation IDs", ""},
|
||||
{"getwalletinfo", "Get wallet state info", ""},
|
||||
{"backupwallet", "Back up wallet to a file", "\"destination\""},
|
||||
{"dumpprivkey", "Dump private key for an address", "\"address\""},
|
||||
{"importprivkey", "Import a private key into the wallet", "\"privkey\" [\"label\"] [rescan]"},
|
||||
{"dumpwallet", "Dump all wallet keys to a file", "\"filename\""},
|
||||
{"importwallet", "Import wallet from a dump file", "\"filename\""},
|
||||
{"z_exportkey", "Export spending key for a z-address", "\"zaddr\""},
|
||||
{"z_importkey", "Import a z-address spending key", "\"zkey\" [rescan] [startheight]"},
|
||||
{"z_exportviewingkey", "Export viewing key for a z-address", "\"zaddr\""},
|
||||
{"z_importviewingkey", "Import a z-address viewing key", "\"vkey\" [rescan] [startheight]"},
|
||||
{"z_exportwallet", "Export all wallet keys (including z-keys) to file", "\"filename\""},
|
||||
{"signmessage", "Sign a message with an address key", "\"address\" \"message\""},
|
||||
{"settxfee", "Set the transaction fee per kB", "amount"},
|
||||
{"walletpassphrase", "Unlock the wallet with passphrase", "\"passphrase\" timeout"},
|
||||
{"walletlock", "Lock the wallet", ""},
|
||||
{"encryptwallet", "Encrypt the wallet with a passphrase", "\"passphrase\""},
|
||||
};
|
||||
static const CmdEntry rawTxCmds[] = {
|
||||
{"createrawtransaction", "Create a raw transaction spending given inputs", "[{\"txid\":\"...\",\"vout\":n},...] {\"address\":amount,...}"},
|
||||
{"decoderawtransaction", "Decode raw transaction hex string", "\"hexstring\""},
|
||||
{"decodescript", "Decode a hex-encoded script", "\"hex\""},
|
||||
{"getrawtransaction", "Get raw transaction data by txid", "\"txid\" [verbose]"},
|
||||
{"sendrawtransaction", "Submit raw transaction to the network", "\"hexstring\" [allowhighfees]"},
|
||||
{"signrawtransaction", "Sign a raw transaction with private keys", "\"hexstring\""},
|
||||
{"fundrawtransaction", "Add inputs to meet output value", "\"hexstring\""},
|
||||
};
|
||||
static const CmdEntry utilCmds[] = {
|
||||
{"validateaddress", "Validate a transparent address", "\"address\""},
|
||||
{"z_validateaddress", "Validate a z-address", "\"zaddr\""},
|
||||
{"estimatefee", "Estimate fee for a transaction", "nblocks"},
|
||||
{"verifymessage", "Verify a signed message", "\"address\" \"signature\" \"message\""},
|
||||
{"createmultisig", "Create a multisig address", "nrequired [\"key\",...]"},
|
||||
{"invalidateblock", "Mark a block as invalid", "\"hash\""},
|
||||
{"reconsiderblock", "Reconsider a previously invalidated block", "\"hash\""},
|
||||
};
|
||||
|
||||
struct CmdCategory { const char* name; const CmdEntry* commands; int count; };
|
||||
|
||||
static const CmdCategory categories[] = {
|
||||
{"Control", controlCmds, IM_ARRAYSIZE(controlCmds)},
|
||||
{"Network", networkCmds, IM_ARRAYSIZE(networkCmds)},
|
||||
{"Blockchain", blockchainCmds, IM_ARRAYSIZE(blockchainCmds)},
|
||||
{"Mining", miningCmds, IM_ARRAYSIZE(miningCmds)},
|
||||
{"Wallet", walletCmds, IM_ARRAYSIZE(walletCmds)},
|
||||
{"Raw Transactions", rawTxCmds, IM_ARRAYSIZE(rawTxCmds)},
|
||||
{"Utility", utilCmds, IM_ARRAYSIZE(utilCmds)},
|
||||
};
|
||||
const auto& categories = consoleCommandCategories();
|
||||
|
||||
std::string filter(cmdFilter);
|
||||
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
|
||||
@@ -1602,12 +1417,7 @@ void ConsoleTab::executeCommand(const std::string& cmd, rpc::RPCClient* rpc, rpc
|
||||
{
|
||||
using namespace material;
|
||||
// Add to history (avoid duplicates)
|
||||
if (command_history_.empty() || command_history_.back() != cmd) {
|
||||
command_history_.push_back(cmd);
|
||||
if (command_history_.size() > 100) {
|
||||
command_history_.erase(command_history_.begin());
|
||||
}
|
||||
}
|
||||
AppendConsoleHistory(command_history_, cmd, 100);
|
||||
history_index_ = -1;
|
||||
|
||||
// Echo command
|
||||
@@ -1645,77 +1455,11 @@ void ConsoleTab::executeCommand(const std::string& cmd, rpc::RPCClient* rpc, rpc
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse command and arguments (shell-like: handles quotes and JSON brackets)
|
||||
std::vector<std::string> args;
|
||||
{
|
||||
size_t i = 0;
|
||||
size_t len = cmd.size();
|
||||
while (i < len) {
|
||||
// Skip whitespace
|
||||
while (i < len && (cmd[i] == ' ' || cmd[i] == '\t')) i++;
|
||||
if (i >= len) break;
|
||||
auto call = BuildConsoleRpcCall(cmd);
|
||||
if (!call.valid) return;
|
||||
|
||||
std::string tok;
|
||||
if (cmd[i] == '"' || cmd[i] == '\'') {
|
||||
// Quoted string — collect until matching close quote
|
||||
char quote = cmd[i++];
|
||||
while (i < len && cmd[i] != quote) tok += cmd[i++];
|
||||
if (i < len) i++; // skip closing quote
|
||||
} else if (cmd[i] == '[' || cmd[i] == '{') {
|
||||
// JSON array/object — collect until matching bracket
|
||||
char open = cmd[i];
|
||||
char close = (open == '[') ? ']' : '}';
|
||||
int depth = 0;
|
||||
while (i < len) {
|
||||
if (cmd[i] == open) depth++;
|
||||
else if (cmd[i] == close) depth--;
|
||||
tok += cmd[i++];
|
||||
if (depth == 0) break;
|
||||
}
|
||||
} else {
|
||||
// Unquoted token — collect until whitespace
|
||||
while (i < len && cmd[i] != ' ' && cmd[i] != '\t') tok += cmd[i++];
|
||||
}
|
||||
if (!tok.empty()) args.push_back(tok);
|
||||
}
|
||||
}
|
||||
|
||||
if (args.empty()) return;
|
||||
|
||||
std::string method = args[0];
|
||||
nlohmann::json params = nlohmann::json::array();
|
||||
|
||||
// Convert remaining args to JSON params
|
||||
for (size_t i = 1; i < args.size(); i++) {
|
||||
const std::string& arg = args[i];
|
||||
|
||||
// Try to parse as JSON first (handles objects, arrays, etc.)
|
||||
if (!arg.empty() && (arg[0] == '{' || arg[0] == '[')) {
|
||||
auto parsed = nlohmann::json::parse(arg, nullptr, false);
|
||||
if (!parsed.is_discarded()) {
|
||||
params.push_back(parsed);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to parse as number or bool
|
||||
if (arg == "true") {
|
||||
params.push_back(true);
|
||||
} else if (arg == "false") {
|
||||
params.push_back(false);
|
||||
} else {
|
||||
try {
|
||||
if (arg.find('.') != std::string::npos) {
|
||||
params.push_back(std::stod(arg));
|
||||
} else {
|
||||
params.push_back(std::stoll(arg));
|
||||
}
|
||||
} catch (...) {
|
||||
// Keep as string
|
||||
params.push_back(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string method = call.method;
|
||||
nlohmann::json params = call.params;
|
||||
|
||||
// Execute RPC call on worker thread to avoid blocking UI
|
||||
if (worker) {
|
||||
@@ -1726,9 +1470,6 @@ void ConsoleTab::executeCommand(const std::string& cmd, rpc::RPCClient* rpc, rpc
|
||||
bool is_error = false;
|
||||
try {
|
||||
result_str = rpc->callRaw(method, params);
|
||||
if (result_str == "null") {
|
||||
result_str = "(no result)";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
result_str = e.what();
|
||||
is_error = true;
|
||||
@@ -1736,51 +1477,22 @@ void ConsoleTab::executeCommand(const std::string& cmd, rpc::RPCClient* rpc, rpc
|
||||
return [result_str, is_error, self]() {
|
||||
// Process results on main thread where ImGui colors are available
|
||||
using namespace material;
|
||||
if (is_error) {
|
||||
self->addLine("Error: " + result_str, COLOR_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_json = false;
|
||||
if (!result_str.empty()) {
|
||||
char first = result_str[0];
|
||||
is_json = (first == '{' || first == '[');
|
||||
}
|
||||
ImU32 json_key_col = WithAlpha(Secondary(), 255);
|
||||
ImU32 json_str_col = WithAlpha(Success(), 255);
|
||||
ImU32 json_num_col = WithAlpha(Warning(), 255);
|
||||
ImU32 json_brace_col = IM_COL32(200, 200, 200, 150);
|
||||
|
||||
std::istringstream stream(result_str);
|
||||
std::string line;
|
||||
while (std::getline(stream, line)) {
|
||||
if (is_json && !line.empty()) {
|
||||
std::string trimmed = line;
|
||||
size_t first = trimmed.find_first_not_of(" \t");
|
||||
if (first != std::string::npos) trimmed = trimmed.substr(first);
|
||||
|
||||
ImU32 lineCol = COLOR_RESULT;
|
||||
if (trimmed[0] == '{' || trimmed[0] == '}' ||
|
||||
trimmed[0] == '[' || trimmed[0] == ']') {
|
||||
lineCol = json_brace_col;
|
||||
} else if (trimmed[0] == '\"') {
|
||||
size_t colon = trimmed.find("\": ");
|
||||
if (colon != std::string::npos || trimmed.find("\":") != std::string::npos) {
|
||||
lineCol = json_key_col;
|
||||
} else {
|
||||
lineCol = json_str_col;
|
||||
}
|
||||
} else if (std::isdigit(trimmed[0]) || trimmed[0] == '-') {
|
||||
lineCol = json_num_col;
|
||||
} else if (trimmed == "true," || trimmed == "false," ||
|
||||
trimmed == "true" || trimmed == "false" ||
|
||||
trimmed == "null," || trimmed == "null") {
|
||||
lineCol = json_num_col;
|
||||
}
|
||||
self->addLine(line, lineCol);
|
||||
} else {
|
||||
self->addLine(line, COLOR_RESULT);
|
||||
for (const auto& resultLine : FormatConsoleRpcResultLines(result_str, is_error)) {
|
||||
ImU32 lineCol = COLOR_RESULT;
|
||||
switch (resultLine.role) {
|
||||
case ConsoleResultLineRole::Error: lineCol = COLOR_ERROR; break;
|
||||
case ConsoleResultLineRole::JsonKey: lineCol = json_key_col; break;
|
||||
case ConsoleResultLineRole::JsonString: lineCol = json_str_col; break;
|
||||
case ConsoleResultLineRole::JsonNumber: lineCol = json_num_col; break;
|
||||
case ConsoleResultLineRole::JsonBrace: lineCol = json_brace_col; break;
|
||||
case ConsoleResultLineRole::Result: break;
|
||||
}
|
||||
self->addLine(resultLine.text, lineCol);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1788,14 +1500,13 @@ void ConsoleTab::executeCommand(const std::string& cmd, rpc::RPCClient* rpc, rpc
|
||||
// Fallback: synchronous execution if no worker available
|
||||
try {
|
||||
std::string result_str = rpc->callRaw(method, params);
|
||||
if (result_str == "null") result_str = "(no result)";
|
||||
std::istringstream stream(result_str);
|
||||
std::string line;
|
||||
while (std::getline(stream, line)) {
|
||||
addLine(line, COLOR_RESULT);
|
||||
for (const auto& resultLine : FormatConsoleRpcResultLines(result_str, false)) {
|
||||
addLine(resultLine.text, COLOR_RESULT);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
addLine("Error: " + std::string(e.what()), COLOR_ERROR);
|
||||
for (const auto& resultLine : FormatConsoleRpcResultLines(e.what(), true)) {
|
||||
addLine(resultLine.text, COLOR_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user