Skip to content

Latest commit

 

History

History
1041 lines (828 loc) · 38.1 KB

File metadata and controls

1041 lines (828 loc) · 38.1 KB

The Gnaw Manual

1 Introduction

gnaw.el lets you browse and manage BONE reports from Emacs.

BONE (Backlog Of Notable Emails) is a bug tracker that never asks anyone to file a bug. Most trackers split a project’s feedback in two: the discussions happen somewhere (on a list, on IRC or other (a)sync channels), while the “real” reports live behind a web form or another email address. BONE removes the need for this split by watching the mailing list itself: a mail tagged [BUG], [PATCH] or [FR] becomes a report, and the replies update it—through commands like Confirmed. or Acked. and review trailers like Reviewed-by:. The list stays the one place where things happen; the tracker is derived from it1.

BONE publishes the resulting reports as plain reports.json files. The tracker state is public data anyone can fetch: you can follow the bugs and patches of several projects without holding an account on several forges.

gnaw.el is the Emacs way to work with those files. {{{kbd(M-x gnaw)}}} lists the reports of the sources you follow; from the list you can search them, read the underlying emails, view or apply the patches and attachments they carry, and organize your own work with local sticky and dismiss marks.

Beyond the browser, this library is the data layer shared by mail front-ends like gnus-gnaw, notmuch-gnaw and mu4e-gnaw. It:

  • reads the gnaw configuration (config.edn) and report sources,
  • manages the local cache of remote reports.json files,
  • parses and serializes the EDN config and state.edn files shared with the gnaw CLI,
  • exposes the report list, the local-mark API and the MUA-independent presentation helpers (annotation string, topic filtering) the front-ends use.

2 Installation

gnaw.el requires Emacs 28.1 or later, built with native JSON support.

On Emacs 30 or later, install it straight from its git repository with the built-in package-vc; the :doc spec also builds and installs this manual as an Info page:

(use-package gnaw
  :init
  (unless (package-installed-p 'gnaw)
    (package-vc-install
     '(gnaw :url "https://codeberg.org/bzg/gnaw.el"
            :doc "doc/gnaw.org"))))

You need the :init form because the :vc keyword of use-package does not support :doc. Interactively, without the manual:

M-x package-vc-install RET https://codeberg.org/bzg/gnaw.el RET

Update later with {{{kbd(M-x package-vc-upgrade RET gnaw RET)}}}.

3 Getting Started

{{{kbd(M-x gnaw)}}} opens the *gnaw* report browser. With no source configured, it runs the full interactive setup:

  1. a source URL—a base URL, a reports/meta.json, or a reports/...json file (all resolve to the reports/ directory);
  2. which report files to track, chosen from meta.json’s reports-files (default all-open.json; you can pick several);
  3. a source name (defaults to meta.json’s :source);
  4. a letter identifying the source in the browser’s S column (suggesting the first free letter of the name, so Org mode ML suggests O);
  5. one or more local git repositories for that source, used to apply patches;
  6. how to open report mail (auto, mua, gnus, notmuch, mu4e or web);
  7. when gnus is chosen, the Gnus group holding the mails.

The setup writes the source, its letter and the patch repository to config.edn (see *The configuration file).

A typical session then goes: {{{kbd(G)}}} to refresh the sources, {{{kbd(/)}}} or the filter keys to narrow the list, {{{kbd(RET)}}} or {{{kbd(SPC)}}} to read a report’s mail, {{{kbd(A)}}} to apply its patches, {{{kbd(!)}}} to keep a report in sight and {{{kbd(d)}}} then {{{kbd(x)}}} to dismiss the dealt-with ones (see <a href=”*The Report Browser”>*The Report Browser).

Each launch ends with a tip presenting a randomly chosen command of the report list; set gnaw-inhibit-startup-tip to a non-nil value to start silently.

{{{kbd(M-x gnaw-configure)}}} reruns the full setup later on, keeping the existing configuration—sources, repositories and mail setup—displayed in a window while the prompts run, each prompt defaulting to the configured value. {{{kbd(M-x gnaw-add-source)}}} only adds or updates a source. An updated source keeps the repositories it is linked to, and the setup offers to link more.

