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.jsonfiles, - parses and serializes the EDN config and
state.ednfiles 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.
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)}}}.
{{{kbd(M-x gnaw)}}} opens the *gnaw* report browser. With no source
configured, it runs the full interactive setup:
- a source URL—a base URL, a
reports/meta.json, or areports/...jsonfile (all resolve to thereports/directory); - which report files to track, chosen from
meta.json’sreports-files(defaultall-open.json; you can pick several); - a source name (defaults to
meta.json’s:source); - a letter identifying the source in the browser’s
Scolumn (suggesting the first free letter of the name, so Org mode ML suggestsO); - one or more local git repositories for that source, used to apply patches;
- how to open report mail (
auto,mua,gnus,notmuch,mu4eorweb); - when
gnusis 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.
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.
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.jsonreport file) but keeps them out of the list until a query names a close flag (flags:E) or theclosed: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 totodo.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.
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.
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.
{{{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.
| Long | Short | Matches |
|---|---|---|
from:bzg | f: | from address or name |
subject:crash,latex | s: | subject (a bare word matches subject) |
similar:a+b+c | subjects sharing 3 of the +-joined words | |
topic:agenda | T: | topic |
source:O source:emacs | S: | source letter (one char) or name |
type:patch | t: | report type |
priority:2 | p: | 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:false | u: i: | priority bits (set or not) |
flags:AO | F: | Flags letters, all required (A O C R E S) |
att:~+ | A: | Att glyphs, all required (. ~ + x @ #) |
date:3d date:A..B | d: | created (durations Nd/Nw/Nm, ranges) |
deadline:2m | D: | due within / on (forward) |
expired:10d | e: | expiring within (forward) |
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:*).
{{{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.
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.
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.
{{{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.
{{{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.
{: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:letteridentifying the source in theScolumn when several sources are configured (asked interactively when missing) and a:repowhere 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).
All the defcustom options, by theme ({{{kbd(M-x customize-group RET
gnaw)}}}).
gnaw-config-dir-
Default:
~/.config/gnaw. Holdsconfig.edn,state.ednandcache/. Sources always come fromconfig.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).
gnaw-open-message-method,gnaw-gnus-group-
Per-source open method and Gnus group (see *Reading Messages).
gnaw-apply-repo-
Fallback git repo when the source has no
:repo. gnaw-git-apply-options,gnaw-git-am-options-
Default:
--3way. Extra arguments forgit applyandgit am. gnaw-am-branch-function-
Proposes the branch name for
git am. gnaw-am-create-worktree-
Default: nil. Apply
git amin 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-statusordired) after a successfulgit am. gnaw-am-undo-on-failure-
Default:
ask. Whether a failedgit amundoes the run’s setup: branch, checkout and worktree (ask,nilort). 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. Whethergit applychecks out a patch’s recordedbase-commit:first (ask,nilort). gnaw-save-no-confirm-
Default: nil. When non-nil, the save commands write into the first configured
:repoand overwrite without asking; a prefix argument inverts the setting for one call.
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.
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.
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.
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.edninto an alist. (gnaw-write-state STATE)-
Serialize an alist to
state.edn. (gnaw-toggle-mark MID INFO ACTION)-
Toggle
:stickyor: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=.
- Send a bug report with
[BUG] gnaw.el: <SHORT EXPLICIT BUG DESCRIPTION> - Send a patch with
[PATCH] gnaw.el: <COMMIT SUMMARY> - Send a feature request with
[FR] gnaw.el: <FEATURE REQUEST> - Share any other question or idea
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
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.
1 See The issue with issue trackers for a rationale.