cli: add options --offset and --limit to notmuch show

notmuch search does not output header values. However, when browsing
through a large email corpus, it can be time saving to be able to
paginate without running notmuch show for each message/thread.

Add --offset and --limit options to notmuch show. This is inspired from
commit 796b629c3b ("cli: add options --offset and --limit to notmuch
search").

Update man page, shell completion and add a test case to ensure it works
as expected.

Cc: Tim Culverhouse <tim@timculverhouse.com>
Cc: Tomi Ollila <tomi.ollila@iki.fi>
Signed-off-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
Robin Jarry 2022-10-18 21:41:58 +02:00 committed by David Bremner
parent b6565c1c54
commit 793f298091
6 changed files with 138 additions and 7 deletions

View file

@ -530,7 +530,7 @@ _notmuch_show()
! $split && ! $split &&
case "${cur}" in case "${cur}" in
-*) -*)
local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt= --include-html ${_notmuch_shared_options}" local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt= --include-html --limit= --offset= ${_notmuch_shared_options}"
compopt -o nospace compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;; ;;

View file

@ -245,6 +245,8 @@ _notmuch_show() {
'--exclude=[respect excluded tags setting]:exclude tags:(true false)' \ '--exclude=[respect excluded tags setting]:exclude tags:(true false)' \
'--body=[output body]:output body content:(true false)' \ '--body=[output body]:output body content:(true false)' \
'--include-html[include text/html parts in the output]' \ '--include-html[include text/html parts in the output]' \
'--limit=[limit the number of displayed results]:limit: ' \
'--offset=[skip displaying the first N results]:offset: ' \
'*::search term:_notmuch_search_term' '*::search term:_notmuch_search_term'
} }

View file

@ -130,6 +130,15 @@ Supported options for **show** include
By default, results will be displayed in reverse chronological By default, results will be displayed in reverse chronological
order, (that is, the newest results will be displayed first). order, (that is, the newest results will be displayed first).
.. option:: --offset=[-]N
Skip displaying the first N results. With the leading '-', start
at the Nth result from the end.
.. option:: --limit=N
Limit the number of displayed results to N.
.. option:: --verify .. option:: --verify
Compute and report the validity of any MIME cryptographic Compute and report the validity of any MIME cryptographic

View file