{{{kbd(M-x gnaw-update)}}} refreshes the local cache of the remote reports.json files; with a prefix argument it forces a re-download. The sources download in parallel in the background, so Emacs stays responsive meanwhile.

{{{kbd(M-x gnaw-cache-cleanup)}}} deletes, after showing the tally and asking, the cache files gnaw no longer needs: the report caches of sources no longer configured, and—when gnaw-cache-attachments-max-age is set to a number of days, nil by default—the attachments untouched for that long. Attachments hold most of the disk weight: with the default nil they are all kept. With a prefix argument it also empties the configured sources’ report caches, re-downloaded on the next update. It never touches state.edn.

4 The Report Browser

4.1 The report list

Each row is a report. The default columns are Mark, Type, Votes, Flags, Att, Msgs, From, Subject and Created; the variable gnaw-list-columns configures them, and its docstring shows how to re-add the left-out Pri (priority), Activity (last activity) and Topic columns.

The Flags column compresses the report state into letters: A when the report is acked, O when it is owned, then a closed-state letter—=C= canceled, R resolved, E expired, S superseded.

The Att column has three positions: . when the report awaits a reply, ~ when it has related reports, then one attachment glyph—=+= a single patch, x several, @ calendar events (.ics), # plain-text files, in that priority order. The help echo of the Flags and Att cells ({{{kbd(C-h .)}}}, or hovering) spells them out.

With several sources configured, a leading S column identifies each report’s source by its letter (the :letter of its config.edn entry, asked on the first reload when missing); its help echo shows the full source name. With a single source, gnaw omits the column.

Gnaw folds a patch series into a single row (cover letter or first patch) with a status summary like (2 acked, 1 open); {{{kbd(TAB)}}} unfolds the series to list each patch, {{{kbd(TAB)}}} again folds it back.

4.2 Key bindings

Reading and following:

{{{kbd(RET)}}} (gnaw-list-open)

Open the report’s email (see *Reading Messages).

{{{kbd(SPC)}}} (gnaw-list-open-other-window)

Toggle the report’s email in a window below the list, keeping point in the list.

{{{kbd(b)}}} (gnaw-list-browse)

Browse the report’s archived web page.

{{{kbd(w)}}} (gnaw-list-copy-archive-url)

Copy the archived web page URL to the kill ring.

{{{kbd(L)}}} (gnaw-list-store-link)

Store an Org link to the report ({{{kbd(C-c C-l)}}} inserts it).

{{{kbd(f)}}} (gnaw-list-follow-mode)

Toggle follow mode, which shows the email of the report at point as point moves.

{{{kbd(TAB)}}} (gnaw-list-tab)

Fold or unfold the series, or narrow to related reports.

{{{kbd(q)}}} (gnaw-list-quit)

Leave the related view, clear the filter, or quit.

Patches and attachments (see *Patches and Attachments):

{{{kbd(v)}}} (gnaw-list-attachment-view)

View an attachment (patches in diff-mode).

{{{kbd(V)}}} (gnaw-list-attachment-save)

Save an attachment (proposes the configured :repo).

{{{kbd(A)}}} (gnaw-list-patch-apply)

Apply the report’s patches with git apply ({{{kbd(C-u A)}}}: git am).

{{{kbd(:)}}} (gnaw-list-attachments)

Open a menu acting on the patches and attachments.

Filtering and sorting (see *Searching):

{{{kbd(/)}}} (gnaw-list-filter)

Filter the list by a query.

{{{kbd(=)}}} (gnaw-list-filter-transient)

Filter by one field (a transient menu).

{{{kbd(t)}}} (gnaw-list-limit-type)

Limit to a report type (bug, patch, request…).

{{{kbd(T)}}} (gnaw-list-filter-topic)

Filter by a topic, with completion.

{{{kbd(a)}}} (gnaw-list-filter-acked)

Keep only the acked reports (acked:*).

