From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp12.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 eB2hCjPvRWPb5AAAbAwnHQ (envelope-from ) for ; Wed, 12 Oct 2022 00:33:23 +0200 Received: from aspmx1.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp12.migadu.com with LMTPS id iBCACjPvRWObHQEAauVa8A (envelope-from ) for ; Wed, 12 Oct 2022 00:33:23 +0200 Received: from mail.notmuchmail.org (yantan.tethera.net [135.181.149.255]) (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 AA4E031FA5 for ; Wed, 12 Oct 2022 00:33:22 +0200 (CEST) Received: from yantan.tethera.net (localhost [127.0.0.1]) by mail.notmuchmail.org (Postfix) with ESMTP id 4A2635F37A; Tue, 11 Oct 2022 22:33:20 +0000 (UTC) X-Greylist: delayed 802 seconds by postgrey-1.36 at yantan; Tue, 11 Oct 2022 22:33:17 UTC Received: from mslow1.mail.gandi.net (mslow1.mail.gandi.net [217.70.178.240]) by mail.notmuchmail.org (Postfix) with ESMTPS id 67E535F36C for ; Tue, 11 Oct 2022 22:33:17 +0000 (UTC) Received: from relay11.mail.gandi.net (unknown [IPv6:2001:4b98:dc4:8::231]) by mslow1.mail.gandi.net (Postfix) with ESMTP id 945A5C1AF7 for ; Tue, 11 Oct 2022 22:19:56 +0000 (UTC) Received: (Authenticated sender: robin@jarry.cc) by mail.gandi.net (Postfix) with ESMTPSA id 88640100002; Tue, 11 Oct 2022 22:19:54 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=jarry.cc; s=gm1; t=1665526795; 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; bh=G9YOsUFD7+/kLGfJB+3j4Xe60+/9h+lWvf3/dUwZahU=; b=aC3/7bZL27nVCpJiXb1jlbpgyV/B1bwqOcIC855t5UNCVVHpa3wfL0RKhwIhFmMrV0racX xnvISlVFoMHGGSW19mSAVaiTKFzLS6leYxM0Wowjv63gV/6agDQVL3qX+Q/QghZrBLvPzR Zvwz6iPv8wlcsjI6pX/QOE0jWyVTkL9DyZ/fu9lwJtJ2T83O+gQbpeuXoodqfee9wRz6vd 7TAPKdk5rYo50HBL9+OfjVeKd6p31qc3oTsm/6t1aRC4Fgm+ErL3/RLUNrUINlTZ7eczv7 JQkPXkIBWAi7NwiusynRxKfUZ2DNynbMH4fLAYZ5OaddfLMAju5Jdpvw/eq5Lg== From: Robin Jarry To: notmuch@notmuchmail.org Subject: [PATCH] cli: add options --offset and --limit to notmuch show Date: Wed, 12 Oct 2022 00:19:42 +0200 Message-Id: <20221011221942.341732-1-robin@jarry.cc> X-Mailer: git-send-email 2.37.3 MIME-Version: 1.0 Message-ID-Hash: IM2M7TYRCHYNSO6AHIYGK67S3GP5JSZT X-Message-ID-Hash: IM2M7TYRCHYNSO6AHIYGK67S3GP5JSZT 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 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=1665527602; 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:list-id:list-help: list-owner:list-unsubscribe:list-subscribe:list-post:dkim-signature; bh=FM4z6scpWIggsM6pee93umhOmV6b2Cfk5HCN0DXuPfo=; b=qQwDNXwxtBgTCATXI79Zv9SIwjmB+4e3Jpf7x5mVq6fnEKsdVyO2G1iKvBcJOh3uN61rn+ GLPkjl+VYrRy0hpUEw3Smft69Q+7xnMKmZ+rgV33AHWZMqJLanFLHNki9D8erexHSx6nLk 6u8yD7hj2oxCspQV2lWFUC1TvnAEgPJi9c7RM/ZikMzLD/ZRSC0pncLvDd3MT1+G1Jd3G3 Mn4cRPUqucyIGVv/5TTBdaBGiI7PW+O38KsvR3UoX8SSGdqpF1h6dwB9HLMhk2sqYCfxl0 dNODrEsU//SgkGl8hXd8rSW/GSjj9/WVvzS5UZExA7QDH4r9hQNCdS8EXSJgtA== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1665527602; a=rsa-sha256; cv=none; b=G5dQD02MtlX3tSWYbxCEWthucWsKUdGsgD4Cg75B0vpFxbfyPbM/dkcVmzHgGLOZPvCToB iJkOylXWnsiiQD2C0GjRtSNDg70MH4qQFl5q+nvRsUfzWDtkpTWX1JejfNaulVmR6edWNc qD95IN9MpDFM0ICmyJbZ3rZY8qj24dl09EbXWh9KNc75YwhescF5j/JkwbwGD0l0DCKPAF fyIs4qYTiDbmXFMx1fJgWTjLXL/3Q8IMi8um++3x6WdZh76KopPU1s2AM7P0xOVISxRMjz Bg/N0jA61LNReDo0iN/pnnxgqSQc5UGVLfDcxFCsq9k2v09aSvyCZ4HRojzKqA== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=jarry.cc header.s=gm1 header.b="aC3/7bZL"; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 135.181.149.255 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Migadu-Spam-Score: 2.14 Authentication-Results: aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=jarry.cc header.s=gm1 header.b="aC3/7bZL"; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 135.181.149.255 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Migadu-Queue-Id: AA4E031FA5 X-Spam-Score: 2.14 X-Migadu-Scanner: scn0.migadu.com X-TUID: X4jZJTzIIa1A 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 Signed-off-by: Robin Jarry --- 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..ad31e0123268 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 search", 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 search", 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..a3da35944a3e --- /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 + +function 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