Skip to content

Commit 939e6a4

Browse files
committed
[text] save bookmarks for text files
1 parent 47c9f9a commit 939e6a4

11 files changed

Lines changed: 566 additions & 225 deletions

NEWS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ Features:
6161
The `:clear-all-sticky-headers` command removes all
6262
sticky headers in the current view. A hidden
6363
`log_sticky_mark` column is available in log tables
64-
to get/set the sticky state via SQL.
64+
to get/set the sticky state via SQL. Sticky headers
65+
and user bookmarks are saved and restored across
66+
sessions for both log and text views.
6567
* Introducing "Log-Oriented Debugging", a collection of
6668
features to streamline mapping log messages back to
6769
the source code that generated them. For example,

src/base/distributed_slice.hh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ struct dist_slice_container {
114114
}
115115
}
116116

117+
void swap(slice_indexed_array& other)
118+
{
119+
this->sia_array.swap(other.sia_array);
120+
}
121+
117122
private:
118123
const dist_slice_container& sia_slices;
119124
std::vector<U> sia_array;

src/session_data.cc

Lines changed: 268 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,19 @@ CREATE TABLE IF NOT EXISTS regex101_entries (
117117
regex_name <> '' AND
118118
permalink <> '')
119119
);
120+
121+
CREATE TABLE IF NOT EXISTS text_bookmarks (
122+
file_path text NOT NULL,
123+
line_number integer NOT NULL,
124+
line_hash text NOT NULL,
125+
mark_type text NOT NULL DEFAULT 'user',
126+
session_time integer NOT NULL,
127+
128+
PRIMARY KEY (file_path, line_number, mark_type),
129+
130+
CHECK(line_number >= 0),
131+
CHECK(mark_type IN ('user', 'sticky'))
132+
);
120133
)";
121134

122135
static const char* const BOOKMARK_LRU_STMT
@@ -129,6 +142,11 @@ static const char* const NETLOC_LRU_STMT
129142
" (SELECT DISTINCT access_time FROM bookmarks "
130143
" ORDER BY access_time DESC LIMIT 1 OFFSET 10)";
131144

145+
static const char* const TEXT_BOOKMARK_LRU_STMT
146+
= "DELETE FROM text_bookmarks WHERE session_time <= "
147+
" (SELECT DISTINCT session_time FROM text_bookmarks "
148+
" ORDER BY session_time DESC LIMIT 1 OFFSET 50000)";
149+
132150
static const char* const UPGRADE_STMTS[] = {
133151
R"(ALTER TABLE bookmarks ADD COLUMN comment text DEFAULT '';)",
134152
R"(ALTER TABLE bookmarks ADD COLUMN tags text DEFAULT '';)",
@@ -405,6 +423,8 @@ scan_sessions()
405423
return std::make_optional(session_file_names.back());
406424
}
407425

426+
static void load_text_bookmarks(sqlite3* db);
427+
408428
void
409429
load_time_bookmarks()
410430
{
@@ -742,8 +762,8 @@ load_time_bookmarks()
742762
line_cl);
743763
}
744764
if (sticky) {
745-
lss.set_user_mark(
746-
&textview_curses::BM_STICKY, line_cl);
765+
lss.set_user_mark(&textview_curses::BM_STICKY,
766+
line_cl);
747767
}
748768
reload_needed = true;
749769
break;
@@ -894,6 +914,8 @@ load_time_bookmarks()
894914
if (reload_needed) {
895915
lnav_data.ld_views[LNV_LOG].reload_data();
896916
}
917+
918+
load_text_bookmarks(db.in());
897919
}
898920

899921
static int
@@ -1129,18 +1151,16 @@ save_user_bookmarks(sqlite3* db,
11291151
continue;
11301152
}
11311153

1132-
auto is_user = user_marks.bv_tree.find(cl)
1133-
!= user_marks.bv_tree.end();
1134-
auto is_sticky = sticky_marks.bv_tree.find(cl)
1135-
!= sticky_marks.bv_tree.end();
1154+
auto is_user = user_marks.bv_tree.find(cl) != user_marks.bv_tree.end();
1155+
auto is_sticky
1156+
= sticky_marks.bv_tree.find(cl) != sticky_marks.bv_tree.end();
11361157

11371158
// Use part_name "" for user marks, null for sticky-only
11381159
if (is_user) {
11391160
if (sqlite3_bind_text(stmt, 5, "", 0, SQLITE_TRANSIENT)
11401161
!= SQLITE_OK)
11411162
{
1142-
log_error("could not bind part name -- %s",
1143-
sqlite3_errmsg(db));
1163+
log_error("could not bind part name -- %s", sqlite3_errmsg(db));
11441164
return;
11451165
}
11461166
} else {
@@ -1294,6 +1314,228 @@ save_meta_bookmarks(sqlite3* db, sqlite3_stmt* stmt, logfile* lf)
12941314
}
12951315
}
12961316