{{{kbd(o)}}} (gnaw-list-filter-owned)

Keep only the owned reports (owned:*).

{{{kbd(c)}}} (gnaw-list-limit-closed)

Keep only the closed reports (canceled, resolved…), shown in italic. Gnaw loads closed reports when a source lists them (e.g. an all.json report file) but keeps them out of the list until a query names a close flag (flags:E) or the closed: key, as this command does.

{{{kbd(.)}}} (gnaw-list-limit-awaiting)

Keep only the reports awaiting a reply.

{{{kbd()}}} (~gnaw-list-limit-related)

Keep only the reports with related reports.

{{{kbd(+)}}} (gnaw-list-limit-attachments)

Keep only the reports with attachments.

{{{kbd(C-RET)}}} (gnaw-list-filter-cell)

Toggle a filter on the cell at point (see <a href=”*Filtering from the cell at point”>*Filtering from the cell at point).

{{{kbd(\)}}} (gnaw-select-preset-filter)

Apply a preset filter (see *Preset filters).

{{{kbd(|)}}} (gnaw-save-preset-filter)

Save the active filter as a preset (see *Preset filters). With {{{kbd(C-u)}}}, edit all the presets in a buffer instead (gnaw-edit-preset-filters), one query per line; {{{kbd(C-c C-c)}}} saves the list and {{{kbd(C-c C-k)}}} cancels.

{{{kbd(s)}}}, {{{kbd(S)}}} (gnaw-list-sort)

Sort by the column at point; on Mark, by sticky/dismiss.

{{{kbd(^)}}} (gnaw-sort)

Sort by a criterion chosen from a menu.

Marks (see *Marks: sticky and dismiss):

{{{kbd(!)}}} (gnaw-list-toggle-sticky)

Mark the report sticky (! in bold; the gnaw CLI exports it to todo.org).

{{{kbd(d)}}} (gnaw-list-flag-dismiss)

Flag the report for dismissal, shown D.

{{{kbd(x)}}} (gnaw-list-execute-flags)

Dismiss all the flagged reports.

{{{kbd(D)}}} (gnaw-list-toggle-dismiss)

Dismiss immediately: hide the report, shown d.

{{{kbd(u)}}} (gnaw-list-remove-marks)

Remove the sticky/dismiss mark or the flag.

{{{kbd(C-/)}}}, {{{kbd(C-_)}}} (gnaw-list-undo)

Undo the last mark change of this session ({{{kbd(C-_)}}} helps on terminals where {{{kbd(C-/)}}} does not get through).

{{{kbd(_)}}} (gnaw-list-toggle-dismissed)

Show or hide dismissed reports.

Refreshing and help:

{{{kbd(g)}}} (gnaw-list-reload)

Reload from the local cache.

{{{kbd(G)}}} (gnaw-list-update)

Refresh the remote cache in the background, then reload once the download finishes; Emacs stays responsive meanwhile.

{{{kbd(h)}}}, {{{kbd(?)}}} (gnaw-show-help)

List these bindings in a help window.

4.3 Marks: sticky and dismiss

Two mutually exclusive local marks drive the view: sticky promotes a report (! in bold in the Mark column, exported to todo.org by the gnaw CLI), dismiss hides it until {{{kbd(_)}}} reveals them (d, in a dimmed color).

Like in Dired, {{{kbd(d)}}} only flags a report (shown D) and {{{kbd(x)}}} dismisses all the flagged ones at once; {{{kbd(D)}}} dismisses right away; {{{kbd(u)}}} clears the mark or flag. Each of {{{kbd(!)}}}, {{{kbd(d)}}}, {{{kbd(D)}}} and {{{kbd(u)}}} then moves point to the next row, keeping the column. {{{kbd(D)}}} and {{{kbd(x)}}} turn off the follow mode first.

{{{kbd(C-/)}}} (gnaw-undo outside the list buffer) undoes the mark changes of the session, most recent first, restoring the entries as they were, original timestamps included—something re-toggling a mark cannot do. One undo restores a whole {{{kbd(x)}}} batch.

