feat(console): right-click "Copy value" for hashes/addresses under the cursor
Right-clicking the output now offers "Copy <value>" when a long alphanumeric token (txid/blockhash/address, >=16 chars) is under the cursor — resolved via screenToTextPos. Uses right-click so it never conflicts with the left-drag text selection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -549,6 +549,24 @@ void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec)
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Extract a hash/address-like token (a long alphanumeric run, >= 16 chars) around byte
|
||||
// offset `col` in `text`, for the right-click "Copy value" action. Returns "" if none.
|
||||
std::string extractCopyableToken(const std::string& text, int col)
|
||||
{
|
||||
const int n = static_cast<int>(text.size());
|
||||
auto isTok = [](unsigned char c) { return std::isalnum(c) != 0; };
|
||||
int at = col;
|
||||
if (at >= n || (at >= 0 && !isTok(static_cast<unsigned char>(text[at])))) at--;
|
||||
if (at < 0 || at >= n || !isTok(static_cast<unsigned char>(text[at]))) return {};
|
||||
int s = at, e = at;
|
||||
while (s > 0 && isTok(static_cast<unsigned char>(text[s - 1]))) s--;
|
||||
while (e + 1 < n && isTok(static_cast<unsigned char>(text[e + 1]))) e++;
|
||||
std::string tok = text.substr(s, e - s + 1);
|
||||
return tok.size() >= 16 ? tok : std::string(); // txids/blockhashes/addresses are long
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ConsoleTab::renderOutput()
|
||||
{
|
||||
using namespace material;
|
||||
@@ -929,8 +947,27 @@ void ConsoleTab::renderOutput()
|
||||
ImGui::Dummy(ImVec2(0, ImGui::GetTextLineHeight()));
|
||||
}
|
||||
|
||||
// Capture the hash/address under the cursor on right-click, for "Copy value".
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||
TextPos tp = screenToTextPos(ImGui::GetMousePos(),
|
||||
output_line_height_ > 0.0f ? output_line_height_ : line_height);
|
||||
context_token_.clear();
|
||||
if (tp.line >= 0 && tp.line < static_cast<int>(lines_.size()))
|
||||
context_token_ = extractCopyableToken(lines_[tp.line].text, tp.col);
|
||||
}
|
||||
|
||||
// Right-click context menu
|
||||
if (ImGui::BeginPopupContextWindow("ConsoleContextMenu")) {
|
||||
if (!context_token_.empty()) {
|
||||
std::string shortTok = context_token_.size() > 22
|
||||
? context_token_.substr(0, 12) + "\xE2\x80\xA6" + context_token_.substr(context_token_.size() - 6)
|
||||
: context_token_;
|
||||
std::string label = std::string(TR("console_copy_value")) + " \"" + shortTok + "\"";
|
||||
if (ImGui::MenuItem(label.c_str())) {
|
||||
ImGui::SetClipboardText(context_token_.c_str());
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
if (ImGui::MenuItem(TR("copy"), "Ctrl+C", false, has_selection_)) {
|
||||
std::string selected = getSelectedText();
|
||||
if (!selected.empty()) {
|
||||
|
||||
@@ -153,6 +153,7 @@ private:
|
||||
// Output filter
|
||||
char filter_text_[128] = {0};
|
||||
mutable int filter_match_count_ = 0; // lines matching the text filter (for the toolbar)
|
||||
std::string context_token_; // hash/address under the cursor at right-click
|
||||
mutable std::vector<int> visible_indices_; // Cached for selection mapping
|
||||
|
||||
// Wrapped line height caching (for variable-height text wrapping)
|
||||
|
||||
@@ -983,6 +983,7 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["console_help_stop"] = " stop - Stop the daemon";
|
||||
strings_["console_line_count"] = "%zu lines";
|
||||
strings_["console_matches"] = "matches";
|
||||
strings_["console_copy_value"] = "Copy";
|
||||
strings_["console_new_lines"] = "%d new lines";
|
||||
strings_["console_no_daemon"] = "No daemon";
|
||||
strings_["console_not_connected"] = "Error: Not connected to daemon";
|
||||
|
||||
Reference in New Issue
Block a user