1317+
static void
1318+
save_text_bookmarks(sqlite3* db)
1319+
{
1320+
auto& tss = lnav_data.ld_text_source;
1321+
1322+
if (tss.empty()) {
1323+
return;
1324+
}
1325+
1326+
tss.copy_bookmarks_to_current_file();
1327+
1328+
auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1329+
1330+
if (sqlite3_prepare_v2(db,
1331+
"DELETE FROM text_bookmarks WHERE session_time = ?",
1332+
-1,
1333+
stmt.out(),
1334+
nullptr)
1335+
!= SQLITE_OK)
1336+
{
1337+
log_error("could not prepare text_bookmarks delete -- %s",
1338+
sqlite3_errmsg(db));
1339+
return;
1340+
}
1341+
sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_time);
1342+
if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1343+
log_error("could not execute text_bookmarks delete -- %s",
1344+
sqlite3_errmsg(db));
1345+
return;
1346+
}
1347+
1348+
if (sqlite3_prepare_v2(
1349+
db,
1350+
"REPLACE INTO text_bookmarks"
1351+
" (file_path, line_number, line_hash, mark_type, session_time)"
1352+
" VALUES (?, ?, ?, ?, ?)",
1353+
-1,
1354+
stmt.out(),
1355+
nullptr)
1356+
!= SQLITE_OK)
1357+
{
1358+
log_error("could not prepare text_bookmarks replace statement -- %s",
1359+
sqlite3_errmsg(db));
1360+
return;
1361+
}
1362+
1363+
for (const auto& fvs : tss.get_file_states()) {
1364+
auto& lf = fvs->fvs_file;
1365+
if (lf == nullptr || lf->size() == 0) {
1366+
continue;
1367+
}
1368+
1369+
auto file_path = lf->get_path_for_key().string();
1370+
auto line_count = static_cast<int>(lf->size());
1371+
1372+
static const bookmark_type_t* SAVE_TYPES[] = {
1373+
&textview_curses::BM_USER,
1374+
&textview_curses::BM_STICKY,
1375+
};
1376+
1377+
for (const auto* bm_type : SAVE_TYPES) {
1378+
auto& bv = fvs->fvs_bookmarks[bm_type];
1379+
if (bv.empty()) {
1380+
continue;
1381+
}
1382+
1383+
for (const auto& vl : bv.bv_tree) {
1384+
if (static_cast<int>(vl) >= line_count) {
1385+
continue;
1386+
}
1387+
1388+
auto line_iter = lf->begin() + static_cast<int>(vl);
1389+
auto fr = lf->get_file_range(line_iter, false);
1390+
auto read_result = lf->read_range(fr);
1391+
1392+
if (read_result.isErr()) {
1393+
continue;
1394+
}
1395+
1396+
auto line_hash
1397+
= read_result
1398+
.map([](auto sbr) {
1399+
return hasher()
1400+
.update(sbr.get_data(), sbr.length())
1401+
.to_string();
1402+
})
1403+
.unwrap();
1404+
1405+
sqlite3_clear_bindings(stmt.in());
1406+
bind_to_sqlite(stmt.in(), 1, file_path);
1407+
sqlite3_bind_int(stmt.in(), 2, static_cast<int>(vl));
1408+
bind_to_sqlite(stmt.in(), 3, line_hash);
1409+
bind_to_sqlite(stmt.in(), 4, bm_type->get_name());
1410+
sqlite3_bind_int64(stmt.in(), 5, lnav_data.ld_session_time);
1411+
1412+
if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1413+
log_error("could not execute text_bookmarks insert -- %s",
1414+
sqlite3_errmsg(db));
1415+
return;
1416+
}
1417+
1418+
sqlite3_reset(stmt.in());
1419+
}
1420+
}
1421+
}
1422+
}
1423+
1424+
static void
1425+
load_text_bookmarks(sqlite3* db)
1426+
{
1427+
static const char* const TEXT_BOOKMARK_STMT = R"(
1428+
SELECT line_number, line_hash, mark_type
1429+
FROM text_bookmarks
1430+
WHERE file_path = ?
1431+
ORDER BY line_number
1432+
)";
1433+
1434+
auto& tss = lnav_data.ld_text_source;
1435+
auto& tc = lnav_data.ld_views[LNV_TEXT];
1436+
1437+
if (tss.empty()) {
1438+
return;
1439+
}
1440+
1441+
auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1442+
if (sqlite3_prepare_v2(db, TEXT_BOOKMARK_STMT, -1, stmt.out(), nullptr)
1443+
!= SQLITE_OK)
1444+
{
1445+
log_error("could not prepare text_bookmarks select -- %s",
1446+
sqlite3_errmsg(db));
1447+
return;
1448+
}
1449+
1450+
for (const auto& fvs : tss.get_file_states()) {
1451+
auto& lf = fvs->fvs_file;
1452+
if (lf == nullptr || lf->size() == 0) {
1453+
continue;
1454+
}
1455+
1456+
auto file_path = lf->get_path_for_key().string();
1457+
auto line_count = static_cast<int>(lf->size());
1458+
sqlite3_reset(stmt.in());
1459+
sqlite3_clear_bindings(stmt.in());
1460+
bind_to_sqlite(stmt.in(), 1, file_path);
1461+
1462+
bool done = false;
1463+
while (!done) {
1464+
auto rc = sqlite3_step(stmt.in());
1465+
1466+
switch (rc) {
1467+
case SQLITE_OK:
1468+
case SQLITE_DONE:
1469+
done = true;
1470+
break;
1471+
1472+
case SQLITE_ROW: {
1473+
auto line_number = sqlite3_column_int(stmt.in(), 0);
1474+
auto* stored_hash
1475+
= (const char*) sqlite3_column_text(stmt.in(), 1);
1476+
auto* mark_type
1477+
= (const char*) sqlite3_column_text(stmt.in(), 2);
1478+
1479+
if (line_number >= line_count) {
1480+
continue;
1481+
}
1482+
1483+
auto bm_type_opt = bookmark_type_t::find_type(mark_type);
1484+
if (!bm_type_opt) {
1485+
continue;
1486+
}
1487+
1488+
auto line_iter = lf->begin() + line_number;
1489+
auto fr = lf->get_file_range(line_iter, false);
1490+
auto read_result = lf->read_range(fr);
1491+
1492+
if (read_result.isErr()) {
1493+
continue;
1494+
}
1495+
1496+
auto line_hash
1497+
= read_result
1498+
.map([](auto sbr) {
1499+
return hasher()
1500+
.update(sbr.get_data(), sbr.length())
1501+
.to_string();
1502+
})
1503+
.unwrap();
1504+
1505+
if (line_hash != stored_hash) {
1506+
log_warning("text bookmark hash mismatch at %s:%d",
1507+
file_path.c_str(),
1508+
line_number);
1509+
continue;
1510+
}
1511+
1512+
auto vl = vis_line_t(line_number);
1513+
fvs->fvs_bookmarks[bm_type_opt.value()].insert_once(vl);
1514+
break;
1515+
}
1516+
1517+
default:
1518+
log_error("text bookmark select error: %d -- %s",
1519+
rc,
1520+
sqlite3_errmsg(db));
1521+
done = true;
1522+
break;
1523+
}
1524+
}
1525+
}
1526+
1527+
// Copy the front file's bookmarks into the textview
1528+
if (!tss.get_file_states().empty()) {
1529+
auto& front = tss.get_file_states().front();
1530+
tc.get_bookmarks()[&textview_curses::BM_USER]
1531+
= front->fvs_bookmarks[&textview_curses::BM_USER];
1532+
tc.get_bookmarks()[&textview_curses::BM_STICKY]
1533+
= front->fvs_bookmarks[&textview_curses::BM_STICKY];
1534+
}
1535+
1536+
tc.reload_data();
1537+
}
1538+
12971539
static void
12981540
save_time_bookmarks()
12991541
{
@@ -1597,6 +1839,8 @@ save_time_bookmarks()
15971839
sqlite3_reset(stmt.in());
15981840
}
15991841

