From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp10.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id qG15KpQBT2PeKwEAbAwnHQ (envelope-from ) for ; Tue, 18 Oct 2022 21:42:12 +0200 Received: from aspmx1.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp10.migadu.com with LMTPS id mPegKZQBT2NMJgAAG6o9tA (envelope-from ) for ; Tue, 18 Oct 2022 21:42:12 +0200 Received: from mail.notmuchmail.org (yantan.tethera.net [IPv6:2a01:4f9:c011:7a79::1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 6F826E2F5 for ; Tue, 18 Oct 2022 21:42:12 +0200 (CEST) Received: from yantan.tethera.net (localhost [127.0.0.1]) by mail.notmuchmail.org (Postfix) with ESMTP id 82A645F379; Tue, 18 Oct 2022 19:42:07 +0000 (UTC) Received: from relay1-d.mail.gandi.net (relay1-d.mail.gandi.net [IPv6:2001:4b98:dc4:8::221]) by mail.notmuchmail.org (Postfix) with ESMTPS id 48AFE5E226 for ; Tue, 18 Oct 2022 19:42:04 +0000 (UTC) Received: (Authenticated sender: robin@jarry.cc) by mail.gandi.net (Postfix) with ESMTPSA id 3DB12240007; Tue, 18 Oct 2022 19:42:01 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=jarry.cc; s=gm1; t=1666122123; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=EJAVzYqPzM8a91aDlbG8ApKQLYDKlDW463+KKPf3mQI=; b=F8EODzPe2maJflVKkTcqwr/D0yVqjUKaw9WAqLDjK71Z07WXYdViitTLsXZMcsbcUU1U9a M4YTHGXrYFC+sAck2yaEyTgVGEB8/bASpJ7WVSH/gOxiJmMflOtVyvPYImIvxNb08SQkc8 L/RjRcIh65Y35WL8cV1ERTGvpzoV6MzLbfKsy8LoS2v9F4mAvWwXVLVtxDskPKDCaNfL+N 35MnWSzkyeS/sDjW2YUdiXixkhinJATn5a2tmRnhXH6Zge5J/1GtqYKWkeexnFreLWwz/W y/S2Vj5OcVbOFSKGJ0KK+zyPa31mEzn+HiO52g9ppeE9RTnlsHzLnMaC5VYfBA== From: Robin Jarry To: notmuch@notmuchmail.org Subject: [PATCH v2] cli: add options --offset and --limit to notmuch show Date: Tue, 18 Oct 2022 21:41:58 +0200 Message-Id: <20221018194158.973260-1-robin@jarry.cc> X-Mailer: git-send-email 2.37.3 In-Reply-To: <20221005142405.20896-1-robin@jarry.cc> References: <20221005142405.20896-1-robin@jarry.cc> MIME-Version: 1.0 Message-ID-Hash: YMYDDB5EPBL6MLBJEBXNVWU4I67GCMQ7 X-Message-ID-Hash: YMYDDB5EPBL6MLBJEBXNVWU4I67GCMQ7 X-MailFrom: robin@jarry.cc X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-notmuch.notmuchmail.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Robin Jarry , Tim Culverhouse , Tomi Ollila X-Mailman-Version: 3.3.3 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit X-Migadu-Flow: FLOW_IN X-Migadu-Country: DE ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1666122132; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-owner:list-unsubscribe:list-subscribe:list-post:dkim-signature; bh=mrRFtL1ko2Nx/qWdxnbqAsg4kp2P7PUcofFmLWfNy40=; b=cEn1fY5NNPEWwYJmbWMQHdcJyeRuFlHHYFxAHsS7pGQCn7ZO3ocVEdToXVErOi8mqxZyZ+ CetBkG2svDhByk0DTIA97PziWZqsEFZ4+ViyOM2CAtuhQdVXOAQU09a84A+uIMObJiAbw0 K+RzG7uhmSByUVmLXIjNx5iUb0n8R7TtMct39V3eMgySQDtistwp//8us1UieNhvcVD1TN TXzg0Xp1MC1rVrRNkemBFDWylsjaQpd7dxpiigJwa8Pfzx4ijOGdsLOMjlj1a1YebsVGQI KhWZ3fXj1xxaP73hIcC5WJ50BLYzugE0qH8UGvXhhEIwJ5kCzD3IYAHXF1BEwQ== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1666122132; a=rsa-sha256; cv=none; b=fw3QXeiE12FSDDLMHYcaUU7hU/LZUTIKdA1ZCatZPhEqJn9agLcyPm/gjcIZCLhuqEdanz 5XhjgslxKUytq4abYWq9zYFah+jEjEJt8R9tTIJ3H6GDu5c5PP9j6TG9CHmCI0ntRMeocb cyhfSy5E21K6Vo61fd3TSEwsPjMdliQcDiBRMSXzqbG+XeF5MEapDwtRM+KrD1M0sZuYdj +BrwhiFEUWAvIQW+QYB6VmjPW6mIRlll82P8IIFcpqX7T6b69mA2Hc9MGJqpatNIK0KX2c yP80S/i1IyGSgITuAq/QvJEL4WWZEQdMK9aW+J216LnIYkuRmQkWm+kGAi/W/A== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=jarry.cc header.s=gm1 header.b=F8EODzPe; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 2a01:4f9:c011:7a79::1 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Migadu-Spam-Score: 2.51 Authentication-Results: aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=jarry.cc header.s=gm1 header.b=F8EODzPe; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 2a01:4f9:c011:7a79::1 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Migadu-Queue-Id: 6F826E2F5 X-Spam-Score: 2.51 X-Migadu-Scanner: scn0.migadu.com X-TUID: Jxs4wKscNmIx 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 796b629c3b82 ("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 Cc: Tomi Ollila Signed-off-by: Robin Jarry --- v1 -> v2: - fixed typo in print_status_query (search -> show) - changed all shell variable expansions without braces - removed the bash-specific 'function' keyword completion/notmuch-completion.bash | 2 +- completion/zsh/_notmuch | 2 + doc/man1/notmuch-show.rst | 9 ++++ notmuch-client.h | 2 + notmuch-show.c | 49 +++++++++++++++--- test/T131-show-limiting.sh | 81 ++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 7 deletions(-) create mode 100755 test/T131-show-limiting.sh diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash index 0022b54bff5d..3748846edf83 100644 --- a/completion/notmuch-completion.bash +++ b/completion/notmuch-completion.bash @@ -530,7 +530,7 @@ _notmuch_show() ! $split && 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 COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) ;; diff --git a/completion/zsh/_notmuch b/completion/zsh/_notmuch index e207d90b7202..0bdd7f772a7a 100644 --- a/completion/zsh/_notmuch +++ b/completion/zsh/_notmuch @@ -245,6 +245,8 @@ _notmuch_show() { '--exclude=[respect excluded tags setting]:exclude tags:(true false)' \ '--body=[output body]:output body content:(true false)' \ '--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' } diff --git a/doc/man1/notmuch-show.rst b/doc/man1/notmuch-show.rst index 2c0a0de6ad16..c13d94de0244 100644 --- a/doc/man1/notmuch-show.rst +++ b/doc/man1/notmuch-show.rst @@ -130,6 +130,15 @@ Supported options for **show** include By default, results will be displayed in reverse chronological 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 Compute and report the validity of any MIME cryptographic diff --git a/notmuch-client.h b/notmuch-client.h index 21b49908ae24..1a87240d3c21 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -77,6 +77,8 @@ typedef struct notmuch_show_params { bool output_body; int duplicate; int part; + int offset; + int limit; _notmuch_crypto_t crypto; bool include_html; GMimeStream *out_stream; diff --git a/notmuch-show.c b/notmuch-show.c index ee9efa7448d7..7fb40ce9ab5d 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1159,6 +1159,18 @@ do_show_threaded (void *ctx, notmuch_thread_t *thread; notmuch_messages_t *messages; 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); if (print_status_query ("notmuch show", query, status)) @@ -1166,11 +1178,16 @@ do_show_threaded (void *ctx, sp->begin_list (sp); - for (; - notmuch_threads_valid (threads); - notmuch_threads_move_to_next (threads)) { + for (i = 0; + notmuch_threads_valid (threads) && (params->limit < 0 || i < params->offset + params->limit); + notmuch_threads_move_to_next (threads), i++) { thread = notmuch_threads_get (threads); + if (i < params->offset) { + notmuch_thread_destroy (thread); + continue; + } + messages = notmuch_thread_get_toplevel_messages (thread); if (messages == NULL) @@ -1201,6 +1218,18 @@ do_show_unthreaded (void *ctx, notmuch_message_t *message; notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; 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); if (print_status_query ("notmuch show", query, status)) @@ -1208,9 +1237,13 @@ do_show_unthreaded (void *ctx, sp->begin_list (sp); - for (; - notmuch_messages_valid (messages); - notmuch_messages_move_to_next (messages)) { + for (i = 0; + notmuch_messages_valid (messages) && (params->limit < 0 || i < params->offset + params->limit); + notmuch_messages_move_to_next (messages), i++) { + if (i < params->offset) { + continue; + } + 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 = { .part = -1, .duplicate = 0, + .offset = 0, + .limit = -1, /* unlimited */ .omit_excluded = true, .output_body = true, .crypto = { .decrypt = NOTMUCH_DECRYPT_AUTO }, @@ -1328,6 +1363,8 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[]) { .opt_bool = ¶ms.output_body, .name = "body" }, { .opt_bool = ¶ms.include_html, .name = "include-html" }, { .opt_int = ¶ms.duplicate, .name = "duplicate" }, + { .opt_int = ¶ms.limit, .name = "limit" }, + { .opt_int = ¶ms.offset, .name = "offset" }, { .opt_inherit = notmuch_shared_options }, { } }; diff --git a/test/T131-show-limiting.sh b/test/T131-show-limiting.sh new file mode 100755 index 000000000000..30d1f25499b0 --- /dev/null +++ b/test/T131-show-limiting.sh @@ -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\{.*\.*/&/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 -- 2.37.3