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 GOF8IA4YR2NXBwEAbAwnHQ (envelope-from ) for ; Wed, 12 Oct 2022 21:39:58 +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 sMafIA4YR2Oo/AAAauVa8A (envelope-from ) for ; Wed, 12 Oct 2022 21:39:58 +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 E7E903C172 for ; Wed, 12 Oct 2022 21:39:57 +0200 (CEST) Received: from yantan.tethera.net (localhost [127.0.0.1]) by mail.notmuchmail.org (Postfix) with ESMTP id ECE915F37A; Wed, 12 Oct 2022 19:39:54 +0000 (UTC) Received: from meesny.iki.fi (meesny.iki.fi [195.140.195.201]) by mail.notmuchmail.org (Postfix) with ESMTPS id 6A16F5E012 for ; Wed, 12 Oct 2022 19:39:52 +0000 (UTC) Received: from c53 (gw1.nor.fi [185.218.193.67]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: too) by meesny.iki.fi (Postfix) with ESMTPSA id DD9272022A; Wed, 12 Oct 2022 22:39:50 +0300 (EEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iki.fi; s=meesny; t=1665603591; 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: in-reply-to:in-reply-to:references:references; bh=y9Gqozrj/hB8gzYaI763GYYnmDgfTLp6ckajgi/Lm9A=; b=FCJKRKxzjI9utifSggoiLyjO6OCYG/FnbTKR3ajnw2opj6H4OZ1nUkB++YFWX/Nyqqky+f XAXf1h8FQodUdyXYd08AAsfpMrER2Rz6zUUJqfMTZ6L3EOdN9apLbXjpO09JtTDlKL9ND3 aMhsB501UUrhtSo34hJzOSY1BWQzCN0= From: Tomi Ollila To: Robin Jarry , notmuch@notmuchmail.org Subject: Re: [PATCH] cli: add options --offset and --limit to notmuch show In-Reply-To: <20221011221942.341732-1-robin@jarry.cc> References: <20221011221942.341732-1-robin@jarry.cc> User-Agent: Notmuch/0.37+18~g2a896dd (https://notmuchmail.org) Emacs/27.1 X-Face: HhBM'cA~ MIME-Version: 1.0 ARC-Authentication-Results: i=1; ORIGINATING; auth=pass smtp.auth=too smtp.mailfrom=tomi.ollila@iki.fi ARC-Seal: i=1; s=meesny; d=iki.fi; t=1665603591; a=rsa-sha256; cv=none; b=HLkkjntP9WDOlAZ4il3kQv/xW1JBQNQWpBNcJPTHGFVEOyykDWBVV21VMuvwweFJ22dm7f hAUgDIUoY9UHmLATysPiPGe8oq/bOHlL2FUW/ydxK7PZq7sIfLoIjvlEJIxCp2MvgDNgau ulZZc9dhx8B5sevSsELkKXpdrWO4DLY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=iki.fi; s=meesny; t=1665603591; 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: in-reply-to:in-reply-to:references:references; bh=y9Gqozrj/hB8gzYaI763GYYnmDgfTLp6ckajgi/Lm9A=; b=qMUbxTM1BjqR5mtycKXWvvORV4rdE5UgByqhYADUNKAjSVfz3F8fVQavDTg6MenLb/7vxR hWHBZIeq21vOlimcMWE+3wZHlVVGMzv9T519PZh1iMAhs6ukax3SrjZ2/VwE6g9wM7qnSP QwJw3xIRQWAZ2On6aw97p83AQMxpKTw= Message-ID-Hash: KJPV7CVA62WVR4SBAU7PQ6YNUF3UUPPR X-Message-ID-Hash: KJPV7CVA62WVR4SBAU7PQ6YNUF3UUPPR X-MailFrom: tomi.ollila@iki.fi 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=2; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1665603598; 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=QRMvugpIsEleSguIJBf4vDq4pMNSu3yafYIY9YHs53o=; b=o+Yh0BLRYLDfrws1ME9uLysa76Zvaow0ibs0JQAz+3CLPoS0iJy0ZzQjkHfZit6MeQd/79 cqJh/Hwh/Et0nS6Ww8PnOHec2YBjAwLc2uqsnBry5QKMVaJxmHEWp5ZpKxNbpBUmphBVoh 3ttvN5CIhQokWoM7Yn9THGd2tJ6b5OoJihWU+Sv4g5ZFhi0d95O984v2eJsAodDE/QSTkE e8uzhjiu7KULumcuFNyMrL0yH+72/IKSTQQAPIQRA1PHxWBmKVMq0FjLaja0ttpsbDcNBT CjhCvHITW/GQvFpPeoUmTtTOXkfc8iSjvix/Y9BBLnmEOE90YJXEzh1dPbVJ4g== ARC-Seal: i=2; s=key1; d=yhetil.org; t=1665603598; a=rsa-sha256; cv=fail; b=P+s8xGaP7T8fGuXqFc4PTrl4vB/2bBJi/grJgu8b+EIkQ52d3H1A3nGk3ElOP61TZr7R9E 25ErWrI9Ft8DWxB7vW/dT9MReJbv9pwZYkyUvu525QBh/o7CRvqq+51Jj1naPqaOaTop5h A2QhhQ1/YmXeQDErxsTlZMtGMsuvGd9uvertJJWnk+jIhOFraWC/LuVFk4oChXHyMTSqYt TZh36PieX6I6aXZgAXXuSCObtj6I2NCbfhXu1YLDDZEEs+rkZSUnMOwdoxtWEGAPnplcP1 FVMefyk0nk3Bp2pN3idqr84XFydD9mFxUbTBS7gxqi2xZGPYNl5EEmBp1n+pBw== ARC-Authentication-Results: i=2; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=iki.fi header.s=meesny header.b=FCJKRKxz; arc=reject ("signature check failed: fail, {[1] = sig:iki.fi:reject}"); 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.96 Authentication-Results: aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=iki.fi header.s=meesny header.b=FCJKRKxz; arc=reject ("signature check failed: fail, {[1] = sig:iki.fi:reject}"); 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: E7E903C172 X-Spam-Score: 2.96 X-Migadu-Scanner: scn1.migadu.com X-TUID: UMnSDCHoyVSY On Wed, Oct 12 2022, Robin Jarry wrote: > 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) this check and setting it to 0 is mystic to me, probably same code as in search (?) probably it is good (?) (will not comment the same below) > + 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() { 'function' not used in other function defitions in other files, so this is inconsistent (otherwise the content looks "better" than what I see usual ;D) > + 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" inconsistent ${outp} (where $outp used elsewhere) ... > + 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