@ -77,6 +77,8 @@ typedef struct notmuch_show_params {
bool output_body; bool output_body;
int duplicate; int duplicate;
int part; int part;
int offset;
int limit;
_notmuch_crypto_t crypto; _notmuch_crypto_t crypto;
bool include_html; bool include_html;
GMimeStream *out_stream; GMimeStream *out_stream;

View file

@ -1159,6 +1159,18 @@ do_show_threaded (void *ctx,
notmuch_thread_t *thread; notmuch_thread_t *thread;
notmuch_messages_t *messages; notmuch_messages_t *messages;
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
int i;
if (params->offset < 0) {
unsigned count;
notmuch_status_t s = notmuch_query_count_threads (query, &count);
if (print_status_query ("notmuch show", query, s))
return 1;
params->offset += count;
if (params->offset < 0)
params->offset = 0;
}
status = notmuch_query_search_threads (query, &threads); status = notmuch_query_search_threads (query, &threads);
if (print_status_query ("notmuch show", query, status)) if (print_status_query ("notmuch show", query, status))
@ -1166,11 +1178,16 @@ do_show_threaded (void *ctx,
sp->begin_list (sp); sp->begin_list (sp);
for (; for (i = 0;
notmuch_threads_valid (threads); notmuch_threads_valid (threads) && (params->limit < 0 || i < params->offset + params->limit);
notmuch_threads_move_to_next (threads)) { notmuch_threads_move_to_next (threads), i++) {
thread = notmuch_threads_get (threads); thread = notmuch_threads_get (threads);
if (i < params->offset) {
notmuch_thread_destroy (thread);
continue;
}
messages = notmuch_thread_get_toplevel_messages (thread); messages = notmuch_thread_get_toplevel_messages (thread);
if (messages == NULL) if (messages == NULL)
@ -1201,6 +1218,18 @@ do_show_unthreaded (void *ctx,
notmuch_message_t *message; notmuch_message_t *message;
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
notmuch_bool_t excluded; notmuch_bool_t excluded;
int i;
if (params->offset < 0) {
unsigned count;
notmuch_status_t s = notmuch_query_count_messages (query, &count);
if (print_status_query ("notmuch show", query, s))
return 1;
params->offset += count;
if (params->offset < 0)
params->offset = 0;
}
status = notmuch_query_search_messages (query, &messages); status = notmuch_query_search_messages (query, &messages);
if (print_status_query ("notmuch show", query, status)) if (print_status_query ("notmuch show", query, status))
@ -1208,9 +1237,13 @@ do_show_unthreaded (void *ctx,
sp->begin_list (sp); sp->begin_list (sp);
for (; for (i = 0;
notmuch_messages_valid (messages); notmuch_messages_valid (messages) && (params->limit < 0 || i < params->offset + params->limit);
notmuch_messages_move_to_next (messages)) { notmuch_messages_move_to_next (messages), i++) {
if (i < params->offset) {
continue;
}
sp->begin_list (sp); sp->begin_list (sp);
sp->begin_list (sp); sp->begin_list (sp);
@ -1287,6 +1320,8 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
notmuch_show_params_t params = { notmuch_show_params_t params = {
.part = -1, .part = -1,
.duplicate = 0, .duplicate = 0,
.offset = 0,
.limit = -1, /* unlimited */
.omit_excluded = true, .omit_excluded = true,
.output_body = true, .output_body = true,
.crypto = { .decrypt = NOTMUCH_DECRYPT_AUTO }, .crypto = { .decrypt = NOTMUCH_DECRYPT_AUTO },
@ -1328,6 +1363,8 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
{ .opt_bool = &params.output_body, .name = "body" }, { .opt_bool = &params.output_body, .name = "body" },
{ .opt_bool = &params.include_html, .name = "include-html" }, { .opt_bool = &params.include_html, .name = "include-html" },
{ .opt_int = &params.duplicate, .name = "duplicate" }, { .opt_int = &params.duplicate, .name = "duplicate" },
{ .opt_int = &params.limit, .name = "limit" },
{ .opt_int = &params.offset, .name = "offset" },
{ .opt_inherit = notmuch_shared_options }, { .opt_inherit = notmuch_shared_options },
{ } { }
}; };

81
test/T131-show-limiting.sh Executable file
View file

@ -0,0 +1,81 @@
#!/usr/bin/env bash
test_description='"notmuch show" --offset and --limit parameters'
. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
show () {
local kind="$1"
shift
if [ "$kind" = messages ]; then
set -- --unthreaded "$@"
fi
notmuch show --body=false --format=text --entire-thread=false "$@" "*" |
sed -nre 's/^.message\{.*\<depth:0\>.*/&/p'
}
for outp in messages threads; do
test_begin_subtest "$outp: limit does the right thing"
show $outp | head -n 20 >expected
show $outp --limit=20 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: concatenation of limited shows"
show $outp | head -n 20 >expected
show $outp --limit=10 >output
show $outp --limit=10 --offset=10 >>output
test_expect_equal_file expected output
test_begin_subtest "$outp: limit larger than result set"
N=$(notmuch count --output=$outp "*")
show $outp >expected
show $outp --limit=$((1 + N)) >output
test_expect_equal_file expected output
test_begin_subtest "$outp: limit = 0"
test_expect_equal "$(show $outp --limit=0)" ""
test_begin_subtest "$outp: offset does the right thing"
# note: tail -n +N is 1-based
show $outp | tail -n +21 >expected
show $outp --offset=20 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: offset = 0"
show $outp >expected
show $outp --offset=0 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset"
show $outp | tail -n 20 >expected
show $outp --offset=-20 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset"
show $outp | tail -n 1 >expected
show $outp --offset=-1 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset combined with limit"
show $outp | tail -n 20 | head -n 10 >expected
show $outp --offset=-20 --limit=10 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset combined with equal limit"
show $outp | tail -n 20 >expected
show $outp --offset=-20 --limit=20 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset combined with large limit"
show $outp | tail -n 20 >expected
show $outp --offset=-20 --limit=50 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset larger than results"
N=$(notmuch count --output=$outp "*")
show $outp >expected
show $outp --offset=-$((1 + N)) >output
test_expect_equal_file expected output
done
test_done