Marks live in state.edn, shared with the gnaw CLI and the front-ends (see *The Front-end API); the undo only restores the keys gnaw.el owns, so the marks the CLI wrote in the meantime survive it.

4.4 Related reports

On a report with related reports, {{{kbd(TAB)}}} narrows the list to the report and its relations, closed ones in italic; {{{kbd(TAB)}}} or {{{kbd(q)}}} restores the full list, and so does setting or clearing a filter.

5 Searching

5.1 Query syntax

{{{kbd(/)}}} filters the list with key:value tokens.

For example, "type:bug subject:latex acked:bzg" narrows the list to the bug reports whose subject matches “latex”, acked by someone whose address matches “bzg”. See *Search keys for all the keys.

While you type, the list previews what the query matches, refreshing at each pause (gnaw-list-filter-live-delay, 0.2 seconds). A half-typed trailing token stays out of the preview until it can match something sensible: a key awaiting its value (subject:), an open /regexp/ or "quote, or a free-text word shorter than gnaw-list-filter-live-min-chars (3). {{{kbd(C-g)}}} restores the view the preview replaced; set gnaw-list-filter-live to nil to disable the preview altogether.

Operator | is OR between clauses, space is AND between tokens, and a comma inside a value is OR (acked:alice,bob). So a b c | d reads as (a b c) | d. A leading - negates its whole token, comma alternatives included: -type:patch, -latex, -acked:alice,bob (acked by neither), or acked:* -acked:bzg (acked, but not by bzg).

Double quotes make a value literal and keep it whole—=”preamble shuffling”= and subject:"preamble shuffling" match that phrase; spaces, commas, | and slashes lose their meaning inside the quotes.

An empty value matches nothing, whatever the key.

A value in slashes matches as a case-insensitive Emacs regexp on the text fields (from:, subject: and bare words, topic:, mid:, and the person fields acked: owned: closed:): subject:/v[0-9]+/. The regexp is always the whole value—a , loses its meaning inside, and | being a clause separator, \| cannot appear either: to OR two regexps, use two clauses (s:/a/ | s:/b/).

The mode line shows the active query; {{{kbd(q)}}} (or {{{kbd(C-u )}}}, or {{{kbd()}}} then {{{kbd(RET)}}}) clears it.

5.2 Search keys