1842+
save_text_bookmarks(db.in());
1843+
16001844
log_info("saved %d bookmarks", sqlite3_changes(db.in()));
16011845

16021846
if (sqlite3_exec(db.in(), "COMMIT", nullptr, nullptr, errmsg.out())
@@ -1627,6 +1871,18 @@ save_time_bookmarks()
16271871
if (netloc_changes > 0) {
16281872
log_info("deleted %d old netlocs", netloc_changes);
16291873
}
1874+
1875+
if (sqlite3_exec(
1876+
db.in(), TEXT_BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
1877+
!= SQLITE_OK)
1878+
{
1879+
log_error("unable to delete old text bookmarks -- %s", errmsg.in());
1880+
return;
1881+
}
1882+
auto text_bm_changes = sqlite3_changes(db.in());
1883+
if (text_bm_changes > 0) {
1884+
log_info("deleted %d old text bookmarks", text_bm_changes);
1885+
}
16301886
}
16311887

16321888
static void
@@ -1868,6 +2124,10 @@ reset_session()
18682124
tc.reload_data();
18692125
}
18702126

2127+
for (auto& fvs : lnav_data.ld_text_source.get_file_states()) {
2128+
fvs->fvs_bookmarks.clear();
2129+
}
2130+
18712131
lnav_data.ld_filter_view.reload_data();
18722132
lnav_data.ld_files_view.reload_data();
18732133
for (const auto& format : log_format::get_root_formats()) {

0 commit comments

Comments
 (0)