Skip to content

Commit bffb582

Browse files
authored
Merge pull request #10 from janb84/feature/select_recent_blocks
Added option to navigate to the blocks and select them.
2 parents 287ea00 + 5dfd83c commit bffb582

File tree

6 files changed

+100
-27
lines changed

6 files changed

+100
-27
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All notable changes to bitcoin-tui are documented here.
44

5+
## [0.6.1] - 2026-03-04
6+
7+
### Added
8+
- **Block navigation in Mempool tab** - press `down-arrow` to enter block selection mode; use `left/right` to move between blocks (newest first); press `Enter` to open the full block detail overlay (same as searching by height); press `Esc` to deselect and return to normal tab navigation
9+
510
## [0.6.0] - 2026-03-04
611

712
### Added

src/main.cpp

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ static int run(int argc, char* argv[]) {
206206
int peer_selected = -1;
207207
bool peer_detail_open = false;
208208

209+
// Mempool tab: selected block index (-1 = none, 0 = newest/leftmost)
210+
int mempool_sel = -1;
211+
209212
std::atomic<bool> running{true};
210213

211214
// FTXUI screen
@@ -358,7 +361,7 @@ static int run(int argc, char* argv[]) {
358361
}
359362

360363
// Mempool content is always the background layer
361-
auto base = vbox({render_mempool(snap), filler()}) | flex;
364+
auto base = vbox({render_mempool(snap, mempool_sel), filler()}) | flex;
362365

363366
// No search yet — just show the mempool
364367
if (ss.txid.empty()) {
@@ -671,6 +674,13 @@ static int run(int argc, char* argv[]) {
671674
: overlay_is_confirmed_tx
672675
? hbox({text(" [↑/↓] navigate [Esc] dismiss [q] quit ") | color(Color::Yellow)})
673676
: overlay_visible ? hbox({text(" [Esc] dismiss [q] quit ") | color(Color::Yellow)})
677+
: (tab_index == 1 && mempool_sel >= 0)
678+
? hbox({text(" [↵] view block [←/→] navigate [Esc] deselect [q] quit ") |
679+
color(Color::Yellow)})
680+
: (tab_index == 1)
681+
? hbox({refresh_indicator,
682+
text(" [↓] select block [Tab/←/→] switch [/] search [q] quit ") |
683+
color(Color::GrayDark)})
674684
: (tab_index == 3 && peer_detail_open)
675685
? hbox({text(" [Esc] back [q] quit ") | color(Color::Yellow)})
676686
: (tab_index == 3 && peer_selected >= 0)
@@ -924,6 +934,60 @@ static int run(int argc, char* argv[]) {
924934
return true;
925935
}
926936
}
937+
// Mempool tab: block navigation (↓ to enter, ←/→ to navigate, Enter to view)
938+
if (tab_index == 1) {
939+
bool has_overlay;
940+
{
941+
std::lock_guard lock(search_mtx);
942+
has_overlay = !search_state.txid.empty();
943+
}
944+
if (!has_overlay) {
945+
// arrow-down enters block navigation mode
946+
if (event == Event::ArrowDown && mempool_sel < 0) {
947+
int n;
948+
{
949+
std::lock_guard lock(state_mtx);
950+
n = static_cast<int>(state.recent_blocks.size());
951+
}
952+
if (n > 0) {
953+
mempool_sel = 0;
954+
screen.PostEvent(Event::Custom);
955+
return true;
956+
}
957+
}
958+
// arrow-left/arrow-right navigate blocks once in block navigation mode
959+
if (mempool_sel >= 0 && (event == Event::ArrowLeft || event == Event::ArrowRight)) {
960+
int n;
961+
{
962+
std::lock_guard lock(state_mtx);
963+
n = static_cast<int>(state.recent_blocks.size());
964+
}
965+
if (event == Event::ArrowLeft)
966+
mempool_sel = std::max(mempool_sel - 1, 0);
967+
else
968+
mempool_sel = std::min(mempool_sel + 1, n - 1);
969+
screen.PostEvent(Event::Custom);
970+
return true;
971+
}
972+
if (event == Event::Return && mempool_sel >= 0) {
973+
std::string height_str;
974+
{
975+
std::lock_guard lock(state_mtx);
976+
if (mempool_sel < static_cast<int>(state.recent_blocks.size()))
977+
height_str = std::to_string(state.recent_blocks[mempool_sel].height);
978+
}
979+
if (!height_str.empty()) {
980+
trigger_tx_search(height_str, false);
981+
return true;
982+
}
983+
}
984+
if (event == Event::Escape && mempool_sel >= 0) {
985+
mempool_sel = -1;
986+
screen.PostEvent(Event::Custom);
987+
return true;
988+
}
989+
}
990+
}
927991
// Normal mode
928992
if (event == Event::Character('/')) {
929993
global_search_active = true;

src/poll.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ void poll_rpc(RpcClient& rpc, AppState& state, std::mutex& mtx,
6363
state.peers.clear();
6464
for (const auto& p : pi) {
6565
PeerInfo peer;
66-
peer.id = p.value("id", 0);
67-
peer.addr = p.value("addr", "");
68-
peer.network = p.value("network", "");
69-
peer.subver = p.value("subver", "");
70-
peer.inbound = p.value("inbound", false);
71-
peer.bytes_sent = p.value("bytessent", 0LL);
72-
peer.bytes_recv = p.value("bytesrecv", 0LL);
66+
peer.id = p.value("id", 0);
67+
peer.addr = p.value("addr", "");
68+
peer.network = p.value("network", "");
69+
peer.subver = p.value("subver", "");
70+
peer.inbound = p.value("inbound", false);
71+
peer.bytes_sent = p.value("bytessent", 0LL);
72+
peer.bytes_recv = p.value("bytesrecv", 0LL);
7373
peer.version = p.value("version", 0);
7474
peer.synced_blocks = p.value("synced_blocks", 0LL);
7575
peer.conntime = p.value("conntime", 0LL);
@@ -80,7 +80,8 @@ void poll_rpc(RpcClient& rpc, AppState& state, std::mutex& mtx,
8080
if (p.contains("servicesnames") && p["servicesnames"].is_array()) {
8181
std::string svc;
8282
for (const auto& s : p["servicesnames"]) {
83-
if (!svc.empty()) svc += ", ";
83+
if (!svc.empty())
84+
svc += ", ";
8485
svc += s.get<std::string>();
8586
}
8687
peer.services = svc;

src/render.cpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Element render_dashboard(const AppState& s) {
103103
}
104104

105105
// --- Mempool ----------------------------------------------------------------
106-
Element render_mempool(const AppState& s) {
106+
Element render_mempool(const AppState& s, int mempool_sel) {
107107
double usage_frac = s.mempool_max > 0 ? static_cast<double>(s.mempool_usage) /
108108
static_cast<double>(s.mempool_max)
109109
: 0.0;
@@ -146,8 +146,8 @@ Element render_mempool(const AppState& s) {
146146
bool anim_slide = s.block_anim_active && !s.block_anim_old.empty();
147147

148148
// During slide: render old blocks minus the last (it slides off the right edge).
149-
const std::vector<BlockStat>& src = anim_slide ? s.block_anim_old : s.recent_blocks;
150-
int num = static_cast<int>(src.size());
149+
const std::vector<BlockStat>& src = anim_slide ? s.block_anim_old : s.recent_blocks;
150+
int num = static_cast<int>(src.size());
151151
int max_cols = std::max(1, (Terminal::Size().dimx - 4) / (COL_WIDTH + 1));
152152
int max_render = std::min(anim_slide ? std::max(0, num - 1) : num, max_cols);
153153

@@ -181,10 +181,12 @@ Element render_mempool(const AppState& s) {
181181
if (!block_cols.empty())
182182
block_cols.push_back(text(" "));
183183

184+
bool is_selected = (i == mempool_sel);
184185
block_cols.push_back(
185186
vbox({
186187
vbox(std::move(bar)),
187-
text(fmt_height(b.height)) | center,
188+
is_selected ? text(fmt_height(b.height)) | center | inverted | bold
189+
: text(fmt_height(b.height)) | center,
188190
text(fmt_int(b.txs) + " tx") | center | color(Color::GrayDark),
189191
text(fmt_bytes(b.total_size)) | center | color(Color::GrayDark),
190192
text(b.time > 0 ? fmt_time_ago(b.time) : "") | center | color(Color::GrayDark),
@@ -386,13 +388,14 @@ Element render_tools(const AppState& snap, const BroadcastState& bs, bool input_
386388
if (input_active) {
387389
bcast_rows.push_back(separator());
388390
// Wrap hex across rows of 70 chars; all rows shown.
389-
constexpr int kHexCols = 70;
390-
const auto& h = hex_str;
391-
int total = (int)h.size();
391+
constexpr int kHexCols = 70;
392+
const auto& h = hex_str;
393+
int total = (int)h.size();
392394
std::vector<std::string> chunks;
393395
for (int off = 0; off < std::max(total, 1); off += kHexCols)
394396
chunks.push_back(h.substr(off, std::min(kHexCols, total - off)));
395-
if (chunks.empty()) chunks.push_back("");
397+
if (chunks.empty())
398+
chunks.push_back("");
396399
for (int i = 0; i < (int)chunks.size(); ++i) {
397400
bool is_last = i == (int)chunks.size() - 1;
398401
auto prefix = i == 0 && chunks.size() == 1
@@ -423,9 +426,9 @@ Element render_tools(const AppState& snap, const BroadcastState& bs, bool input_
423426
} else {
424427
bcast_rows.push_back(text(" Error: ") | color(Color::Red) | bold);
425428
// Word-wrap error at ~72 chars with consistent indent (newlines treated as spaces).
426-
constexpr int kErrCols = 72;
427-
constexpr auto kIndent = " ";
428-
constexpr int kIndentLen = 2;
429+
constexpr int kErrCols = 72;
430+
constexpr auto kIndent = " ";
431+
constexpr int kIndentLen = 2;
429432
std::istringstream ss(bs.result_error);
430433
std::string word, cur = kIndent;
431434
while (ss >> word) {

src/render.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ ftxui::Element label_value(const std::string& lbl, const std::string& val,
1212
ftxui::Color val_color = ftxui::Color::Default);
1313

1414
ftxui::Element render_dashboard(const AppState& s);
15-
ftxui::Element render_mempool(const AppState& s);
15+
ftxui::Element render_mempool(const AppState& s, int mempool_sel = -1);
1616
ftxui::Element render_network(const AppState& s);
1717
ftxui::Element render_peers(const AppState& s, int selected = -1);
1818
ftxui::Element render_peer_detail(const PeerInfo& p);

src/state.hpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ struct PeerInfo {
3434
double ping_ms = -1.0;
3535
int version = 0;
3636
int64_t synced_blocks = 0;
37-
int64_t conntime = 0;
37+
int64_t conntime = 0;
3838
std::string services;
39-
int64_t startingheight = 0;
40-
bool bip152_hb_from = false;
41-
bool bip152_hb_to = false;
39+
int64_t startingheight = 0;
40+
bool bip152_hb_from = false;
41+
bool bip152_hb_to = false;
4242
std::string connection_type;
4343
std::string transport;
44-
int64_t addr_processed = 0;
45-
double min_ping_ms = -1.0;
44+
int64_t addr_processed = 0;
45+
double min_ping_ms = -1.0;
4646
};
4747

4848
struct AppState {

0 commit comments

Comments
 (0)