LongShortMatches
from:bzgf:from address or name
subject:crash,latexs:subject (a bare word matches subject)
similar:a+b+csubjects sharing 3 of the +-joined words
topic:agendaT:topic
source:O source:emacsS:source letter (one char) or name
type:patcht:report type
priority:2p:exact priority (0-3, shown A/B/C)
votes:3..v:vote score (N, N.., ..N, A..B ranges)
msgs:10..M:thread size, initial mail included
mid:<...>m:message-id
acked:bzg owned:* closed:*a: o: c:by person (or * for any)
urgent:* important:falseu: i:priority bits (set or not)
flags:AOF:Flags letters, all required (A O C R E S)
att:~+A:Att glyphs, all required (. ~ + x @ #)
date:3d date:A..Bd:created (durations Nd/Nw/Nm, ranges)
deadline:2mD:due within / on (forward)
expired:10de:expiring within (forward)

5.3 Filter toggles and the prefix ladder

The fixed filter keys—{{{kbd(a)}}}, {{{kbd(o)}}}, {{{kbd(c)}}}, {{{kbd(.)}}}, {{{kbd(~)}}}, {{{kbd(+)}}}—are toggles: typing the key again while its filter is active clears it and puts point back on the report it was on.

With a prefix argument, the filter keys—{{{kbd(t)}}}, {{{kbd(T)}}}, {{{kbd(a)}}}, {{{kbd(o)}}}, {{{kbd(c)}}}, {{{kbd(.)}}}, {{{kbd(~)}}}, {{{kbd(+)}}}, {{{kbd(C-RET)}}}, and the fields of the {{{kbd(=)}}} menu—add their condition to the active filter (AND) instead of replacing it: {{{kbd(a C-u o)}}} keeps the reports both acked and owned. With two prefix arguments, the toggle keys and {{{kbd(C-RET)}}} negate their condition instead, excluding the matching reports: {{{kbd(C-u C-u o)}}} keeps the reports nobody owns (-owned:*). With three, they add the negated condition to the active filter: {{{kbd(a C-u C-u C-u o)}}} keeps the acked reports nobody owns (acked:* -owned:*).

5.4 Filtering from the cell at point

{{{kbd(C-RET)}}} builds the query from the cell at point: the source on S, the author on From, the report type on Type, at least that vote score on Votes, all the cell’s letters on Flags, all its glyphs on Att, at least as many messages on Msgs, created on or after that date on Created, and similar: on Subject—subjects sharing at least three significant words with the one at point (four letters or more, a hyphenated name like org-element counting as one word, bracketed tags like [PATCH v2] ignored). {{{kbd(C-RET)}}} again, without a prefix argument, restores the view the filter replaced: the previous query, a related-reports narrowing, or the full list.

5.5 Preset filters

gnaw-preset-filters holds a list of predefined filter query strings; {{{kbd(\)}}} prompts to apply one (gnaw-select-preset-filter), and {{{kbd(|)}}} saves the active filter to the list for future sessions (gnaw-save-preset-filter), after letting you edit it.

{{{kbd(C-u |)}}} (gnaw-edit-preset-filters) opens the whole list in a buffer, one query per line: add, change, reorder or delete lines, then type {{{kbd(C-c C-c)}}} to save the result for future sessions, or {{{kbd(C-c C-k)}}} to cancel.

6 Reading Messages

gnaw-open-message-method selects how {{{kbd(RET)}}} opens a report, per source: an alist mapping a source name to auto, mua, gnus, notmuch, mu4e or web (the entry keyed by t is the default). gnus reads the group from gnaw-gnus-group (an alist; asked with completion otherwise); notmuch and mu4e open by message-id; web fetches the public-inbox archive.

The web method displays the fetched message in a gnaw-message-mode buffer, where {{{kbd(w)}}} (gnaw-message-browse) opens the archived page in a browser and {{{kbd(q)}}} quits.

{{{kbd(SPC)}}} toggles the mail in a window below the list, keeping point in the list; {{{kbd(f)}}} (gnaw-list-follow-mode) does this automatically as point moves.

Front-ends set gnaw-open-message-function to handle auto and mua. {{{kbd(M-x gnaw-configure-email-client)}}} configures this per source, including the Gnus group when gnus is selected.

7 Patches and Attachments

7.1 Viewing and saving

{{{kbd(v)}}} views an attachment and {{{kbd(V)}}} saves one: with a single attachment they act right away, otherwise they ask which one (with an all patches candidate on multi-patch reports).

{{{kbd(:)}}} opens the generic menu on the attachments of the report at point. A single patch opens the patch menu: {{{kbd(v)}}} view in diff-mode, {{{kbd(a)}}} runs git apply, {{{kbd(m)}}} git am, {{{kbd(s)}}} save to a directory. Gnaw displays a single calendar (.ics) or text attachment right away, and lists several in a buffer where {{{kbd(+)}}} (or {{{kbd(RET)}}}) acts the same way on the attachment at point, with an extra first row acting on all the patches at once when several are patches.

Saving proposes the source’s :repo (the first one, when it lists several) as the target directory and asks before overwriting; set gnaw-save-no-confirm to skip both prompts, and a prefix argument ({{{kbd(C-u V)}}}) inverts that setting for one call.

7.2 Applying patches

{{{kbd(A)}}} applies the report’s patches with git apply (patch the working tree) and {{{kbd(C-u A)}}} with git am (apply as commits on a branch it offers to create).

Git runs in the source’s :repo (falling back to gnaw-apply-repo or a prompt), warning when a patch series looks incomplete. When the source lists several repos, the apply commands first ask which one to use, with completion.

git am first asks for a branch to create—proposing a name built from the sender and subject, like ec/fix-tangle__v2 (gnaw-am-branch-function); empty input keeps a detached HEAD—and for its start point, proposing the patches’ recorded base-commit: when the repo has it.

Set gnaw-am-create-worktree to apply in a new worktree instead, leaving the repo’s checkout untouched; a prefix argument on the am commands ({{{kbd(C-u m)}}} in the menu) inverts that setting for one call.

Gnaw folds the review trailers BONE collects from replies (Acked-by:, Reviewed-by:…) into the commit messages, b4-style, working on temporary copies of the patch files—it never touches the cached originals; set gnaw-am-fold-trailers to nil to disable. On lists where review acts are BONE commands instead (Acked., Confirmed., Reviewed., Reviewed-by:…), gnaw turns the report state into synthetic trailers (gnaw-am-synthetic-trailers): the acked state—BONE’s strong review approval—yields Reviewed-by: and the archived page a Link: line; add owned to also turn the owner into a Reviewed-by:. Gnaw never upgrades a literal Acked-by: collected from the thread: it suppresses the synthetic line instead.

A successful git am pops magit-status (or dired when magit is not loaded) on the directory it applied in; set gnaw-am-show-repo to nil to keep the plain message. See the *gnaw-git* buffer for the output.

When git am fails, gnaw shows the failure and offers to undo the run’s setup: it aborts the am session, removes the worktree it added—else restores the previous checkout—and deletes the branch it created, leaving no trace in the repo. Answer no to keep the mid-am state instead and fix it by hand (resolve the conflicts, then git am --continue—or git am --abort). Set gnaw-am-undo-on-failure to t to always undo without asking, or to nil to always keep the mid-am state.

8 Configuration

8.1 The configuration file

{:sources [{:urls ["https://tracker.orgmode.org/reports/all-open.json"]
            :name "Org mode ML"
            :letter "O"
            :repo "~/src/org-mode"}]
 :my-addresses ["me@example.org"]
 :skip-columns ["owner"]}
:sources
Each has :urls (a vector of reports.json URLs), an optional :name, a :letter identifying the source in the S column when several sources are configured (asked interactively when missing) and a :repo where patches apply—a directory, or a vector of them (:repo ["~/src/org-mode" "~/src/org-contrib"]): applying then asks which repo to use, and saving proposes the first.
:my-addresses
Your email addresses.
:skip-columns
Browser columns to hide (by name, case-insensitive).

8.2 Options

All the defcustom options, by theme ({{{kbd(M-x customize-group RET gnaw)}}}).

8.2.1 General options

gnaw-config-dir

Default: ~/.config/gnaw. Holds config.edn, state.edn and cache/. Sources always come from config.edn.

gnaw-http-timeout

Default: 30. Seconds before a fetch gives up.

gnaw-cache-attachments-max-age

Default: nil (keep forever). Days after which {{{kbd(M-x gnaw-cache-cleanup)}}} discards a cached attachment.

gnaw-after-update-hook

Run once {{{kbd(M-x gnaw-update)}}} finished its background downloads (after the command itself returned).

8.2.2 Reading options

gnaw-open-message-method, gnaw-gnus-group

Per-source open method and Gnus group (see *Reading Messages).

8.2.3 Patch and attachment options

gnaw-apply-repo

Fallback git repo when the source has no :repo.

gnaw-git-apply-options, gnaw-git-am-options

Default: --3way. Extra arguments for git apply and git am.

gnaw-am-branch-function

Proposes the branch name for git am.

gnaw-am-create-worktree

Default: nil. Apply git am in a new worktree.

gnaw-am-read-worktree-function

Reads the to-be-created worktree directory, called with the repo and the branch name; the default proposes a sibling of the repo named after both, like org-mode-ec-fix-tangle.

gnaw-am-fold-trailers

Default: t. Fold the review trailers collected by BONE into the commits created by git am (see *Applying patches).

gnaw-am-show-repo

Default: t. Show the directory (magit-status or dired) after a successful git am.

gnaw-am-undo-on-failure

Default: ask. Whether a failed git am undoes the run’s setup: branch, checkout and worktree (ask, nil or t).

gnaw-am-synthetic-trailers

Default: (acked link). Trailers synthesized from the report state—acked state, archived page, optionally the owner—and folded along the collected ones.

gnaw-checkout-base

Default: ask. Whether git apply checks out a patch’s recorded base-commit: first (ask, nil or t).

gnaw-save-no-confirm

Default: nil. When non-nil, the save commands write into the first configured :repo and overwrite without asking; a prefix argument inverts the setting for one call.

8.2.4 Report list options

gnaw-list-columns

Columns of the report list (see *The report list)—by default Mark, Type, Votes, Flags, Att, Msgs, From, Subject and Created. The docstring shows how to re-add the left-out Pri (priority), Activity (last activity) and Topic columns.

gnaw-list-sort-key

Default: ("Created" . t). Initial sort column and direction, most recently created first; {{{kbd(s)}}} (or {{{kbd(S)}}}) on a column re-sorts interactively.

gnaw-list-follow-delay

Default: 0.3. Idle seconds before follow mode ({{{kbd(f)}}}) shows the report at point.

gnaw-inhibit-startup-tip

Default: nil. When non-nil, {{{kbd(M-x gnaw)}}} starts silently instead of displaying a tip presenting a random command.

8.2.5 Filtering options

gnaw-preset-filters

Default: nil. List of predefined filter query strings (see <a href=”*Preset filters”>*Preset filters); {{{kbd(\)}}} prompts to apply one (gnaw-select-preset-filter), {{{kbd(|)}}} saves the active filter here (gnaw-save-preset-filter) and {{{kbd(C-u |)}}} edits the whole list in a buffer (gnaw-edit-preset-filters).

gnaw-list-filter-live

Default: t. Whether {{{kbd(/)}}} previews its results while typing (see *Query syntax).

gnaw-list-filter-live-delay

Default: 0.2. Idle seconds before the live preview refreshes.

gnaw-list-filter-live-min-chars

Default: 3. Minimum needle length before it takes part in the live preview.

8.3 Faces

The list cells inherit standard faces (gnaw-type-bug, gnaw-type-patch, gnaw-date, gnaw-acked, gnaw-owned, gnaw-votes…), so colors follow your theme; {{{kbd(M-x customize-group RET gnaw)}}} lists them all.

9 The Front-end API

This API is what the mail front-ends build on: gnus-gnaw, notmuch-gnaw and mu4e-gnaw all use it to list the reports next to their mail and to toggle the local marks from there.

(gnaw-reports)

Return the open reports as (MID . INFO) pairs.

(gnaw-sources)

Return the resolved report URLs and paths from config.edn.

(gnaw-load-config)

Return a plist (:addresses :skip-columns :source-configs :sources).

(gnaw-read-state)

Read state.edn into an alist.

(gnaw-write-state STATE)

Serialize an alist to state.edn.

(gnaw-toggle-mark MID INFO ACTION)

Toggle :sticky or :dismiss, and persist.

(gnaw-action-on-p STATE MID ACTION)

Return non-nil when the ACTION mark is set.

(gnaw-normalize-mid MID)

Ensure angle brackets around a message-id.

INFO is a plist with :type :subject :date :from :from-name (used to enrich state) plus display and action keys =:flags :priority :votes :deadline :expiry :last-activity :replies :topic :source :source-name :source-letter :archived-at :patches :events :texts :awaiting :related :series :patch-seq :acked :owned :owned-name :closed=.

10 Contributing

You can also send an email to the author and support this work on liberapay.

This project uses Intentional Versioning, with three audiences:

x
end users
y
indirect users, via the front-ends
z
contributors to the codebase

11 Main Index

12 Command and Function Index

13 Variable Index

14 Copying

This manual is for gnaw.el version {{{version}}}.

Copyright © 2026 Bastien Guerry

This manual is free documentation: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This manual is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

15 Export Setup

16 Notes

1 See The issue with issue trackers for a rationale.