diff --git a/.github/REFACTORING_PATTERNS.md b/.github/REFACTORING_PATTERNS.md new file mode 100644 index 00000000000..0597be2f728 --- /dev/null +++ b/.github/REFACTORING_PATTERNS.md @@ -0,0 +1,49 @@ +## Pattern: Parse/Execute Separation + +**Use when**: Refactoring command parsing functions for testability + +**Template**: +```c +// 1. Define data structure +struct ParseMyCommandData { + char *arg1; + int flags; + // ... fields for all parsed components +}; + +// 2. Define array if multiple items +ARRAY_HEAD(ParseMyCommandArray, struct ParseMyCommandData *); + +// 3. Create parser (returns bool, takes Buffer *err) +static bool parse_mycommand_args(struct Command *cmd, struct Buffer *line, + struct Buffer *err, struct ParseMyCommandArray *array); + +// 4. Create executor (performs action) +static enum CommandResult parse_mycommand_exec(struct Command *cmd, + struct ParseMyCommandArray *array, + struct Buffer *err); + +// 5. Create cleanup helpers +static void parse_mycommand_data_free(struct ParseMyCommandData **ptr); +static void parse_mycommand_array_free(struct ParseMyCommandArray *array); + +// 6. Update original function to orchestrate +enum CommandResult parse_mycommand(struct Buffer *buf, struct Buffer *s, + intptr_t data, struct Buffer *err) { + struct ParseMyCommandArray args = ARRAY_HEAD_INITIALIZER; + + if (!parse_mycommand_args(cmd, s, err, &args)) + goto done; + + rc = parse_mycommand_exec(cmd, &args, err); + +done: + parse_mycommand_array_free(&args); + return rc; +} + +// 7. Write comprehensive tests for parse_mycommand_args() +``` + +**Examples in codebase**: `parse_unbind()`, `parse_mailboxes()`, `parse_folder_hook()` + diff --git a/.github/codeql-custom-queries/CustomSanitizers.qll b/.github/codeql-custom-queries/CustomSanitizers.qll new file mode 100644 index 00000000000..dc207c74a70 --- /dev/null +++ b/.github/codeql-custom-queries/CustomSanitizers.qll @@ -0,0 +1,59 @@ +/** + * Provides custom sanitizers for NeoMutt's Buffer pool operations. + * + * This module should be imported by taint tracking queries to recognize + * when Buffers are sanitized by buf_reset(), memset(), or buf_pool_release(). + */ + +import cpp +import semmle.code.cpp.dataflow.new.TaintTracking + +/** + * A call to a function that clears a Buffer's memory. + */ +class BufferClearingFunction extends FunctionCall { + BufferClearingFunction() { + this.getTarget().getName() in ["buf_reset", "buf_pool_release", "memset"] + } +} + +/** + * A sanitizer node for Buffer pool operations. + * This marks data as clean after certain function calls. + */ +class BufferPoolSanitizer extends DataFlow::Node { + BufferPoolSanitizer() { + // After buf_reset(buf), the buffer is cleared + exists(FunctionCall call | + call.getTarget().getName() = "buf_reset" and + this.asExpr() = call.getArgument(0) + ) + or + // After buf_pool_release(&buf), the buffer is cleared (calls buf_reset internally) + // buf_pool_release takes Buffer** and internally dereferences to call buf_reset(*ptr) + // The sanitization applies to the buffer being released, not the pointer itself + exists(FunctionCall call | + call.getTarget().getName() = "buf_pool_release" and + ( + this.asExpr() = call.getArgument(0) + or + // Also sanitize pointer dereference for dataflow tracking + this.asExpr().(PointerDereferenceExpr).getOperand() = call.getArgument(0) + ) + ) + or + // After memset(ptr, 0, size), the memory is cleared + exists(FunctionCall call | + call.getTarget().getName() = "memset" and + call.getArgument(1).getValue().toInt() = 0 and + this.asExpr() = call.getArgument(0) + ) + or + // buf_pool_get() returns a clean buffer (previously sanitized by buf_pool_release) + // All buffers in the pool have been cleared by buf_reset before being returned to pool + exists(FunctionCall call | + call.getTarget().getName() = "buf_pool_get" and + this.asExpr() = call + ) + } +} diff --git a/.github/codeql-custom-queries/README.md b/.github/codeql-custom-queries/README.md new file mode 100644 index 00000000000..04a61f376a7 --- /dev/null +++ b/.github/codeql-custom-queries/README.md @@ -0,0 +1,48 @@ +# Custom CodeQL Queries for NeoMutt + +This directory contains custom CodeQL queries and libraries to reduce false positives in CodeQL security scanning. + +## Buffer Pool Sanitizers + +### Problem + +CodeQL was reporting false positives related to NeoMutt's Buffer Pool: + +1. A function gets a Buffer from the Buffer Pool using `buf_pool_get()` +2. It reads into that Buffer from untrusted input (e.g., user config) +3. It returns the Buffer to the Pool using `buf_pool_release()` +4. Later, another function gets that same Buffer from the Pool +5. CodeQL incorrectly believed the Buffer was still tainted + +This is a false positive because `buf_pool_release()` calls `buf_reset()` which uses `memset()` to clear the Buffer's memory. + +### Solution + +We created custom CodeQL sanitizers that recognize when Buffers are cleared: + +- **`CustomSanitizers.qll`**: A library file that defines `BufferPoolSanitizer` class +- **`TaintWithBufferSanitizers.ql`**: A query that uses these sanitizers for taint tracking + +### Sanitized Functions + +The following functions are recognized as sanitizers: + +- `buf_reset(buf)`: Clears the Buffer with `memset(buf->data, 0, buf->dsize)` +- `buf_pool_release(&buf)`: Calls `buf_reset()` internally before returning Buffer to Pool +- `memset(ptr, 0, size)`: Explicitly clears memory to zero +- `buf_pool_get()`: Returns a clean Buffer from the Pool + +## Files + +- **`qlpack.yml`**: Defines the CodeQL query pack +- **`CustomSanitizers.qll`**: Library with sanitizer definitions +- **`TaintWithBufferSanitizers.ql`**: Taint tracking query using the sanitizers + +## Usage + +These queries are automatically included by the GitHub Actions CodeQL workflow via the configuration in `.github/codeql.yml`. + +## References + +- [CodeQL C/C++ documentation](https://codeql.github.com/docs/codeql-language-guides/codeql-for-cpp/) +- [CodeQL taint tracking](https://codeql.github.com/docs/codeql-language-guides/using-flow-labels-for-precise-data-flow-analysis/) diff --git a/.github/codeql-custom-queries/TaintWithBufferSanitizers.ql b/.github/codeql-custom-queries/TaintWithBufferSanitizers.ql new file mode 100644 index 00000000000..83ad0783bea --- /dev/null +++ b/.github/codeql-custom-queries/TaintWithBufferSanitizers.ql @@ -0,0 +1,68 @@ +/** + * @name Taint tracking with Buffer pool sanitizers + * @description Detects taint flow with custom sanitizers for NeoMutt Buffer pool + * @kind path-problem + * @problem.severity warning + * @id neomutt/taint-with-buffer-sanitizers + * @tags security + * external/cwe/cwe-079 + * external/cwe/cwe-089 + */ + +import cpp +import semmle.code.cpp.dataflow.new.TaintTracking +import CustomSanitizers + +/** + * Configuration for taint tracking that includes Buffer pool sanitizers. + */ +private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + // Sources: user input, file reads, network reads, config parsing + exists(FunctionCall call | + call.getTarget().getName() in [ + // Standard C input functions + "fgets", "fgetc", "getc", "getchar", "gets", "scanf", "fscanf", + "read", "recv", "recvfrom", "recvmsg", + // NeoMutt specific input functions + "mutt_file_read_line", "mutt_socket_readln" + ] and + (source.asExpr() = call or source.asExpr() = call.getAnArgument()) + ) + } + + predicate isSink(DataFlow::Node sink) { + // Sinks: command execution, SQL queries, format strings + exists(FunctionCall call | + call.getTarget().getName() in [ + // Command execution + "system", "popen", "execl", "execlp", "execle", + "execv", "execvp", "execve", "execvpe", + // Other potentially dangerous functions + "eval" + ] and + sink.asExpr() = call.getAnArgument() + ) + or + // Format string vulnerabilities + exists(FunctionCall call | + call.getTarget().getName() in ["printf", "fprintf", "sprintf", "snprintf"] and + sink.asExpr() = call.getArgument(0) + ) + } + + predicate isBarrier(DataFlow::Node node) { + // Use our custom Buffer pool sanitizers + node instanceof BufferPoolSanitizer + } +} + +private module Flow = TaintTracking::Global; +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, + "Taint flow from $@ to $@.", + source.getNode(), "user input", + sink.getNode(), "dangerous sink" diff --git a/.github/codeql-custom-queries/qlpack.yml b/.github/codeql-custom-queries/qlpack.yml new file mode 100644 index 00000000000..33b3ceaab0b --- /dev/null +++ b/.github/codeql-custom-queries/qlpack.yml @@ -0,0 +1,6 @@ +name: neomutt/buffer-pool-sanitizers +version: 1.0.0 +library: true +dependencies: + codeql/cpp-all: "*" +extractor: cpp diff --git a/.github/codeql.yml b/.github/codeql.yml index 70acfdfd7eb..cf3c8c6bdaa 100644 --- a/.github/codeql.yml +++ b/.github/codeql.yml @@ -1,3 +1,8 @@ query-filters: - exclude: id: cpp/fixme-comment + +# Include custom queries for Buffer pool sanitizers +packs: + - codeql/cpp-queries + - .github/codeql-custom-queries diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000000..c89c0e585ec --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,442 @@ +# GitHub Copilot Instructions for NeoMutt + +## Project Overview + +NeoMutt is a command-line email client (Mail User Agent) written in C. It's a project that gathers all the patches against Mutt and provides a place for developers to collaborate. + +### Architecture + +NeoMutt is organized into modular libraries, each with its own directory: + +- **Core libraries**: `mutt/`, `core/`, `email/`, `config/`, `address/` +- **Protocol handlers**: `imap/`, `pop/`, `nntp/`, `maildir/`, `mbox/`, `mh/` +- **UI components**: `gui/`, `pager/`, `browser/`, `menu/`, `sidebar/` +- **Features**: `notmuch/`, `autocrypt/`, `compress/`, `alias/`, `attach/` +- **Utilities**: `parse/`, `pattern/`, `expando/`, `store/`, `hcache/` + +Each module typically has a `lib.h` file that serves as the public interface. + +## Code Style and Conventions + +### C Style Guidelines + +- **Standard**: C11 with GNU extensions +- **Indentation**: 2 spaces (never tabs for C code, except Makefiles use tabs) +- **Line length**: 80 characters maximum +- **Brace style**: Allman style (braces on separate lines) +- **Pointer alignment**: Right-aligned (e.g., `char *ptr`) +- **Header order**: Follow the `.clang-format` priority order: + 1. `"config.h"` (always first) + 2. Standard library headers (`<...>`) + 3. `"private.h"` (if applicable) + 4. Library headers by category (mutt/, address/, config/, email/, core/, etc.) + 5. Local headers + +### Naming Conventions + +- **Functions**: Use snake_case with module prefix (e.g., `mutt_str_copy()`, `buf_pool_get()`) +- **Types**: CamelCase for structs and typedefs +- **Constants**: UPPER_CASE for macros and enum values +- **Files**: snake_case.c/h + +### Documentation + +- **File headers**: Every file must have a Doxygen `@file` comment describing its purpose +- **Function comments**: Use Doxygen format with `@param`, `@return`, etc. +- **Copyright**: Include appropriate copyright headers with author attributions +- **License**: GPL v2+ + +Example: +```c +/** + * @file + * Brief description of file purpose + * + * @authors + * Copyright (C) YYYY Name + * + * @copyright + * This program is free software: 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 2 of the License, or (at your option) any later + * version. + */ +``` + +## Common Patterns and Practices + +### Buffer Management + +- Use Buffer Pool pattern for temporary buffers: + ```c + struct Buffer *buf = buf_pool_get(); + // ... use buffer ... + buf_pool_release(&buf); + ``` +- Always release buffers, even on error paths (use goto cleanup pattern if needed) + +### Error Handling + +- Use goto labels for cleanup on error paths +- Return meaningful error codes +- Clean up resources in reverse order of allocation + +### String Handling + +- Use NeoMutt's string functions from `mutt/lib.h` (e.g., `mutt_str_copy()`, `mutt_str_equal()`) +- Always check buffer sizes to prevent overflows +- Prefer `mutt_str_*` functions over standard C string functions + +### Hash Tables + +- Use `mutt_hash_walk()` with `HashWalkState` to iterate hash tables +- Example from repository memories: + ```c + struct HashWalkState state = { 0 }; + while (mutt_hash_walk(hash_table, &state)) + { + // Process entry + } + ``` + +### Memory Safety + +- Always initialize pointers to NULL +- Check allocations for failure +- Use `FREE()` macro which sets pointer to NULL after freeing +- Be careful with pointer arithmetic + +## Common Pitfalls to Avoid + +### Parse/Execute Pattern + +When refactoring command parsing: +- ALWAYS separate parsing from execution (see parse_unbind() pattern) +- Create struct to hold parsed data +- Return early on parse errors with clear messages +- Write tests for parser BEFORE implementation + +### Buffer Pool +✅ ALWAYS release buffers on ALL code paths: +```c +struct Buffer *buf = buf_pool_get(); +if (error_condition) { + buf_pool_release(&buf); // Release before return! + return -1; +} +buf_pool_release(&buf); +``` + +## Build and Test + +NeoMutt uses GitHub Actions for continuous integration. The main workflow is `.github/workflows/build-and-test.yml` which performs three build configurations: default, minimal (disabled), and full (everything). + +### Standard Build Configurations + +**Default Build:** +```bash +./configure --full-doc +make neomutt +``` + +**Minimal Build (features disabled):** +```bash +./configure --full-doc --disable-nls --disable-pgp --disable-smime --ssl --gsasl +make neomutt +``` + +**Full Build (all features enabled):** +```bash +./configure --full-doc --autocrypt --bdb --fmemopen --gdbm --gnutls --gpgme --gss \ + --kyotocabinet --lmdb --lua --lz4 --notmuch --pcre2 --qdbm --rocksdb --sasl --tdb \ + --testing --tokyocabinet --with-lock=fcntl --zlib --zstd +make neomutt +``` + +### Build Steps + +1. **Configure**: Set up the build with desired features + ```bash + mkdir build-default + cd build-default + ../configure --full-doc [options] + ``` + +2. **Compile**: Build the neomutt binary + ```bash + make neomutt + ``` + +3. **Verify**: Check the build + ```bash + ./neomutt -v # Show version and features + ./neomutt -h all # Show all help + ``` + +4. **Validate Documentation**: + ```bash + make validate-docs + ``` + +### Testing + +**Prerequisites**: Unit tests require test files from a separate repository. + +1. **Setup Test Files** (one-time setup): + ```bash + # Clone test files repository + git clone https://github.com/neomutt/neomutt-test-files test-files + cd test-files + ./setup.sh + cd .. + ``` + +2. **Set Environment Variable**: + ```bash + # For local development: + export NEOMUTT_TEST_DIR=$PWD/test-files + + # In GitHub Actions CI: + export NEOMUTT_TEST_DIR=$GITHUB_WORKSPACE/test-files + ``` + +3. **Build Tests**: + ```bash + make test/neomutt-test + ``` + +4. **Run All Tests**: + ```bash + make test + # Or directly: + test/neomutt-test + ``` + +5. **List Available Tests**: + ```bash + test/neomutt-test -l + ``` + +6. **Run Individual Tests**: + ```bash + test/neomutt-test test_parse_bind + test/neomutt-test test_mutt_str_copy + ``` + +### Test Structure + +Tests are located in `test/` directory, organized by module. Tests use the `acutest.h` framework. + +Test file structure: +```c +#define TEST_NO_MAIN +#include "config.h" +#include "acutest.h" +#include "mutt/lib.h" + +void test_function_name(void) +{ + // Test cases using TEST_CHECK() macros +} +``` + +### Fuzz Testing + +NeoMutt supports fuzz testing using LLVM's LibFuzzer. Fuzzing helps find security vulnerabilities and bugs by testing parsing functions with randomly generated inputs. + +#### Current Fuzz Targets + +Two fuzz executables exist in the `fuzz/` directory: + +- **`fuzz/address-fuzz`**: Tests email header parsing via `mutt_rfc822_read_header()` and MIME parsing via `mutt_parse_part()`. These are security-critical functions susceptible to remote attacks. +- **`fuzz/date-fuzz`**: Tests date parsing via `mutt_date_parse_date()`. + +#### Building the Fuzzers + +Fuzzing requires the `clang` compiler with the `-fsanitize=fuzzer` flag: + +```bash +# Configure with fuzzing enabled +./configure CC=clang --fuzzing --disable-doc --disable-nls --disable-idn2 + +# Build the fuzz executables +make CC=clang CXX=clang fuzz +``` + +**Note**: The `--fuzzing` flag automatically adds `-fsanitize=fuzzer` to CFLAGS and LDFLAGS. You may need `--disable-nls` and `--disable-idn2` if those libraries are not installed. + +#### Running the Fuzzers + +Basic usage: +```bash +# Run with default settings (1200 second timeout) +fuzz/address-fuzz + +# Run for a limited time (recommended for CI) +fuzz/address-fuzz -max_total_time=60 + +# Show help and all available options +fuzz/address-fuzz -help=1 +``` + +Using a corpus directory (recommended): +```bash +# Clone the address corpus +git clone https://github.com/neomutt/corpus-address.git + +# Run fuzzer with corpus +fuzz/address-fuzz corpus-address +``` + +Useful options: +- `-max_total_time=N`: Limit runtime to N seconds +- `-timeout=N`: Timeout per test case in seconds (default: 1200) +- `-jobs=N`: Run N parallel fuzzing jobs +- `-workers=N`: Number of worker processes +- `-verbosity=N`: Output verbosity level + +#### Fuzz Test Implementation + +Fuzz targets implement the LibFuzzer entry point: +```c +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + // Parse the fuzzed data + // Return 0 on success, -1 to reject input + return 0; +} +``` + +See `fuzz/address.c` and `fuzz/date.c` for examples. + +#### Future Fuzzing Targets + +The following libraries are candidates for future fuzz testing: + +- **`libcli`** (`cli/`): Command line parsing + - `cli_parse()`: Parses command line arguments into `CommandLine` struct + - Entry point: `cli/parse.c` + - Security relevance: Processes untrusted command line input + +- **`libparse`** (`parse/`): NeoMutt command parsing + - `parse_rc_line()`: Parses runtime configuration commands + - `parse_extract_token()`: Tokenizes config file lines + - Entry point: `parse/rc.c`, `parse/extract.c` + - Security relevance: Processes config files which may contain malicious content + +### Common Configure Options + +- **TLS/SSL**: `--gnutls` or `--ssl` +- **GPG**: `--gpgme` +- **Search**: `--notmuch` +- **Scripting**: `--lua` +- **Testing**: `--testing` +- **Fuzzing**: `--fuzzing` (requires clang compiler) +- **Authentication**: `--sasl`, `--gss`, `--gsasl` +- **Header Cache**: `--lmdb`, `--tokyocabinet`, `--kyotocabinet`, `--gdbm`, `--bdb`, `--qdbm`, `--rocksdb`, `--tdb` +- **Compression**: `--lz4`, `--zlib`, `--zstd` +- **Documentation**: `--full-doc` (build complete documentation set) + +### Code Formatting + +- Format C code with: `clang-format -i ` +- Settings are in `.clang-format` +- EditorConfig settings in `.editorconfig` for basic formatting + +## Security Considerations + +### Critical Security Practices + +1. **Input Validation**: Always validate and sanitize user input +2. **Buffer Overflows**: Use safe string functions with size limits +3. **Command Injection**: Carefully escape or validate data used in external commands +4. **Path Traversal**: Validate file paths +5. **Email Parsing**: Be careful with email headers and MIME parsing +6. **Credentials**: Never log or expose credentials +7. **Memory Safety**: Prevent use-after-free, double-free, buffer overflows + +### Security-Sensitive Areas + +- Email parsing (`parse/`, `email/`) +- External command execution (`external.c`) +- Network connections (`conn/`, `imap/`, `pop/`, `nntp/`) +- Cryptography (`ncrypt/`, `autocrypt/`) +- Configuration parsing (`config/`) + +## Contributing Guidelines + +### AI Assistance Disclosure + +**IMPORTANT**: If you use AI assistance while contributing, you **must** disclose this in the pull request. See `docs/CONTRIBUTING.md` for details. + +### Commit Message Format + +- First line: Short summary (≤50 characters) +- Blank line +- Detailed description (wrapped at ~80 characters) +- Use bullet points for multiple changes +- Reference relevant issues/PRs + +Example: +``` +hook: fix memory leak in pattern hooks + +- Free pattern before returning on error +- Add goto cleanup label for error paths +- Fixes #12345 +``` + +### Pull Request Checklist + +- [ ] Code follows the style guide (use clang-format) +- [ ] Documentation updated if needed (docs/manual.xml.head) +- [ ] All builds and tests passing +- [ ] Doxygen documentation added for new functions +- [ ] No compiler warnings introduced +- [ ] Security implications considered + +### Code Review Expectations + +- Keep commits clear and focused on single changes +- Avoid combining multiple features/fixes in one commit +- Rewrite Git history if needed to maintain clarity +- Eliminate all compiler warnings + +## Module-Specific Guidelines + +### Hook System (`hook.c`, `hook.h`) + +- Hook parsers are organized by parameter types (global, pattern-based, regex-based) +- Hook commands registered in `HookCommands` array +- Use `mutt_get_hook_type()` to identify hook types by parser function pointers + +### Configuration (`config/`) + +- Configuration variables have specific types +- Use appropriate validator functions +- Follow existing patterns for new config options + +### Email Handling (`email/`, `parse/`) + +- Always validate email headers +- Use safe parsing functions +- Be aware of malformed/malicious emails + +## Resources + +- **Website**: https://neomutt.org +- **Development Guide**: https://neomutt.org/dev.html +- **Code Style Guide**: https://neomutt.org/dev/code +- **Doxygen Documentation**: https://neomutt.org/dev/doxygen +- **Issue Tracker**: https://github.com/neomutt/neomutt/issues + +## Notes for AI Code Assistants + +1. **Understand before changing**: Review surrounding code and existing patterns before making changes +2. **Minimal changes**: Make the smallest possible changes to achieve the goal +3. **Test your changes**: Build and test code after modifications +4. **Follow existing patterns**: Match the style and approach of surrounding code +5. **Security first**: Always consider security implications +6. **Document properly**: Include appropriate Doxygen comments +7. **Clean up**: Use buffer pools correctly, handle errors properly +8. **Respect history**: This is a mature codebase with many contributors; respect existing conventions diff --git a/.github/unused.sh b/.github/unused.sh index 795a4eaa8f2..436cee23a95 100755 --- a/.github/unused.sh +++ b/.github/unused.sh @@ -6,16 +6,12 @@ EXIT_CODE=0 IGNORELIST=( # Used by macros "log_multiline_full" - "mutt_socket_buffer_readln_d" # Used by tests + "expando_serialise" "mutt_mem_malloc" "mutt_str_upper" - "name_expando_domain" "name_expando_node_type" - "name_expando_pad_type" - "name_expando_uid" - "name_format_justify" # Will be used by future code "progress_set_size" diff --git a/.github/workflows/asan.yml b/.github/workflows/asan.yml index 6745ef0a0c3..ab5837b0173 100644 --- a/.github/workflows/asan.yml +++ b/.github/workflows/asan.yml @@ -6,6 +6,7 @@ on: branches: - 'main' - 'devel/**' + - 'copilot/**' workflow_dispatch: jobs: diff --git a/.github/workflows/build-and-test-freebsd.yml b/.github/workflows/build-and-test-freebsd.yml index ae01cf8c540..2d94180b5cb 100644 --- a/.github/workflows/build-and-test-freebsd.yml +++ b/.github/workflows/build-and-test-freebsd.yml @@ -6,6 +6,7 @@ on: branches: - 'main' - 'devel/**' + - 'copilot/**' workflow_dispatch: concurrency: @@ -42,7 +43,7 @@ jobs: cd test-files ./setup.sh cd - - ./configure --full-doc --autocrypt --bdb --fmemopen --gdbm --gnutls --gpgme --gss --lmdb --lua --lz4 --notmuch --pcre2 --qdbm --rocksdb --sasl --tdb --testing --kyotocabinet --tokyocabinet --with-lock=fcntl --zlib --zstd + ./configure --full-doc --autocrypt --bdb --fmemopen --gdbm --gnutls --gpgme --gss --lmdb --lua --lz4 --notmuch --pcre2 --qdbm --rocksdb --sasl --tdb --kyotocabinet --tokyocabinet --with-lock=fcntl --zlib --zstd make -j ${{steps.cpu-cores.outputs.count}} neomutt ./neomutt -v ./neomutt -h all @@ -50,3 +51,15 @@ jobs: make -j ${{steps.cpu-cores.outputs.count}} test/neomutt-test export NEOMUTT_TEST_DIR=$GITHUB_WORKSPACE/test-files make test + make DESTDIR=$HOME/neo-install install + if [ ! -d "$HOME/neo-install" ] || [ -z "$(find "$HOME/neo-install" -type f)" ]; then + echo "Error: install directory is empty or does not exist" + exit 1 + fi + make DESTDIR=$HOME/neo-install uninstall + REMAINING=$(find "$HOME/neo-install" -type f 2>/dev/null | wc -l) + if [ "$REMAINING" -ne 0 ]; then + echo "Error: $REMAINING file(s) left after uninstall:" + find "$HOME/neo-install" -type f + exit 1 + fi diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1544da76b9f..9f507e4e781 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -6,6 +6,7 @@ on: branches: - 'main' - 'devel/**' + - 'copilot/**' workflow_dispatch: jobs: @@ -25,7 +26,7 @@ jobs: - name: disabled options: --disable-nls --disable-pgp --disable-smime --ssl --gsasl - name: everything - options: --autocrypt --bdb --fmemopen --gdbm --gnutls --gpgme --gss --kyotocabinet --lmdb --lua --lz4 --notmuch --pcre2 --qdbm --rocksdb --sasl --tdb --testing --tokyocabinet --with-lock=fcntl --zlib --zstd + options: --autocrypt --bdb --fmemopen --gdbm --gnutls --gpgme --gss --kyotocabinet --lmdb --lua --lz4 --notmuch --pcre2 --qdbm --rocksdb --sasl --tdb --tokyocabinet --with-lock=fcntl --zlib --zstd steps: - name: Get number of CPU cores @@ -82,3 +83,26 @@ jobs: export NEOMUTT_TEST_DIR=$GITHUB_WORKSPACE/test-files make test + - name: Install NeoMutt + run: | + cd build-${{ matrix.cfg.name }} + make DESTDIR=$HOME/neo-install install + if [ ! -d "$HOME/neo-install" ] || [ -z "$(find "$HOME/neo-install" -type f)" ]; then + echo "Error: install directory is empty or does not exist" + exit 1 + fi + + - name: Uninstall NeoMutt + run: | + cd build-${{ matrix.cfg.name }} + make DESTDIR=$HOME/neo-install uninstall + + - name: Check Uninstall + run: | + REMAINING=$(find "$HOME/neo-install" -type f 2>/dev/null | wc -l) + if [ "$REMAINING" -ne 0 ]; then + echo "Error: $REMAINING file(s) left after uninstall:" + find "$HOME/neo-install" -type f + exit 1 + fi + diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml index 1680d578374..73695b5e5d9 100644 --- a/.github/workflows/debug.yml +++ b/.github/workflows/debug.yml @@ -11,7 +11,6 @@ jobs: name: ${{ matrix.cfg.name }} runs-on: ubuntu-latest container: ghcr.io/neomutt/ubuntu - continue-on-error: true env: CONFIGURE_OPTIONS: --autocrypt --bdb --full-doc --fmemopen --gdbm --gnutls --gpgme --gss --kyotocabinet --lmdb --lua --lz4 --notmuch --pcre2 --qdbm --rocksdb --sasl --tdb --tokyocabinet --with-lock=fcntl --zlib --zstd @@ -19,6 +18,7 @@ jobs: strategy: # Limit jobs to one at a time so that ccache really helps later builds max-parallel: 1 + fail-fast: false matrix: cfg: - name: none @@ -33,7 +33,9 @@ jobs: options: --debug-graphviz - name: keymap options: --debug-keymap - - name: notify + - name: logging + options: --debug-logging + - name: names options: --debug-names - name: notify options: --debug-notify @@ -47,7 +49,7 @@ jobs: options: --fuzzing extras: CC=clang-14 CXX=clang-14 fuzz - name: all - options: --debug-backtrace --debug-color --debug-email --debug-graphviz --debug-keymap --debug-names --debug-notify --debug-queue --debug-window --coverage + options: --debug-backtrace --debug-color --debug-email --debug-graphviz --debug-keymap --debug-logging --debug-names --debug-notify --debug-queue --debug-window --coverage steps: - name: Get number of CPU cores diff --git a/.github/workflows/fedora.yml b/.github/workflows/fedora.yml index 7d11be891c7..aee88e23d8c 100644 --- a/.github/workflows/fedora.yml +++ b/.github/workflows/fedora.yml @@ -13,9 +13,9 @@ jobs: fail-fast: false matrix: distro: - - version: 41 - version: 42 - version: 43 + - version: 44 config: - name: Default options: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index a8ddb544007..eef64b19373 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -24,7 +24,7 @@ jobs: uses: Homebrew/actions/setup-homebrew@master - name: Install dependencies - run: brew install notmuch libidn2 libiconv ncurses lmdb + run: brew install notmuch libiconv ncurses lmdb - name: Checkout Code uses: actions/checkout@v6 diff --git a/.gitignore b/.gitignore index 2bb45c33358..6694868dda6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ neomutt pgpewrap test/neomutt-test fuzz/address-fuzz +fuzz/cli-fuzz fuzz/date-fuzz # Build products @@ -60,3 +61,9 @@ compile_commands.* # gcc -fdump-rtl-expand *.expand + +# Copilot +build*/ +test-files/ +_codeql_detected_source_root + diff --git a/ChangeLog.md b/ChangeLog.md index e7c66033059..1cbe28ac2b9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3198 +1,4456 @@ -2025-12-11 Richard Russon \ -* Security - - #4725 deprecate old ssl and tls options -* Features - - #4718 add `$message_id_format` - - Notmuch: hide some old/deprecated features -* Bug Fixes - - #4666 limit: force refresh of index bar - - #4671 Update the status bar on thread [un]collapse - - #4673 shrink history file on every new entry - - #4679 Flush S/MIME passkey on failure to sign - - #4684 execute message-hook sooner - - #4687 fix: exec auto-completion - - #4688 Fix infinite loop in GnuTLS socket I/O when socket blocks - - #4696 `cmd_parse_fetch()`: debug: avoid NULL dereference - - #4697 expunge: guard against segfault - - #4719 alias: complete when only one match - - #4721 browser: fix select/descend folder/mailbox - - #4726 imap: protect against crash on shutdown - - fix resolve for tagged save - - help: fix sorting of unbound functions - - help: hide deprecated functions -* Changed Config - - New: `message_id_format = "<%z@%f>"` - - Deprecated `ssl_use_sslv2` - - Deprecated `ssl_use_sslv3` - - Deprecated `ssl_use_tlsv1` - - Deprecated `ssl_use_tlsv1_1` -* Translations - - 100% Esperanto - - 100% Lithuanian - - 100% Serbian - - 100% Turkish -* Docs - - #4665 Revamp the S/MIME docs - - #4680 Document how to use S/MIME with GPGMe - - #4692 update the contribution guidelines - - fix deprecated $pager example - - lots man pages fixes - - update help for sort options -* Build - - #4668 fix build for re-entrant ncurses - - #4727 make openssl/gnutls mutually exclusive - - libkey: light refactoring - - lua: create liblua -* Code - - #4695 Replace some `TAILQ` uses with `ARRAY` - - convert the ctype wrappers to use `bool` - - global invert `OptNoCurses` to `OptGui` - - key: collapse redirector functions - - move `km_init()` earlier in startup - - opcodes: add flags to `MenuFuncOp` - - pass focused window to global functions - - Use `buf_at()` to get a char from a `Buffer` - - use `StringArray` everywhere - -2025-09-05 Richard Russon \ -* Security - - #4623 ncrypt/crypt.c: Protect Message-ID -* Features - - #4644 Provide an option to confirm on an empty To -* Contrib - - #4645 Add ayu-dark-256 colorscheme -* Bug Fixes - - #4635 Handle a non-existing message_cache_dir - - #4642 Decouple `$crypt_encryption_info` and `$crypt_protected_headers_weed` from `$devel_security` - - #4650 Return success after querying for config -* Changed Config - - Add: `confirm_empty_to = no` - Confirm when sending an e-mail with an empty To: -* Translations - - 100% German - - 100% Turkish - - 99% Hungarian - - 98% Lithuanian - - 89% French -* Build - - #4636 Update FreeBSD CI and workaround `pkg` bug - - actions: use checkout v5 -* Code - - #4023 tweak observer event types - - #4628 Use the standard countof instead of our mutt_array_size() - - #4637 Update types in test dummy code - - #4638 remove useless const qualifier from log_queue_get() - - #4655 Make sure ctype(3) function arguments are valid - - #4657 Fix warning about unused function and data - - #4659 Include term.h and [n]curses.h consistently - -2025-05-10 Richard Russon \ -* Contrib - - #4616 Fix gpg-json output -* Bug Fixes - - #4600 main: don't stop if /var/spool/mail is missing - - #4602 color: fix quoted maths - - #4604 Don't consider "weed" when writing an email to file - - #4605 help: fix leaks - - #4612 imap: check for incomplete Mailboxes on sync - - #4622 fix label completion crash -* Translations - - #4622 update Esperanto translation - - Update lt_LT translations -* Docs - - docs: fix broken functions -* Build - - #4607 Check for DocBook XSL - - #4618 Build and test on FreeBSD - -2025-04-04 Richard Russon \ -* Features - - #4493 config: don't quote enums - - #4493 link config dump to docs - - #4494 refactor the Help Page for clarity - - #4554 CLI: `neomutt -DD` -- Dump Different - - #4593 browser: tag select rather than descend -* Bug Fixes - - #3469 source: fix variable interpretation - - #4370 `mutt_oauth2`: refactor `sasl_string` computation - - #4536 expand tabs to spaces in compose preview - - #4537 fix dumping of initial values - - #4538 move `real_name` init - - #4542 Remove `MUTT_NEWFOLDER`, fix appending to mbox - - #4546 Respect Ignore when modifying an email's headers - - #4549 fix refresh on toggle `hide_thread_subject` - - #4550 buffer: fix seek - - #4551 add comma to single `` match - - #4595 notmuch: check for parse failure - - #4596 query: allow `<>`s around email addresses - - pager: fix normal/stripe colour - - fix colour leaks in pager - - fix array leak in the verify certificate dialog -* Translations - - 100% German - - 100% Turkish - - 96% Lithuanian - - 86% French - - 49% Chinese (Traditional) -* Build - - #4552 Deprecate some configure options that aren't used anymore - - build: workaround for unused-result warning -* Code - - #4492 colour refactoring - - #4543 debug: Chain old SEGV Handler - - #4545 Allow nested `ARRAY_FOREACH()` - - #4553 config: API `has_been_set()` - - #4557 config: drop ConfigSet from API functions - - #4558 drop obsolete pgp/smime menus - - #4559 array: `foreach_reverse()` - - #4560 Change description of verify-key to be crypto-scheme agnostic - - #4561 expando: move EnvList out of library - - #4570 Simplify the management of NeoMutt Commands - - #4571 libcli - parse the command line - - #4580 Split CLI Usage into sections - - #4582 pager: fix lost `NT_PAGER` notifications - - #4591 pager: fix refresh on config/colour changes - - array: upgrade `get_elem_list()` - - Buffer refactoring - - coverity: fix defects - - improve `localise_config()` - - main: drop -B (batch mode) option - - merge init.[ch] into main.c - - refactor version code - - neomutt: `home_dir`, `username`, `env` - - query: unify NeoMutt `-D` and `-Q` - - refactor `main.c`/`init.c` - - sidebar: streamline expando callbacks - - test: lots of parse coverage - - window refactoring - - window: force recalc|repaint on new windows -* Upstream - - Update mutt/queue.h - - Fix NULL pointer dereference when calling `imap_logout_all()` - -2025-01-13 Richard Russon \ -* Bug Fixes - - #4477 fix crash in folder-hook - - #4480 fix memory leak in compose message preview (fixes #4478) - - #4483 query: fix comma-separated names - - #4485 lua: fix `lua_mutt_call()` - - #4490 notmuch: refresh the Email if the filename changes - - fix: no new mail message - - fix display of certificate fingerprints - - fix prompt colour -* Translations - - 100% Czech - - 100% German - - 100% Lithuanian - - 100% Serbian - - 100% Slovakian - - 100% Turkish - - 91% French - - 41% Chinese (Traditional) -* Build - - #4479 Fix DT_NUMBER entries on 32-bit endian platforms -* Code - - #4481 Simplify `mutt_file_fopen()` - - colour refactoring - - standardise variable names for temporary files - -2025-01-09 Richard Russon \ -* BROKEN - Please use 2025-01-13 instead - -2024-12-12 Richard Russon \ -* Features - - #4437 show message preview in compose view - - #4439 add trailing commas when editing addresses -* Bug Fixes - - #4444 expando: fix overflow - - #4461 Spaces can be wide - - #4464 Remove BOM from UTF-8 text - - #4467 Bug with wrong fingerprints in certificate_file - - #4470 fix postponed sorting assertion failure - - #4472 fix: `save_attachment_open()` when overwriting - - #4473 add text-wrapping to compose message preview pager - - #4475 edit_headers: cleanup temporary file on error - - expando: fix crash on empty `%[]` date - - expando: fix container formatting - - browser: fix 'tag-' display - - query: fix memory leak - - fix more arrow_cursor + search -* Changed Config - - Config Renames: - - `$pgp_sort_keys` -> `$pgp_key_sort` - - `$sidebar_sort_method` -> `$sidebar_sort` - - `$sort_alias` -> `$alias_sort` - - `$sort_browser` -> `$browser_sort` - - Changed Defaults: - - `set alias_format = "%3i %f%t %-15a %-56A | %C%> %Y"` - - `set query_format = "%3i %t %-25N %-25E | %C%> %Y"` -* Translations - - 100% German - - 100% Lithuanian - - 100% Serbian - - 100% Turkish - - 89% French - - 39% Chinese (Traditional) -* Coverity Defects - - Explicit null dereferenced - - Overflowed constant - - Overflowed return value - - Resource leak -* Docs - - alias tags -* Build - - #4452 only use `struct tm.tm_gmtoff` if available -* Code - - #4294 refactor memory allocation - - #4442 remove unused fields from ComposeSharedData - - #4447 refactor 'sort' constants - - #4449 add `mutt_window_swap()` - - unify Menu data - - move config to libraries - - unify Alias/Query - - expando factor out callbacks - - refactor `simple_dialog_new()` - - test: add `TEST_CHECK_NUM_EQ()` - - fopen: tidy read-only -* Upstream - - #4448 Update queue.h - -2024-11-14 Richard Russon \ -* Security - - Fixed: CVE-2024-49393 - - Fixed: CVE-2024-49394 - - #4300 Read the protected Message-ID -* Features - - #4336 Allow toggling numeric configs, e.g. `:toggle pager_index_lines` - - #4427 alias: tag/untag pattern - - query: tag with `` -* Contrib - - #4400 `mutt_oauth2.py`: Fix reference to `client_secret` -* Bug Fixes - - #4399 fix duplicate save-hook - - #4403 expando: fix escaping - - #4404 browser: fix enter-quit-enter - - #4405 pager: fix repaint - - #4407 config: warn about deprecated variables - - #4425 Refresh alias/query dialog on alias/query format change - - #4433 compose: fix redraw on attachment - - #4436 compose: fix search with `arrow_cursor` - - #4438 autocrypt: fix `copy_normalize_addr()` - - alias: fix cli crash - - expando: fix relative dates - - expando: padding default to space -* Translations - - 100% German - - 100% Turkish - - 99% Czech - - 99% Slovak - - 82% French -* Docs - - drop refs to always-enabled features - - fix typo in unmacro - - fix broken link - - ncrypt: fix typo in `config.c` - -2024-10-02 Richard Russon \ -* Security - - #4243 - security: kill unnecessary blank lines - - #4251 - more security improvements - - #4282 - improve NeoMutt bailout handling -* Features - - #4329 - remove mixmaster - - #4149 - honour umask in attach save -* Bug Fixes - - #3945 - do not force username in addition to client certificate - - #4341 - Fix '%z' and '%Z in '%{...}' expando - - #4356 - Allow longer maildir filename suffixes - - #4357 - Don't force mbox stats calculations on startup - - #4365 - Fix sorting INBOX and its subfolders - - #4367 - Let `~Y` match each tag individually - - #4371 - ignore macro events during autocrypt initialization - - #4383 - Generate the Message-ID earlier - - compose: fix `$compose_confirm_detach_first` -* Changed Config - - `set crypt_encryption_info = yes` - Add an informative block with details about the encryption - - `set crypt_protected_headers_weed = no` - Controls whether NeoMutt will weed protected header fields - - `set devel_security = no` - Devel feature: Security -- https://github.com/neomutt/neomutt/discussions/4251 - - `$mixmaster` is deprecated - - `$mix_entry_format` is deprecated -* Translations - - 100% German - - 100% Lithuanian - - 100% Serbian - - 100% Spanish - - 81% French -* Docs - - #4350 - Fix configure script name in INSTALL.md - - fix para ordering -* Build - - #4280 - Update autosetup - - #4281 - Update acutest to the latest upstream commit - - #4289 - don't treat stddef.h specially - - #4306 - Add -std to CFLAGS too - - #4307 - require C11 - - #4347 - Support BerkeleyDB 18.1 - - #4362 - Assume 'struct timespec' exists - - fix idn2 typo -* Code - - #4113 - Close the hcache handle on failure to open the store - - #4214 - upgrade `assert()` - - #4283 - mutt/list.c: Use `STAILQ_FOREACH_SAFE()` in stailq deallocators - - #4296 - Use `wmem*()` functions with wide-character strings - - #4297 - ncrypt/crypt.c: Fix allocation size calculation - - #4305 - remove `mutt_expand_path()` - - #4308 - fix `-Wdouble-promotion` warnings - - #4310 - scanf: initialise out-vars - - #4312 - Allow opening the header cache in non-`O_CREAT` mode - - #4337 - Fix function pointer types - - #4348 - Check `mutt_date_parse_date()`s return value - - #4366 - Fix up slashes in `imap_fix_path()` - - #4378 - Fix padding with an empty string - - tidy expando library - -2024-04-25 Richard Russon \ -- Bug Fixes - - #4263 fix: cache naming - - #4261 expando: fix conditional padding - - #4261 expando: fix container - - #4261 expando: add lower-case operator - - #4261 expando: add external filter - - imap: add mailboxes more directly -- Translations - - trans: tidy messages -- Docs - - doxy: add missing params -- Build - - #4268 Filter out CFLAGS with paths from the output of '-v' - - #4273 guard truecolor functions in tests - - #4275 use homebrew in macOS build -- Code - - use Buffer rather than strcat() - - ncrypt: use gpgme types consistently - -2024-04-16 Richard Russon \ -* Features - - #4216 Compose: Hide MixMaster chain if chain is empty - - Expando upgrade - - version: bold labels -* Contrib - - mutt_oauth2.py: Detect recipient for oauth automatically - - mutt_oauth2.py: imap_oauth_refresh_command does not need options -* Bug Fixes - - #4210 mbox: fix sorting for `mbox_resync()` - - #4241 only wrap after first address in header lines - - status: reset Buffer before reuse - - history: truncate file before writing over it - - notmuch: strip leading / from short path - - Fix smtp client `$envelope_from_address` possible dangling pointer - - Fix non-printable keyname printing to use `` syntax - - Filter Arabic Letter Mark due to display corruption - - Loosen `imap_open_mailbox()` SELECT response data parsing - - Change `mailto_allow` to be exact match only - - Fix `mutt_read_rfc822_line()` to use `is_email_wsp()` - - Improve pattern compiler whitespace skipping - - Fix gpgme crash when listing keys in a public key block - - Add SigInt handler for pattern functions - - Fix some mailbox prompts to use mailbox history ring - - Improve GPGME inline processing - - Reset SIGPIPE signal handler in child process before `exec()` - - Filter headers passed via the command line - - Remove trailing slashes when opening maildir/mh mailboxes - - Fix `mutt_paddstr()` to properly filter unprintable chars - - Minor fixes to `match_body_patterns()` - - Fix `mutt_ts_capability()` fallback list loop - - Ensure SIGALRM interrupts connect() in batch mode - - Tighten `$query_command` parsing to allow empty name field -* Changed Config - - #4224 config: add L10N support - - New: `set compose_confirm_detach_first = yes` - Prevent the accidental deletion of the composed message - - Changed: `set reply_regex = "^((re)(\\[[0-9]+\\])*:[ \t]*)*"` - Regex to match message reply subjects like 're: ' - - Changed: `set pager = ""` - External command for viewing messages, or empty to use NeoMutt's -* Translations - - 100% Czech - - 100% German - - 100% Lithuanian - - 100% Serbian - - 100% Slovak - - 99% Turkish - - l10n: document functions - - config: add L10N support -* Docs - - Clarify the manual section on POP3 support - - Document the `<1234>` key syntax for bind - - Document `$sendmail` invocation behavior - - Clarify -H usage in batch mode is not a "pass through" option - -2024-03-29 Richard Russon \ -* Bug Fixes - - #4185 c441f5957 Fix memory leak in trash_append() - - #4189 Fix off-by-one error in %b with notmuch - - #4190 Zero-out mailbox counters on delete - - #4204 colour: honour the normal colour - - #4205 match folder-hook also against mailbox name (fixes #4201) - - wrap colour in - - history: fix saving file - - history: improve error message format -* Docs - - #4182 docs: -C: Fix some accidents - - #4188 Update oauth2 README - - #4193 Update oauth2 README - - fix typos, lots of tidying - - tidy license info -* Build - - #4196 use FreeBSD 14.0 in Cirrus CI - - actions: update cpu count - - actions: use codeql v3 -* Code - - #4186 Buffer refactoring: make_entry() - - address: tidy config handling - - coverage: buf, slist - - graphviz: link labels - - tidy buf_strcpy() calls - - tidy char buffers - - test: default timezone to UTC - -2024-03-23 Richard Russon \ -* Do NOT use this release - -2024-02-01 Richard Russon \ -* Features - - #4134 Command-line Crypto (neomutt -C) -* Bug Fixes - - #4065 track new-mail check time per mailbox - - #4141 fix(change-folder): don't exclude notmuch - - #4147 envelope: manage subject/real_subj together - - #4155 fix parsing of $REPLYTO - - #4158 status: fix refresh after sync-mailbox - - #4166 Fix two memory leaks in notmuch support - - progress: fix percentages -* Translations - - 100% Czech - - 100% German - - 100% Lithuanian - - 100% Serbian - - 100% Slovak - - 100% Turkish -* Docs - - #4172 Several fixes for the manual pages -* Build - - build: openbsd workarounds -* Code - - #4142 add mutt_time_now() - - #4146 config: factor out R_ flags - - #4154 file: upgrade mutt_file_fopen/fclose() - - #4159 upgrade mutt_str_append_item() to use struct Buffer - - #4161 maildir: encapsulate the header cache - - #4162 remove mutt_str_dequote_comment() - - #4165 bufferize mutt_str_inline_replace() as buf_inline_replace() - - #4167 bufferize mutt_strn_rfind() as buf_rfind() - - #4168 replace buf_len() checks with buf_is_empty() - - config: drop unused flags - - use message_new()/message_free() - - Reconsider the config type bitmap entirely - -2023-12-21 Richard Russon \ -* Features - - #4126 - add alias 'tags:' -* Bug Fixes - - #4115 - create HelpBar after colours - - #4116 - Fix Batch Sending of Emails - - #4119 - Fix Header Cache Key Handling - - #4121 - mutt_oauth2.py: error out if ENCRYPTION_PIPE was not supplied - - #4124 - config: fix flag overlaps - - #4125 - compose: restore view-text/pager/mailcap - - color: fix attr_color_copy() - - fix :color dump - - fix leak in completion - - force mail check on current mailbox after `` - - Allow sending an empty mail - - mutt_oauth2.py: Use readline to overcome macOS input() restrictions -* Changed Config - - add $history_format: '%s' -* Translations - - 100% Czech - - 100% German - - 100% Lithuanian - - 100% Serbian - - 100% Slovak - - 100% Turkish - - 99% Spanish - - 99% Hungarian -* Coverity defects - - #4111 Educate Coverity about ARRAYs - - fix defects -* Build - - #4098 - build: use fallthrough attribute - - #4100 - build: split maildir and mh types - - #4101 - version: drop default features - - #4108 - strip non-conditionals - - #4122 - add github action to check for unused functions (xunused) - - update fedora action - - coverage: fix build for lcov v2 - - tests: fix error cases -* Code - - #4097 - config: add DT_ON_STARTUP - - #4104 - Change mutt_default_save() and addr_hook() to take a buffer - - #4105 - Use buffer pool in tests - - #4106 - Switch some buffers to use the buffer pool - - #4109 - Improve the Progress Bar - - #4117 - remove MxOps::path_parent() and mutt_path_parent() - - #4120 - remove unused functions - - #4131 - move editor test code - - #4133 - move log_disp_null() into test folder - - #4137 - move config string name functions into tests - - add: hook_new()/hook_free() - - fix more printf-style params - - rename compare to equal - - hcache: renaming for clarity - -2023-11-03 Richard Russon \ -* Features - - #4080 - info screen: enable \ - - #4075 - add color command - - color: add ANSI RGB support - - color: Support ANSI 2x clear sequences -* Bug Fixes - - #4074 - color: fix palette conversion - - #4081 - fix logging on error - - #4081 - log: vim-style - - #4082 - fix file auto-completion - - #4090 - improve logic for growing mailbox memory -* Translations - - 100% Czech - - 100% German - - 100% Lithuanian - - 100% Serbian - - 100% Slovak - - 100% Turkish -* Build - - #4085 - fix CFLAGS/LDFLAGS for ncurses - - #4085 - configure --with-iconv -* Code - - #4067 - remove unused count of new mails from index private data - - #4086 - smtp: Simplify the API of `smtp_code()` - - #4091 - simplify CLAMP by expressing it with MIN/MAX - - color: introduce ColorElement - - color: log gui info on startup - - color: move business logic out of parsers - - color: tidy OptNoCurses cases - - log: add `log_multiline()` - - test: increase coverage - -2023-10-23 Richard Russon \ -* Bug Fixes - - #4060 - fix crash on exit - - #4061 - fix header colour - - #4064 - fix 32-bit date overflow - - #4078 - fix new mail in limited view - - nntp: fix use-after-free - - color: fix ansi colours - - color: add +truecolor to version string - -2023-10-06 Richard Russon \ -* Features - - #3870 - color: allow 'alert', 'bright', 'light' prefix for colorNNN - - #3871 - color: refactor parsing code - - #3895 - imap: truncate large UIDVALIDITYs to support non-conforming IMAP servers - - #3898 - hcache: shrink Email and Body caches - - #3900 - prompt: treat complete-query as complete where it makes sense - - #3925 - help: add message flags to help screen - - #3932 - help: add alternating colors - - #3982 - mailboxes: add `-label`, `-notify` and `-poll` options - - #4046 - color_directcolor: Also set the default/initial value on startup -* Bug Fixes - - #3897 - maildir: fix sync when a deleted file disappears - - #3878 - gnutls: fix "certificate saved" message - - #3895 - imap: truncate large UIDVALIDITYs to support non-conforming servers - - #3897 - maildir: fix fix error with `` on mbsync - - #3901 - address: parse comments after address - - #3915 - bind: fix truncated binding strings - - #3956 - fix 'from' address when real name isn't set - - #3962 - Fix crash on `` when the ``ed view is empty - - #3985 - browser: fix autocompletion - - #3988 - pager: fix search crash - - #3999 - help: fix search highlight - - #4049 - force mail check on current mailbox after `` - - #4051 - openssl: continue if a signal interrupts an SSL read/write -* Config - - #3881 - Rename `$imap_keepalive` to `$imap_keep_alive` - - #3889 - Change defaults to use `%<...>` conditional syntax - `$attach_format`, `$index_format`, `$mailbox_folder_format`, - `$status_format`, `$ts_icon_format`, `$ts_status_format` - - #3949 - Add `browser_sort_dirs_first` to always list directories first -* Code - - #3877 - imap: factor out tagged emails - - #3799 - address: use struct Buffer instead of plain char pointers - - #3868 - drop notifications relay - - #3869 - move `$delete_untag` out of the backend - - #3873 - respect `--[disable-]fmemopen` in tests - - hcache: optimize storage requirements, reduce config - - logging: catch format string errors - - colour: refactor colour parsing - - refactoring, cleanup - - fixed coverity defects - - convert many functions to use a `Buffer` -* Translations - - 100% :tr: Turkish - - 100% :serbia: Serbian - - 100% :lithuania: Lithuanian - - 100% :de: German - - 99% :czech_republic: Czech - - 99% :poland: Polish - - 98% :slovakia: Slovak - -2023-05-17 Richard Russon \ -* Features - - #3699 - Support 24bit colors, aka truecolor - - #3738 - Show complete MIME structure in attachments - - #3842 - Allow percentages to be localized -* Bug Fixes - - #3813 - Fix crash in `op_browser_subscribe()` - - #3844 - Select the first email when coming from an empty limit - - #3848 - Fix counting new mails in maildir - - #3864 - Fix handling of bright colours - - #3759 - bind: fix incorrect conflict report - - #3781 - index: only refresh the menu on non-focus window changes - - #3856 - tunnel: fix reconnection with `ssl_force=true` - - #3860 - maildir: stop parsing headers at the end of the headers section - - Fix sorting of labels -* Translations - - 100% :serbia: Serbian - - 100% :tr: Turkish - - 100% :lithuania: Lithuanian - - 100% :hungary: Hungarian - - 100% :de: German - - 99% :norway: Norwegian (Bokmål) - - 99% :slovakia: Slovak - - 99% :brazil: Portuguese (Brazil) - - 99% :czech_republic: Czech - - 99% :poland: Polish - - 95% :fr: French -* Build +
+NeoMutt 2026-01-05 + +## 2026-01-05 Richard Russon \ + +### 🔒 Security + +- #4725 deprecate old ssl and tls options + +### 🎁 Features + +- #4718 add `$message_id_format` +- Notmuch: hide some old/deprecated features + +### 🐞 Bug Fixes + +- #4666 limit: force refresh of index bar +- #4671 Update the status bar on thread [un]collapse +- #4673 shrink history file on every new entry +- #4679 Flush S/MIME passkey on failure to sign +- #4684 execute message-hook sooner +- #4687 fix: exec auto-completion +- #4688 Fix infinite loop in GnuTLS socket I/O when socket blocks +- #4696 `cmd_parse_fetch()`: debug: avoid NULL dereference +- #4697 expunge: guard against segfault +- #4719 alias: complete when only one match +- #4721 browser: fix select/descend folder/mailbox +- #4726 imap: protect against crash on shutdown +- fix resolve for tagged save +- help: fix sorting of unbound functions +- help: hide deprecated functions + +### 🔧 Changed Config + +- New: `message_id_format = "<%z@%f>"` +- Deprecated `ssl_use_sslv2` +- Deprecated `ssl_use_sslv3` +- Deprecated `ssl_use_tlsv1` +- Deprecated `ssl_use_tlsv1_1` + +### 🏁 Translations + +- 100% 🏳️ Esperanto +- 100% 🇱🇹 Lithuanian +- 100% 🇷🇸 Serbian +- 100% 🇹🇷 Turkish + +### 📚 Docs + +- #4665 Revamp the S/MIME docs +- #4680 Document how to use S/MIME with GPGMe +- #4692 update the contribution guidelines +- fix deprecated $pager example +- lots man pages fixes +- update help for sort options + +### 🏗 Build + +- #4668 fix build for re-entrant ncurses +- #4727 make openssl/gnutls mutually exclusive +- libkey: light refactoring +- lua: create liblua + +### ⚙️ Code + +- #4695 Replace some `TAILQ` uses with `ARRAY` +- convert the ctype wrappers to use `bool` +- global invert `OptNoCurses` to `OptGui` +- key: collapse redirector functions +- move `km_init()` earlier in startup +- opcodes: add flags to `MenuFuncOp` +- pass focused window to global functions +- Use `buf_at()` to get a char from a `Buffer` +- use `StringArray` everywhere + +
+
+NeoMutt 2025-12-11 (Do not use) + +## 2025-12-11 Richard Russon \ + +### ⚠️ Superseded by 2026-01-05 + +
+
+NeoMutt 2025-09-05 + +## 2025-09-05 Richard Russon \ + +### 🔒 Security + +- #4623 ncrypt/crypt.c: Protect Message-ID + +### 🎁 Features + +- #4644 Provide an option to confirm on an empty To + +### ✨ Contrib + +- #4645 Add ayu-dark-256 colorscheme + +### 🐞 Bug Fixes + +- #4635 Handle a non-existing message_cache_dir +- #4642 Decouple `$crypt_encryption_info` and `$crypt_protected_headers_weed` from `$devel_security` +- #4650 Return success after querying for config + +### 🔧 Changed Config + +- Add: `confirm_empty_to = no` +Confirm when sending an e-mail with an empty To: + +### 🏁 Translations + +- 100% 🇩🇪 German +- 100% 🇹🇷 Turkish +- 99% 🇭🇺 Hungarian +- 98% 🇱🇹 Lithuanian +- 89% 🇫🇷 French + +### 🏗 Build + +- #4636 Update FreeBSD CI and workaround `pkg` bug +- actions: use checkout v5 + +### ⚙️ Code + +- #4023 tweak observer event types +- #4628 Use the standard countof instead of our mutt_array_size() +- #4637 Update types in test dummy code +- #4638 remove useless const qualifier from log_queue_get() +- #4655 Make sure ctype(3) function arguments are valid +- #4657 Fix warning about unused function and data +- #4659 Include term.h and [n]curses.h consistently + +
+
+NeoMutt 2025-05-10 + +## 2025-05-10 Richard Russon \ + +### ✨ Contrib + +- #4616 Fix gpg-json output + +### 🐞 Bug Fixes + +- #4600 main: don't stop if /var/spool/mail is missing +- #4602 color: fix quoted maths +- #4604 Don't consider "weed" when writing an email to file +- #4605 help: fix leaks +- #4612 imap: check for incomplete Mailboxes on sync +- #4622 fix label completion crash + +### 🏁 Translations + +- #4622 update Esperanto translation +- Update lt_LT translations + +### 📚 Docs + +- docs: fix broken functions + +### 🏗 Build + +- #4607 Check for DocBook XSL +- #4618 Build and test on FreeBSD + +
+
+NeoMutt 2025-04-04 + +## 2025-04-04 Richard Russon \ + +### 🎁 Features + +- #4493 config: don't quote enums +- #4493 link config dump to docs +- #4494 refactor the Help Page for clarity +- #4554 CLI: `neomutt -DD` -- Dump Different +- #4593 browser: tag select rather than descend + +### 🐞 Bug Fixes + +- #3469 source: fix variable interpretation +- #4370 `mutt_oauth2`: refactor `sasl_string` computation +- #4536 expand tabs to spaces in compose preview +- #4537 fix dumping of initial values +- #4538 move `real_name` init +- #4542 Remove `MUTT_NEWFOLDER`, fix appending to mbox +- #4546 Respect Ignore when modifying an email's headers +- #4549 fix refresh on toggle `hide_thread_subject` +- #4550 buffer: fix seek +- #4551 add comma to single `` match +- #4595 notmuch: check for parse failure +- #4596 query: allow `<>`s around email addresses +- pager: fix normal/stripe colour +- fix colour leaks in pager +- fix array leak in the verify certificate dialog + +### 🏁 Translations + +- 100% 🇩🇪 German +- 100% 🇹🇷 Turkish +- 96% 🇱🇹 Lithuanian +- 86% 🇫🇷 French +- 49% 🇹🇼 Chinese (Traditional) + +### 🏗 Build + +- #4552 Deprecate some configure options that aren't used anymore +- build: workaround for unused-result warning + +### ⚙️ Code + +- #4492 colour refactoring +- #4543 debug: Chain old SEGV Handler +- #4545 Allow nested `ARRAY_FOREACH()` +- #4553 config: API `has_been_set()` +- #4557 config: drop ConfigSet from API functions +- #4558 drop obsolete pgp/smime menus +- #4559 array: `foreach_reverse()` +- #4560 Change description of verify-key to be crypto-scheme agnostic +- #4561 expando: move EnvList out of library +- #4570 Simplify the management of NeoMutt Commands +- #4571 libcli - parse the command line +- #4580 Split CLI Usage into sections +- #4582 pager: fix lost `NT_PAGER` notifications +- #4591 pager: fix refresh on config/colour changes +- array: upgrade `get_elem_list()` +- Buffer refactoring +- coverity: fix defects +- improve `localise_config()` +- main: drop -B (batch mode) option +- merge init.[ch] into main.c +- refactor version code +- neomutt: `home_dir`, `username`, `env` +- query: unify NeoMutt `-D` and `-Q` +- refactor `main.c`/`init.c` +- sidebar: streamline expando callbacks +- test: lots of parse coverage +- window refactoring +- window: force recalc|repaint on new windows + +### ♻️ Upstream + +- Update mutt/queue.h +- Fix NULL pointer dereference when calling `imap_logout_all()` + +
+
+NeoMutt 2025-01-13 + +## 2025-01-13 Richard Russon \ + +### 🐞 Bug Fixes + +- #4477 fix crash in folder-hook +- #4480 fix memory leak in compose message preview (fixes #4478) +- #4483 query: fix comma-separated names +- #4485 lua: fix `lua_mutt_call()` +- #4490 notmuch: refresh the Email if the filename changes +- fix: no new mail message +- fix display of certificate fingerprints +- fix prompt colour + +### 🏁 Translations + +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇱🇹 Lithuanian +- 100% 🇷🇸 Serbian +- 100% 🇸🇰 Slovakian +- 100% 🇹🇷 Turkish +- 91% 🇫🇷 French +- 41% 🇹🇼 Chinese (Traditional) + +### 🏗 Build + +- #4479 Fix DT_NUMBER entries on 32-bit endian platforms + +### ⚙️ Code + +- #4481 Simplify `mutt_file_fopen()` +- colour refactoring +- standardise variable names for temporary files + +
+
+NeoMutt 2025-01-09 (Do not use) + +## 2025-01-09 Richard Russon \ + +### ⚠️ BROKEN - Please use 2025-01-13 instead + +
+
+NeoMutt 2024-12-12 + +## 2024-12-12 Richard Russon \ + +### 🎁 Features + +- #4437 show message preview in compose view +- #4439 add trailing commas when editing addresses + +### 🐞 Bug Fixes + +- #4444 expando: fix overflow +- #4461 Spaces can be wide +- #4464 Remove BOM from UTF-8 text +- #4467 Bug with wrong fingerprints in certificate_file +- #4470 fix postponed sorting assertion failure +- #4472 fix: `save_attachment_open()` when overwriting +- #4473 add text-wrapping to compose message preview pager +- #4475 edit_headers: cleanup temporary file on error +- expando: fix crash on empty `%[]` date +- expando: fix container formatting +- browser: fix 'tag-' display +- query: fix memory leak +- fix more arrow_cursor + search + +### 🔧 Changed Config + +- Config Renames: + - `$pgp_sort_keys` -> `$pgp_key_sort` + - `$sidebar_sort_method` -> `$sidebar_sort` + - `$sort_alias` -> `$alias_sort` + - `$sort_browser` -> `$browser_sort` +- Changed Defaults: + - `set alias_format = "%3i %f%t %-15a %-56A | %C%> %Y"` + - `set query_format = "%3i %t %-25N %-25E | %C%> %Y"` + +### 🏁 Translations + +- 100% 🇩🇪 German +- 100% 🇱🇹 Lithuanian +- 100% 🇷🇸 Serbian +- 100% 🇹🇷 Turkish +- 89% 🇫🇷 French +- 39% 🇹🇼 Chinese (Traditional) + +### ⚙️ Coverity defects + +- Explicit null dereferenced +- Overflowed constant +- Overflowed return value +- Resource leak + +### 📚 Docs + +- alias tags + +### 🏗 Build + +- #4452 only use `struct tm.tm_gmtoff` if available + +### ⚙️ Code + +- #4294 refactor memory allocation +- #4442 remove unused fields from ComposeSharedData +- #4447 refactor 'sort' constants +- #4449 add `mutt_window_swap()` +- unify Menu data +- move config to libraries +- unify Alias/Query +- expando factor out callbacks +- refactor `simple_dialog_new()` +- test: add `TEST_CHECK_NUM_EQ()` +- fopen: tidy read-only + +### ♻️ Upstream + +- #4448 Update queue.h + +
+
+NeoMutt 2024-11-14 + +## 2024-11-14 Richard Russon \ + +### 🔒 Security + +- Fixed: CVE-2024-49393 +- Fixed: CVE-2024-49394 +- #4300 Read the protected Message-ID + +### 🎁 Features + +- #4336 Allow toggling numeric configs, e.g. `:toggle pager_index_lines` +- #4427 alias: tag/untag pattern +- query: tag with `` + +### ✨ Contrib + +- #4400 `mutt_oauth2.py`: Fix reference to `client_secret` + +### 🐞 Bug Fixes + +- #4399 fix duplicate save-hook +- #4403 expando: fix escaping +- #4404 browser: fix enter-quit-enter +- #4405 pager: fix repaint +- #4407 config: warn about deprecated variables +- #4425 Refresh alias/query dialog on alias/query format change +- #4433 compose: fix redraw on attachment +- #4436 compose: fix search with `arrow_cursor` +- #4438 autocrypt: fix `copy_normalize_addr()` +- alias: fix cli crash +- expando: fix relative dates +- expando: padding default to space + +### 🏁 Translations + +- 100% 🇩🇪 German +- 100% 🇹🇷 Turkish +- 99% 🇨🇿 Czech +- 99% 🇸🇰 Slovak +- 82% 🇫🇷 French + +### 📚 Docs + +- drop refs to always-enabled features +- fix typo in unmacro +- fix broken link +- ncrypt: fix typo in `config.c` + +
+
+NeoMutt 2024-10-02 + +## 2024-10-02 Richard Russon \ + +### 🔒 Security + +- #4243 - security: kill unnecessary blank lines +- #4251 - more security improvements +- #4282 - improve NeoMutt bailout handling + +### 🎁 Features + +- #4329 - remove mixmaster +- #4149 - honour umask in attach save + +### 🐞 Bug Fixes + +- #3945 - do not force username in addition to client certificate +- #4341 - Fix '%z' and '%Z in '%{...}' expando +- #4356 - Allow longer maildir filename suffixes +- #4357 - Don't force mbox stats calculations on startup +- #4365 - Fix sorting INBOX and its subfolders +- #4367 - Let `~Y` match each tag individually +- #4371 - ignore macro events during autocrypt initialization +- #4383 - Generate the Message-ID earlier +- compose: fix `$compose_confirm_detach_first` + +### 🔧 Changed Config + +- `set crypt_encryption_info = yes` + Add an informative block with details about the encryption +- `set crypt_protected_headers_weed = no` + Controls whether NeoMutt will weed protected header fields +- `set devel_security = no` + Devel feature: Security -- https://github.com/neomutt/neomutt/discussions/4251 +- `$mixmaster` is deprecated +- `$mix_entry_format` is deprecated + +### 🏁 Translations + +- 100% 🇩🇪 German +- 100% 🇱🇹 Lithuanian +- 100% 🇷🇸 Serbian +- 100% 🇪🇸 Spanish +- 81% 🇫🇷 French + +### 📚 Docs + +- #4350 - Fix configure script name in INSTALL.md +- fix para ordering + +### 🏗 Build + +- #4280 - Update autosetup +- #4281 - Update acutest to the latest upstream commit +- #4289 - don't treat stddef.h specially +- #4306 - Add -std to CFLAGS too +- #4307 - require C11 +- #4347 - Support BerkeleyDB 18.1 +- #4362 - Assume 'struct timespec' exists +- fix idn2 typo + +### ⚙️ Code + +- #4113 - Close the hcache handle on failure to open the store +- #4214 - upgrade `assert()` +- #4283 - mutt/list.c: Use `STAILQ_FOREACH_SAFE()` in stailq deallocators +- #4296 - Use `wmem*()` functions with wide-character strings +- #4297 - ncrypt/crypt.c: Fix allocation size calculation +- #4305 - remove `mutt_expand_path()` +- #4308 - fix `-Wdouble-promotion` warnings +- #4310 - scanf: initialise out-vars +- #4312 - Allow opening the header cache in non-`O_CREAT` mode +- #4337 - Fix function pointer types +- #4348 - Check `mutt_date_parse_date()`s return value +- #4366 - Fix up slashes in `imap_fix_path()` +- #4378 - Fix padding with an empty string +- tidy expando library + +
+
+NeoMutt 2024-04-25 + +## 2024-04-25 Richard Russon \ + +### 🐞 Bug Fixes + +- #4263 fix: cache naming +- #4261 expando: fix conditional padding +- #4261 expando: fix container +- #4261 expando: add lower-case operator +- #4261 expando: add external filter +- imap: add mailboxes more directly + +### 🏁 Translations + +- trans: tidy messages + +### 📚 Docs + +- doxy: add missing params + +### 🏗 Build + +- #4268 Filter out CFLAGS with paths from the output of '-v' +- #4273 guard truecolor functions in tests +- #4275 use homebrew in macOS build + +### ⚙️ Code + +- use Buffer rather than strcat() +- ncrypt: use gpgme types consistently + +
+
+NeoMutt 2024-04-16 + +## 2024-04-16 Richard Russon \ + +### 🎁 Features + +- #4216 Compose: Hide MixMaster chain if chain is empty +- Expando upgrade +- version: bold labels + +### ✨ Contrib + +- mutt_oauth2.py: Detect recipient for oauth automatically +- mutt_oauth2.py: imap_oauth_refresh_command does not need options + +### 🐞 Bug Fixes + +- #4210 mbox: fix sorting for `mbox_resync()` +- #4241 only wrap after first address in header lines +- status: reset Buffer before reuse +- history: truncate file before writing over it +- notmuch: strip leading / from short path +- Fix smtp client `$envelope_from_address` possible dangling pointer +- Fix non-printable keyname printing to use `` syntax +- Filter Arabic Letter Mark due to display corruption +- Loosen `imap_open_mailbox()` SELECT response data parsing +- Change `mailto-allow` to be exact match only +- Fix `mutt_read_rfc822_line()` to use `is_email_wsp()` +- Improve pattern compiler whitespace skipping +- Fix gpgme crash when listing keys in a public key block +- Add SigInt handler for pattern functions +- Fix some mailbox prompts to use mailbox history ring +- Improve GPGME inline processing +- Reset SIGPIPE signal handler in child process before `exec()` +- Filter headers passed via the command line +- Remove trailing slashes when opening maildir/mh mailboxes +- Fix `mutt_paddstr()` to properly filter unprintable chars +- Minor fixes to `match_body_patterns()` +- Fix `mutt_ts_capability()` fallback list loop +- Ensure SIGALRM interrupts connect() in batch mode +- Tighten `$query_command` parsing to allow empty name field + +### 🔧 Changed Config + +- #4224 config: add L10N support +- New: `set compose_confirm_detach_first = yes` + Prevent the accidental deletion of the composed message +- Changed: `set reply_regex = "^((re)(\\[[0-9]+\\])*:[ \t]*)*"` + Regex to match message reply subjects like 're: ' +- Changed: `set pager = ""` + External command for viewing messages, or empty to use NeoMutt's + +### 🏁 Translations + +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇱🇹 Lithuanian +- 100% 🇷🇸 Serbian +- 100% 🇸🇰 Slovak +- 99% 🇹🇷 Turkish +- l10n: document functions +- config: add L10N support + +### 📚 Docs + +- Clarify the manual section on POP3 support +- Document the `<1234>` key syntax for bind +- Document `$sendmail` invocation behavior +- Clarify -H usage in batch mode is not a "pass through" option + +
+
+NeoMutt 2024-03-29 + +## 2024-03-29 Richard Russon \ + +### 🐞 Bug Fixes + +- #4185 c441f5957 Fix memory leak in trash_append() +- #4189 Fix off-by-one error in %b with notmuch +- #4190 Zero-out mailbox counters on delete +- #4204 colour: honour the normal colour +- #4205 match folder-hook also against mailbox name (fixes #4201) +- wrap colour in +- history: fix saving file +- history: improve error message format + +### 📚 Docs + +- #4182 docs: -C: Fix some accidents +- #4188 Update oauth2 README +- #4193 Update oauth2 README +- fix typos, lots of tidying +- tidy license info + +### 🏗 Build + +- #4196 use FreeBSD 14.0 in Cirrus CI +- actions: update cpu count +- actions: use codeql v3 + +### ⚙️ Code + +- #4186 Buffer refactoring: make_entry() +- address: tidy config handling +- coverage: buf, slist +- graphviz: link labels +- tidy buf_strcpy() calls +- tidy char buffers +- test: default timezone to UTC + +
+
+NeoMutt 2024-03-23 (Do not use) + +## 2024-03-23 Richard Russon \ + +### ⚠️ Do NOT use this release + +
+
+NeoMutt 2024-02-01 + +## 2024-02-01 Richard Russon \ + +### 🎁 Features + +- #4134 Command-line Crypto (neomutt -C) + +### 🐞 Bug Fixes + +- #4065 track new-mail check time per mailbox +- #4141 fix(change-folder): don't exclude notmuch +- #4147 envelope: manage subject/real_subj together +- #4155 fix parsing of $REPLYTO +- #4158 status: fix refresh after sync-mailbox +- #4166 Fix two memory leaks in notmuch support +- progress: fix percentages + +### 🏁 Translations + +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇱🇹 Lithuanian +- 100% 🇷🇸 Serbian +- 100% 🇸🇰 Slovak +- 100% 🇹🇷 Turkish + +### 📚 Docs + +- #4172 Several fixes for the manual pages + +### 🏗 Build + +- build: openbsd workarounds + +### ⚙️ Code + +- #4142 add mutt_time_now() +- #4146 config: factor out R_ flags +- #4154 file: upgrade mutt_file_fopen/fclose() +- #4159 upgrade mutt_str_append_item() to use struct Buffer +- #4161 maildir: encapsulate the header cache +- #4162 remove mutt_str_dequote_comment() +- #4165 bufferize mutt_str_inline_replace() as buf_inline_replace() +- #4167 bufferize mutt_strn_rfind() as buf_rfind() +- #4168 replace buf_len() checks with buf_is_empty() +- config: drop unused flags +- use message_new()/message_free() +- Reconsider the config type bitmap entirely + +
+
+NeoMutt 2023-12-21 + +## 2023-12-21 Richard Russon \ + +### 🎁 Features + +- #4126 - add alias 'tags:' + +### 🐞 Bug Fixes + +- #4115 - create HelpBar after colours +- #4116 - Fix Batch Sending of Emails +- #4119 - Fix Header Cache Key Handling +- #4121 - mutt_oauth2.py: error out if ENCRYPTION_PIPE was not supplied +- #4124 - config: fix flag overlaps +- #4125 - compose: restore view-text/pager/mailcap +- color: fix attr_color_copy() +- fix :color dump +- fix leak in completion +- force mail check on current mailbox after `` +- Allow sending an empty mail +- mutt_oauth2.py: Use readline to overcome macOS input() restrictions + +### 🔧 Changed Config + +- add $history_format: '%s' + +### 🏁 Translations + +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇱🇹 Lithuanian +- 100% 🇷🇸 Serbian +- 100% 🇸🇰 Slovak +- 100% 🇹🇷 Turkish +- 99% 🇪🇸 Spanish +- 99% 🇭🇺 Hungarian + +### ⚙️ Coverity defects + +- #4111 Educate Coverity about ARRAYs +- fix defects + +### 🏗 Build + +- #4098 - build: use fallthrough attribute +- #4100 - build: split maildir and mh types +- #4101 - version: drop default features +- #4108 - strip non-conditionals +- #4122 - add github action to check for unused functions (xunused) +- update fedora action +- coverage: fix build for lcov v2 +- tests: fix error cases + +### ⚙️ Code + +- #4097 - config: add DT_ON_STARTUP +- #4104 - Change mutt_default_save() and addr_hook() to take a buffer +- #4105 - Use buffer pool in tests +- #4106 - Switch some buffers to use the buffer pool +- #4109 - Improve the Progress Bar +- #4117 - remove MxOps::path_parent() and mutt_path_parent() +- #4120 - remove unused functions +- #4131 - move editor test code +- #4133 - move log_disp_null() into test folder +- #4137 - move config string name functions into tests +- add: hook_new()/hook_free() +- fix more printf-style params +- rename compare to equal +- hcache: renaming for clarity + +
+
+NeoMutt 2023-11-03 + +## 2023-11-03 Richard Russon \ + +### 🎁 Features + +- #4080 - info screen: enable \ +- #4075 - add color command +- color: add ANSI RGB support +- color: Support ANSI 2x clear sequences + +### 🐞 Bug Fixes + +- #4074 - color: fix palette conversion +- #4081 - fix logging on error +- #4081 - log: vim-style +- #4082 - fix file auto-completion +- #4090 - improve logic for growing mailbox memory + +### 🏁 Translations + +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇱🇹 Lithuanian +- 100% 🇷🇸 Serbian +- 100% 🇸🇰 Slovak +- 100% 🇹🇷 Turkish + +### 🏗 Build + +- #4085 - fix CFLAGS/LDFLAGS for ncurses +- #4085 - configure --with-iconv + +### ⚙️ Code + +- #4067 - remove unused count of new mails from index private data +- #4086 - smtp: Simplify the API of `smtp_code()` +- #4091 - simplify CLAMP by expressing it with MIN/MAX +- color: introduce ColorElement +- color: log gui info on startup +- color: move business logic out of parsers +- color: tidy OptNoCurses cases +- log: add `log_multiline()` +- test: increase coverage + +
+
+NeoMutt 2023-10-23 + +## 2023-10-23 Richard Russon \ + +### 🐞 Bug Fixes + +- #4060 - fix crash on exit +- #4061 - fix header colour +- #4064 - fix 32-bit date overflow +- #4078 - fix new mail in limited view +- nntp: fix use-after-free +- color: fix ansi colours +- color: add +truecolor to version string + +
+
+NeoMutt 2023-10-06 + +## 2023-10-06 Richard Russon \ + +### 🎁 Features + +- #3870 - color: allow 'alert', 'bright', 'light' prefix for colorNNN +- #3871 - color: refactor parsing code +- #3895 - imap: truncate large UIDVALIDITYs to support non-conforming IMAP servers +- #3898 - hcache: shrink Email and Body caches +- #3900 - prompt: treat complete-query as complete where it makes sense +- #3925 - help: add message flags to help screen +- #3932 - help: add alternating colors +- #3982 - mailboxes: add `-label`, `-notify` and `-poll` options +- #4046 - color_directcolor: Also set the default/initial value on startup + +### 🐞 Bug Fixes + +- #3897 - maildir: fix sync when a deleted file disappears +- #3878 - gnutls: fix "certificate saved" message +- #3895 - imap: truncate large UIDVALIDITYs to support non-conforming servers +- #3897 - maildir: fix fix error with `` on mbsync +- #3901 - address: parse comments after address +- #3915 - bind: fix truncated binding strings +- #3956 - fix 'from' address when real name isn't set +- #3962 - Fix crash on `` when the ``ed view is empty +- #3985 - browser: fix autocompletion +- #3988 - pager: fix search crash +- #3999 - help: fix search highlight +- #4049 - force mail check on current mailbox after `` +- #4051 - openssl: continue if a signal interrupts an SSL read/write + +### 🔧 Config + +- #3881 - Rename `$imap_keepalive` to `$imap_keep_alive` +- #3889 - Change defaults to use `%<...>` conditional syntax + `$attach_format`, `$index_format`, `$mailbox_folder_format`, + `$status_format`, `$ts_icon_format`, `$ts_status_format` +- #3949 - Add `browser_sort_dirs_first` to always list directories first + +### ⚙️ Code + +- #3877 - imap: factor out tagged emails +- #3799 - address: use struct Buffer instead of plain char pointers +- #3868 - drop notifications relay +- #3869 - move `$delete_untag` out of the backend +- #3873 - respect `--[disable-]fmemopen` in tests +- hcache: optimize storage requirements, reduce config +- logging: catch format string errors +- colour: refactor colour parsing +- refactoring, cleanup +- fixed coverity defects +- convert many functions to use a `Buffer` + +### 🏁 Translations + +- 100% 🇹🇷 Turkish +- 100% 🇷🇸 Serbian +- 100% 🇱🇹 Lithuanian +- 100% 🇩🇪 German +- 99% 🇨🇿 Czech +- 99% 🇵🇱 Polish +- 98% 🇸🇰 Slovak + +
+
+NeoMutt 2023-05-17 + +## 2023-05-17 Richard Russon \ + +### 🎁 Features + +- #3699 - Support 24bit colors, aka truecolor +- #3738 - Show complete MIME structure in attachments +- #3842 - Allow percentages to be localized + +### 🐞 Bug Fixes + +- #3813 - Fix crash in `op_browser_subscribe()` +- #3844 - Select the first email when coming from an empty limit +- #3848 - Fix counting new mails in maildir +- #3864 - Fix handling of bright colours +- #3759 - bind: fix incorrect conflict report +- #3781 - index: only refresh the menu on non-focus window changes +- #3856 - tunnel: fix reconnection with `ssl_force=true` +- #3860 - maildir: stop parsing headers at the end of the headers section +- Fix sorting of labels + +### 🏁 Translations + +- 100% 🇷🇸 Serbian +- 100% 🇹🇷 Turkish +- 100% 🇱🇹 Lithuanian +- 100% 🇭🇺 Hungarian +- 100% 🇩🇪 German +- 99% 🇳🇴 Norwegian (Bokmål) +- 99% 🇸🇰 Slovak +- 99% 🇧🇷 Portuguese (Brazil) +- 99% 🇨🇿 Czech +- 99% 🇵🇱 Polish +- 95% 🇫🇷 French + +### 🏗 Build + - #3798 - Build with libidn2 by default, remove support for libidn1 -* Code - - config: dynamically create/delete variables - - config: unify handling of NeoMutt and user (my_) variables - - config: cache config variables used often - - speed: various speedups in parsing emails - - cleanups: lots of code cleanups - - Huge refactoring towards a separation of Mailbox/MailboxView - -2023-05-12 Richard Russon \ -* BROKEN - Please use 2023-05-17 instead - -2023-04-07 Richard Russon \ -* Features - - #3769 - imap : support IMAP4 ID extension (RFC2971) - - #3753 - parse: query all changed (`set`) / all (`set all`) config variables -* Bug Fixes - - #3785 - lua: fix command registration - - #3793 - postpone: use colours from the right mailbox - - #3794 - smtp: ignore oauth if it isn't configured and not requested -* Config - - #3779 - New: `$imap_send_id` - Send IMAP ID command when logging in -* Translations - - 100% :czech_republic: Czech - - 100% :de: German - - 100% :hungary: Hungarian - - 100% :lithuania: Lithuanian - - 100% :brazil: Portuguese (Brazil) - - 100% :serbia: Serbian - - 100% :slovakia: Slovak - - 99% :poland: Polish -* Docs - - Recommend GPGME -* Code - - #3767 - libmutt: eliminate use of config variables - - #3774 - fix ubsan warning - - #3802 - mutt: optimize and inline `mutt_str_is_email_wsp()` - - #3803 - progress: update ncurses only when there is actual progress - - #3801 - email: Read `$assumed_charset` outside loops - - #3805 - hcache: do less work when not in use - - #3777 - pager: add helper for getting `$pager` - - #3797 - hcache: remove spurious +1 from Buffer serialization. -* Build - - #3787 - fix race condition in `make install` - - #3780 - fallback to detect SASL manually if pkg-config fails, e.g., homebrew - -2023-03-22 Richard Russon \ -* Features - - #3372 - use DT_SLIST for charset variables - - #3383 - support viewing html with embedded images - - #3408 - account command, see the [feature page](https://neomutt.org/feature/account-cmd) - - #3411 - check that `$sendmail` and `$inews` don't contain shell meta characters - - #3412 - browser: add `$mailbox_folder_format` config variable - - #3421 - enter: add function `` - - #3414 - account command: add macOS keychain sample provider - - #3430 - account command: add GPG+JSON sample provider - - #3474 - expose italics attribute for colour scheme - - #3471 - allow `source` in hooks to point to relative paths - - #3506 - resolve alternates when subscribing/unsubscribing - - #3492 - notmuch: allow specifying configuration file - - #3547 - notmuch: allow usage of notmuch profiles - - #3524 - add GNU SASL support for authentication (`--gsasl` configure option) - - #3548 - extend colour objects to support patterns - - #3586 - detect and fixup maildirs with missing "new" and "tmp" directories - - #3634 - generate standard MIME types as application/pkcs7-* instead of legacy application/x-pkcs7-* - - #3639 - compose: add Smime: pseudo header - - #3641 - handle more X-Mutt pseudo-headers with `$edit_headers` - - #3702 - use `$socket_timeout` to time out read/write operations - - #3717 - allow `%[fmt]` in `$folder_format` - - #3719 - respect `$attribution_locale` in `$indent_string` and `$post_indent_string` - - #3720 - pattern: add `~K` to search Bcc, include Bcc in `~C`, `%C`, `~L`, and `~p` - - #3726 - colour postponed emails list - - #3734 - allow querying user-defined variables (`$my_var`) with `-Q` - - #3737 - dump user-defined variables (`$my_var`) with `-D` - - #3655 - generate purely random `Message-ID` headers - - #3752 - allow an empty `$sidebar_divider_char` - - #3745 - fix handling and display of group addresses -* Bug Fixes - - #3386 - fix `$status_on_top` to work on complex windows, e.g., attach - - #3397 - imap: fix off-by-one error causing bogus "Progress message 10/9" message - - #3423 - attach: fix segfault when viewing HTML attachment in compose mode - - #3434 - allow for longer expansions in e.g., `$index_format` - - #3450 - accept unpadded base64-encoded data, as some mailers produce - - #3465 - fix hangup when trying to add email address from help screens - - #3468 - handle corrupted header caches - - #3518 - fix slowdown when changing folders - - #3828 - improve error detection for invalid `color` regexes - - #3533 - distinguish between old/new with `$mark_old` unset - - #3539 - parse mboxes with unconventional `From` lines - - #3572 - fix hostname detection for hostname ending with a "." - - #3596 - fix truncated SMTP lines in case of very long lines - - #3600 - use `$smime_sign_as` instead of `$pgp_sign_as` when signing S/MIME messages - - #3697 - set `$smime_sign_as` instead of `$smime_default_key` when signing - - #3609 - fix wrong message being marked as read with `$pager_read_delay = 1` - - #3653 - fix negative new-mail count on maildir - - #3656 - skip zero width non-joiner character in the pager - - #3664 - handle text/vcard as not being an attachment, same as for text/x-vcard - - #3666 - fix `hdr_order` not sorting last header correctly - - #3673 - make exiting via SIGINT more graceful - - #3700 - fix `unhook index-format-hook` - - #3709 - send: delete signature when sending fails #3709 - - #3727 - SMTP: try all available methods even if SASL is not compiled in - - #3730 - fix decryption issue when postponing S/MIME encrypted mails - - avoid unnecessary refreshes - - fixed a number of memory leaks and crashes -* Config - - #3604 - rename `$ask_follow_up` to `$ask_followup_to` - - #3659 - rename `sidebar_whitelist`/`unsidebar_whitelist` to `sidebar_pin`/`sidebar_unpin` - - #3629 - skip line rest of line after a warning - - #3670 - `$vfolder_format` is now deprecated, use `$folder_format` - - #3702 - rename `$connect_timeout` to `$socket_timeout` - - #3697 - `pgp_entry_format`: add %i expand for the key fingerprint - - #3724 - rename `$attribution` to `$attribution_intro` and - `$post_indent_string` to `$attribution_trailer` - - config variables are now properly spelled with underscores between names, - e.g., `$implicit_autoview` -> `$implicit_auto_view`, `$message_cachedir` -> - `$message_cache_dir`; the old names were kept as synonyms -* Translations - - 100% Czech - - 100% German - - 100% Hungarian - - 100% Lithuanian - - 100% Portuguese (Brazil) - - 100% Serbian - - 100% Slovak - - 100% Turkish - - 99% Spanish - - 99% Ukrainian - - 94% Polish - - 72% Catalan -* Docs - - lots of documentation cleanups and updates -* Code - - a lot of refactor to make the code more organizes, especially in these - areas: windowing, menu, browser, enter, function dispatching, key handling, - auto-completion - - fewer global variables - - removal of some unmaintained contrib code - - new maintained sample config and examples are in the `data` directory - - the contrib script mutt_oauth2.py received a lot of love -* Build - - #3548 - support building with Undefined Behaviour Sanitizer (`--ubsan` configure option) - - #3722 - generate compile_commands.json (`--compile-commands` configure option) - - use pkg-config to locate most of the 3rd party dependencies - - fix curses for netbsd - - improve our CI stack - - create libparse - parsing functions that can be easily tested - - refactor commands / icommands - -2022-04-29 Richard Russon \ -* Bug Fixes - - Do not crash on an invalid use_threads/sort combination - - Fix: stuck browser cursor - - Resolve (move) the cursor after `` - - Index: fix menu size on new mail - - Don't overlimit LMDB mmap size - - OpenBSD y/n translation fix - - Generic: split out OP_EXIT binding - - Fix parsing of sendmail cmd - - Fix: crash with `$menu_move_off=no` - - Newsrc: bugfix; `$nntp_user` and `$nntp_pass` ignored - - Menu: ensure config changes cause a repaint - - Mbox: fix sync duplicates - - Make sure the index redraws all that's needed -* Translations - - 100% Chinese (Simplified) - - 100% Czech - - 100% German - - 100% Hungarian - - 100% Lithuanian - - 100% Serbian - - 100% Turkish -* Docs - - add missing pattern modifier ~I for `$external_search_command` -* Code - - menu: eliminate custom_redraw() - - modernise mixmaster - - Kill global and Propagate display attach status through State - -2022-04-15 Richard Russon \ -* Security - - Fix uudecode buffer overflow (CVE-2022-1328) -* Features - - Colours, colours, colours -* Bug Fixes - - Pager: fix `$pager_stop` - - Merge colours with normal - - Color: disable mono command - - Fix forwarding text attachments when `$honor_disposition` is set - - Pager: drop the nntp change-group bindings - - Use `mailbox_check()` flags coherently, add IMMEDIATE flag - - Fix: tagging in attachment list - - Fix: misalignment of mini-index - - Make sure to update the menu size after a resort -* Translations - - 100% Hungarian -* Build - - Update acutest -* Code - - Unify pipe functions - - Index: notify if navigation fails - - Gui: set colour to be merged with normal - - Fix: leak in `tls_check_one_certificate()` -* Upstream - - Flush `iconv()` in `mutt_convert_string()` - - Fix integer overflow in `mutt_convert_string()` - - Fix uudecode cleanup on unexpected eof - -2022-04-08 Richard Russon \ -* Features - - Compose multipart emails -* Bug Fixes - - Fix screen mode after attempting decryption - - imap: increase max size of oauth2 token - - Fix autocrypt - - Unify Alias/Query workflow - - Fix colours - - Say which file exists when saving attachments - - Force SMTP authentication if `$smtp_user` is set - - Fix selecting the right email after limiting - - Make sure we have enough memory for a new email - - Don't overwrite with zeroes after unlinking the file - - Fix crash when forwarding attachments - - Fix help reformatting on window resize - - Fix poll to use PollFdsCount and not PollFdsLen - - regex: range check arrays strictly - - Fix Coverity defects - - Fix out of bounds write with long log lines - - Apply `$fast_reply` to 'to', 'cc', or 'bcc' - - Prevent warning on empty emails -* Changed Config - - New default: `set rfc2047_parameters = yes` -* Translations - - 100% German - - 100% Lithuanian - - 100% Serbian - - 100% Czech - - 100% Turkish - - 72% Hungarian -* Docs - - Improve header cache explanation - - Improve description of some notmuch variables - - Explain how timezones and `!`s work inside `%{}`, `%[]` and `%()` - - Document config synonyms and deprecations -* Build - - Create lots of GitHub Actions - - Drop TravisCI - - Add automated Fuzzing tests - - Add automated ASAN tests - - Create Dockers for building Centos/Fedora - - Build fixes for Solaris 10 - - New libraries: browser, enter, envelope - - New configure options: `--fuzzing` `--debug-color` `--debug-queue` -* Code - - Split Index/Pager GUIs/functions - - Add lots of function dispatchers - - Eliminate `menu_loop()` - - Refactor function opcodes - - Refactor cursor setting - - Unify Alias/Query functions - - Refactor Compose/Envelope functions - - Modernise the Colour handling - - Refactor the Attachment View - - Eliminate the global `Context` - - Upgrade `mutt_get_field()` - - Refactor the `color quoted` code - - Fix lots of memory leaks - - Refactor Index resolve code - - Refactor PatternList parsing - - Refactor Mailbox freeing - - Improve key mapping - - Factor out charset hooks - - Expose mutt_file_seek API - - Improve API of `strto*` wrappers -* Upstream - - imap QRESYNC fixes - - Allow an empty To: address prompt - - Fix argc==0 handling - - Don't queue IMAP close commands - - Fix IMAP UTF-7 for code points >= U+10000 - - Don't include inactive messages in msgset generation - -2021-10-29 Richard Russon \ -* Features - - Notmuch: support separate database and mail roots without .notmuch -* Bug Fixes - - fix notmuch crash on open failure - - fix crypto crash handling pgp keys - - fix ncrypt/pgp file_get_size return check - - fix restore case-insensitive header sort - - fix pager redrawing of long lines - - fix notmuch: check database dir for xapian dir - - fix notmuch: update index count after `` - - fix protect hash table against empty keys - - fix prevent real_subj being set but empty - - fix leak when saving fcc - - fix leak after `` - - fix leak after trash to hidden mailbox - - fix leak restoring postponed emails - -2021-10-22 Richard Russon \ -* Bug Fixes - - fix new mail notifications - - fix pattern compilation error for ~( !~>(~P) ) - - fix menu display on window resize - - Stop batch mode emails with no argument or recipients - - Add sanitize call in print mailcap function - - fix `hdr_order` to use the longest match - - fix (un)setenv to not return an error with unset env vars - - fix Imap sync when closing a mailbox - - fix segfault on OpenBSD current - - sidebar: restore `sidebar_spoolfile` colour - - fix assert when displaying a file from the browser - - fix exec command in compose - - fix `check_stats` for Notmuch mailboxes - - Fallback: Open Notmuch database without config - - fix gui hook commands on startup -* Changed Config - - Re-enable `$ssl_force_tls` -* Translations - - 100% Serbian - - 100% Lithuanian - - 100% German -* Build - - Warn about deprecated configure options - -2021-10-15 Richard Russon \ -* Security - - Fix CVE-2021-32055 -* Features - - threads: implement the `$use_threads` feature - https://neomutt.org/feature/use-threads - - hooks: allow a -noregex param to folder and mbox hooks - - mailing lists: implement list-(un)subscribe using RFC2369 headers - - mailcap: implement x-neomutt-nowrap flag - - pager: add `$local_date_header` option - - imap, smtp: add support for authenticating using XOAUTH2 - - Allow ` to fail quietly - - imap: speed up server-side searches - - pager: improve `` and `` - - notmuch: open database with user's configuration - - notmuch: implement `` - - config: allow `+=` modification of my_ variables - - notmuch: tolerate file renames behind neomutt's back - - pager: implement `$pager_read_delay` - - notmuch: validate `$nm_query_window_timebase` - - notmuch: make `$nm_record` work in non-notmuch mailboxes - - compose: add `$greeting` - a welcome message on top of emails - - notmuch: show additional mail in query windows -* Changed Config -- Renamed lots of config, e.g. `$askbcc` to `$ask_bcc`. -* Bug Fixes - - imap: fix crash on external IMAP events - - notmuch: handle missing libnotmuch version bumps - - imap: add sanity check for qresync - - notmuch: allow windows with 0 duration - - index: fix index selection on `` - - imap: fix crash when sync'ing labels - - search: fix searching by Message-Id in `` - - threads: fix double sorting of threads - - stats: don't check mailbox stats unless told - - alias: fix crash on empty query - - pager: honor mid-message config changes - - mailbox: don't propagate read-only state across reopens - - hcache: fix caching new labels in the header cache - - crypto: set invalidity flags for gpgme/smime keys - - notmuch: fix parsing of multiple `type=` - - notmuch: validate `$nm_default_url` - - messages: avoid unnecessary opening of messages - - imap: fix seqset iterator when it ends in a comma - - build: refuse to build without pcre2 when pcre2 is linked in ncurses -* Translations - - 100% Serbian - - 100% Lithuanian - - 100% German - - 100% Czech - - 96% Spanish - - 92% Polish - - 85% Norwegian - - 80% French - - 78% Russian - - 74% Esperanto - - 66% Greek - -2021-02-05 Richard Russon \ -* Features - - Add `` to skip past message headers in pager - - Add `` function to attachment menu -* Bug Fixes - - Fix detection of mbox files with new mail - - Fix crash on collapsed thread - - Fix `` - - Clear the message window on resize - - Do not crash on return from shell-exec if there's no open mailbox - - Abort IMAP open if condstore/qresync updates fetch fails - - Fix smtp crash on invalid `$smtp_authenticators` list - - Fix pager dropped input on screen resize - - Fix mime forwarding - - Check config after hooks - - Always recreate a mailbox after `folder-hook` -* Translations - - 88% Slovakian -* Docs - - Adjust doc to explicitly mention `$count_alternatives` - - Restore correct `$sort_re` documentation - - Clarify pattern completion - - Man pages: Clear up "-H" and "-O" -* Build - - Update to latest acutest - - Update to latest autosetup - - Make the location of /tmp configurable - -2020-11-20 Richard Russon \ -* Bug Fixes - - Fix crash when saving an alias -* Translations - - 70% Russian -* Code - - Remove redundant function call - -2020-11-20 Richard Russon \ -* Security - - imap: close connection on all failures -* Features - - alias: add `` function to Alias/Query dialogs - - config: add validators for `{imap,smtp,pop}_authenticators` - - config: warn when signature file is missing or not readable - - smtp: support for native SMTP LOGIN auth mech - - notmuch: show originating folder in index -* Bug Fixes - - sidebar: prevent the divider colour bleeding out - - sidebar: fix `` - - notmuch: fix `` query for current email - - restore shutdown-hook functionality - - crash in reply-to - - user-after-free in `folder-hook` - - fix some leaks - - fix application of limits to modified mailboxes - - write Date header when postponing -* Translations - - 100% Lithuanian - - 100% Czech - - 70% Turkish -* Docs - - Document that `$alias_sort` affects the query menu -* Build - - improve ASAN flags - - add SASL and S/MIME to `--everything` - - fix contrib (un)install -* Code - - `my_hdr` compose screen notifications - - add contracts to the MXAPI - - maildir refactoring - - further reduce the use of global variables -* Upstream - - Add `$count_alternatives` to count attachments inside alternatives - -2020-09-25 Richard Russon \ -* Features - - Compose: display user-defined headers - - Address Book / Query: live sorting - - Address Book / Query: patterns for searching - - Config: Add `+=` and `-=` operators for String Lists - - Config: Add `+=` operator for Strings - - Allow postfix query `:setenv NAME?` for env vars -* Bug Fixes - - Fix crash when searching with invalid regexes - - Compose: Prevent infinite loop of `send2-hook`s - - Fix sidebar on new/removed mailboxes - - Restore indentation for named mailboxes - - Prevent half-parsing an alias - - Remove folder creation prompt for POP path - - Show error if `$message_cachedir` doesn't point to a valid directory - - Fix tracking LastDir in case of IMAP paths with Unicode characters - - Make sure all mail gets applied the index limit - - Add warnings to -Q query CLI option - - Fix index tracking functionality -* Changed Config - - Add `$compose_show_user_headers` (yes) -* Translations - - 100% Czech - - 100% Lithuanian - - Split up usage strings -* Build - - Run shellcheck on `hcachever.sh` - - Add the Address Sanitizer - - Move compose files to lib under compose/ - - Move address config into libaddress - - Update to latest acutest - fixes a memory leak in the unit tests -* Code - - Implement ARRAY API - - Deglobalised the Config Sort functions - - Refactor the Sidebar to be Event-Driven - - Refactor the Color Event - - Refactor the Commands list - - Make `ctx_update_tables()` private - - Reduce the scope/deps of some Validator functions - - Use the Email's IMAP UID instead of an increasing number as index - - debug: log window focus - -2020-08-21 Richard Russon \ -* Bug Fixes - - fix maildir flag generation - - fix query notmuch if file is missing - - notmuch: don't abort sync on error - - fix type checking for send config variables -* Changed Config - - `$sidebar_format` - Use `%D` rather than `%B` for named mailboxes -* Translations - - 96% Lithuanian - - 90% Polish - -2020-08-14 Richard Russon \ -* Security - - Add mitigation against DoS from thousands of parts -* Features - - Allow index-style searching in postpone menu - - Open NeoMutt using a mailbox name - - Add `cd` command to change the current working directory - - Add tab-completion menu for patterns - - Allow renaming existing mailboxes - - Check for missing attachments in alternative parts - - Add one-liner docs to config items -* Bug Fixes - - Fix logic in checking an empty From address - - Fix Imap crash in `cmd_parse_expunge()` - - Fix setting attributes with S-Lang - - Fix: redrawing of `$pager_index_lines` - - Fix progress percentage for syncing large mboxes - - Fix sidebar drawing in presence of indentation + named mailboxes - - Fix retrieval of drafts when `$postponed` is not in the mailboxes list - - Do not add comments to address group terminators - - Fix alias sorting for degenerate addresses - - Fix attaching emails - - Create directories for nonexistent file hcache case - - Avoid creating mailboxes for failed subscribes - - Fix crash if rejecting cert -* Changed Config - - Add `$copy_decode_weed`, `$pipe_decode_weed`, `$print_decode_weed` - - Change default of `$crypt_protected_headers_subject` to "..." - - Add default keybindings to history-up/down -* Translations - - 100% Czech - - 100% Spanish -* Build - - Allow building against Lua 5.4 - - Fix when `sqlite3.h` is missing -* Docs - - Add a brief section on stty to the manual - - Update section "Terminal Keybindings" in the manual - - Clarify PGP Pseudo-header `S` duration -* Code - - Clean up String API - - Make the Sidebar more independent - - De-centralise the Config Variables - - Refactor dialogs - - Refactor: Help Bar generation - - Make more APIs Context-free - - Adjust the edata use in Maildir and Notmuch - - Window refactoring - - Convert libsend to use Config functions - - Refactor notifications to reduce noise - - Convert Keymaps to use STAILQ - - Track currently selected email by msgid - - Config: no backing global variable - - Add events for key binding -* Upstream - - Fix imap postponed mailbox use-after-free error - - Speed up thread sort when many long threads exist - - Fix ~v tagging when switching to non-threaded sorting - - Add message/global to the list of known "message" types - - Print progress meter when copying/saving tagged messages - - Remove ansi formatting from autoview generated quoted replies - - Change postpone mode to write Date header too - - Unstuff `format=flowed` - -2020-08-07 Richard Russon \ -* Devel Release - see 2020-08-14 - -2020-06-26 Richard Russon \ -* Bug Fixes - - Avoid opening the same hcache file twice - - Re-open Mailbox after `folder-hook` - - Fix the matching of the `$spool_file` Mailbox - - Fix link-thread to link all tagged emails -* Changed Config - - Add `$tunnel_is_secure` config, defaulting to true -* Upstream - - Don't check IMAP PREAUTH encryption if `$tunnel` is in use - - Add recommendation to use `$ssl_force_tls` - -2020-06-19 Richard Russon \ -* Security - - Abort GnuTLS certificate check if a cert in the chain is rejected - - TLS: clear data after a starttls acknowledgement - - Prevent possible IMAP MITM via PREAUTH response -* Features - - add config operations `+=`/`-=` for number,long - - Address book has a comment field - - Query menu has a comment field -* Contrib - - sample.neomuttrc-starter: Do not echo prompted password -* Bug Fixes - - make "news://" and "nntp://" schemes interchangeable - - Fix CRLF to LF conversion in base64 decoding - - Double comma in query - - compose: fix redraw after history - - Crash inside empty query menu - - mmdf: fix creating new mailbox - - mh: fix creating new mailbox - - mbox: error out when an mbox/mmdf is a pipe - - Fix `` by correct parsing of List-Post headers - - Decode references according to RFC2047 - - fix tagged message count - - hcache: fix keylen not being considered when building the full key - - sidebar: fix path comparison - - Don't mess with the original pattern when running IMAP searches - - Handle IMAP "NO" resps by issuing a msg instead of failing badly - - imap: use the connection delimiter if provided - - Memory leaks -* Changed Config - - `$alias_format` default changed to include `%c` comment - - `$query_format` default changed to include `%e` extra info -* Translations - - 100% Lithuanian - - 84% French - - Log the translation in use -* Docs - - Add missing commands unbind, unmacro to man pages -* Build - - Check size of long using `LONG_MAX` instead of `__WORDSIZE` - - Allow ./configure to not record cflags - - fix out-of-tree build - - Avoid locating gdbm symbols in qdbm library -* Code - - Refactor unsafe TAILQ returns - - add window notifications - - flip negative ifs - - Update to latest `acutest.h` - - test: add store tests - - test: add compression tests - - graphviz: email - - make more opcode info available - - refactor: `main_change_folder()` - - refactor: `mutt_mailbox_next()` - - refactor: `generate_body()` - - compress: add `{min,max}_level` to ComprOps - - emphasise empty loops: "// do nothing" - - prex: convert `is_from()` to use regex - - Refactor IMAP's search routines - -2020-05-01 Richard Russon \ -* Bug Fixes - - Make sure buffers are initialized on error - - fix(sidebar): use abbreviated path if possible -* Translations - - 100% Lithuanian -* Docs - - make header cache config more explicit - -2020-04-24 Richard Russon \ -* Bug Fixes - - Fix history corruption - - Handle pretty much anything in a URL query part - - Correctly parse escaped characters in header phrases - - Fix crash reading received header - - Fix sidebar indentation - - Avoid crashing on failure to parse an IMAP mailbox - - Maildir: handle deleted emails correctly - - Ensure OP_NULL is always first -* Translations - - 100% Czech -* Build - - cirrus: enable pcre2, make pkgconf a special case - - Fix finding pcre2 w/o pkgconf - - build: `tdb.h` needs size_t, bring it in with `stddef.h` - -2020-04-17 Richard Russon \ -* Features - - Fluid layout for Compose Screen, see: https://vimeo.com/407231157 - - Trivial Database (TDB) header cache backend - - RocksDB header cache backend - - Add `` and `` functions -* Bug Fixes - - add error for CLI empty emails - - Allow spaces and square brackets in paths - - browser: fix hidden mailboxes - - fix initial email display - - notmuch: fix time window search. - - fix resize bugs - - notmuch: fix ``: update current email pointer - - sidebar: support indenting and shortening of names - - Handle variables inside backticks in `sidebar_pin` - - browser: fix mask regex error reporting -* Translations - - 100% Lithuanian - - 99% Chinese (simplified) -* Build - - Use regexes for common parsing tasks: urls, dates - - Add configure option `--pcre2` -- Enable PCRE2 regular expressions - - Add configure option `--tdb` -- Use TDB for the header cache - - Add configure option `--rocksdb` -- Use RocksDB for the header cache - - Create libstore (key/value backends) - - Update to latest autosetup - - Update to latest `acutest.h` - - Rename `doc/` directory to `docs/` - - make: fix location of .Po dependency files - - Change libcompress to be more universal - - Fix test fails on х32 - - fix uidvalidity to unsigned 32-bit int -* Code - - Increase test coverage - - Fix memory leaks - - Fix null checks -* Upstream - - Buffer refactoring - - Fix use-after-free in `mutt_str_replace()` - - Clarify PGP Pseudo-header `S` duration - - Try to respect MUTT_QUIET for IMAP contexts too - - Limit recurse depth when parsing mime messages - -2020-03-20 Richard Russon \ -* Bug Fixes - - Fix COLUMNS env var - - Fix sync after delete - - Fix crash in notmuch - - Fix sidebar indent - - Fix emptying trash - - Fix command line sending - - Fix reading large address lists - - Resolve symlinks only when necessary -* Translations - - 100% Lithuanian - - 96% Spanish -* Docs - - Include OpenSSL/LibreSSL/GnuTLS version in neomutt -v output - - Fix case of GPGME and SQLite -* Build - - Create libcompress (lz4, zlib, zstd) - - Create libhistory - - Create libbcache - - Move zstrm to libconn -* Code - - Add more test coverage - - Rename magic to type - - Use `mutt_file_fopen()` on config variables - - Change commands to use intptr_t for data - -2020-03-13 Richard Russon \ -* Features - - UI: add number of old messages to `$sidebar_format` - - UI: support ISO 8601 calendar date - - UI: fix commands that don’t need to have a non-empty mailbox to be valid - - PGP: inform about successful decryption of inline PGP messages - - PGP: try to infer the signing key from the From address - - PGP: enable GPGME by default - - Notmuch: use query as name for `` - - IMAP: add network traffic compression (COMPRESS=DEFLATE, RFC4978) - - Header cache: add support for generic header cache compression -* Bug Fixes - - Fix `$uncollapse_jump` - - Only try to perform `` on maildir/mh mailboxes - - Fix crash in pager - - Avoid logging single new lines at the end of header fields - - Fix listing mailboxes - - Do not recurse a non-threaded message - - Fix initial window order - - Fix leaks on IMAP error paths - - Notmuch: compose(attach-message): support notmuch backend - - Fix IMAP flag comparison code - - Fix `$move` for IMAP mailboxes - - Maildir: `maildir_mbox_check_stats()` should only update mailbox stats if requested - - Fix unmailboxes for virtual mailboxes - - Maildir: sanitize filename before hashing - - OAuth: if 'login' name isn't available use 'user' - - Add error message on failed encryption - - Fix a bunch of crashes - - Force C locale for email date - - Abort if run without a terminal -* Changed Config - - `$crypt_use_gpgme` - Now defaults to 'yes' (enabled) - - `$abort_backspace` - Hitting backspace against an empty prompt aborts the prompt - - `$abort_key` - String representation of key to abort prompts - - `$arrow_string` - Use a custom string for `$arrow_cursor` - - `$crypt_opportunistic_encrypt_strong_keys` - Enable encryption only when strong a key is available - - `$header_cache_compress_dictionary` - Filepath to dictionary for zstd compression - - `$header_cache_compress_level` - Level of compression for method - - `$header_cache_compress_method` - Enable generic hcache database compression - - `$imap_deflate` - Compress network traffic - - `$smtp_user` - Username for the SMTP server -* Translations - - 100% Lithuanian - - 81% Spanish - - 78% Russian -* Build - - Add libdebug - - Rename public headers to `lib.h` - - Create libcompress for compressed folders code - - Enable Cirrus CI for FreeBSD -* Code - - Refactor Windows and Dialogs - - Lots of code tidying - - Refactor: `mutt_addrlist_{search,write}()` - - Lots of improvements to the Config code - - Use Buffers more pervasively - - Unify API function naming - - Rename library shared headers - - Refactor libconn gui dependencies - - Refactor: init.[ch] - - Refactor config to use subsets - - Config: add path type - - Remove backend deps from the connection code -* Upstream - - Allow ~b ~B ~h patterns in send2-hook - - Rename smime oppenc mode parameter to `get_keys_by_addr()` - - Add `$crypt_opportunistic_encrypt_strong_keys` config var - - Fix crash when polling a closed ssl connection - - Turn off auto-clear outside of autocrypt initialization - - Add protected-headers="v1" to Content-Type when protecting headers - - Fix segv in IMAP postponed menu caused by reopen_allow - - Adding ISO 8601 calendar date - - Fix `$fcc_attach` to not prompt in batch mode - - Convert remaining `mutt_encode_path()` call to use struct Buffer - - Fix rendering of replacement_char when Charset_is_utf8 - - Update to latest `acutest.h` - -2019-12-07 Richard Russon \ -* Features - - compose: draw status bar with highlights -* Bug Fixes - - crash opening notmuch mailbox - - crash in `mutt_autocrypt_ui_recommendation()` - - Avoid negative allocation - - Mbox new mail - - Setting of DT_MAILBOX type variables from Lua - - imap: empty cmdbuf before connecting - - imap: select the mailbox on reconnect - - compose: fix attach message -* Build - - make files conditional - - add gpgme check for RHEL6 -* Code - - enum-ify log levels - - fix function prototypes - - refactor virtual email lookups - - factor out global Context - -2019-11-29 Richard Russon \ -* Features - - Add raw mailsize expando (%cr) -* Bug Fixes - - Avoid double question marks in bounce confirmation msg - - Fix bounce confirmation - - fix new-mail flags and behaviour - - fix: browser `` - - fix ssl crash - - fix move to trash - - fix flickering - - Do not check hidden mailboxes for new mail - - Fix `$new_mail_command` notifications - - fix crash in `examine_mailboxes()` - - fix crash in `mutt_sort_threads()` - - fix: crash after sending - - Fix crash in tunnel's `conn_close()` - - fix fcc for deep dirs - - imap: fix crash when new mail arrives - - fix colour 'quoted9' - - quieten messages on exit - - fix: crash after failed `mbox_check()` - - browser: default to a file/dir view when attaching a file -* Changed Config - - Change `$write_bcc` to default off -* Translations - - 100% Portuguese (Brazil) - - 92% Polish -* Docs - - Add a bit more documentation about sending - - Clarify `$write_bcc` documentation. - - Update documentation for raw size expando - - docbook: set generate.consistent.ids to make generated html reproducible -* Build - - fix build/tests for 32-bit arches - - tests: fix test that would fail soon - - tests: fix context for failing idna tests - -2019-11-11 Richard Russon \ -* Bug Fixes - - browser: fix directory view - - fix crash in `parse_extract_token()` - - force a screen refresh - - fix crash sending message from command line - - notmuch: use `$nm_default_url` if no mailbox data - - fix forward attachments - - fix: vfprintf undefined behaviour in `body_handler()` - - Fix relative symlink resolution - - fix: trash to non-existent file/dir - - fix re-opening of mbox Mailboxes - - close logging as late as possible - - log unknown mailboxes - - fix crash in command line postpone - - fix memory leaks - - fix icommand parsing - - fix new mail interaction with `$mail_check_recent` - -2019-11-02 Richard Russon \ -* Bug Fixes - - Mailboxes command with empty backticks - - Mbox save-to-trash - - Mkdir for new maildir folders - - Maildir: new mail detection - - Truncation of "set" command on a path variable - - Update crash (when changing folder) - - Resolve symbolic links when saving a message - - Folder-hook calling `unmailboxes *` - - Failed ssl negotiation - - Crash when using "alias -group" - - LibIDN error when `$charset` wasn't set - - Notmuch abort `` if database lacks message - -2019-10-25 Richard Russon \ -* Features - - Add `$fcc_before_send`, defaulting unset - - Deprecate TLS 1.0 and 1.1 by default - - Turn on `$ssl_force_tls` by default - - Command line -z and -Z options to work with IMAP - - Add size display configuration variables - - Summary pages: version, set, set all, bind, macro - - CONDSTORE and QRESYNC support - - OAUTHBEARER support - - inotify support - - add index-format-hook - - Add `$auto_subscribe` variable - - Allow relative date hour/min/sec offsets - - Add attributes support on color declarations - - Style Menu Options - - Add new pattern type ~I for external searches - - Add `` command -* Changed Config - - `$folder_format` - - `$pgp_use_gpg_agent` - - `$shell` - - `$ssl_force_tls` - - `$ssl_use_tlsv1` - - `$ssl_use_tlsv1_1` - - `$status_format` - - `$to_chars` - - `$user_agent` -* New Config - - `$attach_save_dir` - - `$attach_save_without_prompting` - - `$autocrypt` - - `$autocrypt_acct_format` - - `$autocrypt_dir` - - `$autocrypt_reply` - - `$auto_subscribe` - - `$crypt_chars` - - `$crypt_protected_headers_read` - - `$crypt_protected_headers_save` - - `$crypt_protected_headers_subject` - - `$crypt_protected_headers_write` - - `$external_search_command` - - `$fcc_before_send` - - `$forward_attachments` - - `$imap_condstore` - - `$imap_fetch_chunk_size` - - `$imap_oauth_refresh_command` - - `$imap_qresync` - - `$imap_rfc5161` - - `$include_encrypted` - - `$nm_flagged_tag` - - `$nm_replied_tag` - - `$pop_oauth_refresh_command` - - `$sidebar_non_empty_mailbox_only` - - `$size_show_bytes` - - `$size_show_fractions` - - `$size_show_mb` - - `$size_units_on_left` - - `$smtp_oauth_refresh_command` - - `$ssl_use_tlsv1_3` -* New Commands - - `:index-format-hook` - - `:named-mailboxes` - - `:unbind` - - `:unmacro` -* New Functions - - `` - - `` - - `` - - `` - - `` - - `` - - `` - - `` - - `` - - `` - - `` - - `` - - `` -* Bug Fixes - - Fix crashes - - Fix memory leaks - - Fix undefined behaviour - - Fix coverity defects -* Translations - - 100% Lithuanian - - 100% Chinese (Simplified) - - 100% Portuguese (Brazil) - - 95% German - - 95% Finnish - - 95% Czech - - 91% Polish - - 78% Japanese - - 73% Dutch - - 72% Spanish - - 62% Swedish - - 55% Slovak -* Docs - - OpenPGP and S/MIME configuration - - Quick-starter config section - - Autocrypt feature - - "Message Composition Flow" section to manual - - OAUTH support - -2018-07-16 Richard Russon \ -* Features - - `` function -* Bug Fixes - - Lots - -2018-06-22 Richard Russon \ -* Features - - Expand variables inside backticks - - Honour SASL-IR IMAP capability in SASL PLAIN -* Bug Fixes - - Fix `` - - Do not truncate shell commands on ; or # - - pager: index must be rebuilt on MUTT_REOPENED - - Handle a BAD response in AUTH PLAIN w/o initial response - - fcc_attach: Don't ask every time - - Enlarge path buffers PATH_MAX (4096) - - Move LSUB call from connection establishment to mailbox SELECTion -* Translations - - Update Chinese (Simplified): 100% - - Update Czech: 100% - - Update German: 100% - - Update Lithuanian: 100% - - Update Portuguese (Brazil): 100% - - Update Slovak: 59% - - Reduce duplication of messages -* Code - - Tidy up the mailbox API - - Tidy up the header cache API - - Tidy up the encryption API - - Add doxygen docs for more functions - - Refactor more structs to use STAILQ - -2018-05-12 Richard Russon \ -* Features - - echo command - - Add `$browser_abbreviate_mailboxes` - - Add ~M pattern to match mime Content-Types - - Add support for multipart/multilingual emails - - Jump to a collapsed email - - Add support for idn2 (IDNA2008) -* Bug Fixes - - Let `mutt_ch_choose()` report conversion failure - - minor IMAP string handling fixes -* Translations - - Chinese (Simplified) (100%) - - Czech (100%) - - German (100%) - - Lithuanian (62%) - - Portuguese (Brazil) (100%) -* Coverity defects - - match prototypes to their functions - - make logic clearer - - reduce scope of variables - - fix coverity defects -* Docs - - development: analysis - - development: easy tasks - - development: roadmap -* Code - - start refactoring libconn - - split out progress functions - - split out window functions - - split out terminal setting - - convert MyVars to use TAILQ - - split `mutt_file_{lock,unlock}()` - - Move IDN version string to `mutt/idna.c` - - refactor: `init_locale()` - - Eliminate static variable in `mutt_file_dirname()` -* Tidy - - test int functions against 0 - - rename lots of constants - - rename lots of functions - - sort lots of fields/definitions -* Upstream - - Increase account.user/login size to 128 - - Fix comparison of flags with multiple bits set - - Change `mutt_error()` call in `mutt_gpgme_set_sender()` to dprint - - Improve the error message when a signature is missing - - pager specific "show incoming mailboxes list" macro - - Improve gss debug printing of status_string - - Remove trailing null count from gss_buffer_desc.length field - - Add a comment in auth_gss about RFCs and NUL-termination - - Change prompt string for `$crypt_verify_sig` - -2018-03-23 Richard Russon \ -* Features - - unify logging/messaging - - add alert (blink) colors -* Contrib - - Vim syntax for NeoMutt log files -* Bug Fixes - - Fix progress bar range - - notmuch: stop if db open fails - - Improve index color cache flushing behavior - - lua: fix crash when setting a string -* Translations - - Update Czech translation (100%) - - Update German translation (100%) - - Update Polish translation (94%) - - Update Portuguese (BR) translation (100%) - - Update Spanish translation (64%) - - Update Turkish translation (75%) - - Merge similar messages -* Docs - - Clarify precedence of settings in config files - - Fix subjectrx example in the manual -* Website - - Update Gentoo distro page - - Devel: Static analysis -* Build - - Support —with-sysroot configure arg - - Expose EXTRA_CFLAGS_FOR_BUILD and EXTRA_LDFLAGS_FOR_BUIlD - - Update to latest autosetup - - Make sure `git_ver.h` doesn't eat random 'g's out of tag names -* Code - - Refactor to reduce complexity - - Refactor to reduce variables' scope - - Sort functions/config to make docs more legible - -2018-02-23 Richard Russon \ -* Features - - browser: `` function bound to "p" - - editor: `` function bound to "Ctrl-r" - - Cygwin support: https://neomutt.org/distro/cygwin - - openSUSE support: https://neomutt.org/distro/suse - - Upstream Homebrew support: Very soon - https://neomutt.org/distro/homebrew -* Bug Fixes - - gmail server-size search - - nested-if: correctly handle "\<" and "\>" with %? - - display of special chars - - lua: enable myvars - - for pgpewrap in default gpg.rc - - `$reply_regexp` which wasn't formatted correctly. - - parsing of urls containing '?' - - out-of-bounds read in `mutt_str_lws_len()` -* Translations - - Review fuzzy lt translations - - Updated French translation -* Website - - Installation guide for Cygwin - - Installation guide for openSUSE - - Installation guide for CRUX -* Build - - check that DTDs are installed - - autosetup improvements - - option for which version of bdb to use - - drop test for resizeterm -- it's always present -* Code - - split if's containing assignments - - doxygen: add/improve comments - - rename functions / parameters for consistency - - add missing {}s for clarity - - move functions to library - - reduce scope of variables - - boolify more variables - - iwyu: remove unnecessary headers - - name unicode chars - - tailq: migrate parameter api - - md5: refactor and tidy - - rfc2047: refactor and tidy - - buffer: improvements - - create unit test framework - - fix several coverity defects -* Upstream - - Fix s/mime certificate deletion bug - - Disable message security if the backend is not available - - Fix improper signed int conversion of IMAP uid and msn values - - Change imap literal counts to parse and store unsigned ints - - Fix imap status count range check - - `cmd_handle_fatal()`: make error message a bit more descriptive - - Create pgp and s/mime default and sign_as key vars - - Add missing setup calls when resuming encrypted drafts - - `mutt_pretty_size()`: show real number for small files - - `examine_directory()`: set directory/symlink size to zero - - Add `` function, bound to ctrl-r - - Avoid a potential integer overflow if a Content-Length value is huge - -2017-12-15 Richard Russon \ -* Bug Fixes - - Fix some regressions in the previous release - -2017-12-08 Richard Russon \ -* Features - - Enhance ifdef feature to support my_ vars - - Add `` - - Remove vim syntax file from the main repo - - Support reading FQDN from mailname files -* Bug Fixes - - Do not turn CRLF into LF when dealing with transfer-encoding=base64 - - Cleanup "SSL is unavailable" error in mutt_conn_find - - Don't clear the macro buffer during startup - - Fixup smart `` for !tag case - - Add sleep after SMTP error - - Restore folder settings after `folder-hook` - - Fix segfault when pipe'ing a deleted message -* Docs - - Display_filter escape sequence - - Correct spelling mistakes - - Add a sentence to `` docs - - Modify gpg.rc to accommodate GPG 2.1 changes -* Build - - Fix build for RHEL6 - - Define NCURSES_WIDECHAR to require wide-char support from ncurses - - Autosetup: fix check for missing sendmail - - Respect `--with-ssl` path - - Check that OpenSSL md5 supports -r before using it - - Autosetup: expand `--everything` in `neomutt -v` - - Make sure objects are not compiled before `git_ver.h` is generated - - Build: fix update-po target - - Fix out-of-tree builds - - Fix stdout + stderr redirection in `hcachever.sh` - - Build: moved the check for idn before the check for notmuch - - Define prefix in Makefile.autosetup - - Install stuff to $(PACKAGE) in $(libexecdir), not $(libdir) - - Update autosetup to latest master -* Code - - Rename files - - Rename functions - - Rename variables - - Rename constants - - Remove unused parameters - - Document functions - - Rearrange functions - - Move functions to libraries - - Add new library functions - - Rearrange switch statements - - Boolification - - Drop #ifdef DEBUG - - Fix Coverity defects - - Insert braces - - Split ifs - - Fallthrough - - Fix shadow variable - - Replace mutt_debug with a macro - - Return early where possible -* Upstream - - Note which ssl config vars are GnuTLS or OpenSSL only - - Add message count to `$move` quadoption prompt - - Add %R (number of read messages) for `$status_format` - - Add `$change_folder_next` option to control mailbox suggestion order - - Fix `$smart_wrap` to not be disabled by whitespace-prefixed lines - - Remove useless else branch in the `$smart_wrap` code - - Fix ansi escape sequences with both reset and color parameters - -2017-10-27 Richard Russon \ -* Bug Fixes - - variable type when using fread - - prevent timezone overflow - - tags: Show fake header for all backends - - notmuch: virtual-mailboxes should accept a limit - - Issue 888: Fix imap mailbox flag logging - - fix actions on tagged messages - - call the `folder-hook` before saving to `$record` - - Fix smart wrap in pager without breaking header - - Add polling for the IDLE command -* Docs - - imap/notmuch tags: Add some documentation - - English and other cleanups - - compressed and nntp features are now always built -* Website - - Update Arch instructions -* Build - - Fix update-po - - Fix neomutt.pot location, remove from git - - Allow to specify `--docdir` at configure time - - Generate neomuttrc even if configured with `--disable-doc` - - Let autosetup define PWD, do not unnecessarily try to create hcache dir - - Use bundled wcscasecmp if an implementation is not found in libc - - Use host compiler to build the documentation - - Update autosetup to latest master branch - - autosetup: delete makedoc on 'make clean' - - Fixes for endianness detection - - Update autosetup to latest master branch - - Do not use CPPFLAGS / CFLAGS together with CC_FOR_BUILD - - `--enable-everything` includes lua - - autosetup: check for sys_siglist[] -* Code - - move functions to library - - lib: move MIN/MAX macros - - simplify null checks - - kill preproc expansion laziness - - reduce scope of variables - - merge: minor code cleanups - - split up 'if' statements that assign and test - - Refactor: Remove unused return type - - Bool: change functions in `mx.h` - - bool: convert function parameters in `nntp.h` - - add extra checks to `mutt_pattern_exec()` - - Use safe_calloc to initialize memory, simplify size_t overflow check - - Move `mutt_rename_file()` to lib/file.[hc] - - doxygen: fix a few warnings - - minor code fixes - - use `mutt_array_size()` - - refactor out O_NOFOLLOW - - initialise variables - - lib: move List and Queue into library - - url: make notmuch query string parser generic - - Wrap dirname(3) inside a `mutt_dirname()` function - -2017-10-13 Richard Russon \ -* Bug Fixes - - crash using uncolor - - Sort the folders list when browsing an IMAP server - - Prefer a helpful error message over a BEEP -* Build - - Do not fail if deflate is not in libz - - Support EXTRA_CFLAGS and EXTRA_LDFLAGS, kill unused variable - -2017-10-06 Richard Russon \ -* Features - - Add IMAP keywords support -* Bug Fixes - - set mbox_type - - %{fmt} date format - - Fix off-by-one buffer overflow in add_index_color - - crash in `mbox_to_udomain()` - - crash in `mutt_substrdup()` - - crash looking up mime body type - - `$digest_collapse` was broken - - crash using notmuch expando with imap - - imap: Fix mx.mbox leak in `imap_get_parent_path()` - - overflow in `mutt_mktime()` - - add more range-checking on dates/times - - Remove spurious error message - - Unsubscribe after deleting an imap folder - - Do not pop from MuttrcStack what wasn't pushed -* Docs - - replace mutt refs with neomutt - - drop old vim syntax file -* Code - - convert functions to use 'bool' - - convert structs to use STAILQ -* Build - - Autosetup-based configuration - - drop upstream mutt references - - rename everything 'mutt' to 'neomutt' - - move helper programs to lib dir - - rename regexp to regex - - expand buffers to avoid gcc7 warnings -* Upstream - - Remove \Seen flag setting for imap trash - - Change imap copy/save and trash to sync flags, excluding deleted - - Improve imap fetch handler to accept an initial UID - - Display an error message when delete mailbox fails - - Updated French translation - - Fix imap sync segfault due to inactive headers during an expunge - - Close the imap socket for the selected mailbox on error - - Add missing IMAP_CMD_POLL flag in imap mailbox check - - Change maildir and mh check_mailbox to use dynamic sized hash - - Fix uses of `context->changed` as a counter - - Make `cmd_parse_fetch()` more precise about setting reopen/check flags - - Enable `$reply_self` for `` even with $me_too unset - -2017-09-12 Richard Russon \ -* Bug Fixes - - broken check on resend message - - crash in `` -* Build - - Be more formal about quoting in m4 macros - - fix warnings raised by gcc7 - - notmuch: add support for the v5 API - -2017-09-07 Richard Russon \ -* Contrib - - Add guix build support -* Bug Fixes - - Only match real mailboxes when looking for new mail - - Fix the printing of ncurses version in -v output - - Bind editor `` to `` - - Fix overflowing colours - - Fix empty In-Reply-To generation - - Trim trailing slash from completed dirs - - Add guix-neomutt.scm - - Fix setting custom query_type in notmuch query -* Website - - New technical documentation LINK - - Improve Gentoo distro page -* Build - - Better curses identification - - Use the system's wchar_t support - - Use the system's md5 tool (or equivalent) - - Clean up configure.ac - - Teach gen-map-doc about the new opcode header -* Source - - Rename functions (snake_case) - - Rename constants/defines (UPPER_CASE) - - Create library of shared functions - - Much tidying - - Rename globals to match user config - - Drop unnecessary functions/macros - - Use a standard list implementation - - Coverity fixes - - Use explicit NUL for string terminators - - Drop OPS\* in favour of `opcodes.h` -* Upstream - - Fix menu color calls to occur before positioning the cursor - - When guessing an attachment type, don't allow text/plain if there is a null character - - Add `$imap_poll_timeout` to allow mailbox polling to time out - - Handle error if REGCOMP in pager fails when resizing - - Change recvattach to allow nested encryption - - Fix attachment check_traditional and extract_keys operations - - Add edit-content-type helper and warning for decrypted attachments - - Add option to run command to query attachment mime type - - Add warning about using inline pgp with format=flowed - -2017-07-14 Richard Russon \ -* Translations - - Update German translation -* Docs - - compile-time output: use two lists - - doxygen: add config file - - doxygen: tidy existing comments -* Build - - fix `hcachever.sh` script -* Upstream - - Fix crash when `$postponed` is on another server. - -2017-07-07 Richard Russon \ -* Features - - Support Gmail's X-GM-RAW server-side search - - Include pattern for broken threads - - Allow sourcing of multiple files -* Contrib - - vombatidae colorscheme - - zenburn colorscheme - - black 256 solarized colorscheme - - neonwolf colorscheme - - Mutt logos -* Bug Fixes - - flags: update the hdr message last - - gpgme S/MIME non-detached signature handling - - menu: the thread tree color - - Uses CurrentFolder to populate LastDir with IMAP - - stabilise sidebar sort order - - colour emails with a '+' in them - - the padding expando `%>` - - Do not set old flag if `$mark_old` is false - - maildir creation - - Decode CRLF line endings to LF when copying headers - - score address pattern do not match personal name - - open attachments in read-only mode - - Add Cc, In-Reply-To, and References to default mailto_allow - - Improve search for mime.types -* Translations - - Update Chinese (Simplified) translation -* Coverity defects - - dodgy buffers - - leaks in lua get/set options - - some resource leaks -* Docs - - update credits - - limitations of new-mail %f expando - - escape \<\>'s in nested conditions - - add code of conduct - - fix ifdef examples - - update mailmap - - Update `` - - fix mailmap - - drop UPDATING files -* Website - - Changes pages (diff) - - Update Arch distro page - - Update NixOS distro page - - Add new Exherbo distro page - - Update translation hi-score table - - Update code of conduct - - Update Newbies page - - Add page about Rebuilding the Documentation - - Add page of hard problems -* Build - - remove unnecessary steps - - drop instdoc script - - move smime_keys into contrib - - fixes for Solaris - - don't delete non-existent files - - remove another reference to devel-notes.txt - - Handle native Solaris GSSAPI. - - drop configure options `--enable-exact-address` - - drop configure option `--with-exec-shell` - - drop configure option `--enable-nfs-fix` - - drop configure option `--disable-warnings` - - Completely remove dotlock - - More sophisticated check for BDB version + support for DB6 (non default) -* Tidy - - drop VirtIncoming - - split `parse_mailboxes()` into `parse_unmailboxes()` - - tidy some mailbox code - - tidy the version strings -* Upstream - - Add ~\<() and ~\>() immediate parent/children patterns - - Add L10N comments to the GNUTLS certificate prompt - - Add more description for the %S and %Z `$index_format` characters - - Add config vars for forwarded message attribution intro/trailer - - Block SIGWINCH during connect() - - Improve the L10N comment about Sign as - - Auto-pad translation for the GPGME key selection "verify key" headers - - Enable all header fields in the compose menu to be translated - - Force hard redraw after `$sendmail` instead of calling `mutt_endwin()` - - Make GPGME key selection behavior the same as classic-PGP - - Rename 'sign as' to 'Sign as'; makes compose menu more consistent - - Change the compose menu fields to be dynamically padded - -2017-06-09 Richard Russon \ -* Contrib - - unbind mappings before overwriting in vim-keys -* Bug Fixes - - latest coverity issues (#624) - - don't pass colour-codes to filters - - Don't set a colour unless it's been defined. - - crash if no from is set or founds - - ifdef command -* Translations - - fix translations - - fix some remaining translation problems -* Docs - - explain binding warnings - - don't document unsupported arches -* Build - - fix make `git_ver.h` - - allow xsltproc and w3m calls to fail - - fix make dist -* Upstream - - Add a `mutt_endwin()` before invoking `$sendmail` - - Restore setenv function - - Fix `` to not abort on `$timeout` - - Change `km_dokey()` to return -2 on a timeout/sigwinch - - Enable TEXTDOMAINDIR override to make translation testing easier - - Fix "format string is not a string literal" warnings - -2017-06-02 Richard Russon \ -* Features - - Warn on bindkey aliasing - - Drop PATCHES, tidy 'mutt -v' output - - Add %z format strings to `$index_format` - - Add `$debug_level`/`$debug_file` options -* Bug Fixes - - Fix nntp group selection - - Fix status color - - Tidy up S/MIME contrib - - Do not try to create Maildir if it is an NNTP URL - - Fix missing NONULL for `mutt.set()` in Lua -* Translations - - Fix German PGP shortkeys -* Docs - - Remove feature muttrc files - - Merge README.notmuch into manual - - Remove unneeded scripts - - Remove README.SECURITY - - Remove BEWARE and devel-notes.txt - - Update Makefiles - - Delete TODO files - - Remove legacy files - - Don't generate vim-neomutt syntax file - - Remove LaTeX/pdf manual generation - - Add missing docs for expandos - - Fix sidebar howto examples - - Remove some upstream references - - Drop refs to patches - - Improve PR template and CONTRIBUTING.md -* Website - - Fix list items in newbie-tutorial's Mailing List Guidelines - - Remove configure options that no longer exist - - fix newbie tutorial - - document signing tags / releases - - config: drop unused paginate command - - script: split tests up into several - - convert credits page to markdown - - simplify 404 page - - improve newbie tutorial - - remove help.html and integrate its content elsewhere - - make: "graphviz" program is needed for generating diagram - - improve getting started guide // include legacy files - - dev: add list of architectures/operating systems - - numerous small fixes -* Build - - Remove typedefs and rename ~130 structs - - Add separate hcache dir - - Move crypto files to ncrypt dir - - Split up `mutt.h`, `protos.h` - - Always build: sidebar, imap, pop, smtp, compressed, nntp - - Remove `--enable-mailtool` configure option - - Make dotlock optional - - Change gpgme requirement back to 1.1.0 - - Remove `check_sec.sh` - - Fix safe_calloc args - - Remove unused macros - - Remove unused option: SmimeSignOpaqueCommand - - Move configure-generated files - - Update distcheck build flags - - Drop obsolete iconv check - - Unused prototypes - unsupported systems - - Drop many configure tests for things defined in POSIX:2001 - - Kill useless `crypthash.h` file - - Run clang-format on the code - - Fail early if ncursesw cannot be found - - Add names prototype arguments - - Abbreviate pointer tests against NULL - - Initialise pointers to NULL - - Reduce the scope of for loop variables - - Coverity: fix defects -* Upstream - - Convert all exec calls to use `mutt_envlist()`, remove setenv function - - Note that mbox-hooks are dependent on `$move` - - Refresh header color when updating label - - Remove glibc-specific `execvpe()` call in `sendlib.c` - - Add color commands for the compose menu headers and security status - - Fix sidebar count updates when closing mailbox - - Don't modify LastFolder/CurrentFolder upon aborting a change folder operation - - Change message modifying operations to additively set redraw flags - - Improve maildir and mh to report flag changes in `mx_check_mailbox()` - - Add `$header_color_partial` to allow partial coloring of headers - - Rename REDRAW_SIGWINCH to REDRAW_FLOW - - Create R_PAGER_FLOW config variable flag - - Turn IMAP_EXPUNGE_EXPECTED back off when syncing - - Add `$history_remove_dups` option to remove dups from history ring - - Also remove duplicates from the history file - - Don't filter new entries when compacting history save file - - Move the IMAP msn field to IMAP_HEADER_DATA - - Fix imap expunge to match msn and fix index - - Fix `cmd_parse_fetch()` to match against MSN - - Start fixing `imap_read_headers()` to account for MSN gaps - - Add msn_index and max_msn to find and check boundaries by MSN - - Properly adjust fetch ranges when handling new mail - - Small imap fetch fixes - - Don't abort header cache evaluation when there is a hole - - Fix mfc overflow check and uninitialized variable - - Fix potential segv if mx_open_mailbox is passed an empty string - - Don't clean up idata when closing an open-append mailbox - - Don't clean up msn idata when closing an open-append mailbox - - Fix memory leak when closing mailbox and using the sidebar - - Change imap body cache cleanup to use the uid_hash - - Convert classic s/mime to space delimit findKeys output - - Add self-encrypt options for PGP and S/MIME - - Change `$postpone_encrypt` to use self-encrypt variables first - - Automatic post-release commit for mutt-1.8.3 - - Add note about message scoring and thread patterns - -2017-04-28 Richard Russon \ -* Bug Fixes - - Fix and simplify handling of GPGME in configure.ac (@gahr) -* Docs - - Fix typo in README.neomutt (@l2dy) -* Upstream - - Fix `km_error_key()` infinite loop and unget buffer pollution - - Fix error message when opening a mailbox with no read permission - -2017-04-21 Richard Russon \ -* Features - - add lua scripting - - add command-line batch mode - - index_format: add support of %K -* Bug Fixes - - attachment/pager: Use mailcap for test/* except plain - - Fix `$uncollapse_new` in pager - - fix garbage in chdir prompt due to unescaped string - - Fix inbox-first functionality when using `mutt_pretty_mailbox()` - - add full neomutt version to log startup - - fix bug in uncolor for notmuch tag - - fix broken `$from_chars` behaviour -* Coverity defects - - strfcpy - - add variable - function arg could be NULL/invalid - - add variable - failed function leads to invalid variable - - add variable - Context could become NULL - - add variable - alloc/strdup could return NULL - - add variable - route through code leads to invalid variable - - remove variable test - - test functions - - tidy switches - - unused variables - - refactor only - - check for buffer underruns - - fix leaks - - minor fixes - - bug: add missing break - - bug: don't pass large object by value - - fix: use correct buffer size - - shadow variables - - 0 -\> NULL -* Docs - - many minor updates - - sync translations - - delete trailing whitespace - - indent the docbook manual - - use w3m as default for generating UTF8 manual.txt -* Website - - many minor updates - - fix broken links - - add to list of useful programs - - test automatic html checker - - remove trailing whitespace - - add irc description - - update issue labels (dev) - - new page: closed discussions - - new page: making neomutt (dev) -* Build - - drop obsolete m4 scripts - - don't look for lua libs unless asked for - - lower the gettext requirement 0.18 -\> 0.17 - - add `keymap_alldefs.h` to BUILT_SOURCES - - fix make dist distcheck - - Remove -Iimap from CFLAGS and include `imap/imap.h` explicitly - - mx: fix conditional builds - - Make iconv mandatory (no more `--disable-iconv`) - - refactor: Split out BUFFER-handling functions -* Tidy - - drop control characters from the source - - drop vim modelines - - delete trailing whitespace - - mark all local functions as static - - delete unused functions - - replace FOREVER with while (true) - - drop #if HAVE_CONFIG_H - - use #ifdef for potentially missing symbols - - remove #if 0 code blocks - - drop commented out source - - IMAP auth functions are stored by pointer cannot be static - - force OPS to be rebuilt after a reconfigure - - be specific about void functions - - expand a few more alloc macros - - add argument names to function prototypes - - drop local copy of regex code - - rearrange code to avoid forward declarations - - limit the scope of some functions - - give the compress functions a unique name - - use snake_case for function names - - add missing newlines to mutt_debug - - remove generated files from repo - - look for translations in all files - - fix arguments to printf-style functions - - license text - - unify include-guards - - tidy makefiles - - initialise pointers - - make strcmp-like functions clearer - - unify sizeof usage - - remove forward declarations - - remove ()s from return - - rename files hyphen to underscore - - remove unused macros - - use SEEK_SET, SEEK_CUR, SEEK_END - - remove constant code - - fix typos and grammar in the comments - - Switch to using an external gettext runtime - - apply clang-format to the source code - - boolify returns of 84 functions - - boolify lots of struct members - - boolify some function parameters -* Upstream - - Add `$ssl_verify_partial_chains` option for OpenSSL - - Move the OpenSSL partial chain support check inside configure.ac - - Don't allow storing duplicate certs for OpenSSL interactive prompt - - Prevent skipped certs from showing a second time - - OpenSSL: Don't offer (a)ccept always choice for hostname mismatches - - Add SNI support for OpenSSL - - Add SNI support for GnuTLS - - Add shortcuts for IMAP and POP mailboxes in the file browser - - Change OpenSSL to use SHA-256 for cert comparison - - Fix conststrings type mismatches - - Pass envlist to filter children too - - Fix `envlist_set()` for the case that envlist is null - - Fix setenv overwriting to not truncate the envlist - - Fix `(un)sidebar_pin` to expand paths - - Fix `mutt_refresh()` pausing during macro events - - Add a menu stack to track current and past menus - - Change CurrentMenu to be controlled by the menu stack - - Set refresh when popping the menu stack - - Remove redraw parameter from crypt send_menus - - Don't full redraw the index when handling a command from the pager - - Filter other directional markers that corrupt the screen - - Remove the OPTFORCEREDRAW options - - Remove SidebarNeedsRedraw - - Change `reflow_windows()` to set full redraw - - Create R_MENU redraw option - - Remove refresh parameter from `mutt_enter_fname()` - - Remove redraw flag setting after `mutt_endwin()` - - Change `km_dokey()` to pass SigWinch on for the MENU_EDITOR - - Separate out the compose menu redrawing - - Separate out the index menu redrawing - - Prepare for pager redraw separation - - Separate out the pager menu redrawing - - Don't create query menu until after initial prompt - - Silence imap progress messages for `` - - Ensure mutt stays in endwin during calls to `pipe_msg()` - - Fix memleak when attaching files - - Add `$ssl_verify_partial_chains` option for OpenSSL - - Move the OpenSSL partial chain support check inside configureac - - Don't allow storing duplicate certs for OpenSSL interactive prompt - - Prevent skipped certs from showing a second time - - OpenSSL: Don't offer (a)ccept always choice for hostname mismatches - - Add SNI support for OpenSSL - - Add SNI support for GnuTLS - - Add shortcuts for IMAP and POP mailboxes in the file browser - - Updated French translation - - Change OpenSSL to use SHA-256 for cert comparison - - Fix conststrings type mismatches - - Pass envlist to filter children too - - Fix `envlist_set()` for the case that envlist is null - - Fix setenv overwriting to not truncate the envlist - - Fix `mutt_refresh()` pausing during macro events - - Add a menu stack to track current and past menus - - Change CurrentMenu to be controlled by the menu stack - - Set refresh when popping the menu stack - - Remove redraw parameter from crypt send_menus - - Don't full redraw the index when handling a command from the pager - - Fix `(un)sidebar_pin` to expand paths - - Filter other directional markers that corrupt the screen - - Remove the OPTFORCEREDRAW options - - Remove SidebarNeedsRedraw - - Change `reflow_windows()` to set full redraw - - Create R_MENU redraw option - - Remove refresh parameter from `mutt_enter_fname()` - - Remove redraw flag setting after `mutt_endwin()` - - Change `km_dokey()` to pass SigWinch on for the MENU_EDITOR - - Separate out the compose menu redrawing - - Separate out the index menu redrawing - - Prepare for pager redraw separation - - Separate out the pager menu redrawing - - Don't create query menu until after initial prompt - - Silence imap progress messages for `` - - Ensure mutt stays in endwin during calls to `pipe_msg()` - - Fix memleak when attaching files - - automatic post-release commit for mutt-181 - - Added tag mutt-1-8-1-rel for changeset f44974c10990 - - mutt-181 signed - - Add ifdefs around new mutt_resize_screen calls - - Add multiline and sigwinch handling to `mutt_multi_choice()` - - Set pager's REDRAW_SIGWINCH when reflowing windows - - Add multiline and sigwinch handling to mutt_yesorno - - Change the sort prompt to use (s)ort style prompts - - Handle the pager sort prompt inside the pager - - Fix GPG_TTY to be added to envlist - - automatic post-release commit for mutt-182 - -2017-03-06 Richard Russon \ -* Bug Fixes - - Get the correct buffer size under fmemopen/torify (#441) - - Use static inlines to make gcc 4.2.1 happy - - getdnsdomainname: cancel getaddrinfo_a if needed - - imap: remove useless code (#434) (origin/master) - - Fixes missing semi-colon compilation issue (#433) -* Docs - - github: added template for Pull Requests, issues and a CONTRIBUTION.md (#339) - - editorconfig: support for new files, fix whitespace (#439) - - add blocking fmemopen bug on debian to manual (#422) -* Upstream - - Increase ACCOUNT.pass field size. (closes #3921) - - SSL: Fix memory leak in subject alternative name code. (closes #3920) - - Prevent segv if open-appending to an mbox fails. (closes #3918) - - Clear out extraneous errors before `SSL_connect()` (see #3916) - -2017-02-25 Richard Russon \ -* Features - - Add option `$show_multipart_alternative` - - notmuch: Allow to use untransformed tag for color - - Use getaddrinfo_a if possible (#420) -* Bug Fixes - - handle sigint within socket operations (#411) - - Avoid browsing the remote `$spool_file` by setting MUTT_SELECT_MULTI attach - - notmuch: fix crash when completing tags (#395) - - Fixes missing failure return of notmuch msg open (#401) - - Fix latest Coverity issues (#387) - - Advance by the correct number of position even for unknown characters (#368) - - Release KyotoCabinet data with `kcfree()` (#384) - - 22 resource leaks -* Translations - - Update translations - - Update the German translation (#397) -* Docs - - fix typo in notmuch example - - remove duplicate "default" in the sidebar intro - - fix confusing description of notmuch operators (#371) - - correct spelling mistakes (#412) -* Website - - link to clang-format config in main repo (#28) - - updated list of useful programs - - update/improve list of useful programs - - `$sidebar_format` has a single default value - - fix name of GNU Guix - - added guix distro - - added link to new afew maintainers - - add code of conduct - - add mutt-addressbook to useful - - remove unnecessary unicode non-breaking spaces - - github merging -* Build - - Enable and run unit-tests on the feature/unit-test branch - - add notmuch to default, feature - - new dbs for mutt - - master is now the main branch - - streamline builds - - fix doc generator - - add a few includes (prelude to clang-format) - - `slcurses.h` defines its own bool type - - travis: use container build - - add clang-format file - - Remove ugly macros and casts from `crypt_gpgme.c` - - fix minor reflow issues in some comments - - editorconfig: use spaces to indent in *.[ch] files - - added comment-blocks for clang-format to ignore - - fix 80 column limit, align statements - - Remove `snprintf.c` from EXTRA_DIST (#406) - - Kill homebrew (v)snprintf implementations, as they are C99 (#402) - - Display charset + small refactoring - - Do not cast or check returns from safe_calloc (#396) - - refactor: create a generic base64 encode/decode - - debug: remove dprint in favor of mutt_debug (#375) - - Fix dubious use macro for `_()` / `gettext()` (#376) - - Use buf_init instead of memset - - Make the heap method and datatype a plain list - - Reverts making AliasFile into a list_t (#379) - - Turn mutt_new_* macros into inline functions - - Do not cast return values from malloc (or similar) -* Upstream - - Simplify `mutt_label_complete()` - - Permit tab completion of pattern expressions with ~y (labels) - - Fix the `mutt_label_complete()` pos parameter - - Fix the x-label update code check location - - Improve the label completion hash table usage - - Adds label completion - - Add `hash_find_elem()` to get the hash element - - Minor fixes to the x-label patch from David - - Adds capability to edit x-labels inside mutt, and to sort by label - - Allow "unsubjectrx *" to remove all patterns - - Add subjectrx command to replace matching subjects with something else - - Abstract the SPAM_LIST as a generic REPLACE_LIST - - Improve Reply-to vs From comparison when replying - - Fix sidebar references to the "new count" to be "unread" - - Fix several alias hashtable issues - - Add casecmp and strdup_key flags to hash_create() - - Improve error handling in mbox magic detection - - Allow initial blank lines in local mailboxes - - Fix minor documentation issues - - Convert cmd_parse_search to use the uid hash - - Create a uid hash for imap - - Convert HASH to be indexable by unsigned int - - Fix imap server-side search to call `uid2msgno()` only once - - Add a pattern_cache_t to speed up a few repeated matches - - Canonicalize line endings for GPGME S/MIME encryption - - Fix build for bdb - - Create function to free header cache data - - Add Kyoto Cabinet support to the header cache - - Prevent null pointer exception for `h->ai_canonname` - - Show SHA1 fp in interactive cert check menu - - Fix potential cert memory leak in `check_certificate_by_digest()` - - Plug memory leak in weed-expired-certs code - - Filter expired local certs for OpenSSL verification - - Change "allow_dups" into a flag at hash creation - -2017-02-06 Richard Russon \ -* Bug Fixes - - Unicode 0x202F is a non-break space too (#358) - - improve readability of `find_subject()` - - Import hcache-lmdb fixes from upstream (#363) - - Rework the "inbox-first" implementation to make code self-explanatory (#356) - - If possible, only redraw after gpgme has invoked pinentry (#352) - - Remove two use-after free in global hooks (#353) - - Handle BAD as IMAP_AUTH_UNAVAIL (#351) - - Do not crash when closing a non-opened mailbox - - Import hcache benchmark - - fix: bug introduced by mkdir changes (#350) - - change pager to allow timehook-hook to fire -* Docs - - Update documentation about `` - -2017-01-28 Richard Russon \ -* Features - - Add option for missing subject replacement - - notmuch: Allow `` to toggle labels - - Support for aborting mailbox loading - - Do a mailbox check after shell escape - - Support of relative paths sourcing and cyclic source detection - - Support of multiple config files as CLI arguments - - Extend the ~m pattern to allow relative ranges - - Implement SASL's PLAIN mechanism as a standalone authenticator - - Add support for sensitive config options - - Searching with a window over notmuch vfolders -* Contrib - - fix vim syntax file for index-color commands - - add .editorconfig -* Bug Fixes - - fix global hooks to not take a pattern - - Avoid breaking relative paths when avoiding cyclic checks on - - Fix sorting when using '/' as a namespace separator -* Docs - - Added waffle badges to readme - - Describe the new message ranges - - add documentation for -DS command line switch - - fix typos in section on config locations - - remove reference to missing keybinding - - fix docbook validation -* Build - - Start migrating to stdbool logic - - add recursive `mkdir()` - - reformat the source to mutt standards - - appease `check_sec.sh` - -2017-01-13 Richard Russon \ -* Features - - Allow custom status flags in `$index_format` - - `$from_chars` highlights differences in authorship - - notmuch: make 'Folder' and 'Tags' respect (un)ignore - - notmuch: add "virtual-unmailboxes" command -* Bug Fixes - - pick smarter default for `$sidebar_divider_char` - - status color breaks "mutt -D" - - Enable `` in the pager - - manually touch 'atime' when reading a mbox file - - allow `$to_chars` to contain Unicode characters - - increase the max lmdb database size - - restore limit current thread - - don't reset the alarm unless we set it - - some more places that may get NULL pointers - - rework initials to allow unicode characters -* Translations - - Spanish translation - - German translation -* Docs - - Improve whitespace and grammar on the NNTP feature page - - make `$to_chars` docs more legible - - de-tab the DocBook - - fix 301 redirects -* Build - - New configure option `--enable-everything` - - add a constant for an aborted question - - enhance `mutt_to_base64()` (and callers) - - Fix configure.ac to require md5 if hcache is enabled - - Bail if a selected hcache backend cannot be found - - refactor mutt_matches_ignore - - fix hcache + make dist - - add unicode string helper function - - Re-indent configure.ac - - generate devel version suffix - - fix `check_sec.sh` warnings - - remove unnecessary #ifdef's - - add missing #ifdef for nntp - - ignore some configure temp files - - fix "make dist" target - - fix function prototypes - - fix coverity warnings - - notmuch: drop strndup, replace with mutt_substrdup -* Upstream - - Fix failure with GPGME 1.8: do not steal the gpgme_ prefix. - - search muttrc file according to XDG Base Specification (closes #3207) - - Improve openssl interactive_check_cert. (closes #3899) - - Add mutt_array_size macro, change `interactive_check_cert()` to use it. (see #3899) - - Return to pager upon aborting a jump operation. (closes #3901) - - Change sidebar_spool_file coloring to be lower precedence. - - Move '@' pattern modifier documentation to the right section. - - Add setenv/unsetenv commands. - - Rework OpenSSL certificate verification to support alternative chains. (closes #3903) - - Add option to control whether threads uncollapse when new mail arrives. - - In the manual, replaced 2 para by example (similar to the first example). - - Create MbTable type for multibyte character arrays. (see #3024) - - Make `$to_chars` and `$status_chars` accept mulitibyte characters. (closes #3024) - -2016-11-26 Richard Russon \ -* Features - - Upstream adoption of compress - - Multiple hcache backends and run-time selection - - `$forward_references` includes References: header on forwards - - Hooks: define hooks for startup and shutdown - - Add `$collapse_all` to close threads automatically -* Bug Fixes - - Index in pager crash - - Tag with multiple labels - - Make sure gdbm's symbols are not resolved in QDBM's compatibility layer - - Fix crash when doing collapse_all on an empty folder - - Fix: crash when browsing empty dir - - Initialize imap_authenticate's return value to something meaningful -* Translations - - Update German translation - - Update Slovak translation - - Update French translation - - Add English (British) translation - - Convert files to utf-8 - - Mass tidy up of the translation messages -* Docs - - new-mail bug is fixed - - add since date for features - - expand example command options for compress - - fix entries for `$beep` and `new-mail-command` - - add a version number to the generated vimrc - - fix links in README - - don't use smart quotes in manual examples - - `` and `\e` means refers to both alt and escape key -* Build - - Travis: test messages - - Add option to disable translation messages - - Split hcache code into per-backend files - - Doc/Makefile clean neomutt-syntax.vim - - Improve discovery for the Berkeley Database - - Fix nntp/notmuch conditionals - - Implement `mutt_strchrnul()` - - Rename vim-keybindings to vim-keys -* Upstream - - `$attach_format`: add new %F placeholder - - Compose: add operation to rename an attachment - - Chain `%d->%F->%f` in the attachment menu - - Move mbox close-append logic inside `mbox_close_mailbox()` - - When `$flag_safe` is set, flagged messages cannot be deleted - - Adds the '@' pattern modifier to limit matches to known aliases - - Adds `` binding to create "hotkeys" for messages - - Updated requirement on the C compiler - - Fix `` translation and keybind menu - - More openssl1.1 fixes: remove uses of `X509->name` in debugging. (closes #3870) - - Don't close stderr when opening a tunnel. (closes #3726) - - Minor resource and error logic cleanup in tunnel_socket_open() - - Make sure that the output of X509_NAME_oneline is NUL-terminated - -2016-11-04 Richard Russon \ -* Bug Fixes - - don't crash when the imap connection dies -* Upstream - - Add `` function to jump to root message in thread. - - Updated French translation. - - Prevent an integer overflow in `mutt_mktime()` (closes #3880) - - Fix pager segfault when lineInfo.chunks overflows. (closes #3888) - - Perform charset conversion on text attachments when piping. (closes #3773) (see #3886) - - Add a `--disable-doc` configuration option. - - Make ncurses and ncursesw header checking the same. - - Attempt to silence a clang range warning. (closes #3891) - - Fixed issue from changeset 4da647a80c55. (closes #3892) - - Define PATH_MAX, it's missing on the GNU Hurd. (closes #3815) - -2016-10-28 Richard Russon \ -* Features - - nntp: use safe_{fopen,fclose} - - nntp: fix resource leak - - forgotten-attachment: Ignore lines matching `$quote_regex` - - forgotten-attachment: Fix checking logic. - - forgotten-attachment: Update docs regarding `$quote_regex` - - notmuch: Add a fake "Folder" header to viewed emails - - sidebar: consider description when using pinning - - skip-quoted: skip to body -* Bug Fixes - - sensible-browser/notmuch changing mailbox - - "inbox" sorting function - - overhaul the index/pager updates - - crash in hdrline - - remove stray line introduced by pager fix - - Possible fix for random pager crashes. -* Docs - - use a more expressive coverity scan badge - - light tidying -* Build - - replace the ugly `strfcpy()` macro with a function - - build: Look for tgetent in ncurses, fallback to tinfo only if not found - - build: fix a couple of build warnings - - travis: install doc dependencies - - build: fix install/dist/distcheck targets -* Upstream - - Fix POP3 SASL authentication mechanism DIGEST-MD5. (closes #3862) - - Add a few explanatory comments to `pop_auth_sasl()` (see #3862) - - Fix GPGME signature zero timestamp and locale awareness issues. (closes #3882) - - Handle presence of '--' delimiter in `$sendmail` (closes #3168) - - Allow IPv6 literal addresses in URLs. (closes #3681) - - Fix gpgme segfault in `create_recipient_set()` - - Use `mutt_strlen()` and `mutt_strncmp()` in `sidebar.c` - - Change sidebar to only match `$folder` prefix on a `$sidebar_divider_char.` (closes #3887) - - Actually fix gpgme segfault in `create_recipient_set()` - -2016-10-14 Richard Russon \ -* Features - - sidebar: Make sure INBOX appears first in the list. - - notmuch: Synchronise tags to flags -* Bug Fixes - - updates when pager is open - - crash when neither `$spool_file`, `$folder` are set - - forgotten-attachment: fix empty regex expression - - status-color when `$pager_index_lines > 0` - - buffer underrun when no menu item is selected - - crash handling keywords/labels -* Docs - - update notmuch references -* Build - - update references to 1.7.1 - - `strfcpy()` improvement -* Upstream - - automatic post-release commit for mutt-1.7.1 - - Mark IMAP fast-trash'ed messages as read before copying. (see #3860) - - Updated Czech translation. - - Preserve forwarded attachment names in d_filename. - -2016-10-03 Richard Russon \ -* Build - - Fix install and dist targets - -2016-10-02 Richard Russon \ -* Features - - Kyoto Cabinet header cache - - Compose to Sender - - Forgotten Attachment uses a regex - - Optimize LMDB's hcache backend - - Sensible-browser behaviour fixes -* Bug Fixes - - Fixes repaint problem with `$pager_index_lines` #159 - - Quasi-Delete: check there's a selection - - Bulletproof the pager - - Typo in the version string -* Docs - - Add badges to README.neomutt - - Document the Kyoto cabinet hcache backend - - Fix the layout of the syntax file - - Make the license clear to github - - Fix the alignment in a 'nested-if' example - - Fix notmuch vim syntax file - - Added Mailinglist mailto links to "Where is NeoMutt" section - - Fix build of neomutt-syntax.vim - - Fixed typo of devel mailinglist name -* Build - - Travis: install the kyoto-cabinet dev files - - Build source before docs - - Build fix for strndup / malloc - - Change gcc build options to prevent crashes -* Upstream - - Ensure signatures exist when verifying multipart/signed emails (closes #3881) - - RFC2047-decode mailto url headers after RFC2822 parsing (closes #3879) - - RFC2047-decode mailto header values (closes #3879) - - Reset invalid parsed received dates to 0 (closes #3878) - - Clear pager position when toggling headers - - Don't abort the menu editor on sigwinch (closes #3875) - - Mark some gpgme pgp menu keybinding translations as fuzzy (closes #3874) - - Check for NULL mx_ops in mxc - - Use body color for gpgme output (closes #3872) - - Fix gpgme segfault when querying candidates with a '+' in the address (closes #3873) - -2016-09-16 Richard Russon \ -* Bug Fixes - - Avoid segfault when listing mailboxes on startup - John Swinbank - - Fix buffer overrun in search for attach keyword - James McCoy - - Fix off-by-one in error message - Antonio Radici - - fix AC_INIT tarname parameter - - fix crash when exiting the pager - - fix another crash in the pager - - nntp: close message handles - - fix: make the pager more robust - - fix sidebar sort order - - fix notmuch tag completion -* Docs - - doc: Removes bug entry in new-mail docs - Santiago Torres - - fix some translations in `crypt_gpgme.c` - Antonio Radici - - docs: mass tidy up -* Upstream - - Fix sidebar documentation a bit - - Add sidebar_pin command - - Remove the $locale configuration variable - - Add `$attribution_locale` configuration variable - - Add missing include `` to `send.c` and `edit.c` - - Filter out zero width no-break space (U+FEFF) - - Update a confusing and obsolete comment - - Moves `mutt_copy_list()` to `muttlib.c`, where it belongs - - Redraw screen after an SSL cert prompt - - Preserve message-id and mft headers for recalled messages - - Fix openssl 1.1 compilation issues - -2016-09-10 Richard Russon \ -* New Features - - Colouring Attachments with Regex - Guillaume Brogi - - PGP Encrypt to Self - Guillaume Brogi - - Sensible Browser - Pierre-Elliott Bécue - - Reply using X-Original-To: header - Pierre-Elliott Bécue - - Purge Thread - Darshit Shah - - Forgotten attachment - Darshit Shah - - Add `sidebar_ordinary` color -* Bug Fixes - - align the nntp code with mutt - Fabian Groffen - - check for new mail while in pager when idle - Stefan Assmann - - Allow the user to interrupt slow IO operations - Antonio Radici - - keywords: check there are emails to tag - - fix duplicate saved messages - - flatten contrib/keybase dir to fix install - - restore the pager keymapping 'i' to exit - - proposed fix for clearing labels - - notmuch: sync `$vfolder_format` to `$folder_format` -* Docs - - Update List of Features and Authors -* Build - - fix configure check for fmemopen - - use fixed version strings -* Upstream - - Increase date buffer size for `$folder_format` - - Disable ~X when message scoring - - Fix pgpring reporting of DSA and Elgamal key lengths - - Stub out `getdnsdomainname()` unless HAVE_GETADDRINFO - - Autoconf: always check for `getaddrinfo()` - - Add missing sidebar contrib sample files to dist tarball - -2016-08-27 Richard Russon \ -* NeoMutt for Mutt 1.7.0 -* Build - - Disable fmemopen until bug is fixed -* Contrib - - Keybase portability improvements - Joshua Jordi (JakkinStewart) - -2016-08-21 Richard Russon \ -* Contrib - - Updates to Keybase Support - Joshua Jordi (JakkinStewart) -* Bug Fixes - - Fix data-loss when appending a compressed file - - Don't paint invisible progress bars - - Revert to Mutt keybindings - - Don't de-tag emails after labelling them - - Don't whine if `getrandom()` fails - Adam Borowski (kilobyte) - - Fix display when 'from' field is invalid -* Config - - Support for $XDG_CONFIG_HOME and $XDG_CONFIG_DIRS - Marco Hinz (mhinz) -* Docs - - Fix DocBook validation - - Document Notmuch queries -* Build - - More Autoconf improvements - Darshit Shah (darnir) - - Create Distribution Tarballs with autogen sources - Darshit Shah (darnir) - -2016-08-08 Richard Russon \ -* New Features - - Timeout Hook - Run a command periodically - - Multiple fcc - Save multiple copies of outgoing mail -* Contrib - - Keybase Integration - Joshua Jordi (JakkinStewart) -* Devel - - Attached - Prevent missing attachments - Darshit Shah (darnir) - - Virtual Unmailboxes - Remove unwanted virtual mailboxes - Richard Russon (flatcap) -* Bug Fixes - - Sidebar's inbox occasionally shows zero/wrong value - - Fix crash opening a second compressed mailbox -* Config - - Look for /etc/neomuttrc and ~/.neomuttrc -* Docs - - Fix broken links, typos - - Update project link - - Fix version string in the manual -* Build - - Add option to disable fmemopen - - Install all the READMEs and contribs - - Big overhaul of the build - Darshit Shah (darnir) - -2016-07-23 Richard Russon \ -* New Motto: "Teaching an Old Dog New Tricks" - - Thanks to Alok Singh -* New Features - - New Mail Command - Execute a command on receipt of new mail - - vim-keys - Mutt config for vim users - - LMDB: In-memory header caching database - - SMIME Encrypt to Self - Secure storage of sensitive email -* Bug Fixes - - rework `mutt_draw_statusline()` - - fix cursor position after sidebar redraw - - Add `$sidebar_format` flag '%n' to display 'N' on new mail. - - fix `$index_format` truncation problem - - Fix compiler warnings due to always true condition - - Change sidebar next/prev-new to look at `mailbox->new` too. - - Change the default for `$sidebar_format` to use %n. - - sidebar "unsorted" order to match Mailbox list order. - - Include ncurses tinfo library if found. - - Sidebar width problem - - sidebar crash for non-existent mailbox - - Temporary compatibility workaround - - Reset `mailbox->new` for the current mailbox in IMAP. - - `version.sh` regression - - crash when notmuch tries to read a message - - status line wrapping -* Docs - - Mass tidy up of the docs - - Fix xml validation - - Add missing docs for new features -* Travis - - New build system: - https://github.com/neomutt/travis-build - Now we have central control over what gets built - -2016-07-09 Richard Russon \ -* Bug-fixes - - This release was a temporary measure - -2016-06-11 Richard Russon \ -* Change in behaviour - - Temporarily disable `$sidebar_refresh_time` - Unfortunately, this was causing too many problems. - It will be fixed and re-enabled as soon as possible. -* Bug Fixes - - Fix several crashes, on startup, in Keywords - - Reflow text now works as it should - - Lots of typos fixed - - Compress config bug prevented it working - - Some minor bug-fixes from mutt/default - - Single quote at line beginning misinterpreted by groff - - Setting `$sidebar_width` to more than 128 would cause bad things to happen. - - Fix alignment in the compose menu. - - Fix sidebar mailbox stats updating on mailbox close. -* Build Changes - - Sync whitespace to mutt/default - - Alter ChangeLog date format to simplify Makefiles - - Use the new notmuch functions that return a status - - Rename sidebar functions `sb_*` -\> `mutt_sb_*` - -2016-05-23 Richard Russon \ -* New Features: - - Keywords: Email Label/Keywords/Tagging - - Compress: Compressed mailboxes support - - NNTP: Talk to a usenet news server - - Separate mappings for `` and `` - - New configure option: `--enable-quick-build` - - Various build fixes - -2016-05-02 Richard Russon \ -* Update for Mutt-1.6.0 -* Bug Fixes: - - Build for Notmuch works if Sidebar is disabled - - Sidebar functions work even if the Sidebar is hidden - - ``, etc, only find *new* mail, as documented - - Notmuch supports *very* long queries - -2016-04-16 Richard Russon \ -* Big Bugfix Release -* Bug Fixes: - - Fix crash caused by `$sidebar_folder_indent` - - Allow the user to change mailboxes again - - Correct sidebar's messages counts - - Only sort the sidebar if we're asked to - - Fix refresh of pager when toggling the sidebar - - Compose mode: make messages respect the TITLE_FMT - - Conditional include if `sys/syscall.h` - - Build fix for old compilers - - Try harder to keep track of the open mailbox -* Changes to Features - - Allow `$sidebar_divider_char` to be longer - (it was limited to one character) - - Ignore case when sorting the sidebar alphabetically -* Other Changes - - Numerous small tweaks to the docs - - Lots of minor code tidy-ups - - Enabling Notmuch now forcibly enables Sidebar - (it is dependent on it, for now) - - A couple of bug fixes from mutt/stable - -2016-04-04 Richard Russon \ -* Update for Mutt-1.6.0 -* No other changes in this release - -2016-03-28 Richard Russon \ -* New Features - - `` - skip quoted text - - `` - limit index view to current thread -* Sidebar Intro - A Gentle Introduction to the Sidebar (with pictures). - -2016-03-20 Richard Russon \ -* Numerous small bugfixes -* TravisCI integration - -2016-03-17 Richard Russon \ -* New Features - - notmuch - email search support - - ifdef - improvements - -2016-03-07 Richard Russon \ -* First NeoMutt release -* List of Features: - - bug-fixes - various bug fixes - - cond-date - use rules to choose date format - - fmemopen - use memory buffers instead of files - - ifdef - conditional config options - - index-color - theme the email index - - initials - expando for author's initials - - nested-if - allow deeply nested conditions - - progress - show a visual progress bar - - quasi-delete - mark emails to be hidden - - sidebar - overview of mailboxes - - status-color - theming the status bar - - tls-sni - negotiate for a certificate - - trash - move 'deleted' emails to a trash bin +### ⚙️ Code + +- config: dynamically create/delete variables +- config: unify handling of NeoMutt and user (my_) variables +- config: cache config variables used often +- speed: various speedups in parsing emails +- cleanups: lots of code cleanups +- Huge refactoring towards a separation of Mailbox/MailboxView + +
+
+NeoMutt 2023-05-12 (Do not use) + +## 2023-05-12 Richard Russon \ + +### ⚠️ BROKEN - Please use 2023-05-17 instead + +
+
+NeoMutt 2023-04-07 + +## 2023-04-07 Richard Russon \ + +### 🎁 Features + +- #3769 - imap : support IMAP4 ID extension (RFC2971) +- #3753 - parse: query all changed (`set`) / all (`set all`) config variables + +### 🐞 Bug Fixes + +- #3785 - lua: fix command registration +- #3793 - postpone: use colours from the right mailbox +- #3794 - smtp: ignore oauth if it isn't configured and not requested + +### 🔧 Config + +- #3779 - New: `$imap_send_id` - Send IMAP ID command when logging in + +### 🏁 Translations + +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇭🇺 Hungarian +- 100% 🇱🇹 Lithuanian +- 100% 🇧🇷 Portuguese (Brazil) +- 100% 🇷🇸 Serbian +- 100% 🇸🇰 Slovak +- 99% 🇵🇱 Polish + +### 📚 Docs + +- Recommend GPGME + +### ⚙️ Code + +- #3767 - libmutt: eliminate use of config variables +- #3774 - fix ubsan warning +- #3802 - mutt: optimize and inline `mutt_str_is_email_wsp()` +- #3803 - progress: update ncurses only when there is actual progress +- #3801 - email: Read `$assumed_charset` outside loops +- #3805 - hcache: do less work when not in use +- #3777 - pager: add helper for getting `$pager` +- #3797 - hcache: remove spurious +1 from Buffer serialization. + +### 🏗 Build + +- #3787 - fix race condition in `make install` +- #3780 - fallback to detect SASL manually if pkg-config fails, e.g., homebrew + +
+
+NeoMutt 2023-03-22 + +## 2023-03-22 Richard Russon \ + +### 🎁 Features + +- #3372 - use DT_SLIST for charset variables +- #3383 - support viewing html with embedded images +- #3408 - account command, see the [feature page](https://neomutt.org/feature/account-cmd) +- #3411 - check that `$sendmail` and `$inews` don't contain shell meta characters +- #3412 - browser: add `$mailbox_folder_format` config variable +- #3421 - enter: add function `` +- #3414 - account command: add macOS keychain sample provider +- #3430 - account command: add GPG+JSON sample provider +- #3474 - expose italics attribute for colour scheme +- #3471 - allow `source` in hooks to point to relative paths +- #3506 - resolve alternates when subscribing/unsubscribing +- #3492 - notmuch: allow specifying configuration file +- #3547 - notmuch: allow usage of notmuch profiles +- #3524 - add GNU SASL support for authentication (`--gsasl` configure option) +- #3548 - extend colour objects to support patterns +- #3586 - detect and fixup maildirs with missing "new" and "tmp" directories +- #3634 - generate standard MIME types as application/pkcs7-* instead of legacy application/x-pkcs7-* +- #3639 - compose: add Smime: pseudo header +- #3641 - handle more X-Mutt pseudo-headers with `$edit_headers` +- #3702 - use `$socket_timeout` to time out read/write operations +- #3717 - allow `%[fmt]` in `$folder_format` +- #3719 - respect `$attribution_locale` in `$indent_string` and `$post_indent_string` +- #3720 - pattern: add `~K` to search Bcc, include Bcc in `~C`, `%C`, `~L`, and `~p` +- #3726 - colour postponed emails list +- #3734 - allow querying user-defined variables (`$my_var`) with `-Q` +- #3737 - dump user-defined variables (`$my_var`) with `-D` +- #3655 - generate purely random `Message-ID` headers +- #3752 - allow an empty `$sidebar_divider_char` +- #3745 - fix handling and display of group addresses + +### 🐞 Bug Fixes + +- #3386 - fix `$status_on_top` to work on complex windows, e.g., attach +- #3397 - imap: fix off-by-one error causing bogus "Progress message 10/9" message +- #3423 - attach: fix segfault when viewing HTML attachment in compose mode +- #3434 - allow for longer expansions in e.g., `$index_format` +- #3450 - accept unpadded base64-encoded data, as some mailers produce +- #3465 - fix hangup when trying to add email address from help screens +- #3468 - handle corrupted header caches +- #3518 - fix slowdown when changing folders +- #3828 - improve error detection for invalid `color` regexes +- #3533 - distinguish between old/new with `$mark_old` unset +- #3539 - parse mboxes with unconventional `From` lines +- #3572 - fix hostname detection for hostname ending with a "." +- #3596 - fix truncated SMTP lines in case of very long lines +- #3600 - use `$smime_sign_as` instead of `$pgp_sign_as` when signing S/MIME messages +- #3697 - set `$smime_sign_as` instead of `$smime_default_key` when signing +- #3609 - fix wrong message being marked as read with `$pager_read_delay = 1` +- #3653 - fix negative new-mail count on maildir +- #3656 - skip zero width non-joiner character in the pager +- #3664 - handle text/vcard as not being an attachment, same as for text/x-vcard +- #3666 - fix `header-order` not sorting last header correctly +- #3673 - make exiting via SIGINT more graceful +- #3700 - fix `unhook index-format-hook` +- #3709 - send: delete signature when sending fails #3709 +- #3727 - SMTP: try all available methods even if SASL is not compiled in +- #3730 - fix decryption issue when postponing S/MIME encrypted mails +- avoid unnecessary refreshes +- fixed a number of memory leaks and crashes + +### 🔧 Config + +- #3604 - rename `$ask_follow_up` to `$ask_followup_to` +- #3659 - rename `sidebar_whitelist`/`unsidebar_whitelist` to `sidebar-pin`/`sidebar-unpin` +- #3629 - skip line rest of line after a warning +- #3670 - `$vfolder_format` is now deprecated, use `$folder_format` +- #3702 - rename `$connect_timeout` to `$socket_timeout` +- #3697 - `pgp_entry_format`: add %i expand for the key fingerprint +- #3724 - rename `$attribution` to `$attribution_intro` and + `$post_indent_string` to `$attribution_trailer` +- config variables are now properly spelled with underscores between names, + e.g., `$implicit_autoview` -> `$implicit_auto_view`, `$message_cachedir` -> + `$message_cache_dir`; the old names were kept as synonyms + +### 🏁 Translations + +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇭🇺 Hungarian +- 100% 🇱🇹 Lithuanian +- 100% 🇧🇷 Portuguese (Brazil) +- 100% 🇷🇸 Serbian +- 100% 🇸🇰 Slovak +- 100% 🇹🇷 Turkish +- 99% 🇪🇸 Spanish +- 99% 🇺🇦 Ukrainian +- 94% 🇵🇱 Polish +- 72% 🏳️ Catalan + +### 📚 Docs + +- lots of documentation cleanups and updates + +### ⚙️ Code + +- a lot of refactor to make the code more organizes, especially in these + areas: windowing, menu, browser, enter, function dispatching, key handling, + auto-completion +- fewer global variables +- removal of some unmaintained contrib code +- new maintained sample config and examples are in the `data` directory +- the contrib script mutt_oauth2.py received a lot of love + +### 🏗 Build + +- #3548 - support building with Undefined Behaviour Sanitizer (`--ubsan` configure option) +- #3722 - generate compile_commands.json (`--compile-commands` configure option) +- use pkg-config to locate most of the 3rd party dependencies +- fix curses for netbsd +- improve our CI stack +- create libparse - parsing functions that can be easily tested +- refactor commands / icommands + +
+
+NeoMutt 2022-04-29 + +## 2022-04-29 Richard Russon \ + +### 🐞 Bug Fixes + +- Do not crash on an invalid use_threads/sort combination +- Fix: stuck browser cursor +- Resolve (move) the cursor after `` +- Index: fix menu size on new mail +- Don't overlimit LMDB mmap size +- OpenBSD y/n translation fix +- Generic: split out OP_EXIT binding +- Fix parsing of sendmail cmd +- Fix: crash with `$menu_move_off=no` +- Newsrc: bugfix; `$nntp_user` and `$nntp_pass` ignored +- Menu: ensure config changes cause a repaint +- Mbox: fix sync duplicates +- Make sure the index redraws all that's needed + +### 🏁 Translations + +- 100% 🇨🇳 Chinese (Simplified) +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇭🇺 Hungarian +- 100% 🇱🇹 Lithuanian +- 100% 🇷🇸 Serbian +- 100% 🇹🇷 Turkish + +### 📚 Docs + +- add missing pattern modifier ~I for `$external_search_command` + +### ⚙️ Code + +- menu: eliminate custom_redraw() +- modernise mixmaster +- Kill global and Propagate display attach status through State + +
+
+NeoMutt 2022-04-15 + +## 2022-04-15 Richard Russon \ + +### 🔒 Security + +- Fix uudecode buffer overflow (CVE-2022-1328) + +### 🎁 Features + +- Colours, colours, colours + +### 🐞 Bug Fixes + +- Pager: fix `$pager_stop` +- Merge colours with normal +- Color: disable mono command +- Fix forwarding text attachments when `$honor_disposition` is set +- Pager: drop the nntp change-group bindings +- Use `mailbox_check()` flags coherently, add IMMEDIATE flag +- Fix: tagging in attachment list +- Fix: misalignment of mini-index +- Make sure to update the menu size after a resort + +### 🏁 Translations + +- 100% 🇭🇺 Hungarian + +### 🏗 Build + +- Update acutest + +### ⚙️ Code + +- Unify pipe functions +- Index: notify if navigation fails +- Gui: set colour to be merged with normal +- Fix: leak in `tls_check_one_certificate()` + +### ♻️ Upstream + +- Flush `iconv()` in `mutt_convert_string()` +- Fix integer overflow in `mutt_convert_string()` +- Fix uudecode cleanup on unexpected eof + +
+
+NeoMutt 2022-04-08 + +## 2022-04-08 Richard Russon \ + +### 🎁 Features + +- Compose multipart emails + +### 🐞 Bug Fixes + +- Fix screen mode after attempting decryption +- imap: increase max size of oauth2 token +- Fix autocrypt +- Unify Alias/Query workflow +- Fix colours +- Say which file exists when saving attachments +- Force SMTP authentication if `$smtp_user` is set +- Fix selecting the right email after limiting +- Make sure we have enough memory for a new email +- Don't overwrite with zeroes after unlinking the file +- Fix crash when forwarding attachments +- Fix help reformatting on window resize +- Fix poll to use PollFdsCount and not PollFdsLen +- regex: range check arrays strictly +- Fix Coverity defects +- Fix out of bounds write with long log lines +- Apply `$fast_reply` to 'to', 'cc', or 'bcc' +- Prevent warning on empty emails + +### 🔧 Changed Config + +- New default: `set rfc2047_parameters = yes` + +### 🏁 Translations + +- 100% 🇩🇪 German +- 100% 🇱🇹 Lithuanian +- 100% 🇷🇸 Serbian +- 100% 🇨🇿 Czech +- 100% 🇹🇷 Turkish +- 72% 🇭🇺 Hungarian + +### 📚 Docs + +- Improve header cache explanation +- Improve description of some notmuch variables +- Explain how timezones and `!`s work inside `%{}`, `%[]` and `%()` +- Document config synonyms and deprecations + +### 🏗 Build + +- Create lots of GitHub Actions +- Drop TravisCI +- Add automated Fuzzing tests +- Add automated ASAN tests +- Create Dockers for building Centos/Fedora +- Build fixes for Solaris 10 +- New libraries: browser, enter, envelope +- New configure options: `--fuzzing` `--debug-color` `--debug-queue` + +### ⚙️ Code + +- Split Index/Pager GUIs/functions +- Add lots of function dispatchers +- Eliminate `menu_loop()` +- Refactor function opcodes +- Refactor cursor setting +- Unify Alias/Query functions +- Refactor Compose/Envelope functions +- Modernise the Colour handling +- Refactor the Attachment View +- Eliminate the global `Context` +- Upgrade `mutt_get_field()` +- Refactor the `color quoted` code +- Fix lots of memory leaks +- Refactor Index resolve code +- Refactor PatternList parsing +- Refactor Mailbox freeing +- Improve key mapping +- Factor out charset hooks +- Expose mutt_file_seek API +- Improve API of `strto*` wrappers + +### ♻️ Upstream + +- imap QRESYNC fixes +- Allow an empty To: address prompt +- Fix argc==0 handling +- Don't queue IMAP close commands +- Fix IMAP UTF-7 for code points >= U+10000 +- Don't include inactive messages in msgset generation + +
+
+NeoMutt 2021-10-29 + +## 2021-10-29 Richard Russon \ + +### 🎁 Features + +- Notmuch: support separate database and mail roots without .notmuch + +### 🐞 Bug Fixes + +- fix notmuch crash on open failure +- fix crypto crash handling pgp keys +- fix ncrypt/pgp file_get_size return check +- fix restore case-insensitive header sort +- fix pager redrawing of long lines +- fix notmuch: check database dir for xapian dir +- fix notmuch: update index count after `` +- fix protect hash table against empty keys +- fix prevent real_subj being set but empty +- fix leak when saving fcc +- fix leak after `` +- fix leak after trash to hidden mailbox +- fix leak restoring postponed emails + +
+
+NeoMutt 2021-10-22 + +## 2021-10-22 Richard Russon \ + +### 🐞 Bug Fixes + +- fix new mail notifications +- fix pattern compilation error for ~( !~>(~P) ) +- fix menu display on window resize +- Stop batch mode emails with no argument or recipients +- Add sanitize call in print mailcap function +- fix `header-order` to use the longest match +- fix (un)setenv to not return an error with unset env vars +- fix Imap sync when closing a mailbox +- fix segfault on OpenBSD current +- sidebar: restore `sidebar_spoolfile` colour +- fix assert when displaying a file from the browser +- fix exec command in compose +- fix `check_stats` for Notmuch mailboxes +- Fallback: Open Notmuch database without config +- fix gui hook commands on startup + +### 🔧 Changed Config + +- Re-enable `$ssl_force_tls` + +### 🏁 Translations + +- 100% 🇷🇸 Serbian +- 100% 🇱🇹 Lithuanian +- 100% 🇩🇪 German + +### 🏗 Build + +- Warn about deprecated configure options + +
+
+NeoMutt 2021-10-15 + +## 2021-10-15 Richard Russon \ + +### 🔒 Security + +- Fix CVE-2021-32055 + +### 🎁 Features + +- threads: implement the `$use_threads` feature + https://neomutt.org/feature/use-threads +- hooks: allow a -noregex param to folder and mbox hooks +- mailing lists: implement list-(un)subscribe using RFC2369 headers +- mailcap: implement x-neomutt-nowrap flag +- pager: add `$local_date_header` option +- imap, smtp: add support for authenticating using XOAUTH2 +- Allow ` to fail quietly +- imap: speed up server-side searches +- pager: improve `` and `` +- notmuch: open database with user's configuration +- notmuch: implement `` +- config: allow `+=` modification of my_ variables +- notmuch: tolerate file renames behind neomutt's back +- pager: implement `$pager_read_delay` +- notmuch: validate `$nm_query_window_timebase` +- notmuch: make `$nm_record` work in non-notmuch mailboxes +- compose: add `$greeting` - a welcome message on top of emails +- notmuch: show additional mail in query windows + +### 🔧 Changed Config + +- Renamed lots of config, e.g. `$askbcc` to `$ask_bcc`. + +### 🐞 Bug Fixes + +- imap: fix crash on external IMAP events +- notmuch: handle missing libnotmuch version bumps +- imap: add sanity check for qresync +- notmuch: allow windows with 0 duration +- index: fix index selection on `` +- imap: fix crash when sync'ing labels +- search: fix searching by Message-Id in `` +- threads: fix double sorting of threads +- stats: don't check mailbox stats unless told +- alias: fix crash on empty query +- pager: honor mid-message config changes +- mailbox: don't propagate read-only state across reopens +- hcache: fix caching new labels in the header cache +- crypto: set invalidity flags for gpgme/smime keys +- notmuch: fix parsing of multiple `type=` +- notmuch: validate `$nm_default_url` +- messages: avoid unnecessary opening of messages +- imap: fix seqset iterator when it ends in a comma +- build: refuse to build without pcre2 when pcre2 is linked in ncurses + +### 🏁 Translations + +- 100% 🇷🇸 Serbian +- 100% 🇱🇹 Lithuanian +- 100% 🇩🇪 German +- 100% 🇨🇿 Czech +- 96% 🇪🇸 Spanish +- 92% 🇵🇱 Polish +- 85% 🇳🇴 Norwegian +- 80% 🇫🇷 French +- 78% 🇷🇺 Russian +- 74% 🏳️ Esperanto +- 66% 🇬🇷 Greek + +
+
+NeoMutt 2021-02-05 + +## 2021-02-05 Richard Russon \ + +### 🎁 Features + +- Add `` to skip past message headers in pager +- Add `` function to attachment menu + +### 🐞 Bug Fixes + +- Fix detection of mbox files with new mail +- Fix crash on collapsed thread +- Fix `` +- Clear the message window on resize +- Do not crash on return from shell-exec if there's no open mailbox +- Abort IMAP open if condstore/qresync updates fetch fails +- Fix smtp crash on invalid `$smtp_authenticators` list +- Fix pager dropped input on screen resize +- Fix mime forwarding +- Check config after hooks +- Always recreate a mailbox after `folder-hook` + +### 🏁 Translations + +- 88% 🇸🇰 Slovakian + +### 📚 Docs + +- Adjust doc to explicitly mention `$count_alternatives` +- Restore correct `$sort_re` documentation +- Clarify pattern completion +- Man pages: Clear up "-H" and "-O" + +### 🏗 Build + +- Update to latest acutest +- Update to latest autosetup +- Make the location of /tmp configurable + +
+
+NeoMutt 2020-11-27 + +## 2020-11-27 Richard Russon \ + +### 🐞 Bug Fixes + +- Fix crash when saving an alias + +### 🏁 Translations + +- 70% 🇷🇺 Russian + +### ⚙️ Code + +- Remove redundant function call + +
+
+NeoMutt 2020-11-20 + +## 2020-11-20 Richard Russon \ + +### 🔒 Security + +- imap: close connection on all failures + +### 🎁 Features + +- alias: add `` function to Alias/Query dialogs +- config: add validators for `{imap,smtp,pop}_authenticators` +- config: warn when signature file is missing or not readable +- smtp: support for native SMTP LOGIN auth mech +- notmuch: show originating folder in index + +### 🐞 Bug Fixes + +- sidebar: prevent the divider colour bleeding out +- sidebar: fix `` +- notmuch: fix `` query for current email +- restore shutdown-hook functionality +- crash in reply-to +- user-after-free in `folder-hook` +- fix some leaks +- fix application of limits to modified mailboxes +- write Date header when postponing + +### 🏁 Translations + +- 100% 🇱🇹 Lithuanian +- 100% 🇨🇿 Czech +- 70% 🇹🇷 Turkish + +### 📚 Docs + +- Document that `$alias_sort` affects the query menu + +### 🏗 Build + +- improve ASAN flags +- add SASL and S/MIME to `--everything` +- fix contrib (un)install + +### ⚙️ Code + +- `my-header` compose screen notifications +- add contracts to the MXAPI +- maildir refactoring +- further reduce the use of global variables + +### ♻️ Upstream + +- Add `$count_alternatives` to count attachments inside alternatives + +
+
+NeoMutt 2020-09-25 + +## 2020-09-25 Richard Russon \ + +### 🎁 Features + +- Compose: display user-defined headers +- Address Book / Query: live sorting +- Address Book / Query: patterns for searching +- Config: Add `+=` and `-=` operators for String Lists +- Config: Add `+=` operator for Strings +- Allow postfix query `:setenv NAME?` for env vars + +### 🐞 Bug Fixes + +- Fix crash when searching with invalid regexes +- Compose: Prevent infinite loop of `send2-hook`s +- Fix sidebar on new/removed mailboxes +- Restore indentation for named mailboxes +- Prevent half-parsing an alias +- Remove folder creation prompt for POP path +- Show error if `$message_cachedir` doesn't point to a valid directory +- Fix tracking LastDir in case of IMAP paths with Unicode characters +- Make sure all mail gets applied the index limit +- Add warnings to -Q query CLI option +- Fix index tracking functionality + +### 🔧 Changed Config + +- Add `$compose_show_user_headers` (yes) + +### 🏁 Translations + +- 100% 🇨🇿 Czech +- 100% 🇱🇹 Lithuanian +- Split up usage strings + +### 🏗 Build + +- Run shellcheck on `hcachever.sh` +- Add the Address Sanitizer +- Move compose files to lib under compose/ +- Move address config into libaddress +- Update to latest acutest - fixes a memory leak in the unit tests + +### ⚙️ Code + +- Implement ARRAY API +- Deglobalised the Config Sort functions +- Refactor the Sidebar to be Event-Driven +- Refactor the Color Event +- Refactor the Commands list +- Make `ctx_update_tables()` private +- Reduce the scope/deps of some Validator functions +- Use the Email's IMAP UID instead of an increasing number as index +- debug: log window focus + +
+
+NeoMutt 2020-08-21 + +## 2020-08-21 Richard Russon \ + +### 🐞 Bug Fixes + +- fix maildir flag generation +- fix query notmuch if file is missing +- notmuch: don't abort sync on error +- fix type checking for send config variables + +### 🔧 Changed Config + +- `$sidebar_format` - Use `%D` rather than `%B` for named mailboxes + +### 🏁 Translations + +- 96% 🇱🇹 Lithuanian +- 90% 🇵🇱 Polish + +
+
+NeoMutt 2020-08-14 + +## 2020-08-14 Richard Russon \ + +### 🔒 Security + +- Add mitigation against DoS from thousands of parts + +### 🎁 Features + +- Allow index-style searching in postpone menu +- Open NeoMutt using a mailbox name +- Add `cd` command to change the current working directory +- Add tab-completion menu for patterns +- Allow renaming existing mailboxes +- Check for missing attachments in alternative parts +- Add one-liner docs to config items + +### 🐞 Bug Fixes + +- Fix logic in checking an empty From address +- Fix Imap crash in `cmd_parse_expunge()` +- Fix setting attributes with S-Lang +- Fix: redrawing of `$pager_index_lines` +- Fix progress percentage for syncing large mboxes +- Fix sidebar drawing in presence of indentation + named mailboxes +- Fix retrieval of drafts when `$postponed` is not in the mailboxes list +- Do not add comments to address group terminators +- Fix alias sorting for degenerate addresses +- Fix attaching emails +- Create directories for nonexistent file hcache case +- Avoid creating mailboxes for failed subscribes +- Fix crash if rejecting cert + +### 🔧 Changed Config + +- Add `$copy_decode_weed`, `$pipe_decode_weed`, `$print_decode_weed` +- Change default of `$crypt_protected_headers_subject` to "..." +- Add default keybindings to history-up/down + +### 🏁 Translations + +- 100% 🇨🇿 Czech +- 100% 🇪🇸 Spanish + +### 🏗 Build + +- Allow building against Lua 5.4 +- Fix when `sqlite3.h` is missing + +### 📚 Docs + +- Add a brief section on stty to the manual +- Update section "Terminal Keybindings" in the manual +- Clarify PGP Pseudo-header `S` duration + +### ⚙️ Code + +- Clean up String API +- Make the Sidebar more independent +- De-centralise the Config Variables +- Refactor dialogs +- Refactor: Help Bar generation +- Make more APIs Context-free +- Adjust the edata use in Maildir and Notmuch +- Window refactoring +- Convert libsend to use Config functions +- Refactor notifications to reduce noise +- Convert Keymaps to use STAILQ +- Track currently selected email by msgid +- Config: no backing global variable +- Add events for key binding + +### ♻️ Upstream + +- Fix imap postponed mailbox use-after-free error +- Speed up thread sort when many long threads exist +- Fix ~v tagging when switching to non-threaded sorting +- Add message/global to the list of known "message" types +- Print progress meter when copying/saving tagged messages +- Remove ansi formatting from autoview generated quoted replies +- Change postpone mode to write Date header too +- Unstuff `format=flowed` + +
+
+NeoMutt 2020-08-07 (Do not use) + +## 2020-08-07 Richard Russon \ + +### ⚠️ Devel Release - see 2020-08-14 + +
+
+NeoMutt 2020-06-26 + +## 2020-06-26 Richard Russon \ + +### 🐞 Bug Fixes + +- Avoid opening the same hcache file twice +- Re-open Mailbox after `folder-hook` +- Fix the matching of the `$spool_file` Mailbox +- Fix link-thread to link all tagged emails + +### 🔧 Changed Config + +- Add `$tunnel_is_secure` config, defaulting to true + +### ♻️ Upstream + +- Don't check IMAP PREAUTH encryption if `$tunnel` is in use +- Add recommendation to use `$ssl_force_tls` + +
+
+NeoMutt 2020-06-19 + +## 2020-06-19 Richard Russon \ + +### 🔒 Security + +- Abort GnuTLS certificate check if a cert in the chain is rejected +- TLS: clear data after a starttls acknowledgement +- Prevent possible IMAP MITM via PREAUTH response + +### 🎁 Features + +- add config operations `+=`/`-=` for number,long +- Address book has a comment field +- Query menu has a comment field + +### ✨ Contrib + +- sample.neomuttrc-starter: Do not echo prompted password + +### 🐞 Bug Fixes + +- make "news://" and "nntp://" schemes interchangeable +- Fix CRLF to LF conversion in base64 decoding +- Double comma in query +- compose: fix redraw after history +- Crash inside empty query menu +- mmdf: fix creating new mailbox +- mh: fix creating new mailbox +- mbox: error out when an mbox/mmdf is a pipe +- Fix `` by correct parsing of List-Post headers +- Decode references according to RFC2047 +- fix tagged message count +- hcache: fix keylen not being considered when building the full key +- sidebar: fix path comparison +- Don't mess with the original pattern when running IMAP searches +- Handle IMAP "NO" resps by issuing a msg instead of failing badly +- imap: use the connection delimiter if provided +- Memory leaks + +### 🔧 Changed Config + +- `$alias_format` default changed to include `%c` comment +- `$query_format` default changed to include `%e` extra info + +### 🏁 Translations + +- 100% 🇱🇹 Lithuanian +- 84% 🇫🇷 French +- Log the translation in use + +### 📚 Docs + +- Add missing commands unbind, unmacro to man pages + +### 🏗 Build + +- Check size of long using `LONG_MAX` instead of `__WORDSIZE` +- Allow ./configure to not record cflags +- fix out-of-tree build +- Avoid locating gdbm symbols in qdbm library + +### ⚙️ Code + +- Refactor unsafe TAILQ returns +- add window notifications +- flip negative ifs +- Update to latest `acutest.h` +- test: add store tests +- test: add compression tests +- graphviz: email +- make more opcode info available +- refactor: `main_change_folder()` +- refactor: `mutt_mailbox_next()` +- refactor: `generate_body()` +- compress: add `{min,max}_level` to ComprOps +- emphasise empty loops: "// do nothing" +- prex: convert `is_from()` to use regex +- Refactor IMAP's search routines + +
+
+NeoMutt 2020-05-01 + +## 2020-05-01 Richard Russon \ + +### 🐞 Bug Fixes + +- Make sure buffers are initialized on error +- fix(sidebar): use abbreviated path if possible + +### 🏁 Translations + +- 100% 🇱🇹 Lithuanian + +### 📚 Docs + +- make header cache config more explicit + +
+
+NeoMutt 2020-04-24 + +## 2020-04-24 Richard Russon \ + +### 🐞 Bug Fixes + +- Fix history corruption +- Handle pretty much anything in a URL query part +- Correctly parse escaped characters in header phrases +- Fix crash reading received header +- Fix sidebar indentation +- Avoid crashing on failure to parse an IMAP mailbox +- Maildir: handle deleted emails correctly +- Ensure OP_NULL is always first + +### 🏁 Translations + +- 100% 🇨🇿 Czech + +### 🏗 Build + +- cirrus: enable pcre2, make pkgconf a special case +- Fix finding pcre2 w/o pkgconf +- build: `tdb.h` needs size_t, bring it in with `stddef.h` + +
+
+NeoMutt 2020-04-17 + +## 2020-04-17 Richard Russon \ + +### 🎁 Features + +- Fluid layout for Compose Screen, see: https://vimeo.com/407231157 +- Trivial Database (TDB) header cache backend +- RocksDB header cache backend +- Add `` and `` functions + +### 🐞 Bug Fixes + +- add error for CLI empty emails +- Allow spaces and square brackets in paths +- browser: fix hidden mailboxes +- fix initial email display +- notmuch: fix time window search. +- fix resize bugs +- notmuch: fix ``: update current email pointer +- sidebar: support indenting and shortening of names +- Handle variables inside backticks in `sidebar-pin` +- browser: fix mask regex error reporting + +### 🏁 Translations + +- 100% 🇱🇹 Lithuanian +- 99% 🇨🇳 Chinese (Simplified) + +### 🏗 Build + +- Use regexes for common parsing tasks: urls, dates +- Add configure option `--pcre2` -- Enable PCRE2 regular expressions +- Add configure option `--tdb` -- Use TDB for the header cache +- Add configure option `--rocksdb` -- Use RocksDB for the header cache +- Create libstore (key/value backends) +- Update to latest autosetup +- Update to latest `acutest.h` +- Rename `doc/` directory to `docs/` +- make: fix location of .Po dependency files +- Change libcompress to be more universal +- Fix test fails on х32 +- fix uidvalidity to unsigned 32-bit int + +### ⚙️ Code + +- Increase test coverage +- Fix memory leaks +- Fix null checks + +### ♻️ Upstream + +- Buffer refactoring +- Fix use-after-free in `mutt_str_replace()` +- Clarify PGP Pseudo-header `S` duration +- Try to respect MUTT_QUIET for IMAP contexts too +- Limit recurse depth when parsing mime messages + +
+
+NeoMutt 2020-03-20 + +## 2020-03-20 Richard Russon \ + +### 🐞 Bug Fixes + +- Fix COLUMNS env var +- Fix sync after delete +- Fix crash in notmuch +- Fix sidebar indent +- Fix emptying trash +- Fix command line sending +- Fix reading large address lists +- Resolve symlinks only when necessary + +### 🏁 Translations + +- 100% 🇱🇹 Lithuanian +- 96% 🇪🇸 Spanish + +### 📚 Docs + +- Include OpenSSL/LibreSSL/GnuTLS version in neomutt -v output +- Fix case of GPGME and SQLite + +### 🏗 Build + +- Create libcompress (lz4, zlib, zstd) +- Create libhistory +- Create libbcache +- Move zstrm to libconn + +### ⚙️ Code + +- Add more test coverage +- Rename magic to type +- Use `mutt_file_fopen()` on config variables +- Change commands to use intptr_t for data + +
+
+NeoMutt 2020-03-13 + +## 2020-03-13 Richard Russon \ + +### 🎁 Features + +- UI: add number of old messages to `$sidebar_format` +- UI: support ISO 8601 calendar date +- UI: fix commands that don’t need to have a non-empty mailbox to be valid +- PGP: inform about successful decryption of inline PGP messages +- PGP: try to infer the signing key from the From address +- PGP: enable GPGME by default +- Notmuch: use query as name for `` +- IMAP: add network traffic compression (COMPRESS=DEFLATE, RFC4978) +- Header cache: add support for generic header cache compression + +### 🐞 Bug Fixes + +- Fix `$uncollapse_jump` +- Only try to perform `` on maildir/mh mailboxes +- Fix crash in pager +- Avoid logging single new lines at the end of header fields +- Fix listing mailboxes +- Do not recurse a non-threaded message +- Fix initial window order +- Fix leaks on IMAP error paths +- Notmuch: compose(attach-message): support notmuch backend +- Fix IMAP flag comparison code +- Fix `$move` for IMAP mailboxes +- Maildir: `maildir_mbox_check_stats()` should only update mailbox stats if requested +- Fix unmailboxes for virtual mailboxes +- Maildir: sanitize filename before hashing +- OAuth: if 'login' name isn't available use 'user' +- Add error message on failed encryption +- Fix a bunch of crashes +- Force C locale for email date +- Abort if run without a terminal + +### 🔧 Changed Config + +- `$crypt_use_gpgme` - Now defaults to 'yes' (enabled) +- `$abort_backspace` - Hitting backspace against an empty prompt aborts the prompt +- `$abort_key` - String representation of key to abort prompts +- `$arrow_string` - Use a custom string for `$arrow_cursor` +- `$crypt_opportunistic_encrypt_strong_keys` - Enable encryption only when strong a key is available +- `$header_cache_compress_dictionary` - Filepath to dictionary for zstd compression +- `$header_cache_compress_level` - Level of compression for method +- `$header_cache_compress_method` - Enable generic hcache database compression +- `$imap_deflate` - Compress network traffic +- `$smtp_user` - Username for the SMTP server + +### 🏁 Translations + +- 100% 🇱🇹 Lithuanian +- 81% 🇪🇸 Spanish +- 78% 🇷🇺 Russian + +### 🏗 Build + +- Add libdebug +- Rename public headers to `lib.h` +- Create libcompress for compressed folders code +- Enable Cirrus CI for FreeBSD + +### ⚙️ Code + +- Refactor Windows and Dialogs +- Lots of code tidying +- Refactor: `mutt_addrlist_{search,write}()` +- Lots of improvements to the Config code +- Use Buffers more pervasively +- Unify API function naming +- Rename library shared headers +- Refactor libconn gui dependencies +- Refactor: init.[ch] +- Refactor config to use subsets +- Config: add path type +- Remove backend deps from the connection code + +### ♻️ Upstream + +- Allow ~b ~B ~h patterns in send2-hook +- Rename smime oppenc mode parameter to `get_keys_by_addr()` +- Add `$crypt_opportunistic_encrypt_strong_keys` config var +- Fix crash when polling a closed ssl connection +- Turn off auto-clear outside of autocrypt initialization +- Add protected-headers="v1" to Content-Type when protecting headers +- Fix segv in IMAP postponed menu caused by reopen_allow +- Adding ISO 8601 calendar date +- Fix `$fcc_attach` to not prompt in batch mode +- Convert remaining `mutt_encode_path()` call to use struct Buffer +- Fix rendering of replacement_char when Charset_is_utf8 +- Update to latest `acutest.h` + +
+
+NeoMutt 2019-12-07 + +## 2019-12-07 Richard Russon \ + +### 🎁 Features + +- compose: draw status bar with highlights + +### 🐞 Bug Fixes + +- crash opening notmuch mailbox +- crash in `mutt_autocrypt_ui_recommendation()` +- Avoid negative allocation +- Mbox new mail +- Setting of DT_MAILBOX type variables from Lua +- imap: empty cmdbuf before connecting +- imap: select the mailbox on reconnect +- compose: fix attach message + +### 🏗 Build + +- make files conditional +- add gpgme check for RHEL6 + +### ⚙️ Code + +- enum-ify log levels +- fix function prototypes +- refactor virtual email lookups +- factor out global Context + +
+
+NeoMutt 2019-11-29 + +## 2019-11-29 Richard Russon \ + +### 🎁 Features + +- Add raw mailsize expando (%cr) + +### 🐞 Bug Fixes + +- Avoid double question marks in bounce confirmation msg +- Fix bounce confirmation +- fix new-mail flags and behaviour +- fix: browser `` +- fix ssl crash +- fix move to trash +- fix flickering +- Do not check hidden mailboxes for new mail +- Fix `$new_mail_command` notifications +- fix crash in `examine_mailboxes()` +- fix crash in `mutt_sort_threads()` +- fix: crash after sending +- Fix crash in tunnel's `conn_close()` +- fix fcc for deep dirs +- imap: fix crash when new mail arrives +- fix colour 'quoted9' +- quieten messages on exit +- fix: crash after failed `mbox_check()` +- browser: default to a file/dir view when attaching a file + +### 🔧 Changed Config + +- Change `$write_bcc` to default off + +### 🏁 Translations + +- 100% 🇧🇷 Portuguese (Brazil) +- 92% 🇵🇱 Polish + +### 📚 Docs + +- Add a bit more documentation about sending +- Clarify `$write_bcc` documentation. +- Update documentation for raw size expando +- docbook: set generate.consistent.ids to make generated html reproducible + +### 🏗 Build + +- fix build/tests for 32-bit arches +- tests: fix test that would fail soon +- tests: fix context for failing idna tests + +
+
+NeoMutt 2019-11-11 + +## 2019-11-11 Richard Russon \ + +### 🐞 Bug Fixes + +- browser: fix directory view +- fix crash in `parse_extract_token()` +- force a screen refresh +- fix crash sending message from command line +- notmuch: use `$nm_default_url` if no mailbox data +- fix forward attachments +- fix: vfprintf undefined behaviour in `body_handler()` +- Fix relative symlink resolution +- fix: trash to non-existent file/dir +- fix re-opening of mbox Mailboxes +- close logging as late as possible +- log unknown mailboxes +- fix crash in command line postpone +- fix memory leaks +- fix icommand parsing +- fix new mail interaction with `$mail_check_recent` + +
+
+NeoMutt 2019-11-02 + +## 2019-11-02 Richard Russon \ + +### 🐞 Bug Fixes + +- Mailboxes command with empty backticks +- Mbox save-to-trash +- Mkdir for new maildir folders +- Maildir: new mail detection +- Truncation of "set" command on a path variable +- Update crash (when changing folder) +- Resolve symbolic links when saving a message +- Folder-hook calling `unmailboxes *` +- Failed ssl negotiation +- Crash when using "alias -group" +- LibIDN error when `$charset` wasn't set +- Notmuch abort `` if database lacks message + +
+
+NeoMutt 2019-10-25 + +## 2019-10-25 Richard Russon \ + +### 🎁 Features + +- Add `$fcc_before_send`, defaulting unset +- Deprecate TLS 1.0 and 1.1 by default +- Turn on `$ssl_force_tls` by default +- Command line -z and -Z options to work with IMAP +- Add size display configuration variables +- Summary pages: version, set, set all, bind, macro +- CONDSTORE and QRESYNC support +- OAUTHBEARER support +- inotify support +- add index-format-hook +- Add `$auto_subscribe` variable +- Allow relative date hour/min/sec offsets +- Add attributes support on color declarations +- Style Menu Options +- Add new pattern type ~I for external searches +- Add `` command + +### 🔧 Changed Config + +- `$folder_format` +- `$pgp_use_gpg_agent` +- `$shell` +- `$ssl_force_tls` +- `$ssl_use_tlsv1` +- `$ssl_use_tlsv1_1` +- `$status_format` +- `$to_chars` +- `$user_agent` + +### 🔧 New Config + +- `$attach_save_dir` +- `$attach_save_without_prompting` +- `$autocrypt` +- `$autocrypt_acct_format` +- `$autocrypt_dir` +- `$autocrypt_reply` +- `$auto_subscribe` +- `$crypt_chars` +- `$crypt_protected_headers_read` +- `$crypt_protected_headers_save` +- `$crypt_protected_headers_subject` +- `$crypt_protected_headers_write` +- `$external_search_command` +- `$fcc_before_send` +- `$forward_attachments` +- `$imap_condstore` +- `$imap_fetch_chunk_size` +- `$imap_oauth_refresh_command` +- `$imap_qresync` +- `$imap_rfc5161` +- `$include_encrypted` +- `$nm_flagged_tag` +- `$nm_replied_tag` +- `$pop_oauth_refresh_command` +- `$sidebar_non_empty_mailbox_only` +- `$size_show_bytes` +- `$size_show_fractions` +- `$size_show_mb` +- `$size_units_on_left` +- `$smtp_oauth_refresh_command` +- `$ssl_use_tlsv1_3` + +### 🔧 New Commands + +- `:index-format-hook` +- `:named-mailboxes` +- `:unbind` +- `:unmacro` + +### 🔧 New Functions + +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` + +### 🐞 Bug Fixes + +- Fix crashes +- Fix memory leaks +- Fix undefined behaviour +- Fix coverity defects + +### 🏁 Translations + +- 100% 🇱🇹 Lithuanian +- 100% 🇨🇳 Chinese (Simplified) +- 100% 🇧🇷 Portuguese (Brazil) +- 95% 🇩🇪 German +- 95% 🇫🇮 Finnish +- 95% 🇨🇿 Czech +- 91% 🇵🇱 Polish +- 78% 🇯🇵 Japanese +- 73% 🇳🇱 Dutch +- 72% 🇪🇸 Spanish +- 62% 🇸🇪 Swedish +- 55% 🇸🇰 Slovak + +### 📚 Docs + +- OpenPGP and S/MIME configuration +- Quick-starter config section +- Autocrypt feature +- "Message Composition Flow" section to manual +- OAUTH support + +
+
+NeoMutt 2018-07-16 + +## 2018-07-16 Richard Russon \ + +### 🎁 Features + +- `` function + +### 🐞 Bug Fixes + +- Lots + +
+
+NeoMutt 2018-06-22 + +## 2018-06-22 Richard Russon \ + +### 🎁 Features + +- Expand variables inside backticks +- Honour SASL-IR IMAP capability in SASL PLAIN + +### 🐞 Bug Fixes + +- Fix `` +- Do not truncate shell commands on ; or # +- pager: index must be rebuilt on MUTT_REOPENED +- Handle a BAD response in AUTH PLAIN w/o initial response +- fcc_attach: Don't ask every time +- Enlarge path buffers PATH_MAX (4096) +- Move LSUB call from connection establishment to mailbox SELECTion + +### 🏁 Translations + +- 100% 🇨🇳 Chinese (Simplified) +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇱🇹 Lithuanian +- 100% 🇧🇷 Portuguese (Brazil) +- 59% 🇸🇰 Slovak +- Reduce duplication of messages + +### ⚙️ Code + +- Tidy up the mailbox API +- Tidy up the header cache API +- Tidy up the encryption API +- Add doxygen docs for more functions +- Refactor more structs to use STAILQ + +
+
+NeoMutt 2018-05-12 + +## 2018-05-12 Richard Russon \ + +### 🎁 Features + +- echo command +- Add `$browser_abbreviate_mailboxes` +- Add ~M pattern to match mime Content-Types +- Add support for multipart/multilingual emails +- Jump to a collapsed email +- Add support for idn2 (IDNA2008) + +### 🐞 Bug Fixes + +- Let `mutt_ch_choose()` report conversion failure +- minor IMAP string handling fixes + +### 🏁 Translations + +- 100% 🇨🇳 Chinese (Simplified) +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇧🇷 Portuguese (Brazil) +- 62% 🇱🇹 Lithuanian + +### ⚙️ Coverity defects + +- match prototypes to their functions +- make logic clearer +- reduce scope of variables +- fix coverity defects + +### 📚 Docs + +- development: analysis +- development: easy tasks +- development: roadmap + +### ⚙️ Code + +- start refactoring libconn +- split out progress functions +- split out window functions +- split out terminal setting +- convert MyVars to use TAILQ +- split `mutt_file_{lock,unlock}()` +- Move IDN version string to `mutt/idna.c` +- refactor: `init_locale()` +- Eliminate static variable in `mutt_file_dirname()` + +### ⚙️ Tidy + +- test int functions against 0 +- rename lots of constants +- rename lots of functions +- sort lots of fields/definitions + +### ♻️ Upstream + +- Increase account.user/login size to 128 +- Fix comparison of flags with multiple bits set +- Change `mutt_error()` call in `mutt_gpgme_set_sender()` to dprint +- Improve the error message when a signature is missing +- pager specific "show incoming mailboxes list" macro +- Improve gss debug printing of status_string +- Remove trailing null count from gss_buffer_desc.length field +- Add a comment in auth_gss about RFCs and NUL-termination +- Change prompt string for `$crypt_verify_sig` + +
+
+NeoMutt 2018-03-23 + +## 2018-03-23 Richard Russon \ + +### 🎁 Features + +- unify logging/messaging +- add alert (blink) colors + +### ✨ Contrib + +- Vim syntax for NeoMutt log files + +### 🐞 Bug Fixes + +- Fix progress bar range +- notmuch: stop if db open fails +- Improve index color cache flushing behavior +- lua: fix crash when setting a string + +### 🏁 Translations + +- 100% 🇨🇿 Czech +- 100% 🇩🇪 German +- 100% 🇧🇷 Portuguese (Brazil) +- 94% 🇵🇱 Polish +- 75% 🇹🇷 Turkish +- 64% 🇪🇸 Spanish +- Merge similar messages + +### 📚 Docs + +- Clarify precedence of settings in config files +- Fix subjectrx example in the manual + +### 🔗 Website + +- Update Gentoo distro page +- Devel: Static analysis + +### 🏗 Build + +- Support —with-sysroot configure arg +- Expose EXTRA_CFLAGS_FOR_BUILD and EXTRA_LDFLAGS_FOR_BUIlD +- Update to latest autosetup +- Make sure `git_ver.h` doesn't eat random 'g's out of tag names + +### ⚙️ Code + +- Refactor to reduce complexity +- Refactor to reduce variables' scope +- Sort functions/config to make docs more legible + +
+
+NeoMutt 2018-02-23 + +## 2018-02-23 Richard Russon \ + +### 🎁 Features + +- browser: `` function bound to "p" +- editor: `` function bound to "Ctrl-r" +- Cygwin support: https://neomutt.org/distro/cygwin +- openSUSE support: https://neomutt.org/distro/suse +- Upstream Homebrew support: Very soon - https://neomutt.org/distro/homebrew + +### 🐞 Bug Fixes + +- gmail server-size search +- nested-if: correctly handle "\<" and "\>" with %? +- display of special chars +- lua: enable myvars +- for pgpewrap in default gpg.rc +- `$reply_regexp` which wasn't formatted correctly. +- parsing of urls containing '?' +- out-of-bounds read in `mutt_str_lws_len()` + +### 🏁 Translations + +- Review fuzzy lt translations +- Updated French translation + +### 🔗 Website + +- Installation guide for Cygwin +- Installation guide for openSUSE +- Installation guide for CRUX + +### 🏗 Build + +- check that DTDs are installed +- autosetup improvements +- option for which version of bdb to use +- drop test for resizeterm -- it's always present + +### ⚙️ Code + +- split if's containing assignments +- doxygen: add/improve comments +- rename functions / parameters for consistency +- add missing {}s for clarity +- move functions to library +- reduce scope of variables +- boolify more variables +- iwyu: remove unnecessary headers +- name unicode chars +- tailq: migrate parameter api +- md5: refactor and tidy +- rfc2047: refactor and tidy +- buffer: improvements +- create unit test framework +- fix several coverity defects + +### ♻️ Upstream + +- Fix s/mime certificate deletion bug +- Disable message security if the backend is not available +- Fix improper signed int conversion of IMAP uid and msn values +- Change imap literal counts to parse and store unsigned ints +- Fix imap status count range check +- `cmd_handle_fatal()`: make error message a bit more descriptive +- Create pgp and s/mime default and sign_as key vars +- Add missing setup calls when resuming encrypted drafts +- `mutt_pretty_size()`: show real number for small files +- `examine_directory()`: set directory/symlink size to zero +- Add `` function, bound to ctrl-r +- Avoid a potential integer overflow if a Content-Length value is huge + +
+
+NeoMutt 2017-12-15 + +## 2017-12-15 Richard Russon \ + +### 🐞 Bug Fixes + +- Fix some regressions in the previous release + +
+
+NeoMutt 2017-12-08 + +## 2017-12-08 Richard Russon \ + +### 🎁 Features + +- Enhance ifdef feature to support my_ vars +- Add `` +- Remove vim syntax file from the main repo +- Support reading FQDN from mailname files + +### 🐞 Bug Fixes + +- Do not turn CRLF into LF when dealing with transfer-encoding=base64 +- Cleanup "SSL is unavailable" error in mutt_conn_find +- Don't clear the macro buffer during startup +- Fixup smart `` for !tag case +- Add sleep after SMTP error +- Restore folder settings after `folder-hook` +- Fix segfault when pipe'ing a deleted message + +### 📚 Docs + +- Display_filter escape sequence +- Correct spelling mistakes +- Add a sentence to `` docs +- Modify gpg.rc to accommodate GPG 2.1 changes + +### 🏗 Build + +- Fix build for RHEL6 +- Define NCURSES_WIDECHAR to require wide-char support from ncurses +- Autosetup: fix check for missing sendmail +- Respect `--with-ssl` path +- Check that OpenSSL md5 supports -r before using it +- Autosetup: expand `--everything` in `neomutt -v` +- Make sure objects are not compiled before `git_ver.h` is generated +- Build: fix update-po target +- Fix out-of-tree builds +- Fix stdout + stderr redirection in `hcachever.sh` +- Build: moved the check for idn before the check for notmuch +- Define prefix in Makefile.autosetup +- Install stuff to $(PACKAGE) in $(libexecdir), not $(libdir) +- Update autosetup to latest master + +### ⚙️ Code + +- Rename files +- Rename functions +- Rename variables +- Rename constants +- Remove unused parameters +- Document functions +- Rearrange functions +- Move functions to libraries +- Add new library functions +- Rearrange switch statements +- Boolification +- Drop #ifdef DEBUG +- Fix Coverity defects +- Insert braces +- Split ifs +- Fallthrough +- Fix shadow variable +- Replace mutt_debug with a macro +- Return early where possible + +### ♻️ Upstream + +- Note which ssl config vars are GnuTLS or OpenSSL only +- Add message count to `$move` quadoption prompt +- Add %R (number of read messages) for `$status_format` +- Add `$change_folder_next` option to control mailbox suggestion order +- Fix `$smart_wrap` to not be disabled by whitespace-prefixed lines +- Remove useless else branch in the `$smart_wrap` code +- Fix ansi escape sequences with both reset and color parameters + +
+
+NeoMutt 2017-10-27 + +## 2017-10-27 Richard Russon \ + +### 🐞 Bug Fixes + +- variable type when using fread +- prevent timezone overflow +- tags: Show fake header for all backends +- notmuch: virtual-mailboxes should accept a limit +- Issue 888: Fix imap mailbox flag logging +- fix actions on tagged messages +- call the `folder-hook` before saving to `$record` +- Fix smart wrap in pager without breaking header +- Add polling for the IDLE command + +### 📚 Docs + +- imap/notmuch tags: Add some documentation +- English and other cleanups +- compressed and nntp features are now always built + +### 🔗 Website + +- Update Arch instructions + +### 🏗 Build + +- Fix update-po +- Fix neomutt.pot location, remove from git +- Allow to specify `--docdir` at configure time +- Generate neomuttrc even if configured with `--disable-doc` +- Let autosetup define PWD, do not unnecessarily try to create hcache dir +- Use bundled wcscasecmp if an implementation is not found in libc +- Use host compiler to build the documentation +- Update autosetup to latest master branch +- autosetup: delete makedoc on 'make clean' +- Fixes for endianness detection +- Update autosetup to latest master branch +- Do not use CPPFLAGS / CFLAGS together with CC_FOR_BUILD +- `--enable-everything` includes lua +- autosetup: check for sys_siglist[] + +### ⚙️ Code + +- move functions to library +- lib: move MIN/MAX macros +- simplify null checks +- kill preproc expansion laziness +- reduce scope of variables +- merge: minor code cleanups +- split up 'if' statements that assign and test +- Refactor: Remove unused return type +- Bool: change functions in `mx.h` +- bool: convert function parameters in `nntp.h` +- add extra checks to `mutt_pattern_exec()` +- Use safe_calloc to initialize memory, simplify size_t overflow check +- Move `mutt_rename_file()` to lib/file.[hc] +- doxygen: fix a few warnings +- minor code fixes +- use `mutt_array_size()` +- refactor out O_NOFOLLOW +- initialise variables +- lib: move List and Queue into library +- url: make notmuch query string parser generic +- Wrap dirname(3) inside a `mutt_dirname()` function + +
+
+NeoMutt 2017-10-13 + +## 2017-10-13 Richard Russon \ + +### 🐞 Bug Fixes + +- crash using uncolor +- Sort the folders list when browsing an IMAP server +- Prefer a helpful error message over a BEEP + +### 🏗 Build + +- Do not fail if deflate is not in libz +- Support EXTRA_CFLAGS and EXTRA_LDFLAGS, kill unused variable + +
+
+NeoMutt 2017-10-06 + +## 2017-10-06 Richard Russon \ + +### 🎁 Features + +- Add IMAP keywords support + +### 🐞 Bug Fixes + +- set mbox_type +- %{fmt} date format +- Fix off-by-one buffer overflow in add_index_color +- crash in `mbox_to_udomain()` +- crash in `mutt_substrdup()` +- crash looking up mime body type +- `$digest_collapse` was broken +- crash using notmuch expando with imap +- imap: Fix mx.mbox leak in `imap_get_parent_path()` +- overflow in `mutt_mktime()` +- add more range-checking on dates/times +- Remove spurious error message +- Unsubscribe after deleting an imap folder +- Do not pop from MuttrcStack what wasn't pushed + +### 📚 Docs + +- replace mutt refs with neomutt +- drop old vim syntax file + +### ⚙️ Code + +- convert functions to use 'bool' +- convert structs to use STAILQ + +### 🏗 Build + +- Autosetup-based configuration +- drop upstream mutt references +- rename everything 'mutt' to 'neomutt' +- move helper programs to lib dir +- rename regexp to regex +- expand buffers to avoid gcc7 warnings + +### ♻️ Upstream + +- Remove \Seen flag setting for imap trash +- Change imap copy/save and trash to sync flags, excluding deleted +- Improve imap fetch handler to accept an initial UID +- Display an error message when delete mailbox fails +- Updated French translation +- Fix imap sync segfault due to inactive headers during an expunge +- Close the imap socket for the selected mailbox on error +- Add missing IMAP_CMD_POLL flag in imap mailbox check +- Change maildir and mh check_mailbox to use dynamic sized hash +- Fix uses of `context->changed` as a counter +- Make `cmd_parse_fetch()` more precise about setting reopen/check flags +- Enable `$reply_self` for `` even with $me_too unset + +
+
+NeoMutt 2017-09-12 + +## 2017-09-12 Richard Russon \ + +### 🐞 Bug Fixes + +- broken check on resend message +- crash in `` + +### 🏗 Build + +- Be more formal about quoting in m4 macros +- fix warnings raised by gcc7 +- notmuch: add support for the v5 API + +
+
+NeoMutt 2017-09-07 + +## 2017-09-07 Richard Russon \ + +### ✨ Contrib + +- Add guix build support + +### 🐞 Bug Fixes + +- Only match real mailboxes when looking for new mail +- Fix the printing of ncurses version in -v output +- Bind editor `` to `` +- Fix overflowing colours +- Fix empty In-Reply-To generation +- Trim trailing slash from completed dirs +- Add guix-neomutt.scm +- Fix setting custom query_type in notmuch query + +### 🔗 Website + +- New technical documentation LINK +- Improve Gentoo distro page + +### 🏗 Build + +- Better curses identification +- Use the system's wchar_t support +- Use the system's md5 tool (or equivalent) +- Clean up configure.ac +- Teach gen-map-doc about the new opcode header + +### ⚙️ Source + +- Rename functions (snake_case) +- Rename constants/defines (UPPER_CASE) +- Create library of shared functions +- Much tidying +- Rename globals to match user config +- Drop unnecessary functions/macros +- Use a standard list implementation +- Coverity fixes +- Use explicit NUL for string terminators +- Drop OPS\* in favour of `opcodes.h` + +### ♻️ Upstream + +- Fix menu color calls to occur before positioning the cursor +- When guessing an attachment type, don't allow text/plain if there is a null character +- Add `$imap_poll_timeout` to allow mailbox polling to time out +- Handle error if REGCOMP in pager fails when resizing +- Change recvattach to allow nested encryption +- Fix attachment check_traditional and extract_keys operations +- Add edit-content-type helper and warning for decrypted attachments +- Add option to run command to query attachment mime type +- Add warning about using inline pgp with format=flowed + +
+
+NeoMutt 2017-07-14 + +## 2017-07-14 Richard Russon \ + +### 🏁 Translations + +- Update German translation + +### 📚 Docs + +- compile-time output: use two lists +- doxygen: add config file +- doxygen: tidy existing comments + +### 🏗 Build + +- fix `hcachever.sh` script + +### ♻️ Upstream + +- Fix crash when `$postponed` is on another server. + +
+
+NeoMutt 2017-07-07 + +## 2017-07-07 Richard Russon \ + +### 🎁 Features + +- Support Gmail's X-GM-RAW server-side search +- Include pattern for broken threads +- Allow sourcing of multiple files + +### ✨ Contrib + +- vombatidae colorscheme +- zenburn colorscheme +- black 256 solarized colorscheme +- neonwolf colorscheme +- Mutt logos + +### 🐞 Bug Fixes + +- flags: update the hdr message last +- gpgme S/MIME non-detached signature handling +- menu: the thread tree color +- Uses CurrentFolder to populate LastDir with IMAP +- stabilise sidebar sort order +- colour emails with a '+' in them +- the padding expando `%>` +- Do not set old flag if `$mark_old` is false +- maildir creation +- Decode CRLF line endings to LF when copying headers +- score address pattern do not match personal name +- open attachments in read-only mode +- Add Cc, In-Reply-To, and References to default mailto-allow +- Improve search for mime.types + +### 🏁 Translations + +- Update Chinese (Simplified) translation + +### ⚙️ Coverity defects + +- dodgy buffers +- leaks in lua get/set options +- some resource leaks + +### 📚 Docs + +- update credits +- limitations of new-mail %f expando +- escape \<\>'s in nested conditions +- add code of conduct +- fix ifdef examples +- update mailmap +- Update `` +- fix mailmap +- drop UPDATING files + +### 🔗 Website + +- Changes pages (diff) +- Update Arch distro page +- Update NixOS distro page +- Add new Exherbo distro page +- Update translation hi-score table +- Update code of conduct +- Update Newbies page +- Add page about Rebuilding the Documentation +- Add page of hard problems + +### 🏗 Build + +- remove unnecessary steps +- drop instdoc script +- move smime_keys into contrib +- fixes for Solaris +- don't delete non-existent files +- remove another reference to devel-notes.txt +- Handle native Solaris GSSAPI. +- drop configure options `--enable-exact-address` +- drop configure option `--with-exec-shell` +- drop configure option `--enable-nfs-fix` +- drop configure option `--disable-warnings` +- Completely remove dotlock +- More sophisticated check for BDB version + support for DB6 (non default) + +### ⚙️ Tidy + +- drop VirtIncoming +- split `parse_mailboxes()` into `parse_unmailboxes()` +- tidy some mailbox code +- tidy the version strings + +### ♻️ Upstream + +- Add ~\<() and ~\>() immediate parent/children patterns +- Add L10N comments to the GNUTLS certificate prompt +- Add more description for the %S and %Z `$index_format` characters +- Add config vars for forwarded message attribution intro/trailer +- Block SIGWINCH during connect() +- Improve the L10N comment about Sign as +- Auto-pad translation for the GPGME key selection "verify key" headers +- Enable all header fields in the compose menu to be translated +- Force hard redraw after `$sendmail` instead of calling `mutt_endwin()` +- Make GPGME key selection behavior the same as classic-PGP +- Rename 'sign as' to 'Sign as'; makes compose menu more consistent +- Change the compose menu fields to be dynamically padded + +
+
+NeoMutt 2017-06-09 + +## 2017-06-09 Richard Russon \ + +### ✨ Contrib + +- unbind mappings before overwriting in vim-keys + +### 🐞 Bug Fixes + +- latest coverity issues (#624) +- don't pass colour-codes to filters +- Don't set a colour unless it's been defined. +- crash if no from is set or founds +- ifdef command + +### 🏁 Translations + +- fix translations +- fix some remaining translation problems + +### 📚 Docs + +- explain binding warnings +- don't document unsupported arches + +### 🏗 Build + +- fix make `git_ver.h` +- allow xsltproc and w3m calls to fail +- fix make dist + +### ♻️ Upstream + +- Add a `mutt_endwin()` before invoking `$sendmail` +- Restore setenv function +- Fix `` to not abort on `$timeout` +- Change `km_dokey()` to return -2 on a timeout/sigwinch +- Enable TEXTDOMAINDIR override to make translation testing easier +- Fix "format string is not a string literal" warnings + +
+
+NeoMutt 2017-06-02 + +## 2017-06-02 Richard Russon \ + +### 🎁 Features + +- Warn on bindkey aliasing +- Drop PATCHES, tidy 'mutt -v' output +- Add %z format strings to `$index_format` +- Add `$debug_level`/`$debug_file` options + +### 🐞 Bug Fixes + +- Fix nntp group selection +- Fix status color +- Tidy up S/MIME contrib +- Do not try to create Maildir if it is an NNTP URL +- Fix missing NONULL for `mutt.set()` in Lua + +### 🏁 Translations + +- Fix German PGP shortkeys + +### 📚 Docs + +- Remove feature muttrc files +- Merge README.notmuch into manual +- Remove unneeded scripts +- Remove README.SECURITY +- Remove BEWARE and devel-notes.txt +- Update Makefiles +- Delete TODO files +- Remove legacy files +- Don't generate vim-neomutt syntax file +- Remove LaTeX/pdf manual generation +- Add missing docs for expandos +- Fix sidebar howto examples +- Remove some upstream references +- Drop refs to patches +- Improve PR template and CONTRIBUTING.md + +### 🔗 Website + +- Fix list items in newbie-tutorial's Mailing List Guidelines +- Remove configure options that no longer exist +- fix newbie tutorial +- document signing tags / releases +- config: drop unused paginate command +- script: split tests up into several +- convert credits page to markdown +- simplify 404 page +- improve newbie tutorial +- remove help.html and integrate its content elsewhere +- make: "graphviz" program is needed for generating diagram +- improve getting started guide // include legacy files +- dev: add list of architectures/operating systems +- numerous small fixes + +### 🏗 Build + +- Remove typedefs and rename ~130 structs +- Add separate hcache dir +- Move crypto files to ncrypt dir +- Split up `mutt.h`, `protos.h` +- Always build: sidebar, imap, pop, smtp, compressed, nntp +- Remove `--enable-mailtool` configure option +- Make dotlock optional +- Change gpgme requirement back to 1.1.0 +- Remove `check_sec.sh` +- Fix safe_calloc args +- Remove unused macros +- Remove unused option: SmimeSignOpaqueCommand +- Move configure-generated files +- Update distcheck build flags +- Drop obsolete iconv check +- Unused prototypes - unsupported systems +- Drop many configure tests for things defined in POSIX:2001 +- Kill useless `crypthash.h` file +- Run clang-format on the code +- Fail early if ncursesw cannot be found +- Add names prototype arguments +- Abbreviate pointer tests against NULL +- Initialise pointers to NULL +- Reduce the scope of for loop variables +- Coverity: fix defects + +### ♻️ Upstream + +- Convert all exec calls to use `mutt_envlist()`, remove setenv function +- Note that mbox-hooks are dependent on `$move` +- Refresh header color when updating label +- Remove glibc-specific `execvpe()` call in `sendlib.c` +- Add color commands for the compose menu headers and security status +- Fix sidebar count updates when closing mailbox +- Don't modify LastFolder/CurrentFolder upon aborting a change folder operation +- Change message modifying operations to additively set redraw flags +- Improve maildir and mh to report flag changes in `mx_check_mailbox()` +- Add `$header_color_partial` to allow partial coloring of headers +- Rename REDRAW_SIGWINCH to REDRAW_FLOW +- Create R_PAGER_FLOW config variable flag +- Turn IMAP_EXPUNGE_EXPECTED back off when syncing +- Add `$history_remove_dups` option to remove dups from history ring +- Also remove duplicates from the history file +- Don't filter new entries when compacting history save file +- Move the IMAP msn field to IMAP_HEADER_DATA +- Fix imap expunge to match msn and fix index +- Fix `cmd_parse_fetch()` to match against MSN +- Start fixing `imap_read_headers()` to account for MSN gaps +- Add msn_index and max_msn to find and check boundaries by MSN +- Properly adjust fetch ranges when handling new mail +- Small imap fetch fixes +- Don't abort header cache evaluation when there is a hole +- Fix mfc overflow check and uninitialized variable +- Fix potential segv if mx_open_mailbox is passed an empty string +- Don't clean up idata when closing an open-append mailbox +- Don't clean up msn idata when closing an open-append mailbox +- Fix memory leak when closing mailbox and using the sidebar +- Change imap body cache cleanup to use the uid_hash +- Convert classic s/mime to space delimit findKeys output +- Add self-encrypt options for PGP and S/MIME +- Change `$postpone_encrypt` to use self-encrypt variables first +- Automatic post-release commit for mutt-1.8.3 +- Add note about message scoring and thread patterns + +
+
+NeoMutt 2017-05-26 (Devel) + +## 2017-05-26 Richard Russon \ + +### ⚠️ Development Release + +
+
+NeoMutt 2017-04-28 + +## 2017-04-28 Richard Russon \ + +### 🐞 Bug Fixes + +- Fix and simplify handling of GPGME in configure.ac (@gahr) + +### 📚 Docs + +- Fix typo in README.neomutt (@l2dy) + +### ♻️ Upstream + +- Fix `km_error_key()` infinite loop and unget buffer pollution +- Fix error message when opening a mailbox with no read permission + +
+
+NeoMutt 2017-04-21 + +## 2017-04-21 Richard Russon \ + +### 🎁 Features + +- add lua scripting +- add command-line batch mode +- index_format: add support of %K + +### 🐞 Bug Fixes + +- attachment/pager: Use mailcap for test/* except plain +- Fix `$uncollapse_new` in pager +- fix garbage in chdir prompt due to unescaped string +- Fix inbox-first functionality when using `mutt_pretty_mailbox()` +- add full neomutt version to log startup +- fix bug in uncolor for notmuch tag +- fix broken `$from_chars` behaviour + +### ⚙️ Coverity defects + +- strfcpy +- add variable - function arg could be NULL/invalid +- add variable - failed function leads to invalid variable +- add variable - Context could become NULL +- add variable - alloc/strdup could return NULL +- add variable - route through code leads to invalid variable +- remove variable test +- test functions +- tidy switches +- unused variables +- refactor only +- check for buffer underruns +- fix leaks +- minor fixes +- bug: add missing break +- bug: don't pass large object by value +- fix: use correct buffer size +- shadow variables +- 0 -\> NULL + +### 📚 Docs + +- many minor updates +- sync translations +- delete trailing whitespace +- indent the docbook manual +- use w3m as default for generating UTF8 manual.txt + +### 🔗 Website + +- many minor updates +- fix broken links +- add to list of useful programs +- test automatic html checker +- remove trailing whitespace +- add irc description +- update issue labels (dev) +- new page: closed discussions +- new page: making neomutt (dev) + +### 🏗 Build + +- drop obsolete m4 scripts +- don't look for lua libs unless asked for +- lower the gettext requirement 0.18 -\> 0.17 +- add `keymap_alldefs.h` to BUILT_SOURCES +- fix make dist distcheck +- Remove -Iimap from CFLAGS and include `imap/imap.h` explicitly +- mx: fix conditional builds +- Make iconv mandatory (no more `--disable-iconv`) +- refactor: Split out BUFFER-handling functions + +### ⚙️ Tidy + +- drop control characters from the source +- drop vim modelines +- delete trailing whitespace +- mark all local functions as static +- delete unused functions +- replace FOREVER with while (true) +- drop #if HAVE_CONFIG_H +- use #ifdef for potentially missing symbols +- remove #if 0 code blocks +- drop commented out source +- IMAP auth functions are stored by pointer cannot be static +- force OPS to be rebuilt after a reconfigure +- be specific about void functions +- expand a few more alloc macros +- add argument names to function prototypes +- drop local copy of regex code +- rearrange code to avoid forward declarations +- limit the scope of some functions +- give the compress functions a unique name +- use snake_case for function names +- add missing newlines to mutt_debug +- remove generated files from repo +- look for translations in all files +- fix arguments to printf-style functions +- license text +- unify include-guards +- tidy makefiles +- initialise pointers +- make strcmp-like functions clearer +- unify sizeof usage +- remove forward declarations +- remove ()s from return +- rename files hyphen to underscore +- remove unused macros +- use SEEK_SET, SEEK_CUR, SEEK_END +- remove constant code +- fix typos and grammar in the comments +- Switch to using an external gettext runtime +- apply clang-format to the source code +- boolify returns of 84 functions +- boolify lots of struct members +- boolify some function parameters + +### ♻️ Upstream + +- Add `$ssl_verify_partial_chains` option for OpenSSL +- Move the OpenSSL partial chain support check inside configure.ac +- Don't allow storing duplicate certs for OpenSSL interactive prompt +- Prevent skipped certs from showing a second time +- OpenSSL: Don't offer (a)ccept always choice for hostname mismatches +- Add SNI support for OpenSSL +- Add SNI support for GnuTLS +- Add shortcuts for IMAP and POP mailboxes in the file browser +- Change OpenSSL to use SHA-256 for cert comparison +- Fix conststrings type mismatches +- Pass envlist to filter children too +- Fix `envlist_set()` for the case that envlist is null +- Fix setenv overwriting to not truncate the envlist +- Fix `(un)sidebar-pin` to expand paths +- Fix `mutt_refresh()` pausing during macro events +- Add a menu stack to track current and past menus +- Change CurrentMenu to be controlled by the menu stack +- Set refresh when popping the menu stack +- Remove redraw parameter from crypt send_menus +- Don't full redraw the index when handling a command from the pager +- Filter other directional markers that corrupt the screen +- Remove the OPTFORCEREDRAW options +- Remove SidebarNeedsRedraw +- Change `reflow_windows()` to set full redraw +- Create R_MENU redraw option +- Remove refresh parameter from `mutt_enter_fname()` +- Remove redraw flag setting after `mutt_endwin()` +- Change `km_dokey()` to pass SigWinch on for the MENU_EDITOR +- Separate out the compose menu redrawing +- Separate out the index menu redrawing +- Prepare for pager redraw separation +- Separate out the pager menu redrawing +- Don't create query menu until after initial prompt +- Silence imap progress messages for `` +- Ensure mutt stays in endwin during calls to `pipe_msg()` +- Fix memleak when attaching files +- Add `$ssl_verify_partial_chains` option for OpenSSL +- Move the OpenSSL partial chain support check inside configureac +- Don't allow storing duplicate certs for OpenSSL interactive prompt +- Prevent skipped certs from showing a second time +- OpenSSL: Don't offer (a)ccept always choice for hostname mismatches +- Add SNI support for OpenSSL +- Add SNI support for GnuTLS +- Add shortcuts for IMAP and POP mailboxes in the file browser +- Updated French translation +- Change OpenSSL to use SHA-256 for cert comparison +- Fix conststrings type mismatches +- Pass envlist to filter children too +- Fix `envlist_set()` for the case that envlist is null +- Fix setenv overwriting to not truncate the envlist +- Fix `mutt_refresh()` pausing during macro events +- Add a menu stack to track current and past menus +- Change CurrentMenu to be controlled by the menu stack +- Set refresh when popping the menu stack +- Remove redraw parameter from crypt send_menus +- Don't full redraw the index when handling a command from the pager +- Fix `(un)sidebar-pin` to expand paths +- Filter other directional markers that corrupt the screen +- Remove the OPTFORCEREDRAW options +- Remove SidebarNeedsRedraw +- Change `reflow_windows()` to set full redraw +- Create R_MENU redraw option +- Remove refresh parameter from `mutt_enter_fname()` +- Remove redraw flag setting after `mutt_endwin()` +- Change `km_dokey()` to pass SigWinch on for the MENU_EDITOR +- Separate out the compose menu redrawing +- Separate out the index menu redrawing +- Prepare for pager redraw separation +- Separate out the pager menu redrawing +- Don't create query menu until after initial prompt +- Silence imap progress messages for `` +- Ensure mutt stays in endwin during calls to `pipe_msg()` +- Fix memleak when attaching files +- automatic post-release commit for mutt-181 +- Added tag mutt-1-8-1-rel for changeset f44974c10990 +- mutt-181 signed +- Add ifdefs around new mutt_resize_screen calls +- Add multiline and sigwinch handling to `mutt_multi_choice()` +- Set pager's REDRAW_SIGWINCH when reflowing windows +- Add multiline and sigwinch handling to mutt_yesorno +- Change the sort prompt to use (s)ort style prompts +- Handle the pager sort prompt inside the pager +- Fix GPG_TTY to be added to envlist +- automatic post-release commit for mutt-182 + +
+
+NeoMutt 2017-04-14 (Devel) + +## 2017-04-14 Richard Russon \ + +### ⚠️ Development Release + +
+
+NeoMutt 2017-03-06 + +## 2017-03-06 Richard Russon \ + +### 🐞 Bug Fixes + +- Get the correct buffer size under fmemopen/torify (#441) +- Use static inlines to make gcc 4.2.1 happy +- getdnsdomainname: cancel getaddrinfo_a if needed +- imap: remove useless code (#434) (origin/master) +- Fixes missing semi-colon compilation issue (#433) + +### 📚 Docs + +- github: added template for Pull Requests, issues and a CONTRIBUTION.md (#339) +- editorconfig: support for new files, fix whitespace (#439) +- add blocking fmemopen bug on debian to manual (#422) + +### ♻️ Upstream + +- Increase ACCOUNT.pass field size. (closes #3921) +- SSL: Fix memory leak in subject alternative name code. (closes #3920) +- Prevent segv if open-appending to an mbox fails. (closes #3918) +- Clear out extraneous errors before `SSL_connect()` (see #3916) + +
+
+NeoMutt 2017-02-25 + +## 2017-02-25 Richard Russon \ + +### 🎁 Features + +- Add option `$show_multipart_alternative` +- notmuch: Allow to use untransformed tag for color +- Use getaddrinfo_a if possible (#420) + +### 🐞 Bug Fixes + +- handle sigint within socket operations (#411) +- Avoid browsing the remote `$spool_file` by setting MUTT_SELECT_MULTI attach +- notmuch: fix crash when completing tags (#395) +- Fixes missing failure return of notmuch msg open (#401) +- Fix latest Coverity issues (#387) +- Advance by the correct number of position even for unknown characters (#368) +- Release KyotoCabinet data with `kcfree()` (#384) +- 22 resource leaks + +### 🏁 Translations + +- Update translations +- Update the German translation (#397) + +### 📚 Docs + +- fix typo in notmuch example +- remove duplicate "default" in the sidebar intro +- fix confusing description of notmuch operators (#371) +- correct spelling mistakes (#412) + +### 🔗 Website + +- link to clang-format config in main repo (#28) +- updated list of useful programs +- update/improve list of useful programs +- `$sidebar_format` has a single default value +- fix name of GNU Guix +- added guix distro +- added link to new afew maintainers +- add code of conduct +- add mutt-addressbook to useful +- remove unnecessary unicode non-breaking spaces +- github merging + +### 🏗 Build + +- Enable and run unit-tests on the feature/unit-test branch +- add notmuch to default, feature +- new dbs for mutt +- master is now the main branch +- streamline builds +- fix doc generator +- add a few includes (prelude to clang-format) +- `slcurses.h` defines its own bool type +- travis: use container build +- add clang-format file +- Remove ugly macros and casts from `crypt_gpgme.c` +- fix minor reflow issues in some comments +- editorconfig: use spaces to indent in *.[ch] files +- added comment-blocks for clang-format to ignore +- fix 80 column limit, align statements +- Remove `snprintf.c` from EXTRA_DIST (#406) +- Kill homebrew (v)snprintf implementations, as they are C99 (#402) +- Display charset + small refactoring +- Do not cast or check returns from safe_calloc (#396) +- refactor: create a generic base64 encode/decode +- debug: remove dprint in favor of mutt_debug (#375) +- Fix dubious use macro for `_()` / `gettext()` (#376) +- Use buf_init instead of memset +- Make the heap method and datatype a plain list +- Reverts making AliasFile into a list_t (#379) +- Turn mutt_new_* macros into inline functions +- Do not cast return values from malloc (or similar) + +### ♻️ Upstream + +- Simplify `mutt_label_complete()` +- Permit tab completion of pattern expressions with ~y (labels) +- Fix the `mutt_label_complete()` pos parameter +- Fix the x-label update code check location +- Improve the label completion hash table usage +- Adds label completion +- Add `hash_find_elem()` to get the hash element +- Minor fixes to the x-label patch from David +- Adds capability to edit x-labels inside mutt, and to sort by label +- Allow "unsubjectrx *" to remove all patterns +- Add subjectrx command to replace matching subjects with something else +- Abstract the SPAM_LIST as a generic REPLACE_LIST +- Improve Reply-to vs From comparison when replying +- Fix sidebar references to the "new count" to be "unread" +- Fix several alias hashtable issues +- Add casecmp and strdup_key flags to hash_create() +- Improve error handling in mbox magic detection +- Allow initial blank lines in local mailboxes +- Fix minor documentation issues +- Convert cmd_parse_search to use the uid hash +- Create a uid hash for imap +- Convert HASH to be indexable by unsigned int +- Fix imap server-side search to call `uid2msgno()` only once +- Add a pattern_cache_t to speed up a few repeated matches +- Canonicalize line endings for GPGME S/MIME encryption +- Fix build for bdb +- Create function to free header cache data +- Add Kyoto Cabinet support to the header cache +- Prevent null pointer exception for `h->ai_canonname` +- Show SHA1 fp in interactive cert check menu +- Fix potential cert memory leak in `check_certificate_by_digest()` +- Plug memory leak in weed-expired-certs code +- Filter expired local certs for OpenSSL verification +- Change "allow_dups" into a flag at hash creation + +
+
+NeoMutt 2017-02-06 + +## 2017-02-06 Richard Russon \ + +### 🐞 Bug Fixes + +- Unicode 0x202F is a non-break space too (#358) +- improve readability of `find_subject()` +- Import hcache-lmdb fixes from upstream (#363) +- Rework the "inbox-first" implementation to make code self-explanatory (#356) +- If possible, only redraw after gpgme has invoked pinentry (#352) +- Remove two use-after free in global hooks (#353) +- Handle BAD as IMAP_AUTH_UNAVAIL (#351) +- Do not crash when closing a non-opened mailbox +- Import hcache benchmark +- fix: bug introduced by mkdir changes (#350) +- change pager to allow timehook-hook to fire + +### 📚 Docs + +- Update documentation about `` + +
+
+NeoMutt 2017-01-28 + +## 2017-01-28 Richard Russon \ + +### 🎁 Features + +- Add option for missing subject replacement +- notmuch: Allow `` to toggle labels +- Support for aborting mailbox loading +- Do a mailbox check after shell escape +- Support of relative paths sourcing and cyclic source detection +- Support of multiple config files as CLI arguments +- Extend the ~m pattern to allow relative ranges +- Implement SASL's PLAIN mechanism as a standalone authenticator +- Add support for sensitive config options +- Searching with a window over notmuch vfolders + +### ✨ Contrib + +- fix vim syntax file for index-color commands +- add .editorconfig + +### 🐞 Bug Fixes + +- fix global hooks to not take a pattern +- Avoid breaking relative paths when avoiding cyclic checks on +- Fix sorting when using '/' as a namespace separator + +### 📚 Docs + +- Added waffle badges to readme +- Describe the new message ranges +- add documentation for -DS command line switch +- fix typos in section on config locations +- remove reference to missing keybinding +- fix docbook validation + +### 🏗 Build + +- Start migrating to stdbool logic +- add recursive `mkdir()` +- reformat the source to mutt standards +- appease `check_sec.sh` + +
+
+NeoMutt 2017-01-13 + +## 2017-01-13 Richard Russon \ + +### 🎁 Features + +- Allow custom status flags in `$index_format` +- `$from_chars` highlights differences in authorship +- notmuch: make 'Folder' and 'Tags' respect (un)ignore +- notmuch: add "virtual-unmailboxes" command + +### 🐞 Bug Fixes + +- pick smarter default for `$sidebar_divider_char` +- status color breaks "mutt -D" +- Enable `` in the pager +- manually touch 'atime' when reading a mbox file +- allow `$to_chars` to contain Unicode characters +- increase the max lmdb database size +- restore limit current thread +- don't reset the alarm unless we set it +- some more places that may get NULL pointers +- rework initials to allow unicode characters + +### 🏁 Translations + +- Spanish translation +- German translation + +### 📚 Docs + +- Improve whitespace and grammar on the NNTP feature page +- make `$to_chars` docs more legible +- de-tab the DocBook +- fix 301 redirects + +### 🏗 Build + +- New configure option `--enable-everything` +- add a constant for an aborted question +- enhance `mutt_to_base64()` (and callers) +- Fix configure.ac to require md5 if hcache is enabled +- Bail if a selected hcache backend cannot be found +- refactor mutt_matches_ignore +- fix hcache + make dist +- add unicode string helper function +- Re-indent configure.ac +- generate devel version suffix +- fix `check_sec.sh` warnings +- remove unnecessary #ifdef's +- add missing #ifdef for nntp +- ignore some configure temp files +- fix "make dist" target +- fix function prototypes +- fix coverity warnings +- notmuch: drop strndup, replace with mutt_substrdup + +### ♻️ Upstream + +- Fix failure with GPGME 1.8: do not steal the gpgme_ prefix. +- search muttrc file according to XDG Base Specification (closes #3207) +- Improve openssl interactive_check_cert. (closes #3899) +- Add mutt_array_size macro, change `interactive_check_cert()` to use it. (see #3899) +- Return to pager upon aborting a jump operation. (closes #3901) +- Change sidebar_spool_file coloring to be lower precedence. +- Move '@' pattern modifier documentation to the right section. +- Add setenv/unsetenv commands. +- Rework OpenSSL certificate verification to support alternative chains. (closes #3903) +- Add option to control whether threads uncollapse when new mail arrives. +- In the manual, replaced 2 para by example (similar to the first example). +- Create MbTable type for multibyte character arrays. (see #3024) +- Make `$to_chars` and `$status_chars` accept mulitibyte characters. (closes #3024) + +
+
+NeoMutt 2016-11-26 + +## 2016-11-26 Richard Russon \ + +### 🎁 Features + +- Upstream adoption of compress +- Multiple hcache backends and run-time selection +- `$forward_references` includes References: header on forwards +- Hooks: define hooks for startup and shutdown +- Add `$collapse_all` to close threads automatically + +### 🐞 Bug Fixes + +- Index in pager crash +- Tag with multiple labels +- Make sure gdbm's symbols are not resolved in QDBM's compatibility layer +- Fix crash when doing collapse_all on an empty folder +- Fix: crash when browsing empty dir +- Initialize imap_authenticate's return value to something meaningful + +### 🏁 Translations + +- Update German translation +- Update Slovak translation +- Update French translation +- Add English (British) translation +- Convert files to utf-8 +- Mass tidy up of the translation messages + +### 📚 Docs + +- new-mail bug is fixed +- add since date for features +- expand example command options for compress +- fix entries for `$beep` and `new-mail-command` +- add a version number to the generated vimrc +- fix links in README +- don't use smart quotes in manual examples +- `` and `\e` means refers to both alt and escape key + +### 🏗 Build + +- Travis: test messages +- Add option to disable translation messages +- Split hcache code into per-backend files +- Doc/Makefile clean neomutt-syntax.vim +- Improve discovery for the Berkeley Database +- Fix nntp/notmuch conditionals +- Implement `mutt_strchrnul()` +- Rename vim-keybindings to vim-keys + +### ♻️ Upstream + +- `$attach_format`: add new %F placeholder +- Compose: add operation to rename an attachment +- Chain `%d->%F->%f` in the attachment menu +- Move mbox close-append logic inside `mbox_close_mailbox()` +- When `$flag_safe` is set, flagged messages cannot be deleted +- Adds the '@' pattern modifier to limit matches to known aliases +- Adds `` binding to create "hotkeys" for messages +- Updated requirement on the C compiler +- Fix `` translation and keybind menu +- More openssl1.1 fixes: remove uses of `X509->name` in debugging. (closes #3870) +- Don't close stderr when opening a tunnel. (closes #3726) +- Minor resource and error logic cleanup in tunnel_socket_open() +- Make sure that the output of X509_NAME_oneline is NUL-terminated + +
+
+NeoMutt 2016-11-04 + +## 2016-11-04 Richard Russon \ + +### 🐞 Bug Fixes + +- don't crash when the imap connection dies + +### ♻️ Upstream + +- Add `` function to jump to root message in thread. +- Updated French translation. +- Prevent an integer overflow in `mutt_mktime()` (closes #3880) +- Fix pager segfault when lineInfo.chunks overflows. (closes #3888) +- Perform charset conversion on text attachments when piping. (closes #3773) (see #3886) +- Add a `--disable-doc` configuration option. +- Make ncurses and ncursesw header checking the same. +- Attempt to silence a clang range warning. (closes #3891) +- Fixed issue from changeset 4da647a80c55. (closes #3892) +- Define PATH_MAX, it's missing on the GNU Hurd. (closes #3815) + +
+
+NeoMutt 2016-10-28 + +## 2016-10-28 Richard Russon \ + +### 🎁 Features + +- nntp: use safe_{fopen,fclose} +- nntp: fix resource leak +- forgotten-attachment: Ignore lines matching `$quote_regex` +- forgotten-attachment: Fix checking logic. +- forgotten-attachment: Update docs regarding `$quote_regex` +- notmuch: Add a fake "Folder" header to viewed emails +- sidebar: consider description when using pinning +- skip-quoted: skip to body + +### 🐞 Bug Fixes + +- sensible-browser/notmuch changing mailbox +- "inbox" sorting function +- overhaul the index/pager updates +- crash in hdrline +- remove stray line introduced by pager fix +- Possible fix for random pager crashes. + +### 📚 Docs + +- use a more expressive coverity scan badge +- light tidying + +### 🏗 Build + +- replace the ugly `strfcpy()` macro with a function +- build: Look for tgetent in ncurses, fallback to tinfo only if not found +- build: fix a couple of build warnings +- travis: install doc dependencies +- build: fix install/dist/distcheck targets + +### ♻️ Upstream + +- Fix POP3 SASL authentication mechanism DIGEST-MD5. (closes #3862) +- Add a few explanatory comments to `pop_auth_sasl()` (see #3862) +- Fix GPGME signature zero timestamp and locale awareness issues. (closes #3882) +- Handle presence of '--' delimiter in `$sendmail` (closes #3168) +- Allow IPv6 literal addresses in URLs. (closes #3681) +- Fix gpgme segfault in `create_recipient_set()` +- Use `mutt_strlen()` and `mutt_strncmp()` in `sidebar.c` +- Change sidebar to only match `$folder` prefix on a `$sidebar_divider_char.` (closes #3887) +- Actually fix gpgme segfault in `create_recipient_set()` + +
+
+NeoMutt 2016-10-14 + +## 2016-10-14 Richard Russon \ + +### 🎁 Features + +- sidebar: Make sure INBOX appears first in the list. +- notmuch: Synchronise tags to flags + +### 🐞 Bug Fixes + +- updates when pager is open +- crash when neither `$spool_file`, `$folder` are set +- forgotten-attachment: fix empty regex expression +- status-color when `$pager_index_lines > 0` +- buffer underrun when no menu item is selected +- crash handling keywords/labels + +### 📚 Docs + +- update notmuch references + +### 🏗 Build + +- update references to 1.7.1 +- `strfcpy()` improvement + +### ♻️ Upstream + +- automatic post-release commit for mutt-1.7.1 +- Mark IMAP fast-trash'ed messages as read before copying. (see #3860) +- Updated Czech translation. +- Preserve forwarded attachment names in d_filename. + +
+
+NeoMutt 2016-10-03 + +## 2016-10-03 Richard Russon \ + +### 🏗 Build + +- Fix install and dist targets + +
+
+NeoMutt 2016-10-02 + +## 2016-10-02 Richard Russon \ + +### 🎁 Features + +- Kyoto Cabinet header cache +- Compose to Sender +- Forgotten Attachment uses a regex +- Optimize LMDB's hcache backend +- Sensible-browser behaviour fixes + +### 🐞 Bug Fixes + +- Fixes repaint problem with `$pager_index_lines` #159 +- Quasi-Delete: check there's a selection +- Bulletproof the pager +- Typo in the version string + +### 📚 Docs + +- Add badges to README.neomutt +- Document the Kyoto cabinet hcache backend +- Fix the layout of the syntax file +- Make the license clear to github +- Fix the alignment in a 'nested-if' example +- Fix notmuch vim syntax file +- Added Mailinglist mailto links to "Where is NeoMutt" section +- Fix build of neomutt-syntax.vim +- Fixed typo of devel mailinglist name + +### 🏗 Build + +- Travis: install the kyoto-cabinet dev files +- Build source before docs +- Build fix for strndup / malloc +- Change gcc build options to prevent crashes + +### ♻️ Upstream + +- Ensure signatures exist when verifying multipart/signed emails (closes #3881) +- RFC2047-decode mailto url headers after RFC2822 parsing (closes #3879) +- RFC2047-decode mailto header values (closes #3879) +- Reset invalid parsed received dates to 0 (closes #3878) +- Clear pager position when toggling headers +- Don't abort the menu editor on sigwinch (closes #3875) +- Mark some gpgme pgp menu keybinding translations as fuzzy (closes #3874) +- Check for NULL mx_ops in mxc +- Use body color for gpgme output (closes #3872) +- Fix gpgme segfault when querying candidates with a '+' in the address (closes #3873) + +
+
+NeoMutt 2016-09-16 + +## 2016-09-16 Richard Russon \ + +### 🐞 Bug Fixes + +- Avoid segfault when listing mailboxes on startup + John Swinbank +- Fix buffer overrun in search for attach keyword + James McCoy +- Fix off-by-one in error message + Antonio Radici +- fix AC_INIT tarname parameter +- fix crash when exiting the pager +- fix another crash in the pager +- nntp: close message handles +- fix: make the pager more robust +- fix sidebar sort order +- fix notmuch tag completion + +### 📚 Docs + +- doc: Removes bug entry in new-mail docs + Santiago Torres +- fix some translations in `crypt_gpgme.c` + Antonio Radici +- docs: mass tidy up + +### ♻️ Upstream + +- Fix sidebar documentation a bit +- Add sidebar-pin command +- Remove the $locale configuration variable +- Add `$attribution_locale` configuration variable +- Add missing include `` to `send.c` and `edit.c` +- Filter out zero width no-break space (U+FEFF) +- Update a confusing and obsolete comment +- Moves `mutt_copy_list()` to `muttlib.c`, where it belongs +- Redraw screen after an SSL cert prompt +- Preserve message-id and mft headers for recalled messages +- Fix openssl 1.1 compilation issues + +
+
+NeoMutt 2016-09-10 + +## 2016-09-10 Richard Russon \ + +### 🎁 New Features + +- Colouring Attachments with Regex + Guillaume Brogi +- PGP Encrypt to Self + Guillaume Brogi +- Sensible Browser + Pierre-Elliott Bécue +- Reply using X-Original-To: header + Pierre-Elliott Bécue +- Purge Thread + Darshit Shah +- Forgotten attachment + Darshit Shah +- Add `sidebar_ordinary` color + +### 🐞 Bug Fixes + +- align the nntp code with mutt + Fabian Groffen +- check for new mail while in pager when idle + Stefan Assmann +- Allow the user to interrupt slow IO operations + Antonio Radici +- keywords: check there are emails to tag +- fix duplicate saved messages +- flatten contrib/keybase dir to fix install +- restore the pager keymapping 'i' to exit +- proposed fix for clearing labels +- notmuch: sync `$vfolder_format` to `$folder_format` + +### 📚 Docs + +- Update List of Features and Authors + +### 🏗 Build + +- fix configure check for fmemopen +- use fixed version strings + +### ♻️ Upstream + +- Increase date buffer size for `$folder_format` +- Disable ~X when message scoring +- Fix pgpring reporting of DSA and Elgamal key lengths +- Stub out `getdnsdomainname()` unless HAVE_GETADDRINFO +- Autoconf: always check for `getaddrinfo()` +- Add missing sidebar contrib sample files to dist tarball + +
+
+NeoMutt 2016-08-27 + +## 2016-08-27 Richard Russon \ + +### 🎉 NeoMutt for Mutt 1.7.0 + +### 🏗 Build + +- Disable fmemopen until bug is fixed + +### ✨ Contrib + +- Keybase portability improvements + Joshua Jordi (JakkinStewart) + +
+
+NeoMutt 2016-08-26 + +## 2016-08-26 Richard Russon \ + +### 🏗 Build + +- Disable fmemopen until bug is fixed + +### ✨ Contrib + +- Keybase portability improvements + +### 🐞 Bug Fixes + +- Fix notmuch crash toggling virtual folders +- Fix display of pager index when sidebar toggled + +
+
+NeoMutt 2016-08-22 (Devel) + +## 2016-08-22 Richard Russon \ + +### ⚠️ Development Release + +
+
+NeoMutt 2016-08-21 + +## 2016-08-21 Richard Russon \ + +### ✨ Contrib + +- Updates to Keybase Support + Joshua Jordi (JakkinStewart) + +### 🐞 Bug Fixes + +- Fix data-loss when appending a compressed file +- Don't paint invisible progress bars +- Revert to Mutt keybindings +- Don't de-tag emails after labelling them +- Don't whine if `getrandom()` fails + Adam Borowski (kilobyte) +- Fix display when 'from' field is invalid + +### 🔧 Config + +- Support for `$XDG_CONFIG_HOME` and `$XDG_CONFIG_DIRS` + Marco Hinz (mhinz) + +### 📚 Docs + +- Fix DocBook validation +- Document Notmuch queries + +### 🏗 Build + +- More Autoconf improvements + Darshit Shah (darnir) +- Create Distribution Tarballs with autogen sources + Darshit Shah (darnir) + +
+
+NeoMutt 2016-08-08 + +## 2016-08-08 Richard Russon \ + +### 🎁 New Features + +- Timeout Hook - Run a command periodically +- Multiple fcc - Save multiple copies of outgoing mail + +### ✨ Contrib + +- Keybase Integration + Joshua Jordi (JakkinStewart) + +### ⚠️ Devel + +- Attached - Prevent missing attachments + Darshit Shah (darnir) +- Virtual Unmailboxes - Remove unwanted virtual mailboxes + Richard Russon (flatcap) + +### 🐞 Bug Fixes + +- Sidebar's inbox occasionally shows zero/wrong value +- Fix crash opening a second compressed mailbox + +### 🔧 Config + +- Look for /etc/neomuttrc and ~/.neomuttrc + +### 📚 Docs + +- Fix broken links, typos +- Update project link +- Fix version string in the manual + +### 🏗 Build + +- Add option to disable fmemopen +- Install all the READMEs and contribs +- Big overhaul of the build + Darshit Shah (darnir) + +
+
+NeoMutt 2016-07-23 + +## 2016-07-23 Richard Russon \ + +### 🎉 New Motto: "Teaching an Old Dog New Tricks" + +- Thanks to Alok Singh + +### 🎁 New Features + +- New Mail Command - Execute a command on receipt of new mail +- vim-keys - Mutt config for vim users +- LMDB: In-memory header caching database +- SMIME Encrypt to Self - Secure storage of sensitive email + +### 🐞 Bug Fixes + +- rework `mutt_draw_statusline()` +- fix cursor position after sidebar redraw +- Add `$sidebar_format` flag '%n' to display 'N' on new mail. +- fix `$index_format` truncation problem +- Fix compiler warnings due to always true condition +- Change sidebar next/prev-new to look at `mailbox->new` too. +- Change the default for `$sidebar_format` to use %n. +- sidebar "unsorted" order to match Mailbox list order. +- Include ncurses tinfo library if found. +- Sidebar width problem +- sidebar crash for non-existent mailbox +- Temporary compatibility workaround +- Reset `mailbox->new` for the current mailbox in IMAP. +- `version.sh` regression +- crash when notmuch tries to read a message +- status line wrapping + +### 📚 Docs + +- Mass tidy up of the docs +- Fix xml validation +- Add missing docs for new features + +### 🏗 Travis + +- New build system: + https://github.com/neomutt/travis-build + Now we have central control over what gets built + +
+
+NeoMutt 2016-07-09 + +## 2016-07-09 Richard Russon \ + +### 🐞 Bug-fixes + +- This release was a temporary measure + +
+
+NeoMutt 2016-06-11 + +## 2016-06-11 Richard Russon \ + +### ⚠️ Change in behaviour + +- Temporarily disable `$sidebar_refresh_time` + Unfortunately, this was causing too many problems. + It will be fixed and re-enabled as soon as possible. + +### 🐞 Bug Fixes + +- Fix several crashes, on startup, in Keywords +- Reflow text now works as it should +- Lots of typos fixed +- Compress config bug prevented it working +- Some minor bug-fixes from mutt/default +- Single quote at line beginning misinterpreted by groff +- Setting `$sidebar_width` to more than 128 would cause bad things to happen. +- Fix alignment in the compose menu. +- Fix sidebar mailbox stats updating on mailbox close. + +### 🏗 Build Changes + +- Sync whitespace to mutt/default +- Alter ChangeLog date format to simplify Makefiles +- Use the new notmuch functions that return a status +- Rename sidebar functions `sb_*` -\> `mutt_sb_*` + +
+
+NeoMutt 2016-05-30 + +## 2016-05-30 Richard Russon \ + +### 🎁 New Features: + +- Keywords: Email Label/Keywords/Tagging +- Compress: Compressed mailboxes support +- NNTP: Talk to a usenet news server +- Separate mappings for `` and `` +- New configure option: `--enable-quick-build` +- Various build fixes + +
+
+NeoMutt 2016-05-02 + +## 2016-05-02 Richard Russon \ + +### 🎉 Update for Mutt-1.6.0 + +### 🐞 Bug Fixes: + +- Build for Notmuch works if Sidebar is disabled +- Sidebar functions work even if the Sidebar is hidden +- ``, etc, only find *new* mail, as documented +- Notmuch supports *very* long queries + +
+
+NeoMutt 2016-04-16 + +## 2016-04-16 Richard Russon \ + +### 🎉 Big Bugfix Release + +### 🐞 Bug Fixes: + +- Fix crash caused by `$sidebar_folder_indent` +- Allow the user to change mailboxes again +- Correct sidebar's messages counts +- Only sort the sidebar if we're asked to +- Fix refresh of pager when toggling the sidebar +- Compose mode: make messages respect the TITLE_FMT +- Conditional include if `sys/syscall.h` +- Build fix for old compilers +- Try harder to keep track of the open mailbox + +### 🎁 Changes to Features + +- Allow `$sidebar_divider_char` to be longer + (it was limited to one character) +- Ignore case when sorting the sidebar alphabetically + +### 🐞 Other Changes + +- Numerous small tweaks to the docs +- Lots of minor code tidy-ups +- Enabling Notmuch now forcibly enables Sidebar + (it is dependent on it, for now) +- A couple of bug fixes from mutt/stable + +
+
+NeoMutt 2016-04-04 + +## 2016-04-04 Richard Russon \ + +### 🎉 Update for Mutt-1.6.0 + +### 📖 No other changes in this release + +
+
+NeoMutt 2016-03-28 + +## 2016-03-28 Richard Russon \ + +### 🎁 New Features + +- `` - skip quoted text +- `` - limit index view to current thread + +### 📚 Sidebar Intro - A Gentle Introduction to the Sidebar (with pictures). + +
+
+NeoMutt 2016-03-20 + +## 2016-03-20 Richard Russon \ + +### 🐞 Numerous small bugfixes + +### 🏗 TravisCI integration + +
+
+NeoMutt 2016-03-17 + +## 2016-03-17 Richard Russon \ + +### 🎁 New Features + +- notmuch - email search support +- ifdef - improvements + +
+
+NeoMutt 2016-03-07 + +## 2016-03-07 Richard Russon \ + +### 🎉 First NeoMutt release + +### 🎁 List of Features: + +- bug-fixes - various bug fixes +- cond-date - use rules to choose date format +- fmemopen - use memory buffers instead of files +- ifdef - conditional config options +- index-color - theme the email index +- initials - expando for author's initials +- nested-if - allow deeply nested conditions +- progress - show a visual progress bar +- quasi-delete - mark emails to be hidden +- sidebar - overview of mailboxes +- status-color - theming the status bar +- tls-sni - negotiate for a certificate +- trash - move 'deleted' emails to a trash bin + +
diff --git a/Makefile.autosetup b/Makefile.autosetup index 9f94bb92cb5..fbd47d96be5 100644 --- a/Makefile.autosetup +++ b/Makefile.autosetup @@ -61,13 +61,10 @@ default: all ############################################################################### # neomutt NEOMUTT= neomutt$(EXEEXT) -NEOMUTTOBJS= alternates.o commands.o conststrings.o copy.o editmsg.o \ - enriched.o external.o flags.o git_ver.o globals.o handler.o \ - help.o hook.o mailcap.o maillist.o muttlib.o mutt_body.o \ - mutt_config.o mutt_header.o mutt_logging.o mutt_mailbox.o \ - mutt_signal.o mutt_socket.o mutt_thread.o mview.o mx.o \ - recvcmd.o rfc3676.o score.o subjectrx.o system.o usage.o \ - version.o +NEOMUTTOBJS= conststrings.o editmsg.o external.o flags.o git_ver.o \ + globals.o help.o module.o muttlib.o mutt_config.o \ + mutt_logging.o mutt_mailbox.o mutt_signal.o mutt_socket.o \ + mx.o system.o usage.o version.o @if !ENABLE_FUZZ_TESTS NEOMUTTOBJS+= main.o @endif @@ -75,12 +72,6 @@ NEOMUTTOBJS+= main.o @if USE_INOTIFY NEOMUTTOBJS+= monitor.o @endif -@if !HAVE_TIMEGM -NEOMUTTOBJS+= timegm.o -@endif -@if !HAVE_WCSCASECMP -NEOMUTTOBJS+= wcscasecmp.o -@endif CLEANFILES+= $(NEOMUTT) $(NEOMUTTOBJS) ALLOBJS+= $(NEOMUTTOBJS) @@ -89,7 +80,7 @@ ALLOBJS+= $(NEOMUTTOBJS) # libaddress LIBADDRESS= libaddress.a LIBADDRESSOBJS= address/address.o address/config_type.o address/group.o \ - address/idna.o + address/idna.o address/maillist.o address/module.o CLEANFILES+= $(LIBADDRESS) $(LIBADDRESSOBJS) ALLOBJS+= $(LIBADDRESSOBJS) @@ -105,7 +96,7 @@ LIBALIAS= libalias.a LIBALIASOBJS= alias/alias.o alias/array.o alias/commands.o alias/complete.o \ alias/config.o alias/dlg_alias.o alias/dlg_query.o \ alias/expando.o alias/functions.o alias/gui.o alias/reverse.o \ - alias/sort.o + alias/sort.o alias/module.o CLEANFILES+= $(LIBALIAS) $(LIBALIASOBJS) ALLOBJS+= $(LIBALIASOBJS) @@ -121,7 +112,7 @@ LIBATTACH= libattach.a LIBATTACHOBJS= attach/attach.o attach/cid.o attach/commands.o \ attach/dlg_attach.o attach/expando.o attach/functions.o \ attach/lib.o attach/mutt_attach.o attach/private_data.o \ - attach/recvattach.o + attach/recvattach.o attach/recvcmd.o attach/module.o CLEANFILES+= $(LIBATTACH) $(LIBATTACHOBJS) ALLOBJS+= $(LIBATTACHOBJS) @@ -138,7 +129,7 @@ LIBAUTOCRYPT= libautocrypt.a LIBAUTOCRYPTOBJS=autocrypt/autocrypt.o autocrypt/autocrypt_data.o \ autocrypt/config.o autocrypt/db.o autocrypt/dlg_autocrypt.o \ autocrypt/expando.o autocrypt/functions.o autocrypt/gpgme.o \ - autocrypt/schema.o + autocrypt/schema.o autocrypt/module.o CLEANFILES+= $(LIBAUTOCRYPT) $(LIBAUTOCRYPTOBJS) ALLOBJS+= $(LIBAUTOCRYPTOBJS) @@ -152,7 +143,7 @@ $(PWD)/autocrypt: ############################################################################### # libbcache LIBBCACHE= libbcache.a -LIBBCACHEOBJS= bcache/bcache.o +LIBBCACHEOBJS= bcache/bcache.o bcache/module.o CLEANFILES+= $(LIBBCACHE) $(LIBBCACHEOBJS) ALLOBJS+= $(LIBBCACHEOBJS) @@ -167,7 +158,7 @@ $(PWD)/bcache: LIBBROWSER= libbrowser.a LIBBROWSEROBJS= browser/complete.o browser/config.o browser/dlg_browser.o \ browser/expando.o browser/functions.o browser/private_data.o \ - browser/sort.o + browser/sort.o browser/module.o CLEANFILES+= $(LIBBROWSER) $(LIBBROWSEROBJS) ALLOBJS+= $(LIBBROWSEROBJS) @@ -196,7 +187,7 @@ LIBCOLOR= libcolor.a LIBCOLOROBJS= color/ansi.o color/attr.o color/color.o color/commands.o \ color/curses.o color/dump.o color/merged.o color/notify.o \ color/parse_ansi.o color/parse_color.o color/qstyle.o \ - color/quoted.o color/regex.o color/simple.o + color/quoted.o color/regex.o color/simple.o color/module.o @if USE_DEBUG_COLOR LIBCOLOROBJS+= color/debug.o @endif @@ -209,10 +200,28 @@ $(LIBCOLOR): $(PWD)/color $(LIBCOLOROBJS) $(PWD)/color: $(MKDIR_P) $(PWD)/color +############################################################################### +# libcommands +LIBCOMMANDS= libcommands.a +LIBCOMMANDSOBJS=commands/alternates.o commands/commands.o commands/group.o \ + commands/ifdef.o commands/ignore.o commands/mailboxes.o \ + commands/my_header.o commands/parse.o commands/score.o \ + commands/setenv.o commands/source.o commands/spam.o \ + commands/stailq.o commands/subjectrx.o commands/tags.o \ + commands/module.o +CLEANFILES+= $(LIBCOMMANDS) $(LIBCOMMANDSOBJS) +ALLOBJS+= $(LIBCOMMANDSOBJS) + +$(LIBCOMMANDS): $(PWD)/commands $(LIBCOMMANDSOBJS) + $(AR) cr $@ $(LIBCOMMANDSOBJS) + $(RANLIB) $@ +$(PWD)/commands: + $(MKDIR_P) $(PWD)/commands + ############################################################################### # libcomplete LIBCOMPLETE= libcomplete.a -LIBCOMPLETEOBJS=complete/complete.o complete/data.o complete/helpers.o +LIBCOMPLETEOBJS=complete/complete.o complete/data.o complete/helpers.o complete/module.o CLEANFILES+= $(LIBCOMPLETE) $(LIBCOMPLETEOBJS) ALLOBJS+= $(LIBCOMPLETEOBJS) @@ -225,7 +234,7 @@ $(PWD)/complete: ############################################################################### # libcompmbox LIBCOMPMBOX= libcompmbox.a -LIBCOMPMBOXOBJS=compmbox/compress.o compmbox/expando.o +LIBCOMPMBOXOBJS=compmbox/compress.o compmbox/expando.o compmbox/module.o CLEANFILES+= $(LIBCOMPMBOX) $(LIBCOMPMBOXOBJS) ALLOBJS+= $(LIBCOMPMBOXOBJS) @@ -241,7 +250,7 @@ LIBCOMPOSE= libcompose.a LIBCOMPOSEOBJS= compose/attach.o compose/attach_data.o compose/cbar.o \ compose/cbar_data.o compose/config.o compose/dlg_compose.o \ compose/expando.o compose/functions.o compose/preview.o \ - compose/shared_data.o + compose/shared_data.o compose/module.o CLEANFILES+= $(LIBCOMPOSE) $(LIBCOMPOSEOBJS) ALLOBJS+= $(LIBCOMPOSEOBJS) @@ -264,7 +273,7 @@ LIBCOMPRESSOBJS+=compress/zlib.o LIBCOMPRESSOBJS+=compress/zstd.o @endif @if USE_LZ4 || USE_ZLIB || USE_ZSTD -LIBCOMPRESSOBJS+=compress/compress.o +LIBCOMPRESSOBJS+=compress/compress.o compress/module.o LIBCOMPRESS= libcompress.a CLEANFILES+= $(LIBCOMPRESS) $(LIBCOMPRESSOBJS) ALLOBJS+= $(LIBCOMPRESSOBJS) @@ -280,10 +289,10 @@ $(PWD)/compress: # libconfig LIBCONFIG= libconfig.a LIBCONFIGOBJS= config/bool.o config/charset.o config/dump.o config/enum.o \ - config/helpers.o config/long.o config/mbtable.o config/myvar.o \ - config/number.o config/path.o config/quad.o config/regex.o \ - config/set.o config/slist.o config/sort.o config/string.o \ - config/subset.o + config/helpers.o config/long.o config/mbtable.o \ + config/myvar.o config/number.o config/path.o config/quad.o \ + config/regex.o config/set.o config/slist.o config/sort.o \ + config/string.o config/subset.o config/module.o CLEANFILES+= $(LIBCONFIG) $(LIBCONFIGOBJS) ALLOBJS+= $(LIBCONFIGOBJS) @@ -298,7 +307,7 @@ $(PWD)/config: LIBCONN= libconn.a LIBCONNOBJS= conn/accountcmd.o conn/config.o conn/connaccount.o \ conn/mutt_account.o conn/raw.o conn/sasl_plain.o \ - conn/socket.o conn/tunnel.o + conn/socket.o conn/tunnel.o conn/module.o @if !DOMAIN LIBCONNOBJS+= conn/getdomain.o @endif @@ -332,7 +341,7 @@ $(PWD)/conn: ############################################################################### # libconvert LIBCONVERT= libconvert.a -LIBCONVERTOBJS= convert/content_info.o convert/convert.o +LIBCONVERTOBJS= convert/content_info.o convert/convert.o convert/module.o CLEANFILES+= $(LIBCONVERT) $(LIBCONVERTOBJS) ALLOBJS+= $(LIBCONVERTOBJS) @@ -347,7 +356,7 @@ $(PWD)/convert: LIBCORE= libcore.a LIBCOREOBJS= core/account.o core/command.o core/config_cache.o \ core/dispatcher.o core/mailbox.o core/message.o core/neomutt.o \ - core/tmp.o + core/tmp.o core/module.o CLEANFILES+= $(LIBCORE) $(LIBCOREOBJS) ALLOBJS+= $(LIBCOREOBJS) @@ -403,7 +412,7 @@ $(PWD)/debug: ############################################################################### # libeditor LIBEDITOR= libeditor.a -LIBEDITOROBJS= editor/enter.o editor/functions.o editor/state.o editor/window.o +LIBEDITOROBJS= editor/enter.o editor/functions.o editor/state.o editor/window.o editor/module.o CLEANFILES+= $(LIBEDITOR) $(LIBEDITOROBJS) ALLOBJS+= $(LIBEDITOROBJS) @@ -416,10 +425,13 @@ $(PWD)/editor: ############################################################################### # libemail LIBEMAIL= libemail.a -LIBEMAILOBJS= email/body.o email/config.o email/email.o email/envelope.o \ - email/from.o email/globals.o email/mime.o email/parameter.o \ - email/parse.o email/rfc2047.o email/rfc2231.o email/sort.o \ - email/tags.o email/thread.o email/url.o +LIBEMAILOBJS= email/body.o email/config.o email/copy_body.o \ + email/copy_email.o email/email.o email/enriched.o \ + email/envelope.o email/from.o email/globals.o email/handler.o \ + email/header.o email/mailcap.o email/mime.o email/parameter.o \ + email/parse.o email/rfc2047.o email/rfc2231.o email/rfc3676.o \ + email/score.o email/sort.o email/tags.o email/thread.o \ + email/url.o email/module.o CLEANFILES+= $(LIBEMAIL) $(LIBEMAILOBJS) ALLOBJS+= $(LIBEMAILOBJS) @@ -432,7 +444,7 @@ $(PWD)/email: ############################################################################### # libenvelope LIBENVELOPE= libenvelope.a -LIBENVELOPEOBJS=envelope/functions.o envelope/wdata.o envelope/window.o +LIBENVELOPEOBJS=envelope/functions.o envelope/wdata.o envelope/window.o envelope/module.o CLEANFILES+= $(LIBENVELOPE) $(LIBENVELOPEOBJS) ALLOBJS+= $(LIBENVELOPEOBJS) @@ -450,7 +462,8 @@ LIBEXPANDOOBJS= expando/config_type.o expando/expando.o expando/filter.o \ expando/node_condbool.o expando/node_conddate.o \ expando/node_condition.o expando/node_container.o \ expando/node_expando.o expando/node_padding.o \ - expando/node_text.o expando/parse.o expando/render.o + expando/node_text.o expando/parse.o expando/render.o \ + expando/serial.o expando/module.o CLEANFILES+= $(LIBEXPANDO) $(LIBEXPANDOOBJS) ALLOBJS+= $(LIBEXPANDOOBJS) @@ -464,10 +477,10 @@ $(PWD)/expando: # libgui LIBGUI= libgui.a LIBGUIOBJS= gui/curs_lib.o gui/dialog.o gui/functions.o gui/global.o \ - gui/msgcont.o gui/msgwin.o gui/msgwin_wdata.o \ - gui/mutt_curses.o gui/mutt_window.o gui/opcodes.o gui/reflow.o \ - gui/resize.o gui/rootwin.o gui/sbar.o gui/simple.o \ - gui/terminal.o + gui/module.o gui/msgcont.o gui/msgwin.o gui/msgwin_wdata.o \ + gui/mutt_curses.o gui/mutt_window.o gui/mview.o gui/opcodes.o \ + gui/reflow.o gui/resize.o gui/rootwin.o gui/sbar.o \ + gui/simple.o gui/terminal.o gui/thread.o CLEANFILES+= $(LIBGUI) $(LIBGUIOBJS) ALLOBJS+= $(LIBGUIOBJS) @@ -481,7 +494,7 @@ $(PWD)/gui: # libhcache @if USE_HCACHE LIBHCACHE= libhcache.a -LIBHCACHEOBJS= hcache/config.o hcache/hcache.o hcache/serialize.o +LIBHCACHEOBJS= hcache/config.o hcache/hcache.o hcache/serialize.o hcache/module.o CLEANFILES+= $(LIBHCACHE) $(LIBHCACHEOBJS) ALLOBJS+= $(LIBHCACHEOBJS) @@ -496,7 +509,7 @@ $(PWD)/hcache: ############################################################################### # libhelpbar LIBHELPBAR= libhelpbar.a -LIBHELPBAROBJS= helpbar/config.o helpbar/helpbar.o helpbar/wdata.o +LIBHELPBAROBJS= helpbar/config.o helpbar/helpbar.o helpbar/wdata.o helpbar/module.o CLEANFILES+= $(LIBHELPBAR) $(LIBHELPBAROBJS) ALLOBJS+= $(LIBHELPBAROBJS) @@ -510,7 +523,7 @@ $(PWD)/helpbar: # libhistory LIBHISTORY= libhistory.a LIBHISTORYOBJS= history/config.o history/dlg_history.o history/expando.o \ - history/functions.o history/history.o + history/functions.o history/history.o history/module.o CLEANFILES+= $(LIBHISTORY) $(LIBHISTORYOBJS) ALLOBJS+= $(LIBHISTORYOBJS) @@ -520,13 +533,27 @@ $(LIBHISTORY): $(PWD)/history $(LIBHISTORYOBJS) $(PWD)/history: $(MKDIR_P) $(PWD)/history +############################################################################### +# libhooks +LIBHOOKS= libhooks.a +LIBHOOKSOBJS= hooks/commands.o hooks/dump.o hooks/exec.o hooks/hook.o \ + hooks/module.o hooks/parse.o +CLEANFILES+= $(LIBHOOKS) $(LIBHOOKSOBJS) +ALLOBJS+= $(LIBHOOKSOBJS) + +$(LIBHOOKS): $(PWD)/hooks $(LIBHOOKSOBJS) + $(AR) cr $@ $(LIBHOOKSOBJS) + $(RANLIB) $@ +$(PWD)/hooks: + $(MKDIR_P) $(PWD)/hooks + ############################################################################### # libimap LIBIMAP= libimap.a LIBIMAPOBJS= imap/adata.o imap/auth.o imap/auth_login.o imap/auth_oauth.o \ imap/auth_plain.o imap/browse.o imap/command.o imap/config.o \ imap/edata.o imap/imap.o imap/mdata.o imap/message.o \ - imap/msg_set.o imap/msn.o imap/search.o imap/utf7.o imap/util.o + imap/msg_set.o imap/msn.o imap/search.o imap/utf7.o imap/util.o imap/module.o @if USE_GSS LIBIMAPOBJS+= imap/auth_gss.o @endif @@ -554,7 +581,7 @@ LIBINDEX= libindex.a LIBINDEXOBJS= index/config.o index/dlg_index.o index/expando_index.o \ index/expando_status.o index/functions.o index/ibar.o \ index/index.o index/ipanel.o index/private_data.o \ - index/shared_data.o index/status.o + index/shared_data.o index/status.o index/module.o CLEANFILES+= $(LIBINDEX) $(LIBINDEXOBJS) ALLOBJS+= $(LIBINDEXOBJS) @@ -567,9 +594,10 @@ $(PWD)/index: ############################################################################### # libkey LIBKEY= libkey.a -LIBKEYOBJS= key/commands.o key/dump.o key/get.o key/init.o key/lib.o +LIBKEYOBJS= key/commands.o key/dump.o key/get.o key/keymap.o key/init.o \ + key/menu.o key/module.o @if HAVE_USE_EXTENDED_NAMES -LIBKEYOBJS+= key/extended.o +LIBKEYOBJS+= key/extended.o @endif CLEANFILES+= $(LIBKEY) $(LIBKEYOBJS) ALLOBJS+= $(LIBKEYOBJS) @@ -584,7 +612,7 @@ $(PWD)/key: # liblua @if USE_LUA LIBLUA= liblua.a -LIBLUAOBJS= lua/commands.o lua/lua.o +LIBLUAOBJS= lua/commands.o lua/lua.o lua/module.o CLEANFILES+= $(LIBLUA) $(LIBLUAOBJS) ALLOBJS+= $(LIBLUAOBJS) @@ -601,7 +629,7 @@ LIBMAILDIR= libmaildir.a LIBMAILDIROBJS= maildir/account.o maildir/config.o maildir/edata.o \ maildir/mailbox.o maildir/maildir.o maildir/mdata.o \ maildir/mdemail.o maildir/message.o maildir/path.o \ - maildir/shared.o + maildir/shared.o maildir/module.o @if USE_HCACHE LIBMAILDIROBJS+=maildir/hcache.o @endif @@ -617,7 +645,7 @@ $(PWD)/maildir: ############################################################################### # libmbox LIBMBOX= libmbox.a -LIBMBOXOBJS= mbox/config.o mbox/mbox.o +LIBMBOXOBJS= mbox/config.o mbox/mbox.o mbox/module.o CLEANFILES+= $(LIBMBOX) $(LIBMBOXOBJS) ALLOBJS+= $(LIBMBOXOBJS) @@ -631,8 +659,8 @@ $(PWD)/mbox: # libmenu LIBMENU= libmenu.a LIBMENUOBJS= menu/config.o menu/draw.o menu/functions.o menu/menu.o \ - menu/move.o menu/observer.o menu/tagging.o menu/type.o \ - menu/window.o + menu/move.o menu/observer.o menu/tagging.o menu/window.o \ + menu/module.o CLEANFILES+= $(LIBMENU) $(LIBMENUOBJS) ALLOBJS+= $(LIBMENUOBJS) @@ -646,7 +674,7 @@ $(PWD)/menu: # libmh LIBMH= libmh.a LIBMHOBJS= mh/config.o mh/mh.o mh/mdata.o mh/mhemail.o mh/mh.o \ - mh/sequence.o mh/shared.o + mh/sequence.o mh/shared.o mh/module.o CLEANFILES+= $(LIBMH) $(LIBMHOBJS) ALLOBJS+= $(LIBMHOBJS) @@ -665,7 +693,14 @@ LIBMUTTOBJS= mutt/atoi.o mutt/base64.o mutt/buffer.o mutt/charset.o \ mutt/mapping.o mutt/mbyte.o mutt/md5.o mutt/memory.o \ mutt/notify.o mutt/path.o mutt/pool.o mutt/prex.o \ mutt/qsort_r.o mutt/random.o mutt/regex.o mutt/signal.o \ - mutt/slist.o mutt/state.o mutt/string.o + mutt/slist.o mutt/state.o mutt/string.o mutt/module.o + +@if !HAVE_TIMEGM +NEOMUTTOBJS+= mutt/timegm.o +@endif +@if !HAVE_WCSCASECMP +NEOMUTTOBJS+= mutt/wcscasecmp.o +@endif CLEANFILES+= $(LIBMUTT) $(LIBMUTTOBJS) ALLOBJS+= $(LIBMUTTOBJS) @@ -680,7 +715,7 @@ $(PWD)/mutt: # libncrypt LIBNCRYPT= libncrypt.a LIBNCRYPTOBJS= ncrypt/config.o ncrypt/crypt.o ncrypt/crypt_mod.o \ - ncrypt/cryptglue.o ncrypt/functions.o + ncrypt/cryptglue.o ncrypt/functions.o ncrypt/module.o @if HAVE_PKG_GPGME LIBNCRYPTOBJS+= ncrypt/crypt_gpgme.o ncrypt/dlg_gpgme.o ncrypt/expando_gpgme.o \ ncrypt/gpgme_functions.o ncrypt/crypt_mod_pgp_gpgme.o \ @@ -711,7 +746,7 @@ $(PWD)/ncrypt: LIBNNTP= libnntp.a LIBNNTPOBJS= nntp/adata.o nntp/complete.o nntp/config.o nntp/edata.o \ nntp/expando_browser.o nntp/expando_newsrc.o nntp/mdata.o \ - nntp/newsrc.o nntp/nntp.o + nntp/newsrc.o nntp/nntp.o nntp/module.o CLEANFILES+= $(LIBNNTP) $(LIBNNTPOBJS) ALLOBJS+= $(LIBNNTPOBJS) @@ -727,7 +762,7 @@ $(PWD)/nntp: LIBNOTMUCH= libnotmuch.a LIBNOTMUCHOBJS= notmuch/adata.o notmuch/complete.o notmuch/config.o \ notmuch/db.o notmuch/edata.o notmuch/mdata.o notmuch/notmuch.o \ - notmuch/query.o notmuch/tag.o + notmuch/query.o notmuch/tag.o notmuch/module.o CLEANFILES+= $(LIBNOTMUCH) $(LIBNOTMUCHOBJS) ALLOBJS+= $(LIBNOTMUCHOBJS) @@ -743,7 +778,7 @@ $(PWD)/notmuch: LIBPAGER= libpager.a LIBPAGEROBJS= pager/config.o pager/display.o pager/dlg_pager.o \ pager/do_pager.o pager/functions.o pager/message.o \ - pager/pager.o pager/pbar.o pager/ppanel.o pager/private_data.o + pager/pager.o pager/pbar.o pager/ppanel.o pager/private_data.o pager/module.o CLEANFILES+= $(LIBPAGER) $(LIBPAGEROBJS) ALLOBJS+= $(LIBPAGEROBJS) @@ -756,7 +791,8 @@ $(PWD)/pager: ############################################################################### # libparse LIBPARSE= libparse.a -LIBPARSEOBJS= parse/extract.o parse/rc.o parse/set.o +LIBPARSEOBJS= parse/dump.o parse/extract.o parse/module.o parse/pcontext.o \ + parse/perror.o parse/rc.o parse/set.o CLEANFILES+= $(LIBPARSE) $(LIBPARSEOBJS) ALLOBJS+= $(LIBPARSEOBJS) @@ -772,7 +808,7 @@ LIBPATTERN= libpattern.a LIBPATTERNOBJS= pattern/compile.o pattern/complete.o pattern/config.o \ pattern/dlg_pattern.o pattern/exec.o pattern/expando.o \ pattern/flags.o pattern/functions.o pattern/message.o \ - pattern/pattern.o pattern/pattern_data.o pattern/search_state.o + pattern/pattern.o pattern/pattern_data.o pattern/search_state.o pattern/module.o CLEANFILES+= $(LIBPATTERN) $(LIBPATTERNOBJS) ALLOBJS+= $(LIBPATTERNOBJS) @@ -786,7 +822,7 @@ $(PWD)/pattern: # libpop LIBPOP= libpop.a LIBPOPOBJS= pop/adata.o pop/auth.o pop/config.o pop/edata.o pop/lib.o \ - pop/pop.o + pop/pop.o pop/module.o CLEANFILES+= $(LIBPOP) $(LIBPOPOBJS) ALLOBJS+= $(LIBPOPOBJS) @@ -799,7 +835,7 @@ $(PWD)/pop: ############################################################################### # libpostpone LIBPOSTPONE= libpostpone.a -LIBPOSTPONEOBJS=postpone/dlg_postpone.o postpone/functions.o postpone/postpone.o +LIBPOSTPONEOBJS=postpone/dlg_postpone.o postpone/functions.o postpone/postpone.o postpone/module.o CLEANFILES+= $(LIBPOSTPONE) $(LIBPOSTPONEOBJS) ALLOBJS+= $(LIBPOSTPONEOBJS) @@ -813,7 +849,7 @@ $(PWD)/postpone: # libprogress LIBPROGRESS= libprogress.a LIBPROGRESSOBJS=progress/config.o progress/progress.o progress/wdata.o \ - progress/window.o + progress/window.o progress/module.o CLEANFILES+= $(LIBPROGRESS) $(LIBPROGRESSOBJS) ALLOBJS+= $(LIBPROGRESSOBJS) @@ -826,7 +862,7 @@ $(PWD)/progress: ############################################################################### # libquestion LIBQUESTION= libquestion.a -LIBQUESTIONOBJS=question/question.o +LIBQUESTIONOBJS=question/question.o question/module.o CLEANFILES+= $(LIBQUESTION) $(LIBQUESTIONOBJS) ALLOBJS+= $(LIBQUESTIONOBJS) @@ -840,8 +876,8 @@ $(PWD)/question: # libsend LIBSEND= libsend.a LIBSENDOBJS= send/body.o send/config.o send/expando_msgid.o \ - send/expando_greeting.o send/header.o send/multipart.o \ - send/send.o send/sendlib.o send/sendmail.o send/smtp.o + send/expando_greeting.o send/header.o send/module.o \ + send/multipart.o send/send.o send/sendlib.o send/sendmail.o send/smtp.o CLEANFILES+= $(LIBSEND) $(LIBSENDOBJS) ALLOBJS+= $(LIBSENDOBJS) @@ -856,7 +892,7 @@ $(PWD)/send: LIBSIDEBAR= libsidebar.a LIBSIDEBAROBJS= sidebar/commands.o sidebar/config.o sidebar/expando.o \ sidebar/functions.o sidebar/observer.o sidebar/sidebar.o \ - sidebar/sort.o sidebar/wdata.o sidebar/window.o + sidebar/sort.o sidebar/wdata.o sidebar/window.o sidebar/module.o CLEANFILES+= $(LIBSIDEBAR) $(LIBSIDEBAROBJS) ALLOBJS+= $(LIBSIDEBAROBJS) @@ -894,7 +930,7 @@ LIBSTOREOBJS+= store/tc.o @endif @if HAVE_BDB || HAVE_GDBM || HAVE_KC || HAVE_LMDB || HAVE_QDBM || HAVE_ROCKSDB || HAVE_TDB || HAVE_TC LIBSTORE= libstore.a -LIBSTOREOBJS+= store/store.o +LIBSTOREOBJS+= store/store.o store/module.o CLEANFILES+= $(LIBSTORE) $(LIBSTOREOBJS) ALLOBJS+= $(LIBSTOREOBJS) @@ -928,18 +964,19 @@ $(ALLOBJS): # The order of these libraries depends on their dependencies. # The libraries with the most dependencies will come first. -MUTTLIBS+= $(LIBINDEX) $(LIBPAGER) $(LIBINDEX) $(LIBPAGER) $(LIBLUA) \ +MUTTLIBS+= $(LIBCORE) $(LIBINDEX) $(LIBPAGER) $(LIBINDEX) $(LIBPAGER) $(LIBLUA) \ $(LIBAUTOCRYPT) $(LIBPOP) $(LIBEDITOR) $(LIBCOMPLETE) \ $(LIBBROWSER) $(LIBCOMPMBOX) $(LIBSTORE) $(LIBPROGRESS) \ $(LIBQUESTION) $(LIBPOSTPONE) $(LIBALIAS) $(LIBSEND) \ $(LIBCONVERT) $(LIBCOMPOSE) $(LIBATTACH) $(LIBKEY) $(LIBGUI) \ - $(LIBNNTP) $(LIBPATTERN) $(LIBMENU) $(LIBCOLOR) $(LIBENVELOPE) \ - $(LIBHELPBAR) $(LIBMBOX) $(LIBMH) $(LIBNOTMUCH) $(LIBMAILDIR) \ - $(LIBEDITOR) $(LIBCOMPLETE) $(LIBNNTP) $(LIBNCRYPT) $(LIBIMAP) \ - $(LIBCONN) $(LIBHCACHE) $(LIBCOMPRESS) $(LIBSIDEBAR) \ - $(LIBBCACHE) $(LIBHISTORY) $(LIBCORE) $(LIBPARSE) \ - $(LIBEXPANDO) $(LIBCONFIG) $(LIBEMAIL) $(LIBADDRESS) \ - $(LIBDEBUG) $(LIBCLI) $(LIBMUTT) + $(LIBNNTP) $(LIBPATTERN) $(LIBMENU) $(LIBCOLOR) \ + $(LIBCOMMANDS) $(LIBHOOKS) $(LIBENVELOPE) $(LIBHELPBAR) \ + $(LIBMBOX) $(LIBMH) $(LIBNOTMUCH) $(LIBMAILDIR) $(LIBEDITOR) \ + $(LIBCOMPLETE) $(LIBNNTP) $(LIBNCRYPT) $(LIBIMAP) $(LIBCONN) \ + $(LIBHCACHE) $(LIBCOMPRESS) $(LIBSIDEBAR) $(LIBBCACHE) \ + $(LIBHISTORY) $(LIBCORE) $(LIBPARSE) $(LIBEXPANDO) \ + $(LIBCONFIG) $(LIBEMAIL) $(LIBADDRESS) $(LIBDEBUG) $(LIBCLI) \ + $(LIBMUTT) # neomutt $(NEOMUTT): $(GENERATED) $(NEOMUTTOBJS) $(MUTTLIBS) @@ -1014,20 +1051,28 @@ coverage: all test $(RM) coverage $(RM) mutt/exit.gc?? lcov --test-name "test" --output-file coverage.info --capture \ + --ignore-errors empty \ --directory address \ + --directory alias \ + --directory attach \ --directory cli \ --directory color \ + --directory commands \ --directory compress \ --directory config \ --directory core \ --directory editor \ --directory email \ --directory expando \ + --directory hooks \ --directory imap \ + --directory key \ + --directory lua \ --directory mutt \ --directory notmuch \ --directory parse \ --directory pattern \ + --directory sidebar \ --directory store \ --directory test/eqi \ --exclude '*/atoi.h' diff --git a/address/group.c b/address/group.c index eba11abf18b..15208206c41 100644 --- a/address/group.c +++ b/address/group.c @@ -3,7 +3,7 @@ * Handling for email address groups * * @authors - * Copyright (C) 2017-2023 Richard Russon + * Copyright (C) 2017-2025 Richard Russon * Copyright (C) 2018 Bo Yu * Copyright (C) 2018-2019 Pietro Cerutti * Copyright (C) 2019 Federico Kircheis @@ -35,13 +35,6 @@ #include "group.h" #include "address.h" -/** - * Groups - Hash Table: "group-name" -> Group - * - * A set of all the Address Groups. - */ -static struct HashTable *Groups = NULL; - /** * group_free - Free an Address Group * @param ptr Group to free @@ -62,16 +55,16 @@ static void group_free(struct Group **ptr) /** * group_new - Create a new Address Group - * @param pat Pattern + * @param name Group name * @retval ptr New Address Group * * @note The pattern will be copied */ -static struct Group *group_new(const char *pat) +static struct Group *group_new(const char *name) { struct Group *g = MUTT_MEM_CALLOC(1, struct Group); - g->name = mutt_str_dup(pat); + g->name = mutt_str_dup(name); STAILQ_INIT(&g->rs); TAILQ_INIT(&g->al); @@ -88,118 +81,126 @@ static void group_hash_free(int type, void *obj, intptr_t data) } /** - * mutt_grouplist_init - Initialize the GroupList singleton - * - * This is called once from init.c when initializing the global structures. + * group_remove - Remove a Group from the Hash Table + * @param groups Groups HashTable + * @param g Group to remove */ -void mutt_grouplist_init(void) +static void group_remove(struct HashTable *groups, struct Group *g) { - Groups = mutt_hash_new(1031, MUTT_HASH_NO_FLAGS); - - mutt_hash_set_destructor(Groups, group_hash_free, 0); + if (!groups || !g) + return; + mutt_hash_delete(groups, g->name, g); } /** - * mutt_grouplist_cleanup - Free GroupList singleton resource - * - * This is called once from init.c when deinitializing the global resources. + * group_is_empty - Is a Group empty? + * @param g Group to test + * @retval true The Group is empty */ -void mutt_grouplist_cleanup(void) +static bool group_is_empty(struct Group *g) { - mutt_hash_free(&Groups); + if (!g) + return true; + return TAILQ_EMPTY(&g->al) && STAILQ_EMPTY(&g->rs); } /** - * mutt_pattern_group - Match a pattern to a Group - * @param pat Pattern to match - * @retval ptr Matching Group, or new Group (if no match) + * group_add_addrlist - Add an Address List to a Group + * @param g Group to add to + * @param al Address List */ -struct Group *mutt_pattern_group(const char *pat) +static void group_add_addrlist(struct Group *g, const struct AddressList *al) { - if (!pat) - return NULL; + if (!g || !al) + return; - struct Group *g = mutt_hash_find(Groups, pat); - if (!g) + struct AddressList al_new = TAILQ_HEAD_INITIALIZER(al_new); + mutt_addrlist_copy(&al_new, al, false); + mutt_addrlist_remove_xrefs(&g->al, &al_new); + struct Address *a = NULL, *tmp = NULL; + TAILQ_FOREACH_SAFE(a, &al_new, entries, tmp) { - mutt_debug(LL_DEBUG2, "Creating group %s\n", pat); - g = group_new(pat); - mutt_hash_insert(Groups, g->name, g); + TAILQ_REMOVE(&al_new, a, entries); + mutt_addrlist_append(&g->al, a); } - - return g; + ASSERT(TAILQ_EMPTY(&al_new)); } /** - * group_remove - Remove a Group from the Hash Table - * @param g Group to remove + * group_add_regex - Add a Regex to a Group + * @param g Group to add to + * @param str Regex string to add + * @param flags Flags, e.g. REG_ICASE + * @param err Buffer for error message + * @retval 0 Success + * @retval -1 Error */ -static void group_remove(struct Group *g) +static int group_add_regex(struct Group *g, const char *str, uint16_t flags, struct Buffer *err) { - if (!g) - return; - mutt_hash_delete(Groups, g->name, g); + return mutt_regexlist_add(&g->rs, str, flags, err); } /** - * mutt_grouplist_clear - Clear a GroupList - * @param gl GroupList to clear + * group_remove_regex - Remove a Regex from a Group + * @param g Group to modify + * @param str Regex string to match + * @retval 0 Success + * @retval -1 Error */ -void mutt_grouplist_clear(struct GroupList *gl) +static int group_remove_regex(struct Group *g, const char *str) { - if (!gl) - return; - - struct GroupNode *np = STAILQ_FIRST(gl); - struct GroupNode *next = NULL; - while (np) - { - group_remove(np->group); - next = STAILQ_NEXT(np, entries); - FREE(&np); - np = next; - } - STAILQ_INIT(gl); + return mutt_regexlist_remove(&g->rs, str); } /** - * empty_group - Is a Group empty? - * @param g Group to test - * @retval true The Group is empty + * group_match - Does a string match an entry in a Group? + * @param g Group to match against + * @param str String to match + * @retval true There's a match */ -static bool empty_group(struct Group *g) +bool group_match(struct Group *g, const char *str) { - if (!g) + if (!g || !str) + return false; + + if (mutt_regexlist_match(&g->rs, str)) return true; - return TAILQ_EMPTY(&g->al) && STAILQ_EMPTY(&g->rs); + struct Address *a = NULL; + TAILQ_FOREACH(a, &g->al, entries) + { + if (a->mailbox && mutt_istr_equal(str, buf_string(a->mailbox))) + return true; + } + + return false; } /** - * mutt_grouplist_add - Add a Group to a GroupList - * @param gl GroupList to add to - * @param group Group to add + * grouplist_add_group - Add a Group to a GroupList + * @param gl GroupList to add to + * @param g Group to add */ -void mutt_grouplist_add(struct GroupList *gl, struct Group *group) +void grouplist_add_group(struct GroupList *gl, struct Group *g) { - if (!gl || !group) + if (!gl || !g) return; struct GroupNode *np = NULL; STAILQ_FOREACH(np, gl, entries) { - if (np->group == group) + if (np->group == g) return; } np = MUTT_MEM_CALLOC(1, struct GroupNode); - np->group = group; + np->group = g; STAILQ_INSERT_TAIL(gl, np, entries); } /** - * mutt_grouplist_destroy - Free a GroupList + * grouplist_destroy - Free a GroupList * @param gl GroupList to free */ -void mutt_grouplist_destroy(struct GroupList *gl) +void grouplist_destroy(struct GroupList *gl) { if (!gl) return; @@ -216,80 +217,127 @@ void mutt_grouplist_destroy(struct GroupList *gl) } /** - * group_add_addrlist - Add an Address List to a Group - * @param g Group to add to - * @param al Address List + * grouplist_add_addrlist - Add Address list to a GroupList + * @param gl GroupList to add to + * @param al Address list to add */ -static void group_add_addrlist(struct Group *g, const struct AddressList *al) +void grouplist_add_addrlist(struct GroupList *gl, struct AddressList *al) { - if (!g || !al) + if (!gl || !al) return; - struct AddressList al_new = TAILQ_HEAD_INITIALIZER(al_new); - mutt_addrlist_copy(&al_new, al, false); - mutt_addrlist_remove_xrefs(&g->al, &al_new); - struct Address *a = NULL, *tmp = NULL; - TAILQ_FOREACH_SAFE(a, &al_new, entries, tmp) + struct GroupNode *np = NULL; + STAILQ_FOREACH(np, gl, entries) { - TAILQ_REMOVE(&al_new, a, entries); - mutt_addrlist_append(&g->al, a); + group_add_addrlist(np->group, al); } - ASSERT(TAILQ_EMPTY(&al_new)); } /** - * group_add_regex - Add a Regex to a Group - * @param g Group to add to - * @param s Regex string to add + * grouplist_add_regex - Add matching Addresses to a GroupList + * @param gl GroupList to add to + * @param str Address regex string to match * @param flags Flags, e.g. REG_ICASE * @param err Buffer for error message * @retval 0 Success * @retval -1 Error */ -static int group_add_regex(struct Group *g, const char *s, uint16_t flags, struct Buffer *err) +int grouplist_add_regex(struct GroupList *gl, const char *str, uint16_t flags, + struct Buffer *err) { - return mutt_regexlist_add(&g->rs, s, flags, err); + if (!gl || !str) + return -1; + + int rc = 0; + + struct GroupNode *np = NULL; + STAILQ_FOREACH(np, gl, entries) + { + rc = group_add_regex(np->group, str, flags, err); + if (rc) + return rc; + } + return rc; } /** - * group_remove_regex - Remove a Regex from a Group - * @param g Group to modify - * @param s Regex string to match - * @retval 0 Success - * @retval -1 Error + * groups_new - Create a HashTable for the Address Groups + * @retval ptr New Groups HashTable */ -static int group_remove_regex(struct Group *g, const char *s) +struct HashTable *groups_new(void) { - return mutt_regexlist_remove(&g->rs, s); + struct HashTable *groups = mutt_hash_new(1031, MUTT_HASH_NO_FLAGS); + + mutt_hash_set_destructor(groups, group_hash_free, 0); + + return groups; } /** - * mutt_grouplist_add_addrlist - Add Address list to a GroupList - * @param gl GroupList to add to - * @param al Address list to add + * groups_free - Free Address Groups HashTable + * @param pptr Groups HashTable to free */ -void mutt_grouplist_add_addrlist(struct GroupList *gl, struct AddressList *al) +void groups_free(struct HashTable **pptr) { - if (!gl || !al) + mutt_hash_free(pptr); +} + +/** + * groups_get_group - Get a Group by its name + * @param groups Groups HashTable + * @param name Name to find + * @retval ptr Matching Group, or new Group (if no match) + */ +struct Group *groups_get_group(struct HashTable *groups, const char *name) +{ + if (!groups || !name) + return NULL; + + struct Group *g = mutt_hash_find(groups, name); + if (!g) + { + mutt_debug(LL_DEBUG2, "Creating group %s\n", name); + g = group_new(name); + mutt_hash_insert(groups, g->name, g); + } + + return g; +} + +/** + * groups_remove_grouplist - Clear a GroupList + * @param groups Groups HashTable + * @param gl GroupList to clear + */ +void groups_remove_grouplist(struct HashTable *groups, struct GroupList *gl) +{ + if (!groups || !gl) return; - struct GroupNode *np = NULL; - STAILQ_FOREACH(np, gl, entries) + struct GroupNode *np = STAILQ_FIRST(gl); + struct GroupNode *next = NULL; + while (np) { - group_add_addrlist(np->group, al); + group_remove(groups, np->group); + next = STAILQ_NEXT(np, entries); + FREE(&np); + np = next; } + STAILQ_INIT(gl); } /** - * mutt_grouplist_remove_addrlist - Remove an AddressList from a GroupList - * @param gl GroupList to remove from - * @param al AddressList to remove + * groups_remove_addrlist - Remove an AddressList from a GroupList + * @param groups Groups HashTable + * @param gl GroupList to remove from + * @param al AddressList to remove * @retval 0 Success * @retval -1 Error */ -int mutt_grouplist_remove_addrlist(struct GroupList *gl, struct AddressList *al) +int groups_remove_addrlist(struct HashTable *groups, struct GroupList *gl, + struct AddressList *al) { - if (!gl || !al) + if (!groups || !gl || !al) return -1; struct GroupNode *gnp = NULL; @@ -300,9 +348,9 @@ int mutt_grouplist_remove_addrlist(struct GroupList *gl, struct AddressList *al) { mutt_addrlist_remove(&gnp->group->al, buf_string(a->mailbox)); } - if (empty_group(gnp->group)) + if (group_is_empty(gnp->group)) { - group_remove(gnp->group); + group_remove(groups, gnp->group); } } @@ -310,76 +358,27 @@ int mutt_grouplist_remove_addrlist(struct GroupList *gl, struct AddressList *al) } /** - * mutt_grouplist_add_regex - Add matching Addresses to a GroupList - * @param gl GroupList to add to - * @param s Address to match - * @param flags Flags, e.g. REG_ICASE - * @param err Buffer for error message + * groups_remove_regex - Remove matching addresses from a GroupList + * @param groups Groups HashTable + * @param gl GroupList to remove from + * @param str Regex string to match * @retval 0 Success * @retval -1 Error */ -int mutt_grouplist_add_regex(struct GroupList *gl, const char *s, - uint16_t flags, struct Buffer *err) +int groups_remove_regex(struct HashTable *groups, struct GroupList *gl, const char *str) { - if (!gl || !s) + if (!groups || !gl || !str) return -1; int rc = 0; - struct GroupNode *np = NULL; STAILQ_FOREACH(np, gl, entries) { - rc = group_add_regex(np->group, s, flags, err); - if (rc) + rc = group_remove_regex(np->group, str); + if (group_is_empty(np->group)) + group_remove(groups, np->group); + if (rc != 0) return rc; } return rc; } - -/** - * mutt_grouplist_remove_regex - Remove matching addresses from a GroupList - * @param gl GroupList to remove from - * @param s Address to match - * @retval 0 Success - * @retval -1 Error - */ -int mutt_grouplist_remove_regex(struct GroupList *gl, const char *s) -{ - if (!gl || !s) - return -1; - - int rc = 0; - struct GroupNode *np = NULL; - STAILQ_FOREACH(np, gl, entries) - { - rc = group_remove_regex(np->group, s); - if (empty_group(np->group)) - group_remove(np->group); - if (rc) - return rc; - } - return rc; -} - -/** - * mutt_group_match - Does a string match an entry in a Group? - * @param g Group to match against - * @param s String to match - * @retval true There's a match - */ -bool mutt_group_match(struct Group *g, const char *s) -{ - if (!g || !s) - return false; - - if (mutt_regexlist_match(&g->rs, s)) - return true; - struct Address *a = NULL; - TAILQ_FOREACH(a, &g->al, entries) - { - if (a->mailbox && mutt_istr_equal(s, buf_string(a->mailbox))) - return true; - } - - return false; -} diff --git a/address/group.h b/address/group.h index 15db6a56c71..42925e8cb25 100644 --- a/address/group.h +++ b/address/group.h @@ -3,7 +3,7 @@ * Handling for email address groups * * @authors - * Copyright (C) 2017-2023 Richard Russon + * Copyright (C) 2017-2025 Richard Russon * Copyright (C) 2018 Pietro Cerutti * * @copyright @@ -29,9 +29,6 @@ #include "mutt/lib.h" #include "address.h" -#define MUTT_GROUP 0 ///< 'group' config command -#define MUTT_UNGROUP 1 ///< 'ungroup' config command - /** * struct Group - A set of email addresses */ @@ -52,17 +49,22 @@ struct GroupNode }; STAILQ_HEAD(GroupList, GroupNode); -void mutt_grouplist_add (struct GroupList *gl, struct Group *group); -void mutt_grouplist_add_addrlist (struct GroupList *gl, struct AddressList *a); -int mutt_grouplist_add_regex (struct GroupList *gl, const char *s, uint16_t flags, struct Buffer *err); -void mutt_grouplist_cleanup (void); -void mutt_grouplist_clear (struct GroupList *gl); -void mutt_grouplist_destroy (struct GroupList *gl); -void mutt_grouplist_init (void); -int mutt_grouplist_remove_addrlist(struct GroupList *gl, struct AddressList *a); -int mutt_grouplist_remove_regex (struct GroupList *gl, const char *s); +// Single Address Group +bool group_match(struct Group *g, const char *str); + +// List of Addres Groups +void grouplist_add_group (struct GroupList *gl, struct Group *g); +void grouplist_add_addrlist(struct GroupList *gl, struct AddressList *al); +int grouplist_add_regex (struct GroupList *gl, const char *str, uint16_t flags, struct Buffer *err); +void grouplist_destroy (struct GroupList *gl); + +// HashTable of Address Groups +struct HashTable *groups_new (void); +void groups_free(struct HashTable **pptr); -bool mutt_group_match (struct Group *g, const char *s); -struct Group *mutt_pattern_group(const char *pat); +struct Group *groups_get_group (struct HashTable *groups, const char *name); +int groups_remove_addrlist (struct HashTable *groups, struct GroupList *gl, struct AddressList *al); +void groups_remove_grouplist(struct HashTable *groups, struct GroupList *gl); +int groups_remove_regex (struct HashTable *groups, struct GroupList *gl, const char *str); #endif /* MUTT_ADDRESS_GROUP_H */ diff --git a/address/lib.h b/address/lib.h index 8a1c8c73f6c..f5933a0a3fd 100644 --- a/address/lib.h +++ b/address/lib.h @@ -31,6 +31,8 @@ * | address/config_type.c | @subpage addr_config_type | * | address/group.c | @subpage addr_group | * | address/idna.c | @subpage addr_idna | + * | address/maillist.c | @subpage addr_maillist | + * | address/module.c | @subpage addr_module | */ #ifndef MUTT_ADDRESS_LIB_H @@ -41,6 +43,7 @@ #include "config_type.h" #include "group.h" #include "idna2.h" +#include "maillist.h" // IWYU pragma: end_keep #endif /* MUTT_ADDRESS_LIB_H */ diff --git a/maillist.c b/address/maillist.c similarity index 98% rename from maillist.c rename to address/maillist.c index 07b0cfbbc97..e12be52d985 100644 --- a/maillist.c +++ b/address/maillist.c @@ -22,7 +22,7 @@ */ /** - * @page neo_maillist Handle mailing lists + * @page addr_maillist Handle mailing lists * * Handle mailing lists */ @@ -31,9 +31,9 @@ #include #include #include "mutt/lib.h" -#include "address/lib.h" #include "email/lib.h" #include "maillist.h" +#include "address.h" #include "muttlib.h" /** diff --git a/maillist.h b/address/maillist.h similarity index 92% rename from maillist.h rename to address/maillist.h index 55697532c40..07959f92185 100644 --- a/maillist.h +++ b/address/maillist.h @@ -20,8 +20,8 @@ * this program. If not, see . */ -#ifndef MUTT_MAILLIST_H -#define MUTT_MAILLIST_H +#ifndef MUTT_ADDRESS_MAILLIST_H +#define MUTT_ADDRESS_MAILLIST_H #include #include @@ -35,4 +35,4 @@ bool first_mailing_list (char *buf, size_t buflen, struct AddressList *a bool mutt_is_mail_list (const struct Address *addr); bool mutt_is_subscribed_list (const struct Address *addr); -#endif /* MUTT_MAILLIST_H */ +#endif /* MUTT_ADDRESS_MAILLIST_H */ diff --git a/address/module.c b/address/module.c new file mode 100644 index 00000000000..eda153b1cd5 --- /dev/null +++ b/address/module.c @@ -0,0 +1,58 @@ +/** + * @file + * Definition of the Address Module + * + * @authors + * Copyright (C) 2025-2026 Richard Russon + * + * @copyright + * This program is free software: 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 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page addr_module Definition of the Address Module + * + * Definition of the Address Module + */ + +#include "config.h" +#include +#include +#include "config/lib.h" +#include "core/lib.h" + +extern const struct ConfigSetType CstAddress; + +/** + * address_config_define_types - Set up Config Types - Implements Module::config_define_types() + */ +static bool address_config_define_types(struct NeoMutt *n, struct ConfigSet *cs) +{ + return cs_register_type(cs, &CstAddress); +} + +/** + * ModuleAddress - Module for the Address library + */ +const struct Module ModuleAddress = { + "address", + NULL, // init + address_config_define_types, + NULL, // config_define_variables + NULL, // commands_register + NULL, // gui_init + NULL, // gui_cleanup + NULL, // cleanup + NULL, // mod_data +}; diff --git a/alias/alias.c b/alias/alias.c index bb4f890c822..d32dc57453f 100644 --- a/alias/alias.c +++ b/alias/alias.c @@ -3,7 +3,7 @@ * Representation of a single alias to an email address * * @authors - * Copyright (C) 2017-2023 Richard Russon + * Copyright (C) 2017-2025 Richard Russon * Copyright (C) 2017-2023 Pietro Cerutti * Copyright (C) 2019 Federico Kircheis * Copyright (C) 2023 Anna Figueiredo Gomes @@ -44,21 +44,19 @@ #include "email/lib.h" #include "core/lib.h" #include "gui/lib.h" -#include "mutt.h" #include "alias.h" #include "lib.h" #include "browser/lib.h" +#include "commands/lib.h" #include "editor/lib.h" #include "history/lib.h" #include "question/lib.h" #include "send/lib.h" -#include "alternates.h" #include "globals.h" -#include "maillist.h" #include "muttlib.h" #include "reverse.h" -struct AliasList Aliases = TAILQ_HEAD_INITIALIZER(Aliases); ///< List of all the user's email aliases +struct AliasArray Aliases = ARRAY_HEAD_INITIALIZER; ///< List of all the user's email aliases /** * write_safe_address - Defang malicious email addresses @@ -275,10 +273,12 @@ static bool string_is_address(const char *str, const char *user, const char *dom */ struct AddressList *alias_lookup(const char *name) { - struct Alias *a = NULL; + struct Alias **ap = NULL; - TAILQ_FOREACH(a, &Aliases, entries) + ARRAY_FOREACH(ap, &Aliases) { + struct Alias *a = *ap; + if (mutt_istr_equal(name, a->name)) return &a->addr; } @@ -508,7 +508,7 @@ void alias_create(struct AddressList *al, const struct ConfigSubset *sub) } alias_reverse_add(alias); - TAILQ_INSERT_TAIL(&Aliases, alias, entries); + ARRAY_ADD(&Aliases, alias); const char *const c_alias_file = cs_subset_path(sub, "alias_file"); buf_strcpy(buf, c_alias_file); @@ -519,7 +519,7 @@ void alias_create(struct AddressList *al, const struct ConfigSubset *sub) { goto done; } - buf_expand_path(buf); + expand_path(buf, false); fp_alias = mutt_file_fopen(buf_string(buf), "a+"); if (!fp_alias) { @@ -686,22 +686,21 @@ void alias_free(struct Alias **ptr) /** * aliaslist_clear - Empty a List of Aliases - * @param al AliasList to empty + * @param aa AliasArray to empty * - * Each Alias will be freed and the AliasList will be left empty. + * Each Alias will be freed and the AliasArray will be left empty. */ -void aliaslist_clear(struct AliasList *al) +void aliaslist_clear(struct AliasArray *aa) { - if (!al) + if (!aa) return; - struct Alias *np = NULL, *tmp = NULL; - TAILQ_FOREACH_SAFE(np, al, entries, tmp) + struct Alias **ap = NULL; + ARRAY_FOREACH(ap, aa) { - TAILQ_REMOVE(al, np, entries); - alias_free(&np); + alias_free(ap); } - TAILQ_INIT(al); + ARRAY_FREE(aa); } /** @@ -717,10 +716,10 @@ void alias_init(void) */ void alias_cleanup(void) { - struct Alias *np = NULL; - TAILQ_FOREACH(np, &Aliases, entries) + struct Alias **ap = NULL; + ARRAY_FOREACH(ap, &Aliases) { - alias_reverse_delete(np); + alias_reverse_delete(*ap); } aliaslist_clear(&Aliases); alias_reverse_shutdown(); diff --git a/alias/alias.h b/alias/alias.h index 345284592f4..9aa55ca8dcf 100644 --- a/alias/alias.h +++ b/alias/alias.h @@ -37,11 +37,10 @@ struct Alias struct AddressList addr; ///< List of Addresses the Alias expands to char *comment; ///< Free-form comment string struct TagList tags; ///< Tags - TAILQ_ENTRY(Alias) entries; ///< Linked list }; -TAILQ_HEAD(AliasList, Alias); +ARRAY_HEAD(AliasArray, struct Alias *); -extern struct AliasList Aliases; +extern struct AliasArray Aliases; /** * enum NotifyAlias - Alias notification types @@ -70,6 +69,6 @@ struct EventAlias void alias_free(struct Alias **ptr); struct Alias *alias_new (void); -void aliaslist_clear(struct AliasList *al); +void aliaslist_clear(struct AliasArray *aa); #endif /* MUTT_ALIAS_ALIAS_H */ diff --git a/alias/commands.c b/alias/commands.c index d7547bbca06..c1773fe7842 100644 --- a/alias/commands.c +++ b/alias/commands.c @@ -4,7 +4,7 @@ * * @authors * Copyright (C) 2020 Pietro Cerutti - * Copyright (C) 2020-2023 Richard Russon + * Copyright (C) 2020-2025 Richard Russon * * @copyright * This program is free software: you can redistribute it and/or modify it under @@ -28,15 +28,14 @@ */ #include "config.h" -#include #include #include "mutt/lib.h" #include "address/lib.h" #include "config/lib.h" #include "email/lib.h" #include "core/lib.h" -#include "commands.h" #include "lib.h" +#include "commands/lib.h" #include "parse/lib.h" #include "alias.h" #include "reverse.h" @@ -131,39 +130,45 @@ void parse_alias_comments(struct Alias *alias, const char *com) * parse_alias - Parse the 'alias' command - Implements Command::parse() - @ingroup command_parse * * e.g. "alias jim James Smith # Pointy-haired boss" + * + * Parse: + * - `alias [ -group ... ]
[,
...] [ # [ ] [ tags:... ]]` */ -enum CommandResult parse_alias(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) +enum CommandResult parse_alias(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, struct ParseError *pe) { - struct Alias *tmp = NULL; - struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl); - enum NotifyAlias event; + struct Buffer *err = pe->message; - if (!MoreArgs(s)) + if (!MoreArgs(line)) { - buf_strcpy(err, _("alias: no address")); + buf_printf(err, _("%s: too few arguments"), cmd->name); return MUTT_CMD_WARNING; } + struct Alias *a = NULL; + enum NotifyAlias event; + struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl); + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_ERROR; + /* name */ - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - mutt_debug(LL_DEBUG5, "First token is '%s'\n", buf->data); - if (parse_grouplist(&gl, buf, s, err) == -1) - { - return MUTT_CMD_ERROR; - } - char *name = mutt_str_dup(buf->data); + parse_extract_token(token, line, TOKEN_NO_FLAGS); + mutt_debug(LL_DEBUG5, "First token is '%s'\n", buf_string(token)); + if (parse_grouplist(&gl, token, line, err, NeoMutt->groups) == -1) + goto done; + + char *name = mutt_str_dup(buf_string(token)); /* address list */ - parse_extract_token(buf, s, TOKEN_QUOTE | TOKEN_SPACE | TOKEN_SEMICOLON); - mutt_debug(LL_DEBUG5, "Second token is '%s'\n", buf->data); + parse_extract_token(token, line, TOKEN_QUOTE | TOKEN_SPACE | TOKEN_SEMICOLON); + mutt_debug(LL_DEBUG5, "Second token is '%s'\n", buf_string(token)); struct AddressList al = TAILQ_HEAD_INITIALIZER(al); - int parsed = mutt_addrlist_parse2(&al, buf->data); + int parsed = mutt_addrlist_parse2(&al, buf_string(token)); if (parsed == 0) { - buf_printf(err, _("Warning: Bad address '%s' in alias '%s'"), buf->data, name); + buf_printf(err, _("Warning: Bad address '%s' in alias '%s'"), buf_string(token), name); FREE(&name); - goto bail; + goto done; } /* IDN */ @@ -173,110 +178,132 @@ enum CommandResult parse_alias(struct Buffer *buf, struct Buffer *s, buf_printf(err, _("Warning: Bad IDN '%s' in alias '%s'"), estr, name); FREE(&name); FREE(&estr); - goto bail; + goto done; } /* check to see if an alias with this name already exists */ - TAILQ_FOREACH(tmp, &Aliases, entries) + struct Alias **ap = NULL; + ARRAY_FOREACH(ap, &Aliases) { - if (mutt_istr_equal(tmp->name, name)) + if (mutt_istr_equal((*ap)->name, name)) + { + a = *ap; break; + } } - if (tmp) + if (a) { FREE(&name); - alias_reverse_delete(tmp); + alias_reverse_delete(a); /* override the previous value */ - mutt_addrlist_clear(&tmp->addr); - FREE(&tmp->comment); + mutt_addrlist_clear(&a->addr); + FREE(&a->comment); event = NT_ALIAS_CHANGE; } else { /* create a new alias */ - tmp = alias_new(); - tmp->name = name; - TAILQ_INSERT_TAIL(&Aliases, tmp, entries); + a = alias_new(); + a->name = name; + ARRAY_ADD(&Aliases, a); event = NT_ALIAS_ADD; } - tmp->addr = al; + a->addr = al; - mutt_grouplist_add_addrlist(&gl, &tmp->addr); + grouplist_add_addrlist(&gl, &a->addr); const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level"); if (c_debug_level > LL_DEBUG4) { - /* A group is terminated with an empty address, so check a->mailbox */ - struct Address *a = NULL; - TAILQ_FOREACH(a, &tmp->addr, entries) + /* A group is terminated with an empty address, so check addr->mailbox */ + struct Address *addr = NULL; + TAILQ_FOREACH(addr, &a->addr, entries) { - if (!a->mailbox) + if (!addr->mailbox) break; - if (a->group) - mutt_debug(LL_DEBUG5, " Group %s\n", buf_string(a->mailbox)); + if (addr->group) + mutt_debug(LL_DEBUG5, " Group %s\n", buf_string(addr->mailbox)); else - mutt_debug(LL_DEBUG5, " %s\n", buf_string(a->mailbox)); + mutt_debug(LL_DEBUG5, " %s\n", buf_string(addr->mailbox)); } } - mutt_grouplist_destroy(&gl); - if (!MoreArgs(s) && (s->dptr[0] == '#')) + + if (!MoreArgs(line) && (line->dptr[0] == '#')) { - s->dptr++; // skip over the "# " - if (*s->dptr == ' ') - s->dptr++; + line->dptr++; // skip over the "# " + if (*line->dptr == ' ') + line->dptr++; - parse_alias_comments(tmp, s->dptr); - *s->dptr = '\0'; // We're done parsing + parse_alias_comments(a, line->dptr); + *line->dptr = '\0'; // We're done parsing } - alias_reverse_add(tmp); + alias_reverse_add(a); mutt_debug(LL_NOTIFY, "%s: %s\n", - (event == NT_ALIAS_ADD) ? "NT_ALIAS_ADD" : "NT_ALIAS_CHANGE", tmp->name); - struct EventAlias ev_a = { tmp }; + (event == NT_ALIAS_ADD) ? "NT_ALIAS_ADD" : "NT_ALIAS_CHANGE", a->name); + struct EventAlias ev_a = { a }; notify_send(NeoMutt->notify, NT_ALIAS, event, &ev_a); - return MUTT_CMD_SUCCESS; + rc = MUTT_CMD_SUCCESS; -bail: - mutt_grouplist_destroy(&gl); - return MUTT_CMD_ERROR; +done: + buf_pool_release(&token); + grouplist_destroy(&gl); + return rc; } /** * parse_unalias - Parse the 'unalias' command - Implements Command::parse() - @ingroup command_parse + * + * Parse: + * - `unalias { * | ... }` */ -enum CommandResult parse_unalias(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) +enum CommandResult parse_unalias(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, struct ParseError *pe) { + struct Buffer *err = pe->message; + + if (!MoreArgs(line)) + { + buf_printf(err, _("%s: too few arguments"), cmd->name); + return MUTT_CMD_WARNING; + } + + struct Buffer *token = buf_pool_get(); + do { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); + parse_extract_token(token, line, TOKEN_NO_FLAGS); - struct Alias *np = NULL; - if (mutt_str_equal("*", buf->data)) + struct Alias **ap = NULL; + if (mutt_str_equal("*", buf_string(token))) { - TAILQ_FOREACH(np, &Aliases, entries) + ARRAY_FOREACH(ap, &Aliases) { - alias_reverse_delete(np); + alias_reverse_delete(*ap); } aliaslist_clear(&Aliases); - return MUTT_CMD_SUCCESS; + goto done; } - TAILQ_FOREACH(np, &Aliases, entries) + ARRAY_FOREACH(ap, &Aliases) { - if (!mutt_istr_equal(buf->data, np->name)) + if (!mutt_istr_equal(buf_string(token), (*ap)->name)) continue; - TAILQ_REMOVE(&Aliases, np, entries); - alias_reverse_delete(np); - alias_free(&np); + struct Alias *a = *ap; + ARRAY_REMOVE(&Aliases, ap); + alias_reverse_delete(a); + alias_free(&a); break; } - } while (MoreArgs(s)); + } while (MoreArgs(line)); + +done: + buf_pool_release(&token); return MUTT_CMD_SUCCESS; } diff --git a/alias/config.c b/alias/config.c index e9c23b4d1f3..be9c041ce1a 100644 --- a/alias/config.c +++ b/alias/config.c @@ -4,7 +4,7 @@ * * @authors * Copyright (C) 2020 Romeu Vieira - * Copyright (C) 2020-2024 Richard Russon + * Copyright (C) 2020-2026 Richard Russon * Copyright (C) 2023 наб * * @copyright @@ -29,7 +29,6 @@ */ #include "config.h" -#include #include #include "mutt/lib.h" #include "config/lib.h" @@ -110,7 +109,7 @@ static const struct ExpandoDefinition QueryFormatDef[] = { /** * AliasVars - Config definitions for the alias library */ -static struct ConfigDef AliasVars[] = { +struct ConfigDef AliasVars[] = { // clang-format off { "alias_file", DT_PATH|D_PATH_FILE, IP "~/.neomuttrc", 0, NULL, "Save new aliases to this file" @@ -133,11 +132,3 @@ static struct ConfigDef AliasVars[] = { { NULL }, // clang-format on }; - -/** - * config_init_alias - Register alias config variables - Implements ::module_init_config_t - @ingroup cfg_module_api - */ -bool config_init_alias(struct ConfigSet *cs) -{ - return cs_register_variables(cs, AliasVars); -} diff --git a/alias/dlg_alias.c b/alias/dlg_alias.c index cb8ef5f480a..5a237abce06 100644 --- a/alias/dlg_alias.c +++ b/alias/dlg_alias.c @@ -3,7 +3,7 @@ * Address book * * @authors - * Copyright (C) 2017-2024 Richard Russon + * Copyright (C) 2017-2025 Richard Russon * Copyright (C) 2020 Romeu Vieira * Copyright (C) 2020-2023 Pietro Cerutti * Copyright (C) 2023 Anna Figueiredo Gomes @@ -291,12 +291,14 @@ static bool dlg_alias(struct AliasMenuData *mdata) // Event Loop int rc = 0; int op = OP_NULL; + struct KeyEvent event = { 0, OP_NULL }; do { - menu_tagging_dispatcher(menu->win, op); + menu_tagging_dispatcher(menu->win, &event); window_redraw(NULL); - op = km_dokey(MENU_ALIAS, GETCH_NO_FLAGS); + event = km_dokey(MENU_ALIAS, GETCH_NO_FLAGS); + op = event.op; mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", opcodes_get_name(op), op); if (op < 0) continue; @@ -307,11 +309,11 @@ static bool dlg_alias(struct AliasMenuData *mdata) } mutt_clear_error(); - rc = alias_function_dispatcher(sdw.dlg, op); + rc = alias_function_dispatcher(sdw.dlg, &event); if (rc == FR_UNKNOWN) - rc = menu_function_dispatcher(menu->win, op); + rc = menu_function_dispatcher(menu->win, &event); if (rc == FR_UNKNOWN) - rc = global_function_dispatcher(menu->win, op); + rc = global_function_dispatcher(menu->win, &event); } while ((rc != FR_DONE) && (rc != FR_CONTINUE)); // --------------------------------------------------------------------------- @@ -334,7 +336,6 @@ static bool dlg_alias(struct AliasMenuData *mdata) */ int alias_complete(struct Buffer *buf, struct ConfigSubset *sub) { - struct Alias *np = NULL; char bestname[8192] = { 0 }; struct Alias *a_best = NULL; int count = 0; @@ -345,22 +346,23 @@ int alias_complete(struct Buffer *buf, struct ConfigSubset *sub) if (buf_at(buf, 0) != '\0') { - TAILQ_FOREACH(np, &Aliases, entries) + struct Alias **ap = NULL; + ARRAY_FOREACH(ap, &Aliases) { - if (np->name && mutt_strn_equal(np->name, buf_string(buf), buf_len(buf))) + struct Alias *a = *ap; + if (a->name && mutt_strn_equal(a->name, buf_string(buf), buf_len(buf))) { - a_best = np; + a_best = a; count++; if (bestname[0] == '\0') /* init */ { - mutt_str_copy(bestname, np->name, - MIN(mutt_str_len(np->name) + 1, sizeof(bestname))); + mutt_str_copy(bestname, a->name, MIN(mutt_str_len(a->name) + 1, sizeof(bestname))); } else { int i; - for (i = 0; np->name[i] && (np->name[i] == bestname[i]); i++) + for (i = 0; a->name[i] && (a->name[i] == bestname[i]); i++) ; // do nothing bestname[i] = '\0'; @@ -383,9 +385,9 @@ int alias_complete(struct Buffer *buf, struct ConfigSubset *sub) { // Create a View Array of all the Aliases FREE(&mdata.limit); - TAILQ_FOREACH(np, &Aliases, entries) + ARRAY_FOREACH(ap, &Aliases) { - alias_array_alias_add(&mdata.ava, np); + alias_array_alias_add(&mdata.ava, *ap); } } else @@ -406,13 +408,14 @@ int alias_complete(struct Buffer *buf, struct ConfigSubset *sub) } /* build alias list and show it */ - TAILQ_FOREACH(np, &Aliases, entries) + ARRAY_FOREACH(ap, &Aliases) { - int aasize = alias_array_alias_add(&mdata.ava, np); + struct Alias *a = *ap; + int aasize = alias_array_alias_add(&mdata.ava, a); struct AliasView *av = ARRAY_GET(&mdata.ava, aasize - 1); - if (np->name && !mutt_strn_equal(np->name, buf_string(buf), buf_len(buf))) + if (a->name && !mutt_strn_equal(a->name, buf_string(buf), buf_len(buf))) { av->is_visible = false; } @@ -422,9 +425,10 @@ int alias_complete(struct Buffer *buf, struct ConfigSubset *sub) if (ARRAY_EMPTY(&mdata.ava)) { - TAILQ_FOREACH(np, &Aliases, entries) + struct Alias **ap = NULL; + ARRAY_FOREACH(ap, &Aliases) { - alias_array_alias_add(&mdata.ava, np); + alias_array_alias_add(&mdata.ava, *ap); } mutt_pattern_alias_func(NULL, &mdata, PAA_VISIBLE, NULL); @@ -456,7 +460,16 @@ int alias_complete(struct Buffer *buf, struct ConfigSubset *sub) if (!avp->is_deleted) continue; - TAILQ_REMOVE(&Aliases, avp->alias, entries); + // Find and remove the alias from the Aliases array + struct Alias **ap = NULL; + ARRAY_FOREACH(ap, &Aliases) + { + if (*ap == avp->alias) + { + ARRAY_REMOVE(&Aliases, ap); + break; + } + } alias_free(&avp->alias); } @@ -475,15 +488,15 @@ int alias_complete(struct Buffer *buf, struct ConfigSubset *sub) */ void alias_dialog(struct Mailbox *m, struct ConfigSubset *sub) { - struct Alias *np = NULL; + struct Alias **ap = NULL; struct AliasMenuData mdata = { ARRAY_HEAD_INITIALIZER, NULL, sub }; mdata.search_state = search_state_new(); // Create a View Array of all the Aliases - TAILQ_FOREACH(np, &Aliases, entries) + ARRAY_FOREACH(ap, &Aliases) { - alias_array_alias_add(&mdata.ava, np); + alias_array_alias_add(&mdata.ava, *ap); } if (!dlg_alias(&mdata)) @@ -515,7 +528,15 @@ void alias_dialog(struct Mailbox *m, struct ConfigSubset *sub) { if (avp->is_deleted) { - TAILQ_REMOVE(&Aliases, avp->alias, entries); + // Find and remove the alias from the Aliases array + ARRAY_FOREACH(ap, &Aliases) + { + if (*ap == avp->alias) + { + ARRAY_REMOVE(&Aliases, ap); + break; + } + } alias_free(&avp->alias); } } diff --git a/alias/dlg_query.c b/alias/dlg_query.c index 9bbd1d3d481..3f9c79e3d00 100644 --- a/alias/dlg_query.c +++ b/alias/dlg_query.c @@ -3,7 +3,7 @@ * Routines for querying an external address book * * @authors - * Copyright (C) 2017-2024 Richard Russon + * Copyright (C) 2017-2025 Richard Russon * Copyright (C) 2020-2023 Pietro Cerutti * Copyright (C) 2023 Dennis Schön * Copyright (C) 2023-2024 Tóth János @@ -80,7 +80,6 @@ #include "email/lib.h" #include "core/lib.h" #include "gui/lib.h" -#include "mutt.h" #include "lib.h" #include "editor/lib.h" #include "expando/lib.h" @@ -180,12 +179,13 @@ static int query_tag(struct Menu *menu, int sel, int act) * query_run - Run an external program to find Addresses * @param s String to match * @param verbose If true, print progress messages - * @param al Alias list to fill + * @param aa Alias list to fill * @param sub Config items * @retval 0 Success * @retval -1 Error */ -int query_run(const char *s, bool verbose, struct AliasList *al, const struct ConfigSubset *sub) +int query_run(const char *s, bool verbose, struct AliasArray *aa, + const struct ConfigSubset *sub) { FILE *fp = NULL; char *buf = NULL; @@ -249,7 +249,7 @@ int query_run(const char *s, bool verbose, struct AliasList *al, const struct Co mutt_addrlist_parse(&alias->addr, buf); // Email address } - TAILQ_INSERT_TAIL(al, alias, entries); + ARRAY_ADD(aa, alias); } buf_pool_release(&addr); @@ -365,12 +365,14 @@ static bool dlg_query(struct Buffer *buf, struct AliasMenuData *mdata) // Event Loop int rc = 0; int op = OP_NULL; + struct KeyEvent event = { 0, OP_NULL }; do { - menu_tagging_dispatcher(menu->win, op); + menu_tagging_dispatcher(menu->win, &event); window_redraw(NULL); - op = km_dokey(MENU_QUERY, GETCH_NO_FLAGS); + event = km_dokey(MENU_QUERY, GETCH_NO_FLAGS); + op = event.op; mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", opcodes_get_name(op), op); if (op < 0) continue; @@ -381,11 +383,11 @@ static bool dlg_query(struct Buffer *buf, struct AliasMenuData *mdata) } mutt_clear_error(); - rc = alias_function_dispatcher(sdw.dlg, op); + rc = alias_function_dispatcher(sdw.dlg, &event); if (rc == FR_UNKNOWN) - rc = menu_function_dispatcher(menu->win, op); + rc = menu_function_dispatcher(menu->win, &event); if (rc == FR_UNKNOWN) - rc = global_function_dispatcher(menu->win, op); + rc = global_function_dispatcher(menu->win, &event); } while ((rc != FR_DONE) && (rc != FR_CONTINUE)); // --------------------------------------------------------------------------- @@ -406,7 +408,7 @@ int query_complete(struct Buffer *buf, struct ConfigSubset *sub) struct AliasMenuData mdata = { ARRAY_HEAD_INITIALIZER, NULL, sub }; mdata.search_state = search_state_new(); - struct AliasList al = TAILQ_HEAD_INITIALIZER(al); + struct AliasArray aa = ARRAY_HEAD_INITIALIZER; const char *const c_query_command = cs_subset_string(sub, "query_command"); if (!c_query_command) { @@ -414,17 +416,17 @@ int query_complete(struct Buffer *buf, struct ConfigSubset *sub) goto done; } - query_run(buf_string(buf), true, &al, sub); - if (TAILQ_EMPTY(&al)) + query_run(buf_string(buf), true, &aa, sub); + if (ARRAY_EMPTY(&aa)) goto done; - mdata.al = &al; + mdata.aa = &aa; - struct Alias *a_first = TAILQ_FIRST(&al); - if (!TAILQ_NEXT(a_first, entries)) // only one response? + struct Alias **a_first = ARRAY_FIRST(&aa); + if (ARRAY_SIZE(&aa) == 1) // only one response? { struct AddressList addr = TAILQ_HEAD_INITIALIZER(addr); - if (alias_to_addrlist(&addr, a_first)) + if (alias_to_addrlist(&addr, *a_first)) { mutt_addrlist_to_local(&addr); buf_reset(buf); @@ -436,10 +438,10 @@ int query_complete(struct Buffer *buf, struct ConfigSubset *sub) goto done; } - struct Alias *np = NULL; - TAILQ_FOREACH(np, mdata.al, entries) + struct Alias **ap = NULL; + ARRAY_FOREACH(ap, mdata.aa) { - alias_array_alias_add(&mdata.ava, np); + alias_array_alias_add(&mdata.ava, *ap); } /* multiple results, choose from query menu */ @@ -469,7 +471,7 @@ int query_complete(struct Buffer *buf, struct ConfigSubset *sub) FREE(&mdata.title); FREE(&mdata.limit); search_state_free(&mdata.search_state); - aliaslist_clear(&al); + aliaslist_clear(&aa); return 0; } @@ -487,9 +489,9 @@ void query_index(struct Mailbox *m, struct ConfigSubset *sub) return; } - struct AliasList al = TAILQ_HEAD_INITIALIZER(al); + struct AliasArray aa = ARRAY_HEAD_INITIALIZER; struct AliasMenuData mdata = { ARRAY_HEAD_INITIALIZER, NULL, sub }; - mdata.al = &al; + mdata.aa = &aa; mdata.search_state = search_state_new(); struct Buffer *buf = buf_pool_get(); @@ -499,14 +501,14 @@ void query_index(struct Mailbox *m, struct ConfigSubset *sub) goto done; } - query_run(buf_string(buf), false, &al, sub); - if (TAILQ_EMPTY(&al)) + query_run(buf_string(buf), false, &aa, sub); + if (ARRAY_EMPTY(&aa)) goto done; - struct Alias *np = NULL; - TAILQ_FOREACH(np, mdata.al, entries) + struct Alias **ap = NULL; + ARRAY_FOREACH(ap, mdata.aa) { - alias_array_alias_add(&mdata.ava, np); + alias_array_alias_add(&mdata.ava, *ap); } if (!dlg_query(buf, &mdata)) @@ -537,6 +539,6 @@ void query_index(struct Mailbox *m, struct ConfigSubset *sub) FREE(&mdata.title); FREE(&mdata.limit); search_state_free(&mdata.search_state); - aliaslist_clear(&al); + aliaslist_clear(&aa); buf_pool_release(&buf); } diff --git a/alias/functions.c b/alias/functions.c index ce1899cb837..5e52f050598 100644 --- a/alias/functions.c +++ b/alias/functions.c @@ -3,7 +3,7 @@ * Alias functions * * @authors - * Copyright (C) 2022-2023 Richard Russon + * Copyright (C) 2022-2026 Richard Russon * Copyright (C) 2023 Dennis Schön * * @copyright @@ -38,7 +38,6 @@ #include "config/lib.h" #include "core/lib.h" #include "gui/lib.h" -#include "mutt.h" #include "lib.h" #include "editor/lib.h" #include "history/lib.h" @@ -56,7 +55,7 @@ /** * OpAlias - Functions for the Alias Menu */ -const struct MenuFuncOp OpAlias[] = { /* map: alias */ +static const struct MenuFuncOp OpAlias[] = { /* map: alias */ { "delete-entry", OP_DELETE }, { "exit", OP_EXIT }, { "limit", OP_MAIN_LIMIT }, @@ -89,7 +88,7 @@ const struct MenuFuncOp OpQuery[] = { /* map: query */ /** * AliasDefaultBindings - Key bindings for the Alias Menu */ -const struct MenuOpSeq AliasDefaultBindings[] = { /* map: alias */ +static const struct MenuOpSeq AliasDefaultBindings[] = { /* map: alias */ { OP_DELETE, "d" }, { OP_EXIT, "q" }, { OP_MAIL, "m" }, @@ -106,7 +105,7 @@ const struct MenuOpSeq AliasDefaultBindings[] = { /* map: alias */ /** * QueryDefaultBindings - Key bindings for the external Query Menu */ -const struct MenuOpSeq QueryDefaultBindings[] = { /* map: query */ +static const struct MenuOpSeq QueryDefaultBindings[] = { /* map: query */ { OP_CREATE_ALIAS, "a" }, { OP_EXIT, "q" }, { OP_MAIL, "m" }, @@ -122,10 +121,31 @@ const struct MenuOpSeq QueryDefaultBindings[] = { /* map: query */ }; // clang-format on +/** + * alias_init_keys - Initialise the Alias Keybindings - Implements ::init_keys_api + */ +void alias_init_keys(struct SubMenu *sm_generic) +{ + struct MenuDefinition *md = NULL; + struct SubMenu *sm = NULL; + + sm = km_register_submenu(OpAlias); + md = km_register_menu(MENU_ALIAS, "alias"); + km_menu_add_submenu(md, sm); + km_menu_add_submenu(md, sm_generic); + km_menu_add_bindings(md, AliasDefaultBindings); + + sm = km_register_submenu(OpQuery); + md = km_register_menu(MENU_QUERY, "query"); + km_menu_add_submenu(md, sm); + km_menu_add_submenu(md, sm_generic); + km_menu_add_bindings(md, QueryDefaultBindings); +} + /** * op_create_alias - create an alias from a message sender - Implements ::alias_function_t - @ingroup alias_function_api */ -static int op_create_alias(struct AliasMenuData *mdata, int op) +static int op_create_alias(struct AliasMenuData *mdata, const struct KeyEvent *event) { struct Menu *menu = mdata->menu; @@ -165,9 +185,10 @@ static int op_create_alias(struct AliasMenuData *mdata, int op) /** * op_delete - delete the current entry - Implements ::alias_function_t - @ingroup alias_function_api */ -static int op_delete(struct AliasMenuData *mdata, int op) +static int op_delete(struct AliasMenuData *mdata, const struct KeyEvent *event) { struct Menu *menu = mdata->menu; + const int op = event->op; if (menu->tag_prefix) { @@ -197,7 +218,7 @@ static int op_delete(struct AliasMenuData *mdata, int op) /** * op_exit - exit this menu - Implements ::alias_function_t - @ingroup alias_function_api */ -static int op_exit(struct AliasMenuData *mdata, int op) +static int op_exit(struct AliasMenuData *mdata, const struct KeyEvent *event) { return FR_DONE; } @@ -211,7 +232,7 @@ static int op_exit(struct AliasMenuData *mdata, int op) * * @note AliasMenuData.is_tagged will show the user's selection */ -static int op_generic_select_entry(struct AliasMenuData *mdata, int op) +static int op_generic_select_entry(struct AliasMenuData *mdata, const struct KeyEvent *event) { struct Menu *menu = mdata->menu; if (menu->tag_prefix) @@ -241,7 +262,7 @@ static int op_generic_select_entry(struct AliasMenuData *mdata, int op) /** * op_main_limit - show only messages matching a pattern - Implements ::alias_function_t - @ingroup alias_function_api */ -static int op_main_limit(struct AliasMenuData *mdata, int op) +static int op_main_limit(struct AliasMenuData *mdata, const struct KeyEvent *event) { struct Menu *menu = mdata->menu; int rc = mutt_pattern_alias_func(_("Limit to addresses matching: "), mdata, @@ -260,7 +281,7 @@ static int op_main_limit(struct AliasMenuData *mdata, int op) /** * op_main_tag_pattern - Tag messages matching a pattern - Implements ::alias_function_t - @ingroup alias_function_api */ -static int op_main_tag_pattern(struct AliasMenuData *mdata, int op) +static int op_main_tag_pattern(struct AliasMenuData *mdata, const struct KeyEvent *event) { struct Menu *menu = mdata->menu; int rc = mutt_pattern_alias_func(_("Tag addresses matching: "), mdata, PAA_TAG, menu); @@ -276,7 +297,7 @@ static int op_main_tag_pattern(struct AliasMenuData *mdata, int op) /** * op_main_untag_pattern - Untag messages matching a pattern - Implements ::alias_function_t - @ingroup alias_function_api */ -static int op_main_untag_pattern(struct AliasMenuData *mdata, int op) +static int op_main_untag_pattern(struct AliasMenuData *mdata, const struct KeyEvent *event) { struct Menu *menu = mdata->menu; int rc = mutt_pattern_alias_func(_("Untag addresses matching: "), mdata, PAA_UNTAG, menu); @@ -296,7 +317,7 @@ static int op_main_untag_pattern(struct AliasMenuData *mdata, int op) * - OP_QUERY * - OP_QUERY_APPEND */ -static int op_query(struct AliasMenuData *mdata, int op) +static int op_query(struct AliasMenuData *mdata, const struct KeyEvent *event) { struct Buffer *buf = mdata->query; if ((mw_get_field(_("Query: "), buf, MUTT_COMP_NO_FLAGS, HC_OTHER, NULL, NULL) != 0) || @@ -305,36 +326,36 @@ static int op_query(struct AliasMenuData *mdata, int op) return FR_NO_ACTION; } + const int op = event->op; if (op == OP_QUERY) { ARRAY_FREE(&mdata->ava); - aliaslist_clear(mdata->al); + aliaslist_clear(mdata->aa); } struct Menu *menu = mdata->menu; - struct AliasList al = TAILQ_HEAD_INITIALIZER(al); + struct AliasArray aa = ARRAY_HEAD_INITIALIZER; - query_run(buf_string(buf), true, &al, mdata->sub); + query_run(buf_string(buf), true, &aa, mdata->sub); menu_queue_redraw(menu, MENU_REDRAW_FULL); char title[256] = { 0 }; snprintf(title, sizeof(title), "%s%s", _("Query: "), buf_string(buf)); sbar_set_title(mdata->sbar, title); - if (TAILQ_EMPTY(&al)) + if (ARRAY_EMPTY(&aa)) { if (op == OP_QUERY) menu->max = 0; return FR_NO_ACTION; } - struct Alias *np = NULL; - struct Alias *tmp = NULL; - TAILQ_FOREACH_SAFE(np, &al, entries, tmp) + struct Alias **ap = NULL; + ARRAY_FOREACH(ap, &aa) { - alias_array_alias_add(&mdata->ava, np); - TAILQ_REMOVE(&al, np, entries); - TAILQ_INSERT_TAIL(mdata->al, np, entries); // Transfer + alias_array_alias_add(&mdata->ava, *ap); + ARRAY_ADD(mdata->aa, *ap); // Transfer } + ARRAY_FREE(&aa); // Free the array structure but not the aliases alias_array_sort(&mdata->ava, mdata->sub); menu->max = ARRAY_SIZE(&mdata->ava); return FR_SUCCESS; @@ -349,10 +370,10 @@ static int op_query(struct AliasMenuData *mdata, int op) * - OP_SEARCH_OPPOSITE * - OP_SEARCH_REVERSE */ -static int op_search(struct AliasMenuData *mdata, int op) +static int op_search(struct AliasMenuData *mdata, const struct KeyEvent *event) { SearchFlags flags = SEARCH_NO_FLAGS; - switch (op) + switch (event->op) { case OP_SEARCH: flags |= SEARCH_PROMPT; @@ -386,10 +407,11 @@ static int op_search(struct AliasMenuData *mdata, int op) * - OP_SORT * - OP_SORT_REVERSE */ -static int op_sort(struct AliasMenuData *mdata, int op) +static int op_sort(struct AliasMenuData *mdata, const struct KeyEvent *event) { int sort = cs_subset_sort(mdata->sub, "alias_sort"); bool resort = true; + const int op = event->op; bool reverse = (op == OP_SORT_REVERSE); switch (mw_multi_choice(reverse ? @@ -463,15 +485,19 @@ static const struct AliasFunction AliasFunctions[] = { /** * alias_function_dispatcher - Perform a Alias function - Implements ::function_dispatcher_t - @ingroup dispatcher_api */ -int alias_function_dispatcher(struct MuttWindow *win, int op) +int alias_function_dispatcher(struct MuttWindow *win, const struct KeyEvent *event) { // The Dispatcher may be called on any Window in the Dialog struct MuttWindow *dlg = dialog_find(win); - if (!dlg || !dlg->wdata) + if (!event || !dlg || !dlg->wdata) return FR_ERROR; struct Menu *menu = dlg->wdata; struct AliasMenuData *mdata = menu->mdata; + if (!mdata) + return FR_ERROR; + + const int op = event->op; int rc = FR_UNKNOWN; for (size_t i = 0; AliasFunctions[i].op != OP_NULL; i++) @@ -479,7 +505,7 @@ int alias_function_dispatcher(struct MuttWindow *win, int op) const struct AliasFunction *fn = &AliasFunctions[i]; if (fn->op == op) { - rc = fn->function(mdata, op); + rc = fn->function(mdata, event); break; } } diff --git a/alias/functions.h b/alias/functions.h index 407d6620e6f..de2882313d2 100644 --- a/alias/functions.h +++ b/alias/functions.h @@ -27,10 +27,11 @@ struct AddressList; struct Alias; -struct AliasList; +struct AliasArray; struct AliasMenuData; struct AliasViewArray; struct ConfigSubset; +struct KeyEvent; struct MuttWindow; /** @@ -39,11 +40,14 @@ struct MuttWindow; * * Prototype for a Alias Function * - * @param wdata Alias Window data - * @param op Operation to perform, e.g. OP_CREATE_ALIAS + * @param wdata Alias Window data + * @param event Event to process * @retval enum #FunctionRetval + * + * @pre wdata is not NULL + * @pre event is not NULL */ -typedef int (*alias_function_t)(struct AliasMenuData *wdata, int op); +typedef int (*alias_function_t)(struct AliasMenuData *wdata, const struct KeyEvent *event); /** * struct AliasFunction - A NeoMutt function @@ -55,8 +59,8 @@ struct AliasFunction }; void alias_array_sort(struct AliasViewArray *ava, const struct ConfigSubset *sub); -int alias_function_dispatcher(struct MuttWindow *win, int op); +int alias_function_dispatcher(struct MuttWindow *win, const struct KeyEvent *event); bool alias_to_addrlist(struct AddressList *al, struct Alias *alias); -int query_run(const char *s, bool verbose, struct AliasList *al, const struct ConfigSubset *sub); +int query_run(const char *s, bool verbose, struct AliasArray *aa, const struct ConfigSubset *sub); #endif /* MUTT_ALIAS_FUNCTIONS_H */ diff --git a/alias/gui.h b/alias/gui.h index 2227b905245..58b351c443b 100644 --- a/alias/gui.h +++ b/alias/gui.h @@ -53,7 +53,7 @@ ARRAY_HEAD(AliasViewArray, struct AliasView); struct AliasMenuData { struct AliasViewArray ava; ///< All Aliases/Queries - struct AliasList *al; ///< Alias data + struct AliasArray *aa; ///< Alias data struct ConfigSubset *sub; ///< Config items struct Menu *menu; ///< Menu struct Buffer *query; ///< Query string diff --git a/alias/lib.h b/alias/lib.h index 8339b7254a0..cab81f9a85c 100644 --- a/alias/lib.h +++ b/alias/lib.h @@ -37,6 +37,7 @@ * | alias/expando.c | @subpage alias_expando | * | alias/functions.c | @subpage alias_functions | * | alias/gui.c | @subpage alias_gui | + * | alias/module.c | @subpage alias_module | * | alias/reverse.c | @subpage alias_reverse | * | alias/sort.c | @subpage alias_sort | */ @@ -45,7 +46,6 @@ #define MUTT_ALIAS_LIB_H #include -#include #include "core/lib.h" #include "expando.h" // IWYU pragma: keep @@ -55,12 +55,16 @@ struct Alias; struct Buffer; struct ConfigSubset; struct Envelope; +struct ParseContext; +struct ParseError; +struct SubMenu; struct TagList; extern const struct CompleteOps CompleteAliasOps; -void alias_init (void); -void alias_cleanup(void); +void alias_init (void); +void alias_init_keys(struct SubMenu *sm_generic); +void alias_cleanup (void); void alias_create (struct AddressList *al, const struct ConfigSubset *sub); struct AddressList *alias_lookup (const char *name); @@ -70,8 +74,8 @@ void mutt_expand_aliases_env(struct Envelope *env); void mutt_expand_aliases (struct AddressList *al); struct AddressList *mutt_get_address (struct Envelope *env, const char **prefix); -enum CommandResult parse_alias (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_unalias(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); +enum CommandResult parse_alias (const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); +enum CommandResult parse_unalias(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); void alias_tags_to_buffer(struct TagList *tl, struct Buffer *buf); void parse_alias_comments(struct Alias *alias, const char *com); diff --git a/alias/module.c b/alias/module.c new file mode 100644 index 00000000000..63ec9b1f5a9 --- /dev/null +++ b/alias/module.c @@ -0,0 +1,58 @@ +/** + * @file + * Definition of the Alias Module + * + * @authors + * Copyright (C) 2025-2026 Richard Russon + * + * @copyright + * This program is free software: 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 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page alias_module Definition of the Alias Module + * + * Definition of the Alias Module + */ + +#include "config.h" +#include +#include +#include "config/lib.h" +#include "core/lib.h" + +extern struct ConfigDef AliasVars[]; + +/** + * alias_config_define_variables - Define the Config Variables - Implements Module::config_define_variables() + */ +static bool alias_config_define_variables(struct NeoMutt *n, struct ConfigSet *cs) +{ + return cs_register_variables(cs, AliasVars); +} + +/** + * ModuleAlias - Module for the Alias library + */ +const struct Module ModuleAlias = { + "alias", + NULL, // init + NULL, // config_define_types + alias_config_define_variables, + NULL, // commands_register + NULL, // gui_init + NULL, // gui_cleanup + NULL, // cleanup + NULL, // mod_data +}; diff --git a/attach/cid.c b/attach/cid.c index 98c0454fd9d..9f6c2aa1752 100644 --- a/attach/cid.c +++ b/attach/cid.c @@ -35,7 +35,6 @@ #include "core/lib.h" #include "cid.h" #include "attach.h" -#include "mailcap.h" #include "mutt_attach.h" /** diff --git a/attach/commands.c b/attach/commands.c index f205df623b0..4901d0ec4fe 100644 --- a/attach/commands.c +++ b/attach/commands.c @@ -4,7 +4,7 @@ * * @authors * Copyright (C) 2021 Pietro Cerutti - * Copyright (C) 2021-2023 Richard Russon + * Copyright (C) 2021-2025 Richard Russon * * @copyright * This program is free software: you can redistribute it and/or modify it under @@ -29,7 +29,6 @@ #include "config.h" #include -#include #include #include #include "mutt/lib.h" @@ -40,7 +39,6 @@ #include "commands.h" #include "ncrypt/lib.h" #include "parse/lib.h" -#include "mview.h" /** * struct AttachMatch - An attachment matching a regex for attachment counter @@ -304,37 +302,38 @@ void mutt_attachments_reset(struct MailboxView *mv) /** * parse_attach_list - Parse the "attachments" command - * @param buf Buffer for temporary storage - * @param s Buffer containing the attachments command + * @param cmd Command being parsed + * @param line Buffer containing the attachments command * @param head List of AttachMatch to add to * @param err Buffer for error messages * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS */ -static enum CommandResult parse_attach_list(struct Buffer *buf, struct Buffer *s, +static enum CommandResult parse_attach_list(const struct Command *cmd, struct Buffer *line, struct ListHead *head, struct Buffer *err) { struct AttachMatch *a = NULL; char *p = NULL; char *tmpminor = NULL; size_t len; - int rc; + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_ERROR; do { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); + parse_extract_token(token, line, TOKEN_NO_FLAGS); - if (!buf->data || (*buf->data == '\0')) + if (buf_is_empty(token)) continue; a = attachmatch_new(); /* some cheap hacks that I expect to remove */ - if (mutt_istr_equal(buf->data, "any")) + if (mutt_istr_equal(token->data, "any")) a->major = mutt_str_dup("*/.*"); - else if (mutt_istr_equal(buf->data, "none")) + else if (mutt_istr_equal(token->data, "none")) a->major = mutt_str_dup("cheap_hack/this_should_never_match"); else - a->major = mutt_str_dup(buf->data); + a->major = buf_strdup(token); p = strchr(a->major, '/'); if (p) @@ -356,58 +355,64 @@ static enum CommandResult parse_attach_list(struct Buffer *buf, struct Buffer *s tmpminor[len + 2] = '\0'; a->major_int = mutt_check_mime_type(a->major); - rc = REG_COMP(&a->minor_regex, tmpminor, REG_ICASE); + int rc_regex = REG_COMP(&a->minor_regex, tmpminor, REG_ICASE); FREE(&tmpminor); - if (rc != 0) + if (rc_regex != 0) { - regerror(rc, &a->minor_regex, err->data, err->dsize); + regerror(rc_regex, &a->minor_regex, err->data, err->dsize); FREE(&a->major); FREE(&a); - return MUTT_CMD_ERROR; + goto done; } mutt_debug(LL_DEBUG3, "added %s/%s [%d]\n", a->major, a->minor, a->major_int); mutt_list_insert_tail(head, (char *) a); - } while (MoreArgs(s)); + } while (MoreArgs(line)); if (!a) - return MUTT_CMD_ERROR; + goto done; mutt_debug(LL_NOTIFY, "NT_ATTACH_ADD: %s/%s\n", a->major, a->minor); notify_send(AttachmentsNotify, NT_ATTACH, NT_ATTACH_ADD, NULL); - return MUTT_CMD_SUCCESS; + rc = MUTT_CMD_SUCCESS; + +done: + buf_pool_release(&token); + return rc; } /** * parse_unattach_list - Parse the "unattachments" command - * @param buf Buffer for temporary storage - * @param s Buffer containing the unattachments command + * @param cmd Command being parsed + * @param line Buffer containing the unattachments command * @param head List of AttachMatch to remove from * @param err Buffer for error messages * @retval #MUTT_CMD_SUCCESS Always */ -static enum CommandResult parse_unattach_list(struct Buffer *buf, struct Buffer *s, +static enum CommandResult parse_unattach_list(const struct Command *cmd, struct Buffer *line, struct ListHead *head, struct Buffer *err) { + struct Buffer *token = buf_pool_get(); + struct AttachMatch *a = NULL; char *tmp = NULL; char *minor = NULL; do { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); + parse_extract_token(token, line, TOKEN_NO_FLAGS); FREE(&tmp); - if (mutt_istr_equal(buf->data, "any")) + if (mutt_istr_equal(token->data, "any")) tmp = mutt_str_dup("*/.*"); - else if (mutt_istr_equal(buf->data, "none")) + else if (mutt_istr_equal(token->data, "none")) tmp = mutt_str_dup("cheap_hack/this_should_never_match"); else - tmp = mutt_str_dup(buf->data); + tmp = buf_strdup(token); minor = strchr(tmp, '/'); if (minor) @@ -440,12 +445,13 @@ static enum CommandResult parse_unattach_list(struct Buffer *buf, struct Buffer } } - } while (MoreArgs(s)); + } while (MoreArgs(line)); FREE(&tmp); notify_send(AttachmentsNotify, NT_ATTACH, NT_ATTACH_DELETE, NULL); + buf_pool_release(&token); return MUTT_CMD_SUCCESS; } @@ -471,18 +477,28 @@ static int print_attach_list(struct ListHead *h, const char op, const char *name /** * parse_attachments - Parse the 'attachments' command - Implements Command::parse() - @ingroup command_parse + * + * Parse: + * - `attachments { + | - } [ ...]` + * - `attachments ?` */ -enum CommandResult parse_attachments(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) +enum CommandResult parse_attachments(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, struct ParseError *pe) { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - if (!buf->data || (*buf->data == '\0')) + struct Buffer *err = pe->message; + + if (!MoreArgs(line)) { - buf_strcpy(err, _("attachments: no disposition")); + buf_printf(err, _("%s: too few arguments"), cmd->name); return MUTT_CMD_WARNING; } - char *category = buf->data; + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_ERROR; + + parse_extract_token(token, line, TOKEN_NO_FLAGS); + + char *category = token->data; char op = *category++; if (op == '?') @@ -495,7 +511,9 @@ enum CommandResult parse_attachments(struct Buffer *buf, struct Buffer *s, print_attach_list(&InlineAllow, '+', "I"); print_attach_list(&InlineExclude, '-', "I"); mutt_any_key_to_continue(NULL); - return MUTT_CMD_SUCCESS; + + rc = MUTT_CMD_SUCCESS; + goto done; } if ((op != '+') && (op != '-')) @@ -522,30 +540,44 @@ enum CommandResult parse_attachments(struct Buffer *buf, struct Buffer *s, else { buf_strcpy(err, _("attachments: invalid disposition")); - return MUTT_CMD_ERROR; + goto done; } - return parse_attach_list(buf, s, head, err); + rc = parse_attach_list(cmd, line, head, err); + +done: + buf_pool_release(&token); + return rc; } /** * parse_unattachments - Parse the 'unattachments' command - Implements Command::parse() - @ingroup command_parse + * + * Parse: + * - `unattachments { + | - } disposition mime-type [ mime-type ...]` + * - `unattachments *` */ -enum CommandResult parse_unattachments(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) +enum CommandResult parse_unattachments(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, struct ParseError *pe) { - char op; - char *p = NULL; - struct ListHead *head = NULL; + struct Buffer *err = pe->message; - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - if (!buf->data || (*buf->data == '\0')) + if (!MoreArgs(line)) { - buf_strcpy(err, _("unattachments: no disposition")); + buf_printf(err, _("%s: too few arguments"), cmd->name); return MUTT_CMD_WARNING; } - p = buf->data; + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_ERROR; + + char op; + const char *p = NULL; + struct ListHead *head = NULL; + + parse_extract_token(token, line, TOKEN_NO_FLAGS); + + p = buf_string(token); op = *p++; if (op == '*') @@ -557,7 +589,9 @@ enum CommandResult parse_unattachments(struct Buffer *buf, struct Buffer *s, mutt_debug(LL_NOTIFY, "NT_ATTACH_DELETE_ALL\n"); notify_send(AttachmentsNotify, NT_ATTACH, NT_ATTACH_DELETE_ALL, NULL); - return 0; + + rc = MUTT_CMD_SUCCESS; + goto done; } if ((op != '+') && (op != '-')) @@ -582,10 +616,14 @@ enum CommandResult parse_unattachments(struct Buffer *buf, struct Buffer *s, else { buf_strcpy(err, _("unattachments: invalid disposition")); - return MUTT_CMD_ERROR; + goto done; } - return parse_unattach_list(buf, s, head, err); + rc = parse_unattach_list(cmd, line, head, err); + +done: + buf_pool_release(&token); + return rc; } /** diff --git a/attach/dlg_attach.c b/attach/dlg_attach.c index 65499961b8f..8ad484a03f6 100644 --- a/attach/dlg_attach.c +++ b/attach/dlg_attach.c @@ -77,14 +77,13 @@ #include "gui/lib.h" #include "lib.h" #include "expando/lib.h" +#include "hooks/lib.h" #include "key/lib.h" #include "menu/lib.h" #include "attach.h" #include "commands.h" #include "functions.h" -#include "hook.h" #include "mutt_logging.h" -#include "mview.h" #include "private_data.h" #include "recvattach.h" @@ -215,7 +214,7 @@ void dlg_attachment(struct ConfigSubset *sub, struct MailboxView *mv, /* make sure we have parsed this message */ mutt_parse_mime_message(e, fp); - mutt_message_hook(m, e, MUTT_MESSAGE_HOOK); + exec_message_hook(m, e, CMD_MESSAGE_HOOK); struct SimpleDialogWindows sdw = simple_dialog_new(MENU_ATTACHMENT, WT_DLG_ATTACHMENT, AttachmentHelp); @@ -248,12 +247,14 @@ void dlg_attachment(struct ConfigSubset *sub, struct MailboxView *mv, // Event Loop int rc = 0; int op = OP_NULL; + struct KeyEvent event = { 0, OP_NULL }; do { - menu_tagging_dispatcher(menu->win, op); + menu_tagging_dispatcher(menu->win, &event); window_redraw(NULL); - op = km_dokey(MENU_ATTACHMENT, GETCH_NO_FLAGS); + event = km_dokey(MENU_ATTACHMENT, GETCH_NO_FLAGS); + op = event.op; mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", opcodes_get_name(op), op); if (op < 0) continue; @@ -264,15 +265,15 @@ void dlg_attachment(struct ConfigSubset *sub, struct MailboxView *mv, } mutt_clear_error(); - rc = attach_function_dispatcher(sdw.dlg, op); + rc = attach_function_dispatcher(sdw.dlg, &event); if (rc == FR_UNKNOWN) - rc = menu_function_dispatcher(menu->win, op); + rc = menu_function_dispatcher(menu->win, &event); if (rc == FR_UNKNOWN) - rc = global_function_dispatcher(menu->win, op); + rc = global_function_dispatcher(menu->win, &event); if (rc == FR_CONTINUE) { - op = priv->op; + event.op = priv->op; } } while (rc != FR_DONE); diff --git a/attach/functions.c b/attach/functions.c index 3ab7d27209d..06f51ae0c82 100644 --- a/attach/functions.c +++ b/attach/functions.c @@ -3,7 +3,7 @@ * Attachment functions * * @authors - * Copyright (C) 2022-2023 Richard Russon + * Copyright (C) 2022-2026 Richard Russon * Copyright (C) 2023 Dennis Schön * * @copyright @@ -59,7 +59,7 @@ static const char *Function_not_permitted_in_attach_message_mode = N_( /** * OpAttachment - Functions for the Attachment Menu */ -const struct MenuFuncOp OpAttachment[] = { /* map: attachment */ +static const struct MenuFuncOp OpAttachment[] = { /* map: attachment */ { "bounce-message", OP_BOUNCE_MESSAGE }, { "check-traditional-pgp", OP_CHECK_TRADITIONAL }, { "collapse-parts", OP_ATTACHMENT_COLLAPSE }, @@ -95,7 +95,7 @@ const struct MenuFuncOp OpAttachment[] = { /* map: attachment */ /** * AttachmentDefaultBindings - Key bindings for the Attachment Menu */ -const struct MenuOpSeq AttachmentDefaultBindings[] = { /* map: attachment */ +static const struct MenuOpSeq AttachmentDefaultBindings[] = { /* map: attachment */ { OP_ATTACHMENT_COLLAPSE, "v" }, { OP_ATTACHMENT_DELETE, "d" }, { OP_ATTACHMENT_EDIT_TYPE, "\005" }, // @@ -123,6 +123,21 @@ const struct MenuOpSeq AttachmentDefaultBindings[] = { /* map: attachment */ }; // clang-format on +/** + * attach_init_keys - Initialise the Attach Keybindings - Implements ::init_keys_api + */ +void attach_init_keys(struct SubMenu *sm_generic) +{ + struct MenuDefinition *md = NULL; + struct SubMenu *sm = NULL; + + sm = km_register_submenu(OpAttachment); + md = km_register_menu(MENU_ATTACHMENT, "attach"); + km_menu_add_submenu(md, sm); + km_menu_add_submenu(md, sm_generic); + km_menu_add_bindings(md, AttachmentDefaultBindings); +} + /** * attach_collapse - Close the tree of the current attachment * @param actx Attachment context @@ -248,7 +263,7 @@ static int recvattach_pgp_check_traditional(struct AttachCtx *actx, struct Menu /** * op_attachment_collapse - toggle display of subparts - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_collapse(struct AttachPrivateData *priv, int op) +static int op_attachment_collapse(struct AttachPrivateData *priv, const struct KeyEvent *event) { struct AttachPtr *cur_att = current_attachment(priv->actx, priv->menu); if (!cur_att->body->parts) @@ -264,7 +279,7 @@ static int op_attachment_collapse(struct AttachPrivateData *priv, int op) /** * op_attachment_delete - delete the current entry - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_delete(struct AttachPrivateData *priv, int op) +static int op_attachment_delete(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (check_readonly(priv->mailbox)) return FR_ERROR; @@ -340,7 +355,7 @@ static int op_attachment_delete(struct AttachPrivateData *priv, int op) /** * op_attachment_edit_type - edit attachment content type - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_edit_type(struct AttachPrivateData *priv, int op) +static int op_attachment_edit_type(struct AttachPrivateData *priv, const struct KeyEvent *event) { recvattach_edit_content_type(priv->actx, priv->menu, priv->actx->email); menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX); @@ -350,7 +365,7 @@ static int op_attachment_edit_type(struct AttachPrivateData *priv, int op) /** * op_attachment_pipe - pipe message/attachment to a shell command - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_pipe(struct AttachPrivateData *priv, int op) +static int op_attachment_pipe(struct AttachPrivateData *priv, const struct KeyEvent *event) { struct AttachPtr *cur_att = current_attachment(priv->actx, priv->menu); mutt_pipe_attachment_list(priv->actx, cur_att->fp, priv->menu->tag_prefix, @@ -361,7 +376,7 @@ static int op_attachment_pipe(struct AttachPrivateData *priv, int op) /** * op_attachment_print - print the current entry - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_print(struct AttachPrivateData *priv, int op) +static int op_attachment_print(struct AttachPrivateData *priv, const struct KeyEvent *event) { struct AttachPtr *cur_att = current_attachment(priv->actx, priv->menu); mutt_print_attachment_list(priv->actx, cur_att->fp, priv->menu->tag_prefix, @@ -372,7 +387,7 @@ static int op_attachment_print(struct AttachPrivateData *priv, int op) /** * op_attachment_save - save message/attachment to a mailbox/file - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_save(struct AttachPrivateData *priv, int op) +static int op_attachment_save(struct AttachPrivateData *priv, const struct KeyEvent *event) { struct AttachPtr *cur_att = current_attachment(priv->actx, priv->menu); mutt_save_attachment_list(priv->actx, cur_att->fp, priv->menu->tag_prefix, @@ -388,7 +403,7 @@ static int op_attachment_save(struct AttachPrivateData *priv, int op) /** * op_attachment_undelete - undelete the current entry - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_undelete(struct AttachPrivateData *priv, int op) +static int op_attachment_undelete(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (check_readonly(priv->mailbox)) return FR_ERROR; @@ -426,9 +441,9 @@ static int op_attachment_undelete(struct AttachPrivateData *priv, int op) /** * op_attachment_view - view attachment using mailcap entry if necessary - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_view(struct AttachPrivateData *priv, int op) +static int op_attachment_view(struct AttachPrivateData *priv, const struct KeyEvent *event) { - priv->op = mutt_attach_display_loop(priv->sub, priv->menu, op, + priv->op = mutt_attach_display_loop(priv->sub, priv->menu, event->op, priv->actx->email, priv->actx, true); menu_queue_redraw(priv->menu, MENU_REDRAW_FULL); @@ -438,7 +453,8 @@ static int op_attachment_view(struct AttachPrivateData *priv, int op) /** * op_attachment_view_mailcap - force viewing of attachment using mailcap - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_view_mailcap(struct AttachPrivateData *priv, int op) +static int op_attachment_view_mailcap(struct AttachPrivateData *priv, + const struct KeyEvent *event) { struct AttachPtr *cur_att = current_attachment(priv->actx, priv->menu); mutt_view_attachment(cur_att->fp, cur_att->body, MUTT_VA_MAILCAP, @@ -450,7 +466,7 @@ static int op_attachment_view_mailcap(struct AttachPrivateData *priv, int op) /** * op_attachment_view_pager - view attachment in pager using copiousoutput mailcap - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_view_pager(struct AttachPrivateData *priv, int op) +static int op_attachment_view_pager(struct AttachPrivateData *priv, const struct KeyEvent *event) { struct AttachPtr *cur_att = current_attachment(priv->actx, priv->menu); mutt_view_attachment(cur_att->fp, cur_att->body, MUTT_VA_PAGER, @@ -462,7 +478,7 @@ static int op_attachment_view_pager(struct AttachPrivateData *priv, int op) /** * op_attachment_view_text - view attachment as text - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_attachment_view_text(struct AttachPrivateData *priv, int op) +static int op_attachment_view_text(struct AttachPrivateData *priv, const struct KeyEvent *event) { struct AttachPtr *cur_att = current_attachment(priv->actx, priv->menu); mutt_view_attachment(cur_att->fp, cur_att->body, MUTT_VA_AS_TEXT, @@ -474,7 +490,7 @@ static int op_attachment_view_text(struct AttachPrivateData *priv, int op) /** * op_bounce_message - remail a message to another user - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_bounce_message(struct AttachPrivateData *priv, int op) +static int op_bounce_message(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (check_attach(priv)) return FR_ERROR; @@ -488,7 +504,7 @@ static int op_bounce_message(struct AttachPrivateData *priv, int op) /** * op_check_traditional - check for classic PGP - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_check_traditional(struct AttachPrivateData *priv, int op) +static int op_check_traditional(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (((WithCrypto & APPLICATION_PGP) != 0) && recvattach_pgp_check_traditional(priv->actx, priv->menu)) @@ -502,7 +518,7 @@ static int op_check_traditional(struct AttachPrivateData *priv, int op) /** * op_compose_to_sender - compose new message to the current message sender - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_compose_to_sender(struct AttachPrivateData *priv, int op) +static int op_compose_to_sender(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (check_attach(priv)) return FR_ERROR; @@ -515,7 +531,7 @@ static int op_compose_to_sender(struct AttachPrivateData *priv, int op) /** * op_exit - exit this menu - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_exit(struct AttachPrivateData *priv, int op) +static int op_exit(struct AttachPrivateData *priv, const struct KeyEvent *event) { priv->actx->email->attach_del = false; for (int i = 0; i < priv->actx->idxlen; i++) @@ -536,7 +552,7 @@ static int op_exit(struct AttachPrivateData *priv, int op) /** * op_extract_keys - extract supported public keys - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_extract_keys(struct AttachPrivateData *priv, int op) +static int op_extract_keys(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (!(WithCrypto & APPLICATION_PGP)) return FR_NO_ACTION; @@ -550,7 +566,7 @@ static int op_extract_keys(struct AttachPrivateData *priv, int op) /** * op_forget_passphrase - wipe passphrases from memory - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_forget_passphrase(struct AttachPrivateData *priv, int op) +static int op_forget_passphrase(struct AttachPrivateData *priv, const struct KeyEvent *event) { crypt_forget_passphrase(); return FR_SUCCESS; @@ -559,7 +575,7 @@ static int op_forget_passphrase(struct AttachPrivateData *priv, int op) /** * op_forward_message - forward a message with comments - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_forward_message(struct AttachPrivateData *priv, int op) +static int op_forward_message(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (check_attach(priv)) return FR_ERROR; @@ -573,7 +589,7 @@ static int op_forward_message(struct AttachPrivateData *priv, int op) /** * op_list_subscribe - subscribe to a mailing list - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_list_subscribe(struct AttachPrivateData *priv, int op) +static int op_list_subscribe(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (!check_attach(priv)) mutt_send_list_subscribe(priv->mailbox, priv->actx->email); @@ -583,7 +599,7 @@ static int op_list_subscribe(struct AttachPrivateData *priv, int op) /** * op_list_unsubscribe - unsubscribe from a mailing list - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_list_unsubscribe(struct AttachPrivateData *priv, int op) +static int op_list_unsubscribe(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (!check_attach(priv)) mutt_send_list_unsubscribe(priv->mailbox, priv->actx->email); @@ -593,11 +609,12 @@ static int op_list_unsubscribe(struct AttachPrivateData *priv, int op) /** * op_reply - reply to a message - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_reply(struct AttachPrivateData *priv, int op) +static int op_reply(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (check_attach(priv)) return FR_ERROR; + const int op = event->op; SendFlags flags = SEND_REPLY; if (op == OP_GROUP_REPLY) flags |= SEND_GROUP_REPLY; @@ -616,7 +633,7 @@ static int op_reply(struct AttachPrivateData *priv, int op) /** * op_resend - use the current message as a template for a new one - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_resend(struct AttachPrivateData *priv, int op) +static int op_resend(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (check_attach(priv)) return FR_ERROR; @@ -632,7 +649,7 @@ static int op_resend(struct AttachPrivateData *priv, int op) /** * op_followup - followup to newsgroup - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_followup(struct AttachPrivateData *priv, int op) +static int op_followup(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (check_attach(priv)) return FR_ERROR; @@ -649,13 +666,13 @@ static int op_followup(struct AttachPrivateData *priv, int op) return FR_SUCCESS; } - return op_reply(priv, op); + return op_reply(priv, event); } /** * op_forward_to_group - forward to newsgroup - Implements ::attach_function_t - @ingroup attach_function_api */ -static int op_forward_to_group(struct AttachPrivateData *priv, int op) +static int op_forward_to_group(struct AttachPrivateData *priv, const struct KeyEvent *event) { if (check_attach(priv)) return FR_ERROR; @@ -708,11 +725,11 @@ static const struct AttachFunction AttachFunctions[] = { /** * attach_function_dispatcher - Perform a Attach function - Implements ::function_dispatcher_t - @ingroup dispatcher_api */ -int attach_function_dispatcher(struct MuttWindow *win, int op) +int attach_function_dispatcher(struct MuttWindow *win, const struct KeyEvent *event) { // The Dispatcher may be called on any Window in the Dialog struct MuttWindow *dlg = dialog_find(win); - if (!dlg || !dlg->wdata) + if (!event || !dlg || !dlg->wdata) return FR_ERROR; struct Menu *menu = dlg->wdata; @@ -720,13 +737,14 @@ int attach_function_dispatcher(struct MuttWindow *win, int op) if (!priv) return FR_ERROR; + const int op = event->op; int rc = FR_UNKNOWN; for (size_t i = 0; AttachFunctions[i].op != OP_NULL; i++) { const struct AttachFunction *fn = &AttachFunctions[i]; if (fn->op == op) { - rc = fn->function(priv, op); + rc = fn->function(priv, event); break; } } diff --git a/attach/functions.h b/attach/functions.h index dc925def4cc..add75af17bc 100644 --- a/attach/functions.h +++ b/attach/functions.h @@ -24,6 +24,7 @@ #define MUTT_ATTACH_FUNCTIONS_H struct AttachPrivateData; +struct KeyEvent; struct MuttWindow; /** @@ -32,11 +33,14 @@ struct MuttWindow; * * Prototype for an Attachment Function * - * @param priv Private Attach data - * @param op Operation to perform, e.g. OP_ATTACHMENT_COLLAPSE + * @param priv Private Attach data + * @param event Event to process * @retval enum #FunctionRetval + * + * @pre priv is not NULL + * @pre event is not NULL */ -typedef int (*attach_function_t)(struct AttachPrivateData *priv, int op); +typedef int (*attach_function_t)(struct AttachPrivateData *priv, const struct KeyEvent *event); /** * struct AttachFunction - A NeoMutt function @@ -47,6 +51,6 @@ struct AttachFunction attach_function_t function; ///< Function to call }; -int attach_function_dispatcher(struct MuttWindow *win, int op); +int attach_function_dispatcher(struct MuttWindow *win, const struct KeyEvent *event); #endif /* MUTT_ATTACH_FUNCTIONS_H */ diff --git a/attach/lib.h b/attach/lib.h index e1cfe65a701..56ab320517f 100644 --- a/attach/lib.h +++ b/attach/lib.h @@ -34,16 +34,17 @@ * | attach/expando.c | @subpage attach_expando | * | attach/functions.c | @subpage attach_functions | * | attach/lib.c | @subpage attach_lib | + * | attach/module.c | @subpage attach_module | * | attach/mutt_attach.c | @subpage attach_mutt_attach | * | attach/private_data.c | @subpage attach_private_data | * | attach/recvattach.c | @subpage attach_recvattach | + * | attach/recvcmd.c | @subpage attach_recvcmd | */ #ifndef MUTT_ATTACH_LIB_H #define MUTT_ATTACH_LIB_H #include -#include #include "core/lib.h" // IWYU pragma: begin_keep #include "attach.h" @@ -55,6 +56,11 @@ struct Body; struct Buffer; +struct ParseContext; +struct ParseError; +struct SubMenu; + +void attach_init_keys(struct SubMenu *sm_generic); extern const struct ExpandoRenderCallback AttachRenderCallbacks[]; @@ -63,7 +69,7 @@ bool attach_body_parent (struct Body *start, struct Body *start_parent, struct Body *attach_body_ancestor(struct Body *start, struct Body *body, const char *subtype); bool attach_body_previous(struct Body *start, struct Body *body, struct Body **previous); -enum CommandResult parse_attachments (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_unattachments(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); +enum CommandResult parse_attachments (const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); +enum CommandResult parse_unattachments(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); #endif /* MUTT_ATTACH_LIB_H */ diff --git a/test/parse/common.c b/attach/module.c similarity index 59% rename from test/parse/common.c rename to attach/module.c index 1d540fe6351..886a6fdcefb 100644 --- a/test/parse/common.c +++ b/attach/module.c @@ -1,9 +1,9 @@ /** * @file - * Common test code for parsing + * Definition of the Attach Module * * @authors - * Copyright (C) 2023 Richard Russon + * Copyright (C) 2025-2026 Richard Russon * * @copyright * This program is free software: you can redistribute it and/or modify it under @@ -20,25 +20,26 @@ * this program. If not, see . */ -#define TEST_NO_MAIN +/** + * @page attach_module Definition of the Attach Module + * + * Definition of the Attach Module + */ + #include "config.h" -#include "acutest.h" #include -#include "mutt/lib.h" #include "core/lib.h" -#include "parse/lib.h" -struct Command mutt_commands[] = { - // clang-format off - { "reset", parse_set, MUTT_SET_RESET }, - { "set", parse_set, MUTT_SET_SET }, - { "toggle", parse_set, MUTT_SET_INV }, - { "unset", parse_set, MUTT_SET_UNSET }, - { NULL, NULL, 0 }, - // clang-format on +/** + * ModuleAttach - Module for the Attach library + */ +const struct Module ModuleAttach = { + NULL, // init + NULL, // config_define_types + NULL, // config_define_variables + NULL, // commands_register + NULL, // gui_init + NULL, // gui_cleanup + NULL, // cleanup + NULL, // mod_data }; - -void buf_expand_path(struct Buffer *buf) -{ - buf_insert(buf, 0, "expanded/"); -} diff --git a/attach/mutt_attach.c b/attach/mutt_attach.c index 2fbc135f7e7..1cd05837258 100644 --- a/attach/mutt_attach.c +++ b/attach/mutt_attach.c @@ -4,7 +4,7 @@ * * @authors * Copyright (C) 2017-2021 Pietro Cerutti - * Copyright (C) 2017-2023 Richard Russon + * Copyright (C) 2017-2025 Richard Russon * Copyright (C) 2021 Ihor Antonov * Copyright (C) 2022 David Purton * Copyright (C) 2023 Dennis Schön @@ -45,6 +45,7 @@ #include "email/lib.h" #include "core/lib.h" #include "gui/lib.h" +#include "mutt.h" #include "mutt_attach.h" #include "lib.h" #include "imap/lib.h" @@ -54,14 +55,9 @@ #include "send/lib.h" #include "attach.h" #include "cid.h" -#include "copy.h" #include "globals.h" -#include "handler.h" -#include "mailcap.h" #include "muttlib.h" #include "mx.h" -#include "protos.h" -#include "rfc3676.h" /** * mutt_get_tmp_attachment - Get a temporary copy of an attachment diff --git a/attach/recvattach.c b/attach/recvattach.c index 8b206cd38de..cf5185b6475 100644 --- a/attach/recvattach.c +++ b/attach/recvattach.c @@ -45,24 +45,19 @@ #include "email/lib.h" #include "core/lib.h" #include "gui/lib.h" -#include "mutt.h" #include "recvattach.h" #include "browser/lib.h" #include "editor/lib.h" #include "history/lib.h" +#include "hooks/lib.h" #include "menu/lib.h" #include "ncrypt/lib.h" #include "question/lib.h" #include "send/lib.h" #include "attach.h" #include "external.h" -#include "handler.h" -#include "hook.h" -#include "mailcap.h" #include "mutt_attach.h" -#include "mutt_thread.h" #include "muttlib.h" -#include "rfc3676.h" #ifdef ENABLE_NLS #include #endif @@ -312,7 +307,7 @@ static int query_save_attachment(FILE *fp, struct Body *b, struct Email *e, char } prompt = NULL; - buf_expand_path(buf); + expand_path(buf, false); bool is_message = (fp && has_a_message(b)); @@ -395,7 +390,7 @@ static int save_without_prompting(FILE *fp, struct Body *b, struct Email *e) } prepend_savedir(buf); - buf_expand_path(buf); + expand_path(buf, false); bool is_message = (fp && has_a_message(b)); @@ -492,7 +487,7 @@ void mutt_save_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag, { goto cleanup; } - buf_expand_path(buf); + expand_path(buf, false); if (mutt_check_overwrite(b->filename, buf_string(buf), tfile, &opt, NULL)) goto cleanup; } @@ -742,7 +737,7 @@ void mutt_pipe_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag, if (buf_is_empty(buf)) goto cleanup; - buf_expand_path(buf); + expand_path(buf, false); const bool c_attach_split = cs_subset_bool(NeoMutt->sub, "attach_split"); if (!filter && !c_attach_split) diff --git a/recvcmd.c b/attach/recvcmd.c similarity index 99% rename from recvcmd.c rename to attach/recvcmd.c index fe22d6b6fcf..729f219efb5 100644 --- a/recvcmd.c +++ b/attach/recvcmd.c @@ -25,7 +25,7 @@ */ /** - * @page neo_recvcmd Send/reply with an attachment + * @page attach_recvcmd Send/reply with an attachment * * Send/reply with an attachment */ @@ -43,19 +43,15 @@ #include "gui/lib.h" #include "mutt.h" #include "recvcmd.h" -#include "attach/lib.h" #include "editor/lib.h" #include "expando/lib.h" #include "history/lib.h" #include "index/lib.h" #include "question/lib.h" #include "send/lib.h" -#include "copy.h" +#include "attach.h" #include "globals.h" -#include "handler.h" -#include "mutt_body.h" #include "mutt_logging.h" -#include "protos.h" #ifdef ENABLE_NLS #include #endif diff --git a/recvcmd.h b/attach/recvcmd.h similarity index 93% rename from recvcmd.h rename to attach/recvcmd.h index 8867d89091a..81ce80c9fa3 100644 --- a/recvcmd.h +++ b/attach/recvcmd.h @@ -20,8 +20,8 @@ * this program. If not, see . */ -#ifndef MUTT_RECVCMD_H -#define MUTT_RECVCMD_H +#ifndef MUTT_ATTACH_RECVCMD_H +#define MUTT_ATTACH_RECVCMD_H #include #include "send/lib.h" @@ -37,4 +37,4 @@ void mutt_attach_forward (FILE *fp, struct Email *e, struct AttachCtx *actx, void mutt_attach_reply (FILE *fp, struct Mailbox *m, struct Email *e, struct AttachCtx *actx, struct Body *b, SendFlags flags); void mutt_attach_mail_sender(struct AttachCtx *actx, struct Body *b); -#endif /* MUTT_RECVCMD_H */ +#endif /* MUTT_ATTACH_RECVCMD_H */ diff --git a/auto.def b/auto.def index f63cde65811..adf349285d5 100644 --- a/auto.def +++ b/auto.def @@ -403,7 +403,6 @@ if {1} { futimens \ getaddrinfo \ getrandom \ - getsid \ iswblank \ mkdtemp \ qsort_s \ @@ -481,7 +480,10 @@ if {[get-define want-locales-fix]} {define LOCALES_HACK} # Documentation if {[get-define want-doc]} { if {![cc-check-progs xsltproc]} { - user-error "Unable to find xsltproc" + user-error "Unable to find xsltproc. Install package: libxslt / xsltproc." + } + if {![cc-check-progs xmlcatalog]} { + user-error "Unable to find xmlcatalog. Install package: libxml2 / libxml2-utils" } msg-checking "Checking for DocBook DTDs and Stylesheets..." diff --git a/autocrypt/autocrypt.c b/autocrypt/autocrypt.c index 6c67b7b672b..d4ce9825f98 100644 --- a/autocrypt/autocrypt.c +++ b/autocrypt/autocrypt.c @@ -935,7 +935,7 @@ void mutt_autocrypt_scan_mailboxes(void) NULL, MUTT_SEL_NO_FLAGS)) && (!buf_is_empty(folderbuf))) { - buf_expand_path_regex(folderbuf, false); + expand_path(folderbuf, false); struct Mailbox *m_ac = mx_path_resolve(buf_string(folderbuf)); /* NOTE: I am purposely *not* executing folder hooks here, * as they can do all sorts of things like push into the getch() buffer. diff --git a/autocrypt/config.c b/autocrypt/config.c index c5da7b95315..5809fb7d819 100644 --- a/autocrypt/config.c +++ b/autocrypt/config.c @@ -3,7 +3,7 @@ * Config used by libautocrypt * * @authors - * Copyright (C) 2020-2024 Richard Russon + * Copyright (C) 2020-2026 Richard Russon * * @copyright * This program is free software: you can redistribute it and/or modify it under @@ -61,7 +61,7 @@ static const struct ExpandoDefinition AutocryptFormatDef[] = { /** * AutocryptVars - Config definitions for the autocrypt library */ -static struct ConfigDef AutocryptVars[] = { +struct ConfigDef AutocryptVars[] = { // clang-format off { "autocrypt", DT_BOOL, false, 0, NULL, "Enables the Autocrypt feature" @@ -78,17 +78,3 @@ static struct ConfigDef AutocryptVars[] = { { NULL }, // clang-format on }; - -/** - * config_init_autocrypt - Register autocrypt config variables - Implements ::module_init_config_t - @ingroup cfg_module_api - */ -bool config_init_autocrypt(struct ConfigSet *cs) -{ - bool rc = false; - -#if defined(USE_AUTOCRYPT) - rc |= cs_register_variables(cs, AutocryptVars); -#endif - - return rc; -} diff --git a/autocrypt/dlg_autocrypt.c b/autocrypt/dlg_autocrypt.c index 329bdf53fd1..2ea286c1f04 100644 --- a/autocrypt/dlg_autocrypt.c +++ b/autocrypt/dlg_autocrypt.c @@ -271,12 +271,14 @@ void dlg_autocrypt(void) // --------------------------------------------------------------------------- // Event Loop int op = OP_NULL; + struct KeyEvent event = { 0, OP_NULL }; do { - menu_tagging_dispatcher(menu->win, op); + menu_tagging_dispatcher(menu->win, &event); window_redraw(NULL); - op = km_dokey(MENU_AUTOCRYPT, GETCH_NO_FLAGS); + event = km_dokey(MENU_AUTOCRYPT, GETCH_NO_FLAGS); + op = event.op; mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", opcodes_get_name(op), op); if (op < 0) continue; @@ -287,12 +289,12 @@ void dlg_autocrypt(void) } mutt_clear_error(); - int rc = autocrypt_function_dispatcher(sdw.dlg, op); + int rc = autocrypt_function_dispatcher(sdw.dlg, &event); if (rc == FR_UNKNOWN) - rc = menu_function_dispatcher(menu->win, op); + rc = menu_function_dispatcher(menu->win, &event); if (rc == FR_UNKNOWN) - rc = global_function_dispatcher(menu->win, op); + rc = global_function_dispatcher(menu->win, &event); } while (!ad->done); // --------------------------------------------------------------------------- diff --git a/autocrypt/functions.c b/autocrypt/functions.c index 889ade421b2..6a0a4931e99 100644 --- a/autocrypt/functions.c +++ b/autocrypt/functions.c @@ -3,7 +3,7 @@ * Autocrypt functions * * @authors - * Copyright (C) 2022-2023 Richard Russon + * Copyright (C) 2022-2026 Richard Russon * * @copyright * This program is free software: you can redistribute it and/or modify it under @@ -47,11 +47,10 @@ #endif // clang-format off -#ifdef USE_AUTOCRYPT /** * OpAutocrypt - Functions for the Autocrypt Account */ -const struct MenuFuncOp OpAutocrypt[] = { /* map: autocrypt account */ +static const struct MenuFuncOp OpAutocrypt[] = { /* map: autocrypt account */ { "create-account", OP_AUTOCRYPT_CREATE_ACCT }, { "delete-account", OP_AUTOCRYPT_DELETE_ACCT }, { "exit", OP_EXIT }, @@ -59,13 +58,11 @@ const struct MenuFuncOp OpAutocrypt[] = { /* map: autocrypt account */ { "toggle-prefer-encrypt", OP_AUTOCRYPT_TOGGLE_PREFER }, { NULL, 0 } }; -#endif -#ifdef USE_AUTOCRYPT /** * AutocryptDefaultBindings - Key bindings for the Autocrypt Account */ -const struct MenuOpSeq AutocryptDefaultBindings[] = { /* map: autocrypt account */ +static const struct MenuOpSeq AutocryptDefaultBindings[] = { /* map: autocrypt account */ { OP_AUTOCRYPT_CREATE_ACCT, "c" }, { OP_AUTOCRYPT_DELETE_ACCT, "D" }, { OP_AUTOCRYPT_TOGGLE_ACTIVE, "a" }, @@ -73,9 +70,23 @@ const struct MenuOpSeq AutocryptDefaultBindings[] = { /* map: autocrypt account { OP_EXIT, "q" }, { 0, NULL } }; -#endif // clang-format on +/** + * autocrypt_init_keys - Initialise the Autocrypt Keybindings - Implements ::init_keys_api + */ +void autocrypt_init_keys(struct SubMenu *sm_generic) +{ + struct MenuDefinition *md = NULL; + struct SubMenu *sm = NULL; + + sm = km_register_submenu(OpAutocrypt); + md = km_register_menu(MENU_AUTOCRYPT, "autocrypt"); + km_menu_add_submenu(md, sm); + km_menu_add_submenu(md, sm_generic); + km_menu_add_bindings(md, AutocryptDefaultBindings); +} + /** * toggle_active - Toggle whether an Autocrypt account is active * @param entry Menu Entry for the account @@ -111,7 +122,7 @@ static void toggle_prefer_encrypt(struct AccountEntry *entry) /** * op_autocrypt_create_acct - Create a new autocrypt account - Implements ::autocrypt_function_t - @ingroup autocrypt_function_api */ -static int op_autocrypt_create_acct(struct AutocryptData *ad, int op) +static int op_autocrypt_create_acct(struct AutocryptData *ad, const struct KeyEvent *event) { if (mutt_autocrypt_account_init(false) == 0) populate_menu(ad->menu); @@ -122,7 +133,7 @@ static int op_autocrypt_create_acct(struct AutocryptData *ad, int op) /** * op_autocrypt_delete_acct - Delete the current account - Implements ::autocrypt_function_t - @ingroup autocrypt_function_api */ -static int op_autocrypt_delete_acct(struct AutocryptData *ad, int op) +static int op_autocrypt_delete_acct(struct AutocryptData *ad, const struct KeyEvent *event) { if (!ad->menu->mdata) return FR_ERROR; @@ -148,7 +159,7 @@ static int op_autocrypt_delete_acct(struct AutocryptData *ad, int op) /** * op_autocrypt_toggle_active - Toggle the current account active/inactive - Implements ::autocrypt_function_t - @ingroup autocrypt_function_api */ -static int op_autocrypt_toggle_active(struct AutocryptData *ad, int op) +static int op_autocrypt_toggle_active(struct AutocryptData *ad, const struct KeyEvent *event) { if (!ad->menu->mdata) return FR_ERROR; @@ -167,7 +178,7 @@ static int op_autocrypt_toggle_active(struct AutocryptData *ad, int op) /** * op_autocrypt_toggle_prefer - Toggle the current account prefer-encrypt flag - Implements ::autocrypt_function_t - @ingroup autocrypt_function_api */ -static int op_autocrypt_toggle_prefer(struct AutocryptData *ad, int op) +static int op_autocrypt_toggle_prefer(struct AutocryptData *ad, const struct KeyEvent *event) { if (!ad->menu->mdata) return FR_ERROR; @@ -186,7 +197,7 @@ static int op_autocrypt_toggle_prefer(struct AutocryptData *ad, int op) /** * op_exit - Exit this menu - Implements ::autocrypt_function_t - @ingroup autocrypt_function_api */ -static int op_exit(struct AutocryptData *ad, int op) +static int op_exit(struct AutocryptData *ad, const struct KeyEvent *event) { ad->done = true; return FR_SUCCESS; @@ -211,15 +222,18 @@ static const struct AutocryptFunction AutocryptFunctions[] = { /** * autocrypt_function_dispatcher - Perform a Autocrypt function - Implements ::function_dispatcher_t - @ingroup dispatcher_api */ -int autocrypt_function_dispatcher(struct MuttWindow *win, int op) +int autocrypt_function_dispatcher(struct MuttWindow *win, const struct KeyEvent *event) { // The Dispatcher may be called on any Window in the Dialog struct MuttWindow *dlg = dialog_find(win); - if (!dlg || !dlg->wdata) + if (!event || !dlg || !dlg->wdata) return FR_ERROR; + const int op = event->op; struct Menu *menu = dlg->wdata; struct AutocryptData *ad = menu->mdata; + if (!ad) + return FR_ERROR; int rc = FR_UNKNOWN; for (size_t i = 0; AutocryptFunctions[i].op != OP_NULL; i++) @@ -227,7 +241,7 @@ int autocrypt_function_dispatcher(struct MuttWindow *win, int op) const struct AutocryptFunction *fn = &AutocryptFunctions[i]; if (fn->op == op) { - rc = fn->function(ad, op); + rc = fn->function(ad, event); break; } } diff --git a/autocrypt/functions.h b/autocrypt/functions.h index 50251e43a0b..4c54b738cbc 100644 --- a/autocrypt/functions.h +++ b/autocrypt/functions.h @@ -24,6 +24,7 @@ #define MUTT_AUTOCRYPT_FUNCTIONS_H struct AutocryptData; +struct KeyEvent; struct MuttWindow; /** @@ -32,11 +33,14 @@ struct MuttWindow; * * Prototype for a Autocrypt Function * - * @param menu Menu - * @param op Operation to perform, e.g. OP_AUTOCRYPT_CREATE_ACCT + * @param menu Menu + * @param event Event to process * @retval enum #FunctionRetval + * + * @pre menu is not NULL + * @pre event is not NULL */ -typedef int (*autocrypt_function_t)(struct AutocryptData *pd, int op); +typedef int (*autocrypt_function_t)(struct AutocryptData *pd, const struct KeyEvent *event); /** * struct AutocryptFunction - A NeoMutt function @@ -47,6 +51,6 @@ struct AutocryptFunction autocrypt_function_t function; ///< Function to call }; -int autocrypt_function_dispatcher(struct MuttWindow *win, int op); +int autocrypt_function_dispatcher(struct MuttWindow *win, const struct KeyEvent *event); #endif /* MUTT_AUTOCRYPT_FUNCTIONS_H */ diff --git a/autocrypt/lib.h b/autocrypt/lib.h index fd46e6aaa95..da16f187469 100644 --- a/autocrypt/lib.h +++ b/autocrypt/lib.h @@ -89,12 +89,14 @@ * | autocrypt/expando.c | @subpage autocrypt_expando | * | autocrypt/functions.c | @subpage autocrypt_functions | * | autocrypt/gpgme.c | @subpage autocrypt_gpgme | + * | autocrypt/module.c | @subpage autocrypt_module | * | autocrypt/schema.c | @subpage autocrypt_schema | */ #ifndef MUTT_AUTOCRYPT_LIB_H #define MUTT_AUTOCRYPT_LIB_H +#include "config.h" #include #include #include @@ -102,6 +104,7 @@ struct Email; struct Envelope; +struct SubMenu; /** * struct AutocryptAccount - Autocrypt account @@ -170,6 +173,8 @@ enum AutocryptRec extern char *AutocryptSignAs; extern char *AutocryptDefaultKey; +void autocrypt_init_keys(struct SubMenu *sm_generic); + void dlg_autocrypt (void); void mutt_autocrypt_cleanup (void); int mutt_autocrypt_generate_gossip_list (struct Email *e); diff --git a/autocrypt/module.c b/autocrypt/module.c new file mode 100644 index 00000000000..bbd69a0da9f --- /dev/null +++ b/autocrypt/module.c @@ -0,0 +1,64 @@ +/** + * @file + * Definition of the Autocrypt Module + * + * @authors + * Copyright (C) 2025-2026 Richard Russon + * + * @copyright + * This program is free software: 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 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page autocrypt_module Definition of the Autocrypt Module + * + * Definition of the Autocrypt Module + */ + +#include "config.h" +#include +#include +#include "config/lib.h" +#include "core/lib.h" + +extern struct ConfigDef AutocryptVars[]; + +/** + * autocrypt_config_define_variables - Define the Config Variables - Implements Module::config_define_variables() + */ +static bool autocrypt_config_define_variables(struct NeoMutt *n, struct ConfigSet *cs) +{ + bool rc = false; + +#if defined(USE_AUTOCRYPT) + rc |= cs_register_variables(cs, AutocryptVars); +#endif + + return rc; +} + +/** + * ModuleAutocrypt - Module for the Autocrypt library + */ +const struct Module ModuleAutocrypt = { + "autocrypt", + NULL, // init + NULL, // config_define_types + autocrypt_config_define_variables, + NULL, // commands_register + NULL, // gui_init + NULL, // gui_cleanup + NULL, // cleanup + NULL, // mod_data +}; diff --git a/autosetup/jimsh0.c b/autosetup/jimsh0.c index 3c0e8b78a2b..4549d278e6d 100644 --- a/autosetup/jimsh0.c +++ b/autosetup/jimsh0.c @@ -2600,7 +2600,7 @@ static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv) Jim_Obj *objPtr = NULL; int len; int nb; - char *nl = NULL; + const char *nl = NULL; int offset = 0; errno = 0; diff --git a/bcache/bcache.c b/bcache/bcache.c index 7e9e3292bb4..918f125a560 100644 --- a/bcache/bcache.c +++ b/bcache/bcache.c @@ -4,7 +4,7 @@ * * @authors * Copyright (C) 2017-2023 Richard Russon - * Copyright (C) 2020 Pietro Cerutti + * Copyright (C) 2020-2025 Pietro Cerutti * * @copyright * This program is free software: you can redistribute it and/or modify it under diff --git a/bcache/lib.h b/bcache/lib.h index 9b3630e1969..853eb1e7cee 100644 --- a/bcache/lib.h +++ b/bcache/lib.h @@ -28,6 +28,7 @@ * | File | Description | * | :------------------ | :------------------------- | * | bcache/bcache.c | @subpage bcache_bcache | + * | bcache/module.c | @subpage bcache_module | */ #ifndef MUTT_BCACHE_LIB_H diff --git a/bcache/module.c b/bcache/module.c new file mode 100644 index 00000000000..dc2f1a12c36 --- /dev/null +++ b/bcache/module.c @@ -0,0 +1,46 @@ +/** + * @file + * Definition of the Bcache Module + * + * @authors + * Copyright (C) 2025-2026 Richard Russon + * + * @copyright + * This program is free software: 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 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page bcache_module Definition of the Bcache Module + * + * Definition of the Bcache Module + */ + +#include "config.h" +#include +#include "core/lib.h" + +/** + * ModuleBcache - Module for the Bcache library + */ +const struct Module ModuleBcache = { + "bcache", + NULL, // init + NULL, // config_define_types + NULL, // config_define_variables + NULL, // commands_register + NULL, // gui_init + NULL, // gui_cleanup + NULL, // cleanup + NULL, // mod_data +}; diff --git a/browser/config.c b/browser/config.c index fa818906d61..6c7499568b1 100644 --- a/browser/config.c +++ b/browser/config.c @@ -4,7 +4,7 @@ * * @authors * Copyright (C) 2022 Carlos Henrique Lima Melara - * Copyright (C) 2023-2024 Richard Russon + * Copyright (C) 2023-2026 Richard Russon * Copyright (C) 2023 наб * * @copyright @@ -135,7 +135,7 @@ static const struct ExpandoDefinition GroupIndexFormatDef[] = { /** * BrowserVars - Config definitions for the browser */ -static struct ConfigDef BrowserVars[] = { +struct ConfigDef BrowserVars[] = { // clang-format off { "browser_abbreviate_mailboxes", DT_BOOL, true, 0, NULL, "Abbreviate mailboxes using '~' and '=' in the browser" @@ -167,11 +167,3 @@ static struct ConfigDef BrowserVars[] = { { NULL }, // clang-format on }; - -/** - * config_init_browser - Register browser config variables - Implements ::module_init_config_t - @ingroup cfg_module_api - */ -bool config_init_browser(struct ConfigSet *cs) -{ - return cs_register_variables(cs, BrowserVars); -} diff --git a/browser/dlg_browser.c b/browser/dlg_browser.c index 241b500d347..51352859218 100644 --- a/browser/dlg_browser.c +++ b/browser/dlg_browser.c @@ -4,7 +4,7 @@ * * @authors * Copyright (C) 2016 Pierre-Elliott Bécue - * Copyright (C) 2016-2024 Richard Russon + * Copyright (C) 2016-2025 Richard Russon * Copyright (C) 2018 Austin Ray * Copyright (C) 2019-2022 Pietro Cerutti * Copyright (C) 2020 R Primus @@ -887,7 +887,7 @@ void dlg_browser(struct Buffer *file, SelectFileFlags flags, struct Mailbox *m, } else if (!buf_is_empty(file)) { - buf_expand_path(file); + expand_path(file, false); if (imap_path_probe(buf_string(file), NULL) == MUTT_IMAP) { init_state(&priv->state); @@ -1079,12 +1079,14 @@ void dlg_browser(struct Buffer *file, SelectFileFlags flags, struct Mailbox *m, // --------------------------------------------------------------------------- // Event Loop int op = OP_NULL; + struct KeyEvent event = { 0, OP_NULL }; do { - menu_tagging_dispatcher(priv->menu->win, op); + menu_tagging_dispatcher(priv->menu->win, &event); window_redraw(NULL); - op = km_dokey(MENU_BROWSER, GETCH_NO_FLAGS); + event = km_dokey(MENU_BROWSER, GETCH_NO_FLAGS); + op = event.op; mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", opcodes_get_name(op), op); if (op < 0) continue; @@ -1095,12 +1097,12 @@ void dlg_browser(struct Buffer *file, SelectFileFlags flags, struct Mailbox *m, } mutt_clear_error(); - int rc = browser_function_dispatcher(sdw.dlg, op); + int rc = browser_function_dispatcher(sdw.dlg, &event); if (rc == FR_UNKNOWN) - rc = menu_function_dispatcher(menu->win, op); + rc = menu_function_dispatcher(menu->win, &event); if (rc == FR_UNKNOWN) - rc = global_function_dispatcher(menu->win, op); + rc = global_function_dispatcher(menu->win, &event); } while (!priv->done); // --------------------------------------------------------------------------- diff --git a/browser/functions.c b/browser/functions.c index 9ee5c236b33..19b01ad0cda 100644 --- a/browser/functions.c +++ b/browser/functions.c @@ -3,7 +3,7 @@ * Browser functions * * @authors - * Copyright (C) 2021-2023 Richard Russon + * Copyright (C) 2021-2026 Richard Russon * Copyright (C) 2023 Dennis Schön * * @copyright @@ -40,7 +40,6 @@ #include "email/lib.h" #include "core/lib.h" #include "gui/lib.h" -#include "mutt.h" #include "lib.h" #include "attach/lib.h" #include "editor/lib.h" @@ -63,13 +62,13 @@ #include "sort.h" #endif -static int op_subscribe_pattern(struct BrowserPrivateData *priv, int op); +static int op_subscribe_pattern(struct BrowserPrivateData *priv, const struct KeyEvent *event); // clang-format off /** * OpBrowser - Functions for the file Browser Menu */ -const struct MenuFuncOp OpBrowser[] = { /* map: browser */ +static const struct MenuFuncOp OpBrowser[] = { /* map: browser */ { "catchup", OP_CATCHUP }, { "change-dir", OP_CHANGE_DIRECTORY }, { "check-new", OP_CHECK_NEW }, @@ -103,7 +102,7 @@ const struct MenuFuncOp OpBrowser[] = { /* map: browser */ /** * BrowserDefaultBindings - Key bindings for the file Browser Menu */ -const struct MenuOpSeq BrowserDefaultBindings[] = { /* map: browser */ +static const struct MenuOpSeq BrowserDefaultBindings[] = { /* map: browser */ { OP_BROWSER_GOTO_FOLDER, "=" }, { OP_BROWSER_NEW_FILE, "N" }, { OP_BROWSER_SUBSCRIBE, "s" }, @@ -126,6 +125,21 @@ const struct MenuOpSeq BrowserDefaultBindings[] = { /* map: browser */ }; // clang-format on +/** + * browser_init_keys - Initialise the Browser Keybindings - Implements ::init_keys_api + */ +void browser_init_keys(struct SubMenu *sm_generic) +{ + struct MenuDefinition *md = NULL; + struct SubMenu *sm = NULL; + + sm = km_register_submenu(OpBrowser); + md = km_register_menu(MENU_BROWSER, "browser"); + km_menu_add_submenu(md, sm); + km_menu_add_submenu(md, sm_generic); + km_menu_add_bindings(md, BrowserDefaultBindings); +} + /** * destroy_state - Free the BrowserState * @param state State to free @@ -149,7 +163,7 @@ void destroy_state(struct BrowserState *state) /** * op_browser_new_file - Select a new file in this directory - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_browser_new_file(struct BrowserPrivateData *priv, int op) +static int op_browser_new_file(struct BrowserPrivateData *priv, const struct KeyEvent *event) { struct Buffer *buf = buf_pool_get(); buf_printf(buf, "%s/", buf_string(&LastDir)); @@ -176,8 +190,10 @@ static int op_browser_new_file(struct BrowserPrivateData *priv, int op) * - OP_BROWSER_SUBSCRIBE * - OP_BROWSER_UNSUBSCRIBE */ -static int op_browser_subscribe(struct BrowserPrivateData *priv, int op) +static int op_browser_subscribe(struct BrowserPrivateData *priv, const struct KeyEvent *event) { + const int op = event->op; + if (OptNews) { struct NntpAccountData *adata = CurrentNewsSrv; @@ -219,7 +235,7 @@ static int op_browser_subscribe(struct BrowserPrivateData *priv, int op) int index = menu_get_index(priv->menu); struct FolderFile *ff = ARRAY_GET(&priv->state.entry, index); buf_strcpy(buf, ff->name); - buf_expand_path(buf); + expand_path(buf, false); imap_subscribe(buf_string(buf), (op == OP_BROWSER_SUBSCRIBE)); buf_pool_release(&buf); } @@ -229,7 +245,7 @@ static int op_browser_subscribe(struct BrowserPrivateData *priv, int op) /** * op_browser_tell - Display the currently selected file's name - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_browser_tell(struct BrowserPrivateData *priv, int op) +static int op_browser_tell(struct BrowserPrivateData *priv, const struct KeyEvent *event) { int index = menu_get_index(priv->menu); if (ARRAY_EMPTY(&priv->state.entry)) @@ -242,7 +258,7 @@ static int op_browser_tell(struct BrowserPrivateData *priv, int op) /** * op_browser_toggle_lsub - Toggle view all/subscribed mailboxes (IMAP only) - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_browser_toggle_lsub(struct BrowserPrivateData *priv, int op) +static int op_browser_toggle_lsub(struct BrowserPrivateData *priv, const struct KeyEvent *event) { bool_str_toggle(NeoMutt->sub, "imap_list_subscribed", NULL); @@ -253,7 +269,7 @@ static int op_browser_toggle_lsub(struct BrowserPrivateData *priv, int op) /** * op_browser_view_file - View file - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_browser_view_file(struct BrowserPrivateData *priv, int op) +static int op_browser_view_file(struct BrowserPrivateData *priv, const struct KeyEvent *event) { if (ARRAY_EMPTY(&priv->state.entry)) { @@ -298,7 +314,7 @@ static int op_browser_view_file(struct BrowserPrivateData *priv, int op) /** * op_catchup - Mark all articles in newsgroup as read - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_catchup(struct BrowserPrivateData *priv, int op) +static int op_catchup(struct BrowserPrivateData *priv, const struct KeyEvent *event) { if (!OptNews) return FR_NOT_IMPL; @@ -311,7 +327,7 @@ static int op_catchup(struct BrowserPrivateData *priv, int op) int index = menu_get_index(priv->menu); struct FolderFile *ff = ARRAY_GET(&priv->state.entry, index); - if (op == OP_CATCHUP) + if (event->op == OP_CATCHUP) mdata = mutt_newsgroup_catchup(priv->mailbox, CurrentNewsSrv, ff->name); else mdata = mutt_newsgroup_uncatchup(priv->mailbox, CurrentNewsSrv, ff->name); @@ -338,7 +354,7 @@ static int op_catchup(struct BrowserPrivateData *priv, int op) * - OP_GOTO_PARENT * - OP_CHANGE_DIRECTORY */ -static int op_change_directory(struct BrowserPrivateData *priv, int op) +static int op_change_directory(struct BrowserPrivateData *priv, const struct KeyEvent *event) { if (OptNews) return FR_NOT_IMPL; @@ -353,6 +369,7 @@ static int op_change_directory(struct BrowserPrivateData *priv, int op) buf_addch(buf, '/'); } + const int op = event->op; if (op == OP_CHANGE_DIRECTORY) { struct FileCompletionData cdata = { false, priv->mailbox, NULL, NULL, NULL }; @@ -372,7 +389,7 @@ static int op_change_directory(struct BrowserPrivateData *priv, int op) if (!buf_is_empty(buf)) { priv->state.is_mailbox_list = false; - buf_expand_path(buf); + expand_path(buf, false); if (imap_path_probe(buf_string(buf), NULL) == MUTT_IMAP) { buf_copy(&LastDir, buf); @@ -446,7 +463,7 @@ static int op_change_directory(struct BrowserPrivateData *priv, int op) /** * op_create_mailbox - Create a new mailbox (IMAP only) - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_create_mailbox(struct BrowserPrivateData *priv, int op) +static int op_create_mailbox(struct BrowserPrivateData *priv, const struct KeyEvent *event) { if (!priv->state.imap_browse) { @@ -473,7 +490,7 @@ static int op_create_mailbox(struct BrowserPrivateData *priv, int op) /** * op_delete_mailbox - Delete the current mailbox (IMAP only) - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_delete_mailbox(struct BrowserPrivateData *priv, int op) +static int op_delete_mailbox(struct BrowserPrivateData *priv, const struct KeyEvent *event) { int index = menu_get_index(priv->menu); struct FolderFile *ff = ARRAY_GET(&priv->state.entry, index); @@ -521,7 +538,7 @@ static int op_delete_mailbox(struct BrowserPrivateData *priv, int op) /** * op_enter_mask - Enter a file mask - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_enter_mask(struct BrowserPrivateData *priv, int op) +static int op_enter_mask(struct BrowserPrivateData *priv, const struct KeyEvent *event) { const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask"); struct Buffer *buf = buf_pool_get(); @@ -585,13 +602,13 @@ static int op_enter_mask(struct BrowserPrivateData *priv, int op) /** * op_exit - Exit this menu - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_exit(struct BrowserPrivateData *priv, int op) +static int op_exit(struct BrowserPrivateData *priv, const struct KeyEvent *event) { if (priv->multiple) { char **tfiles = NULL; - if (priv->menu->num_tagged) + if (priv->menu->tag_prefix && (priv->menu->num_tagged != 0)) { *priv->numfiles = priv->menu->num_tagged; tfiles = MUTT_MEM_CALLOC(*priv->numfiles, char *); @@ -603,7 +620,7 @@ static int op_exit(struct BrowserPrivateData *priv, int op) { struct Buffer *buf = buf_pool_get(); buf_concat_path(buf, buf_string(&LastDir), ff->name); - buf_expand_path(buf); + expand_path(buf, false); tfiles[j++] = buf_strdup(buf); buf_pool_release(&buf); } @@ -614,7 +631,7 @@ static int op_exit(struct BrowserPrivateData *priv, int op) { *priv->numfiles = 1; tfiles = MUTT_MEM_CALLOC(*priv->numfiles, char *); - buf_expand_path(priv->file); + expand_path(priv->file, false); tfiles[0] = buf_strdup(priv->file); *priv->files = tfiles; } @@ -631,14 +648,15 @@ static int op_exit(struct BrowserPrivateData *priv, int op) * - OP_DESCEND_DIRECTORY * - OP_GENERIC_SELECT_ENTRY */ -static int op_generic_select_entry(struct BrowserPrivateData *priv, int op) +static int op_generic_select_entry(struct BrowserPrivateData *priv, const struct KeyEvent *event) { if (ARRAY_EMPTY(&priv->state.entry)) { - mutt_error(_("No files match the file mask")); + mutt_warning(_("No files match the file mask")); return FR_ERROR; } + const int op = event->op; int index = menu_get_index(priv->menu); struct FolderFile *ff = ARRAY_GET(&priv->state.entry, index); if ((priv->menu->tag_prefix) && (op == OP_GENERIC_SELECT_ENTRY)) @@ -653,7 +671,7 @@ static int op_generic_select_entry(struct BrowserPrivateData *priv, int op) if (priv->state.is_mailbox_list) { buf_strcpy(buf, ff->name); - buf_expand_path(buf); + expand_path(buf, false); } else if (priv->state.imap_browse) { @@ -667,18 +685,13 @@ static int op_generic_select_entry(struct BrowserPrivateData *priv, int op) enum MailboxType type = mx_path_probe(buf_string(buf)); buf_pool_release(&buf); - const bool dot_dot = mutt_str_equal(ff->name, ".."); - - // Descend into a directory, if: - // 1) User selected '..' - // 2) User pressed - // 3) User is looking for a Mailbox and this directory isn't one - if (dot_dot || (op == OP_DESCEND_DIRECTORY) || (priv->folder && (type <= MUTT_UNKNOWN))) + if ((op == OP_DESCEND_DIRECTORY) || (type == MUTT_MAILBOX_ERROR) || + (type == MUTT_UNKNOWN) || ff->inferiors) { /* save the old directory */ buf_copy(priv->old_last_dir, &LastDir); - if (dot_dot) + if (mutt_str_equal(ff->name, "..")) { size_t lastdirlen = buf_len(&LastDir); if ((lastdirlen > 1) && mutt_str_equal("..", buf_string(&LastDir) + lastdirlen - 2)) @@ -708,7 +721,7 @@ static int op_generic_select_entry(struct BrowserPrivateData *priv, int op) else if (priv->state.is_mailbox_list) { buf_strcpy(&LastDir, ff->name); - buf_expand_path(&LastDir); + expand_path(&LastDir, false); } else if (priv->state.imap_browse) { @@ -780,7 +793,7 @@ static int op_generic_select_entry(struct BrowserPrivateData *priv, int op) if (priv->state.is_mailbox_list || OptNews) { buf_strcpy(priv->file, ff->name); - buf_expand_path(priv->file); + expand_path(priv->file, false); } else if (priv->state.imap_browse) { @@ -791,13 +804,13 @@ static int op_generic_select_entry(struct BrowserPrivateData *priv, int op) buf_concat_path(priv->file, buf_string(&LastDir), ff->name); } - return op_exit(priv, op); + return op_exit(priv, event); } /** * op_load_active - Load list of all newsgroups from NNTP server - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_load_active(struct BrowserPrivateData *priv, int op) +static int op_load_active(struct BrowserPrivateData *priv, const struct KeyEvent *event) { if (!OptNews) return FR_NOT_IMPL; @@ -834,7 +847,7 @@ static int op_load_active(struct BrowserPrivateData *priv, int op) /** * op_mailbox_list - List mailboxes with new mail - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_mailbox_list(struct BrowserPrivateData *priv, int op) +static int op_mailbox_list(struct BrowserPrivateData *priv, const struct KeyEvent *event) { mutt_mailbox_list(); return FR_SUCCESS; @@ -843,7 +856,7 @@ static int op_mailbox_list(struct BrowserPrivateData *priv, int op) /** * op_rename_mailbox - Rename the current mailbox (IMAP only) - Implements ::browser_function_t - @ingroup browser_function_api */ -static int op_rename_mailbox(struct BrowserPrivateData *priv, int op) +static int op_rename_mailbox(struct BrowserPrivateData *priv, const struct KeyEvent *event) { int index = menu_get_index(priv->menu); struct FolderFile *ff = ARRAY_GET(&priv->state.entry, index); @@ -874,10 +887,11 @@ static int op_rename_mailbox(struct BrowserPrivateData *priv, int op) * - OP_SORT * - OP_SORT_REVERSE */ -static int op_sort(struct BrowserPrivateData *priv, int op) +static int op_sort(struct BrowserPrivateData *priv, const struct KeyEvent *event) { bool resort = true; int sort = -1; + const int op = event->op; int reverse = (op == OP_SORT_REVERSE); switch (mw_multi_choice((reverse) ? @@ -939,7 +953,7 @@ static int op_sort(struct BrowserPrivateData *priv, int op) * - OP_SUBSCRIBE_PATTERN * - OP_UNSUBSCRIBE_PATTERN */ -static int op_subscribe_pattern(struct BrowserPrivateData *priv, int op) +static int op_subscribe_pattern(struct BrowserPrivateData *priv, const struct KeyEvent *event) { if (!OptNews) return FR_NOT_IMPL; @@ -950,6 +964,7 @@ static int op_subscribe_pattern(struct BrowserPrivateData *priv, int op) char tmp2[256] = { 0 }; + const int op = event->op; struct Buffer *buf = buf_pool_get(); if (op == OP_SUBSCRIBE_PATTERN) snprintf(tmp2, sizeof(tmp2), _("Subscribe pattern: ")); @@ -1024,13 +1039,14 @@ static int op_subscribe_pattern(struct BrowserPrivateData *priv, int op) * - OP_CHECK_NEW * - OP_TOGGLE_MAILBOXES */ -static int op_toggle_mailboxes(struct BrowserPrivateData *priv, int op) +static int op_toggle_mailboxes(struct BrowserPrivateData *priv, const struct KeyEvent *event) { if (priv->state.is_mailbox_list) { priv->last_selected_mailbox = menu_get_index(priv->menu); } + const int op = event->op; if (op == OP_TOGGLE_MAILBOXES) { priv->state.is_mailbox_list = !priv->state.is_mailbox_list; @@ -1128,11 +1144,11 @@ static const struct BrowserFunction BrowserFunctions[] = { /** * browser_function_dispatcher - Perform a Browser function - * @param win Window for the Browser - * @param op Operation to perform, e.g. OP_GOTO_PARENT + * @param win Window for the Browser + * @param event Event to process * @retval num #FunctionRetval, e.g. #FR_SUCCESS */ -int browser_function_dispatcher(struct MuttWindow *win, int op) +int browser_function_dispatcher(struct MuttWindow *win, const struct KeyEvent *event) { // The Dispatcher may be called on any Window in the Dialog struct MuttWindow *dlg = dialog_find(win); @@ -1148,9 +1164,9 @@ int browser_function_dispatcher(struct MuttWindow *win, int op) for (size_t i = 0; BrowserFunctions[i].op != OP_NULL; i++) { const struct BrowserFunction *fn = &BrowserFunctions[i]; - if (fn->op == op) + if (fn->op == event->op) { - rc = fn->function(priv, op); + rc = fn->function(priv, event); break; } } diff --git a/browser/functions.h b/browser/functions.h index ecf074f5a4d..b1828a75531 100644 --- a/browser/functions.h +++ b/browser/functions.h @@ -23,6 +23,8 @@ #ifndef MUTT_BROWSER_FUNCTIONS_H #define MUTT_BROWSER_FUNCTIONS_H +#include "key/lib.h" + struct MuttWindow; struct BrowserPrivateData; @@ -31,11 +33,14 @@ struct BrowserPrivateData; * * Prototype for a Browser Function * - * @param priv Private Browser data - * @param op Operation to perform, e.g. OP_MAIN_LIMIT + * @param priv Private Browser data + * @param event Event to process * @retval enum #FunctionRetval + * + * @pre priv is not NULL + * @pre event is not NULL */ -typedef int (*browser_function_t)(struct BrowserPrivateData *priv, int op); +typedef int (*browser_function_t)(struct BrowserPrivateData *priv, const struct KeyEvent *event); /** * struct BrowserFunction - A NeoMutt function @@ -46,6 +51,6 @@ struct BrowserFunction browser_function_t function; ///< Function to call }; -int browser_function_dispatcher(struct MuttWindow *win, int op); +int browser_function_dispatcher(struct MuttWindow *win, const struct KeyEvent *event); #endif //MUTT_BROWSER_FUNCTIONS_H diff --git a/browser/lib.h b/browser/lib.h index 9adb11d9a38..ad46988b9d2 100644 --- a/browser/lib.h +++ b/browser/lib.h @@ -32,6 +32,7 @@ * | browser/dlg_browser.c | @subpage browser_dlg_browser | * | browser/expando.c | @subpage browser_expando | * | browser/functions.c | @subpage browser_functions | + * | browser/module.c | @subpage browser_module | * | browser/private_data.c | @subpage browser_private_data | * | browser/sort.c | @subpage browser_sorting | */ @@ -49,6 +50,7 @@ struct Mailbox; struct Menu; struct MuttWindow; struct stat; +struct SubMenu; extern struct Buffer LastDir; extern struct Buffer LastDirBackup; @@ -142,12 +144,15 @@ enum ExpandoDataFolder */ struct BrowserState { - struct BrowserEntryArray entry; ///< Array of files / dirs / mailboxes - bool imap_browse; ///< IMAP folder - char *folder; ///< Folder name - bool is_mailbox_list; ///< Viewing mailboxes + struct BrowserEntryArray entry; ///< Array of files / dirs / mailboxes + bool imap_browse; ///< IMAP folder + char *folder; ///< Folder name + bool is_mailbox_list; ///< Viewing mailboxes }; + +void browser_init_keys(struct SubMenu *sm_generic); + void dlg_browser(struct Buffer *file, SelectFileFlags flags, struct Mailbox *m, char ***files, int *numfiles); void mutt_browser_select_dir(const char *f); void mutt_browser_cleanup(void); diff --git a/browser/module.c b/browser/module.c new file mode 100644 index 00000000000..7b737a9fdb9 --- /dev/null +++ b/browser/module.c @@ -0,0 +1,58 @@ +/** + * @file + * Definition of the Browser Module + * + * @authors + * Copyright (C) 2025-2026 Richard Russon + * + * @copyright + * This program is free software: 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 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page browser_module Definition of the Browser Module + * + * Definition of the Browser Module + */ + +#include "config.h" +#include +#include +#include "config/lib.h" +#include "core/lib.h" + +extern struct ConfigDef BrowserVars[]; + +/** + * browser_config_define_variables - Define the Config Variables - Implements Module::config_define_variables() + */ +static bool browser_config_define_variables(struct NeoMutt *n, struct ConfigSet *cs) +{ + return cs_register_variables(cs, BrowserVars); +} + +/** + * ModuleBrowser - Module for the Browser library + */ +const struct Module ModuleBrowser = { + "browser", + NULL, // init + NULL, // config_define_types + browser_config_define_variables, + NULL, // commands_register + NULL, // gui_init + NULL, // gui_cleanup + NULL, // cleanup + NULL, // mod_data +}; diff --git a/cli/lib.h b/cli/lib.h index 26751e8be3a..e98b3e2957e 100644 --- a/cli/lib.h +++ b/cli/lib.h @@ -25,6 +25,10 @@ * * Parse the Command Line * + * Command line flags in use: + * - `abcdefghi..lmn.p..s..v..yz` + * - `A.CDEFGH......O.QRS......Z` + * * | File | Description | * | :--------------- | :---------------------- | * | cli/parse.h | @subpage cli_parse | @@ -34,8 +38,8 @@ #ifndef MUTT_CLI_LIB_H #define MUTT_CLI_LIB_H -// IWYU pragma: begin_keep #include +// IWYU pragma: begin_keep #include "objects.h" // IWYU pragma: end_keep diff --git a/color/commands.c b/color/commands.c index 831048070e8..2a6a0346d8f 100644 --- a/color/commands.c +++ b/color/commands.c @@ -4,7 +4,7 @@ * * @authors * Copyright (C) 2021-2022 Pietro Cerutti - * Copyright (C) 2021-2023 Richard Russon + * Copyright (C) 2021-2025 Richard Russon * Copyright (C) 2023 Dennis Schön * * @copyright @@ -31,7 +31,6 @@ #include "config.h" #include #include -#include #include "mutt/lib.h" #include "config/lib.h" #include "core/lib.h" @@ -136,36 +135,49 @@ void get_colorid_name(unsigned int cid, struct Buffer *buf) /** * parse_object - Identify a colour object - * @param[in] buf Temporary Buffer space - * @param[in] s Buffer containing string to be parsed - * @param[out] cid Object type, e.g. #MT_COLOR_TILDE - * @param[out] err Buffer for error messages + * @param[in] cmd Command being parsed + * @param[in] line Buffer containing string to be parsed + * @param[out] cid Object type, e.g. #MT_COLOR_TILDE + * @param[out] err Buffer for error messages * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS * * Identify a colour object, e.g. "message", "compose header" */ -static enum CommandResult parse_object(struct Buffer *buf, struct Buffer *s, +static enum CommandResult parse_object(const struct Command *cmd, struct Buffer *line, enum ColorId *cid, struct Buffer *err) { - if (mutt_istr_equal(buf_string(buf), "compose")) + if (!MoreArgsF(line, TOKEN_COMMENT)) { - if (!MoreArgs(s)) + buf_printf(err, _("%s: too few arguments"), cmd->name); + return MUTT_CMD_WARNING; + } + + struct Buffer *token = buf_pool_get(); + + parse_extract_token(token, line, TOKEN_NO_FLAGS); + color_debug(LL_DEBUG5, "color: %s\n", buf_string(token)); + + if (mutt_istr_equal(buf_string(token), "compose")) + { + if (!MoreArgs(line)) { - buf_printf(err, _("%s: too few arguments"), "color"); + buf_printf(err, _("%s: too few arguments"), cmd->name); + buf_pool_release(&token); return MUTT_CMD_WARNING; } - struct Buffer *tmp = buf_pool_get(); - parse_extract_token(tmp, s, TOKEN_NO_FLAGS); - buf_fix_dptr(buf); - buf_add_printf(buf, "_%s", buf_string(tmp)); - buf_pool_release(&tmp); + struct Buffer *suffix = buf_pool_get(); + parse_extract_token(suffix, line, TOKEN_NO_FLAGS); + buf_fix_dptr(token); + buf_add_printf(token, "_%s", buf_string(suffix)); + buf_pool_release(&suffix); } - int rc = mutt_map_get_value(buf_string(buf), ColorFields); + int rc = mutt_map_get_value(buf_string(token), ColorFields); if (rc == -1) { - buf_printf(err, _("%s: no such object"), buf_string(buf)); + buf_printf(err, _("%s: no such object"), buf_string(token)); + buf_pool_release(&token); return MUTT_CMD_WARNING; } else @@ -174,111 +186,128 @@ static enum CommandResult parse_object(struct Buffer *buf, struct Buffer *s, } *cid = rc; + buf_pool_release(&token); return MUTT_CMD_SUCCESS; } /** - * parse_uncolor_command - Parse an 'uncolor' command - * @param buf Temporary Buffer space - * @param s Buffer containing string to be parsed - * @param err Buffer for error messages - * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS + * parse_uncolor_command - Parse an 'uncolor' command - Implements Command::parse() - @ingroup command_parse * * Usage: * * uncolor OBJECT [ PATTERN | REGEX | * ] */ -static enum CommandResult parse_uncolor_command(struct Buffer *buf, - struct Buffer *s, struct Buffer *err) +enum CommandResult parse_uncolor_command(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, + struct ParseError *pe) { - if (!MoreArgs(s)) + struct Buffer *err = pe->message; + + if (!MoreArgs(line)) { - buf_printf(err, _("%s: too few arguments"), "uncolor"); + buf_printf(err, _("%s: too few arguments"), cmd->name); return MUTT_CMD_WARNING; } - parse_extract_token(buf, s, TOKEN_NO_FLAGS); + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_ERROR; - if (mutt_str_equal(buf_string(buf), "*")) + // Peek at the next token ('*' won't match a colour name) + if (line->dptr[0] == '*') { - colors_reset(); - return MUTT_CMD_SUCCESS; + parse_extract_token(token, line, TOKEN_NO_FLAGS); + if (mutt_str_equal(buf_string(token), "*")) + { + colors_reset(); + rc = MUTT_CMD_SUCCESS; + goto done; + } } unsigned int cid = MT_COLOR_NONE; - color_debug(LL_DEBUG5, "uncolor: %s\n", buf_string(buf)); - enum CommandResult rc = parse_object(buf, s, &cid, err); + color_debug(LL_DEBUG5, "uncolor: %s\n", buf_string(token)); + rc = parse_object(cmd, line, &cid, err); if (rc != MUTT_CMD_SUCCESS) - return rc; + goto done; if (cid == -1) { - buf_printf(err, _("%s: no such object"), buf_string(buf)); - return MUTT_CMD_ERROR; + buf_printf(err, _("%s: no such object"), buf_string(token)); + rc = MUTT_CMD_ERROR; + goto done; } - if ((cid == MT_COLOR_STATUS) && !MoreArgs(s)) + if ((cid == MT_COLOR_STATUS) && !MoreArgs(line)) { color_debug(LL_DEBUG5, "simple\n"); simple_color_reset(cid); // default colour for the status bar - return MUTT_CMD_SUCCESS; + goto done; } if (!mutt_color_has_pattern(cid)) { color_debug(LL_DEBUG5, "simple\n"); simple_color_reset(cid); - return MUTT_CMD_SUCCESS; + goto done; } - if (!MoreArgs(s)) + if (!MoreArgs(line)) { if (regex_colors_parse_uncolor(cid, NULL)) - return MUTT_CMD_SUCCESS; + rc = MUTT_CMD_SUCCESS; else - return MUTT_CMD_ERROR; + rc = MUTT_CMD_ERROR; + goto done; } do { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - if (mutt_str_equal("*", buf_string(buf))) + parse_extract_token(token, line, TOKEN_NO_FLAGS); + if (mutt_str_equal("*", buf_string(token))) { if (regex_colors_parse_uncolor(cid, NULL)) - return MUTT_CMD_SUCCESS; + rc = MUTT_CMD_SUCCESS; else - return MUTT_CMD_ERROR; + rc = MUTT_CMD_ERROR; + goto done; } - regex_colors_parse_uncolor(cid, buf_string(buf)); + regex_colors_parse_uncolor(cid, buf_string(token)); - } while (MoreArgs(s)); + } while (MoreArgs(line)); - return MUTT_CMD_SUCCESS; + rc = MUTT_CMD_SUCCESS; + +done: + buf_pool_release(&token); + return rc; } /** * parse_color_command - Parse a 'color' command - * @param buf Temporary Buffer space - * @param s Buffer containing string to be parsed + * @param cmd Command being parsed + * @param line Buffer containing string to be parsed * @param err Buffer for error messages * @param callback Function to handle command - Implements ::parser_callback_t - * @param color If true "color", else "mono" * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS * * Usage: * * color OBJECT [ ATTRS ] FG BG [ PATTERN | REGEX ] [ NUM ] * * mono OBJECT ATTRS [ PATTERN | REGEX ] [ NUM ] */ -static enum CommandResult parse_color_command(struct Buffer *buf, - struct Buffer *s, struct Buffer *err, - parser_callback_t callback, bool color) +static enum CommandResult parse_color_command(const struct Command *cmd, + struct Buffer *line, struct Buffer *err, + parser_callback_t callback) { + if (!cmd || !line || !err) + return MUTT_CMD_ERROR; + unsigned int match = 0; enum ColorId cid = MT_COLOR_NONE; enum CommandResult rc = MUTT_CMD_ERROR; struct AttrColor *ac = NULL; + struct Buffer *token = buf_pool_get(); - if (!MoreArgs(s)) + if (!MoreArgs(line)) { if (StartupComplete) { @@ -287,47 +316,45 @@ static enum CommandResult parse_color_command(struct Buffer *buf, } else { - buf_printf(err, _("%s: too few arguments"), color ? "color" : "mono"); + buf_printf(err, _("%s: too few arguments"), cmd->name); rc = MUTT_CMD_WARNING; } goto done; } - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - color_debug(LL_DEBUG5, "color: %s\n", buf_string(buf)); - - rc = parse_object(buf, s, &cid, err); + rc = parse_object(cmd, line, &cid, err); if (rc != MUTT_CMD_SUCCESS) goto done; ac = attr_color_new(); - rc = callback(buf, s, ac, err); + rc = callback(cmd, line, ac, err); if (rc != MUTT_CMD_SUCCESS) goto done; //------------------------------------------------------------------ // Business Logic + rc = MUTT_CMD_ERROR; if ((ac->fg.type == CT_RGB) || (ac->bg.type == CT_RGB)) { #ifndef NEOMUTT_DIRECT_COLORS - buf_printf(err, _("Direct colors support not compiled in: %s"), buf_string(s)); - return MUTT_CMD_ERROR; + buf_printf(err, _("Direct colors support not compiled in: %s"), buf_string(line)); + goto done; #endif const bool c_color_directcolor = cs_subset_bool(NeoMutt->sub, "color_directcolor"); if (!c_color_directcolor) { - buf_printf(err, _("Direct colors support disabled: %s"), buf_string(s)); - return MUTT_CMD_ERROR; + buf_printf(err, _("Direct colors support disabled: %s"), buf_string(line)); + goto done; } } if ((ac->fg.color >= COLORS) || (ac->bg.color >= COLORS)) { - buf_printf(err, _("%s: color not supported by term"), buf_string(s)); - return MUTT_CMD_ERROR; + buf_printf(err, _("%s: color not supported by term"), buf_string(line)); + goto done; } //------------------------------------------------------------------ @@ -337,46 +364,45 @@ static enum CommandResult parse_color_command(struct Buffer *buf, if (mutt_color_has_pattern(cid) && (cid != MT_COLOR_STATUS)) { color_debug(LL_DEBUG5, "regex needed\n"); - if (MoreArgs(s)) + if (MoreArgs(line)) { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); + parse_extract_token(token, line, TOKEN_NO_FLAGS); } else { - buf_strcpy(buf, ".*"); + buf_strcpy(token, ".*"); } } - if (MoreArgs(s) && (cid != MT_COLOR_STATUS)) + if (MoreArgs(line) && (cid != MT_COLOR_STATUS)) { - buf_printf(err, _("%s: too many arguments"), color ? "color" : "mono"); + buf_printf(err, _("%s: too many arguments"), cmd->name); rc = MUTT_CMD_WARNING; goto done; } - if (regex_colors_parse_color_list(cid, buf_string(buf), ac, &rc, err)) + if (regex_colors_parse_color_list(cid, buf_string(token), ac, &rc, err)) { color_debug(LL_DEBUG5, "regex_colors_parse_color_list done\n"); goto done; // do nothing } - else if ((cid == MT_COLOR_STATUS) && MoreArgs(s)) + else if ((cid == MT_COLOR_STATUS) && MoreArgs(line)) { color_debug(LL_DEBUG5, "status\n"); /* 'color status fg bg' can have up to 2 arguments: * 0 arguments: sets the default status color (handled below by else part) * 1 argument : colorize pattern on match * 2 arguments: colorize nth submatch of pattern */ - parse_extract_token(buf, s, TOKEN_NO_FLAGS); + parse_extract_token(token, line, TOKEN_NO_FLAGS); - if (MoreArgs(s)) + if (MoreArgs(line)) { struct Buffer *tmp = buf_pool_get(); - parse_extract_token(tmp, s, TOKEN_NO_FLAGS); + parse_extract_token(tmp, line, TOKEN_NO_FLAGS); if (!mutt_str_atoui_full(buf_string(tmp), &match)) { - buf_printf(err, _("%s: invalid number: %s"), color ? "color" : "mono", - buf_string(tmp)); + buf_printf(err, _("%s: invalid number: %s"), cmd->name, buf_string(tmp)); buf_pool_release(&tmp); rc = MUTT_CMD_WARNING; goto done; @@ -384,14 +410,14 @@ static enum CommandResult parse_color_command(struct Buffer *buf, buf_pool_release(&tmp); } - if (MoreArgs(s)) + if (MoreArgs(line)) { - buf_printf(err, _("%s: too many arguments"), color ? "color" : "mono"); + buf_printf(err, _("%s: too many arguments"), cmd->name); rc = MUTT_CMD_WARNING; goto done; } - rc = regex_colors_parse_status_list(cid, buf_string(buf), ac, match, err); + rc = regex_colors_parse_status_list(cid, buf_string(token), ac, match, err); goto done; } else // Remaining simple colours @@ -405,88 +431,140 @@ static enum CommandResult parse_color_command(struct Buffer *buf, if (rc == MUTT_CMD_SUCCESS) { - get_colorid_name(cid, buf); - color_debug(LL_DEBUG5, "NT_COLOR_SET: %s\n", buf_string(buf)); + get_colorid_name(cid, token); + color_debug(LL_DEBUG5, "NT_COLOR_SET: %s\n", buf_string(token)); struct EventColor ev_c = { cid, NULL }; notify_send(ColorsNotify, NT_COLOR, NT_COLOR_SET, &ev_c); } done: attr_color_free(&ac); + buf_pool_release(&token); return rc; } /** * parse_uncolor - Parse the 'uncolor' command - Implements Command::parse() - @ingroup command_parse + * + * Parse: + * - `uncolor { * | ... }` */ -enum CommandResult parse_uncolor(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) +enum CommandResult parse_uncolor(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, struct ParseError *pe) { + struct Buffer *err = pe->message; + + if (!MoreArgs(line)) + { + buf_printf(err, _("%s: too few arguments"), cmd->name); + return MUTT_CMD_WARNING; + } + + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_SUCCESS; + if (!OptGui) // No GUI, so quietly discard the command { - while (MoreArgs(s)) + while (MoreArgs(line)) { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); + parse_extract_token(token, line, TOKEN_NO_FLAGS); } - return MUTT_CMD_SUCCESS; + goto done; } - color_debug(LL_DEBUG5, "parse: %s\n", buf_string(buf)); - enum CommandResult rc = parse_uncolor_command(buf, s, err); - curses_colors_dump(buf); + color_debug(LL_DEBUG5, "parse: %s\n", buf_string(token)); + rc = parse_uncolor_command(cmd, line, pc, pe); + curses_colors_dump(token); + +done: + buf_pool_release(&token); return rc; } /** * parse_unmono - Parse the 'unmono' command - Implements Command::parse() - @ingroup command_parse + * + * Parse: + * - `unmono { * | ... }` */ -enum CommandResult parse_unmono(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) +enum CommandResult parse_unmono(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, struct ParseError *pe) { - *s->dptr = '\0'; /* fake that we're done parsing */ + // Quietly discard the command + struct Buffer *token = buf_pool_get(); + while (MoreArgs(line)) + { + parse_extract_token(token, line, TOKEN_NO_FLAGS); + } + buf_pool_release(&token); + return MUTT_CMD_SUCCESS; } /** * parse_color - Parse the 'color' command - Implements Command::parse() - @ingroup command_parse + * + * Parse: + * - `color object [ attribute ...] foreground background` + * - `color index [ attribute ...] foreground background [ pattern ]` + * - `color { header | body } [ attribute ...] foreground background regex` */ -enum CommandResult parse_color(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) +enum CommandResult parse_color(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, struct ParseError *pe) { + struct Buffer *err = pe->message; + + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_SUCCESS; + // No GUI, or no colours, so quietly discard the command if (!OptGui || (COLORS == 0)) { - while (MoreArgs(s)) + while (MoreArgs(line)) { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); + parse_extract_token(token, line, TOKEN_NO_FLAGS); } - return MUTT_CMD_SUCCESS; + goto done; } - color_debug(LL_DEBUG5, "parse: %s\n", buf_string(buf)); - enum CommandResult rc = parse_color_command(buf, s, err, parse_color_pair, true); - curses_colors_dump(buf); + color_debug(LL_DEBUG5, "parse: color\n"); + rc = parse_color_command(cmd, line, err, parse_color_pair); + curses_colors_dump(token); + +done: + buf_pool_release(&token); return rc; } /** * parse_mono - Parse the 'mono' command - Implements Command::parse() - @ingroup command_parse + * + * Parse: + * - `mono [ | ]` */ -enum CommandResult parse_mono(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) +enum CommandResult parse_mono(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, struct ParseError *pe) { + struct Buffer *err = pe->message; + + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_SUCCESS; + // No GUI, or colours available, so quietly discard the command if (!OptGui || (COLORS != 0)) { - while (MoreArgs(s)) + while (MoreArgs(line)) { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); + parse_extract_token(token, line, TOKEN_NO_FLAGS); } - return MUTT_CMD_SUCCESS; + goto done; } - color_debug(LL_DEBUG5, "parse: %s\n", buf_string(buf)); - enum CommandResult rc = parse_color_command(buf, s, err, parse_attr_spec, false); - curses_colors_dump(buf); + color_debug(LL_DEBUG5, "parse: %s\n", buf_string(token)); + rc = parse_color_command(cmd, line, err, parse_attr_spec); + curses_colors_dump(token); + +done: + buf_pool_release(&token); return rc; } diff --git a/color/commands.h b/color/commands.h index 76f2312d3d4..2acf7af9ed5 100644 --- a/color/commands.h +++ b/color/commands.h @@ -3,7 +3,7 @@ * Parse colour commands * * @authors - * Copyright (C) 2021-2023 Richard Russon + * Copyright (C) 2021-2025 Richard Russon * * @copyright * This program is free software: you can redistribute it and/or modify it under @@ -24,30 +24,37 @@ #define MUTT_COLOR_COMMANDS_H #include "config.h" -#include #include "core/lib.h" #include "mutt/lib.h" struct AttrColor; +struct ParseContext; +struct ParseError; /** * @defgroup parser_callback_api Colour Parsing API * * Prototype for a function to parse color config * - * @param[in] buf Temporary Buffer space - * @param[in] s Buffer containing string to be parsed + * @param[in] cmd Command being parsed + * @param[in] line Buffer containing string to be parsed * @param[out] ac Colour * @param[out] err Buffer for error messages * @retval 0 Success * @retval -1 Error + * + * @pre cmd is not NULL + * @pre line is not NULL + * @pre ac is not NULL + * @pre err is not NULL */ -typedef enum CommandResult (*parser_callback_t)(struct Buffer *buf, struct Buffer *s, struct AttrColor *ac, struct Buffer *err); +typedef enum CommandResult (*parser_callback_t)(const struct Command *cmd, struct Buffer *line, struct AttrColor *ac, struct Buffer *err); -enum CommandResult parse_color (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_mono (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_uncolor(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_unmono (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); +enum CommandResult parse_color (const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); +enum CommandResult parse_mono (const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); +enum CommandResult parse_uncolor (const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); +enum CommandResult parse_uncolor_command(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); +enum CommandResult parse_unmono (const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); void get_colorid_name(unsigned int color_id, struct Buffer *buf); diff --git a/color/lib.h b/color/lib.h index 23787b5dba1..91150d314e1 100644 --- a/color/lib.h +++ b/color/lib.h @@ -35,6 +35,7 @@ * | color/debug.c | @subpage color_debug | * | color/dump.c | @subpage color_dump | * | color/merged.c | @subpage color_merge | + * | color/module.c | @subpage color_module | * | color/notify.c | @subpage color_notify | * | color/parse_ansi.c | @subpage color_parse_ansi | * | color/parse_color.c | @subpage color_parse_color | diff --git a/color/module.c b/color/module.c new file mode 100644 index 00000000000..c3bfb8ca7d4 --- /dev/null +++ b/color/module.c @@ -0,0 +1,46 @@ +/** + * @file + * Definition of the Color Module + * + * @authors + * Copyright (C) 2025-2026 Richard Russon + * + * @copyright + * This program is free software: 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 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page color_module Definition of the Color Module + * + * Definition of the Color Module + */ + +#include "config.h" +#include +#include "core/lib.h" + +/** + * ModuleColor - Module for the Color library + */ +const struct Module ModuleColor = { + "color", + NULL, // init + NULL, // config_define_types + NULL, // config_define_variables + NULL, // commands_register + NULL, // gui_init + NULL, // gui_cleanup + NULL, // cleanup + NULL, // mod_data +}; diff --git a/color/parse_ansi.c b/color/parse_ansi.c index 1065c5c489a..f61c95ee0c7 100644 --- a/color/parse_ansi.c +++ b/color/parse_ansi.c @@ -4,6 +4,7 @@ * * @authors * Copyright (C) 2023 Richard Russon + * Copyright (C) 2025 Thomas Klausner * * @copyright * This program is free software: you can redistribute it and/or modify it under diff --git a/color/parse_color.c b/color/parse_color.c index 38cbf9f2d5c..d82ad24f330 100644 --- a/color/parse_color.c +++ b/color/parse_color.c @@ -3,7 +3,7 @@ * Parse colour commands * * @authors - * Copyright (C) 2023 Richard Russon + * Copyright (C) 2025 Richard Russon * * @copyright * This program is free software: you can redistribute it and/or modify it under @@ -57,7 +57,7 @@ const struct Mapping ColorNames[] = { /** * AttributeNames - Mapping of attribute names to their IDs */ -static struct Mapping AttributeNames[] = { +static const struct Mapping AttributeNames[] = { // clang-format off { "bold", A_BOLD }, { "italic", A_ITALIC }, @@ -279,27 +279,30 @@ enum CommandResult parse_color_name(const char *s, struct ColorElement *elem, * * Parse a pair of colours, e.g. "red default" */ -enum CommandResult parse_color_pair(struct Buffer *buf, struct Buffer *s, +enum CommandResult parse_color_pair(const struct Command *cmd, struct Buffer *line, struct AttrColor *ac, struct Buffer *err) { + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_WARNING; + while (true) { - if (!MoreArgsF(s, TOKEN_COMMENT)) + if (!MoreArgsF(line, TOKEN_COMMENT)) { - buf_printf(err, _("%s: too few arguments"), "color"); - return MUTT_CMD_WARNING; + buf_printf(err, _("%s: too few arguments"), cmd->name); + goto done; } - parse_extract_token(buf, s, TOKEN_COMMENT); - if (buf_is_empty(buf)) + parse_extract_token(token, line, TOKEN_COMMENT); + if (buf_is_empty(token)) continue; - int attr = mutt_map_get_value(buf_string(buf), AttributeNames); + int attr = mutt_map_get_value(buf_string(token), AttributeNames); if (attr == -1) { - enum CommandResult rc = parse_color_name(buf_string(buf), &ac->fg, err); + rc = parse_color_name(buf_string(token), &ac->fg, err); if (rc != MUTT_CMD_SUCCESS) - return rc; + goto done; break; } @@ -309,39 +312,44 @@ enum CommandResult parse_color_pair(struct Buffer *buf, struct Buffer *s, ac->attrs |= attr; // Merge with other attributes } - if (!MoreArgsF(s, TOKEN_COMMENT)) + if (!MoreArgsF(line, TOKEN_COMMENT)) { buf_printf(err, _("%s: too few arguments"), "color"); - return MUTT_CMD_WARNING; + rc = MUTT_CMD_WARNING; + goto done; } - parse_extract_token(buf, s, TOKEN_COMMENT); + parse_extract_token(token, line, TOKEN_COMMENT); - return parse_color_name(buf_string(buf), &ac->bg, err); + rc = parse_color_name(buf_string(token), &ac->bg, err); + +done: + buf_pool_release(&token); + return rc; } /** * parse_attr_spec - Parse an attribute description - Implements ::parser_callback_t - @ingroup parser_callback_api */ -enum CommandResult parse_attr_spec(struct Buffer *buf, struct Buffer *s, +enum CommandResult parse_attr_spec(const struct Command *cmd, struct Buffer *line, struct AttrColor *ac, struct Buffer *err) { - if (!buf || !s || !ac) - return MUTT_CMD_ERROR; + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_WARNING; - if (!MoreArgs(s)) + if (!MoreArgs(line)) { - buf_printf(err, _("%s: too few arguments"), "mono"); - return MUTT_CMD_WARNING; + buf_printf(err, _("%s: too few arguments"), cmd->name); + goto done; } - parse_extract_token(buf, s, TOKEN_NO_FLAGS); + parse_extract_token(token, line, TOKEN_NO_FLAGS); - int attr = mutt_map_get_value(buf_string(buf), AttributeNames); + int attr = mutt_map_get_value(buf_string(token), AttributeNames); if (attr == -1) { - buf_printf(err, _("%s: no such attribute"), buf_string(buf)); - return MUTT_CMD_WARNING; + buf_printf(err, _("%s: no such attribute"), buf_string(token)); + goto done; } if (attr == A_NORMAL) @@ -349,5 +357,9 @@ enum CommandResult parse_attr_spec(struct Buffer *buf, struct Buffer *s, else ac->attrs |= attr; // Merge with other attributes - return MUTT_CMD_SUCCESS; + rc = MUTT_CMD_SUCCESS; + +done: + buf_pool_release(&token); + return rc; } diff --git a/color/parse_color.h b/color/parse_color.h index ce6675104d1..b7acabbaadb 100644 --- a/color/parse_color.h +++ b/color/parse_color.h @@ -30,7 +30,7 @@ struct AttrColor; extern const struct Mapping ColorNames[]; -enum CommandResult parse_attr_spec (struct Buffer *buf, struct Buffer *s, struct AttrColor *ac, struct Buffer *err); -enum CommandResult parse_color_pair(struct Buffer *buf, struct Buffer *s, struct AttrColor *ac, struct Buffer *err); +enum CommandResult parse_attr_spec (const struct Command *cmd, struct Buffer *line, struct AttrColor *ac, struct Buffer *err); +enum CommandResult parse_color_pair(const struct Command *cmd, struct Buffer *line, struct AttrColor *ac, struct Buffer *err); #endif /* MUTT_COLOR_PARSE_COLOR_H */ diff --git a/color/regex.c b/color/regex.c index 7cdf256455f..d58ec4104b7 100644 --- a/color/regex.c +++ b/color/regex.c @@ -3,7 +3,7 @@ * Regex Colour * * @authors - * Copyright (C) 2021-2023 Richard Russon + * Copyright (C) 2021-2025 Richard Russon * Copyright (C) 2022 Pietro Cerutti * * @copyright diff --git a/commands.c b/commands.c deleted file mode 100644 index 9c1e63254a2..00000000000 --- a/commands.c +++ /dev/null @@ -1,1744 +0,0 @@ -/** - * @file - * Functions to parse commands in a config file - * - * @authors - * Copyright (C) 1996-2002,2007,2010,2012-2013,2016 Michael R. Elkins - * Copyright (C) 2004 g10 Code GmbH - * Copyright (C) 2019-2025 Richard Russon - * Copyright (C) 2020 Aditya De Saha - * Copyright (C) 2020 Matthew Hughes - * Copyright (C) 2020 R Primus - * Copyright (C) 2020-2022 Pietro Cerutti - * Copyright (C) 2022 Marco Sirabella - * Copyright (C) 2023 Dennis Schön - * - * @copyright - * This program is free software: 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 2 of the License, or (at your option) any later - * version. - * - * This program 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. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ - -/** - * @page neo_commands Functions to parse commands in a config file - * - * Functions to parse commands in a config file - */ - -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include "mutt/lib.h" -#include "address/lib.h" -#include "config/lib.h" -#include "email/lib.h" -#include "core/lib.h" -#include "alias/lib.h" -#include "gui/lib.h" -#include "mutt.h" -#include "commands.h" -#include "attach/lib.h" -#include "color/lib.h" -#include "imap/lib.h" -#include "key/lib.h" -#include "menu/lib.h" -#include "pager/lib.h" -#include "parse/lib.h" -#include "store/lib.h" -#include "alternates.h" -#include "globals.h" -#include "muttlib.h" -#include "mx.h" -#include "score.h" -#include "version.h" -#ifdef USE_INOTIFY -#include "monitor.h" -#endif -#ifdef ENABLE_NLS -#include -#endif - -/// LIFO designed to contain the list of config files that have been sourced and -/// avoid cyclic sourcing. -static struct ListHead MuttrcStack = STAILQ_HEAD_INITIALIZER(MuttrcStack); - -#define MAX_ERRS 128 - -/** - * enum TriBool - Tri-state boolean - */ -enum TriBool -{ - TB_UNSET = -1, ///< Value hasn't been set - TB_FALSE, ///< Value is false - TB_TRUE, ///< Value is true -}; - -/** - * enum GroupState - Type of email address group - */ -enum GroupState -{ - GS_NONE, ///< Group is missing an argument - GS_RX, ///< Entry is a regular expression - GS_ADDR, ///< Entry is an address -}; - -/** - * is_function - Is the argument a neomutt function? - * @param name Command name to be searched for - * @retval true Function found - * @retval false Function not found - */ -static bool is_function(const char *name) -{ - for (size_t i = 0; MenuNames[i].name; i++) - { - const struct MenuFuncOp *fns = km_get_table(MenuNames[i].value); - if (!fns) - continue; - - for (int j = 0; fns[j].name; j++) - if (mutt_str_equal(name, fns[j].name)) - return true; - } - return false; -} - -/** - * is_color_object - Is the argument a neomutt colour? - * @param name Colour name to be searched for - * @retval true Function found - * @retval false Function not found - */ -static bool is_color_object(const char *name) -{ - int cid = mutt_map_get_value(name, ColorFields); - - return (cid > 0); -} - -/** - * parse_grouplist - Parse a group context - * @param gl GroupList to add to - * @param buf Temporary Buffer space - * @param s Buffer containing string to be parsed - * @param err Buffer for error messages - * @retval 0 Success - * @retval -1 Error - */ -int parse_grouplist(struct GroupList *gl, struct Buffer *buf, struct Buffer *s, - struct Buffer *err) -{ - while (mutt_istr_equal(buf->data, "-group")) - { - if (!MoreArgs(s)) - { - buf_strcpy(err, _("-group: no group name")); - return -1; - } - - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - mutt_grouplist_add(gl, mutt_pattern_group(buf->data)); - - if (!MoreArgs(s)) - { - buf_strcpy(err, _("out of arguments")); - return -1; - } - - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - } - - return 0; -} - -/** - * parse_rc_line_cwd - Parse and run a muttrc line in a relative directory - * @param line Line to be parsed - * @param cwd File relative where to run the line - * @param err Where to write error messages - * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS - */ -enum CommandResult parse_rc_line_cwd(const char *line, char *cwd, struct Buffer *err) -{ - mutt_list_insert_head(&MuttrcStack, mutt_str_dup(NONULL(cwd))); - - enum CommandResult ret = parse_rc_line(line, err); - - struct ListNode *np = STAILQ_FIRST(&MuttrcStack); - STAILQ_REMOVE_HEAD(&MuttrcStack, entries); - FREE(&np->data); - FREE(&np); - - return ret; -} - -/** - * mutt_get_sourced_cwd - Get the current file path that is being parsed - * @retval ptr File path that is being parsed or cwd at runtime - * - * @note Caller is responsible for freeing returned string - */ -char *mutt_get_sourced_cwd(void) -{ - struct ListNode *np = STAILQ_FIRST(&MuttrcStack); - if (np && np->data) - return mutt_str_dup(np->data); - - // stack is empty, return our own dummy file relative to cwd - struct Buffer *cwd = buf_pool_get(); - mutt_path_getcwd(cwd); - buf_addstr(cwd, "/dummy.rc"); - char *ret = buf_strdup(cwd); - buf_pool_release(&cwd); - return ret; -} - -/** - * source_rc - Read an initialization file - * @param rcfile_path Path to initialization file - * @param err Buffer for error messages - * @retval <0 NeoMutt should pause to let the user know - */ -int source_rc(const char *rcfile_path, struct Buffer *err) -{ - int lineno = 0, rc = 0, warnings = 0; - enum CommandResult line_rc; - struct Buffer *token = NULL, *linebuf = NULL; - char *line = NULL; - char *currentline = NULL; - char rcfile[PATH_MAX + 1] = { 0 }; - size_t linelen = 0; - pid_t pid; - - mutt_str_copy(rcfile, rcfile_path, sizeof(rcfile)); - - size_t rcfilelen = mutt_str_len(rcfile); - if (rcfilelen == 0) - return -1; - - bool ispipe = rcfile[rcfilelen - 1] == '|'; - - if (!ispipe) - { - struct ListNode *np = STAILQ_FIRST(&MuttrcStack); - if (!mutt_path_to_absolute(rcfile, np ? NONULL(np->data) : "")) - { - mutt_error(_("Error: Can't build path of '%s'"), rcfile_path); - return -1; - } - - STAILQ_FOREACH(np, &MuttrcStack, entries) - { - if (mutt_str_equal(np->data, rcfile)) - { - break; - } - } - if (np) - { - mutt_error(_("Error: Cyclic sourcing of configuration file '%s'"), rcfile); - return -1; - } - - mutt_list_insert_head(&MuttrcStack, mutt_str_dup(rcfile)); - } - - mutt_debug(LL_DEBUG2, "Reading configuration file '%s'\n", rcfile); - - FILE *fp = mutt_open_read(rcfile, &pid); - if (!fp) - { - buf_printf(err, "%s: %s", rcfile, strerror(errno)); - return -1; - } - - token = buf_pool_get(); - linebuf = buf_pool_get(); - - const char *const c_config_charset = cs_subset_string(NeoMutt->sub, "config_charset"); - const char *const c_charset = cc_charset(); - while ((line = mutt_file_read_line(line, &linelen, fp, &lineno, MUTT_RL_CONT)) != NULL) - { - const bool conv = c_config_charset && c_charset; - if (conv) - { - currentline = mutt_str_dup(line); - if (!currentline) - continue; - mutt_ch_convert_string(¤tline, c_config_charset, c_charset, MUTT_ICONV_NO_FLAGS); - } - else - { - currentline = line; - } - - buf_strcpy(linebuf, currentline); - - buf_reset(err); - line_rc = parse_rc_buffer(linebuf, token, err); - if (line_rc == MUTT_CMD_ERROR) - { - mutt_error("%s:%d: %s", rcfile, lineno, buf_string(err)); - if (--rc < -MAX_ERRS) - { - if (conv) - FREE(¤tline); - break; - } - } - else if (line_rc == MUTT_CMD_WARNING) - { - /* Warning */ - mutt_warning("%s:%d: %s", rcfile, lineno, buf_string(err)); - warnings++; - } - else if (line_rc == MUTT_CMD_FINISH) - { - if (conv) - FREE(¤tline); - break; /* Found "finish" command */ - } - else - { - if (rc < 0) - rc = -1; - } - if (conv) - FREE(¤tline); - } - - FREE(&line); - mutt_file_fclose(&fp); - if (pid != -1) - filter_wait(pid); - - if (rc) - { - /* the neomuttrc source keyword */ - buf_reset(err); - buf_printf(err, (rc >= -MAX_ERRS) ? _("source: errors in %s") : _("source: reading aborted due to too many errors in %s"), - rcfile); - rc = -1; - } - else - { - /* Don't alias errors with warnings */ - if (warnings > 0) - { - buf_printf(err, ngettext("source: %d warning in %s", "source: %d warnings in %s", warnings), - warnings, rcfile); - rc = -2; - } - } - - if (!ispipe && !STAILQ_EMPTY(&MuttrcStack)) - { - struct ListNode *np = STAILQ_FIRST(&MuttrcStack); - STAILQ_REMOVE_HEAD(&MuttrcStack, entries); - FREE(&np->data); - FREE(&np); - } - - buf_pool_release(&token); - buf_pool_release(&linebuf); - return rc; -} - -/** - * parse_cd - Parse the 'cd' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_cd(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - buf_expand_path(buf); - if (buf_is_empty(buf)) - { - if (NeoMutt->home_dir) - { - buf_strcpy(buf, NeoMutt->home_dir); - } - else - { - buf_printf(err, _("%s: too few arguments"), "cd"); - return MUTT_CMD_ERROR; - } - } - - if (chdir(buf_string(buf)) != 0) - { - buf_printf(err, "cd: %s", strerror(errno)); - return MUTT_CMD_ERROR; - } - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_echo - Parse the 'echo' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_echo(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - if (!MoreArgs(s)) - { - buf_printf(err, _("%s: too few arguments"), "echo"); - return MUTT_CMD_WARNING; - } - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - OptForceRefresh = true; - mutt_message("%s", buf->data); - OptForceRefresh = false; - mutt_sleep(0); - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_finish - Parse the 'finish' command - Implements Command::parse() - @ingroup command_parse - * @retval #MUTT_CMD_FINISH Stop processing the current file - * @retval #MUTT_CMD_WARNING Failed - * - * If the 'finish' command is found, we should stop reading the current file. - */ -static enum CommandResult parse_finish(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - if (MoreArgs(s)) - { - buf_printf(err, _("%s: too many arguments"), "finish"); - return MUTT_CMD_WARNING; - } - - return MUTT_CMD_FINISH; -} - -/** - * parse_group - Parse the 'group' and 'ungroup' commands - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_group(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl); - enum GroupState gstate = GS_NONE; - - do - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - if (parse_grouplist(&gl, buf, s, err) == -1) - goto bail; - - if ((data == MUTT_UNGROUP) && mutt_istr_equal(buf->data, "*")) - { - mutt_grouplist_clear(&gl); - goto out; - } - - if (mutt_istr_equal(buf->data, "-rx")) - { - gstate = GS_RX; - } - else if (mutt_istr_equal(buf->data, "-addr")) - { - gstate = GS_ADDR; - } - else - { - switch (gstate) - { - case GS_NONE: - buf_printf(err, _("%sgroup: missing -rx or -addr"), - (data == MUTT_UNGROUP) ? "un" : ""); - goto warn; - - case GS_RX: - if ((data == MUTT_GROUP) && - (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)) - { - goto bail; - } - else if ((data == MUTT_UNGROUP) && - (mutt_grouplist_remove_regex(&gl, buf->data) < 0)) - { - goto bail; - } - break; - - case GS_ADDR: - { - char *estr = NULL; - struct AddressList al = TAILQ_HEAD_INITIALIZER(al); - mutt_addrlist_parse2(&al, buf->data); - if (TAILQ_EMPTY(&al)) - goto bail; - if (mutt_addrlist_to_intl(&al, &estr)) - { - buf_printf(err, _("%sgroup: warning: bad IDN '%s'"), - (data == 1) ? "un" : "", estr); - mutt_addrlist_clear(&al); - FREE(&estr); - goto bail; - } - if (data == MUTT_GROUP) - mutt_grouplist_add_addrlist(&gl, &al); - else if (data == MUTT_UNGROUP) - mutt_grouplist_remove_addrlist(&gl, &al); - mutt_addrlist_clear(&al); - break; - } - } - } - } while (MoreArgs(s)); - -out: - mutt_grouplist_destroy(&gl); - return MUTT_CMD_SUCCESS; - -bail: - mutt_grouplist_destroy(&gl); - return MUTT_CMD_ERROR; - -warn: - mutt_grouplist_destroy(&gl); - return MUTT_CMD_WARNING; -} - -/** - * parse_ifdef - Parse the 'ifdef' and 'ifndef' commands - Implements Command::parse() - @ingroup command_parse - * - * The 'ifdef' command allows conditional elements in the config file. - * If a given variable, function, command or compile-time symbol exists, then - * read the rest of the line of config commands. - * e.g. - * ifdef sidebar source ~/.neomutt/sidebar.rc - * - * If (data == 1) then it means use the 'ifndef' (if-not-defined) command. - * e.g. - * ifndef imap finish - */ -static enum CommandResult parse_ifdef(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - if (buf_is_empty(buf)) - { - buf_printf(err, _("%s: too few arguments"), (data ? "ifndef" : "ifdef")); - return MUTT_CMD_WARNING; - } - - // is the item defined as: - bool res = cs_subset_lookup(NeoMutt->sub, buf->data) // a variable? - || feature_enabled(buf->data) // a compiled-in feature? - || is_function(buf->data) // a function? - || commands_get(&NeoMutt->commands, buf->data) // a command? - || is_color_object(buf->data) // a color? -#ifdef USE_HCACHE - || store_is_valid_backend(buf->data) // a store? (database) -#endif - || mutt_str_getenv(buf->data); // an environment variable? - - if (!MoreArgs(s)) - { - buf_printf(err, _("%s: too few arguments"), (data ? "ifndef" : "ifdef")); - return MUTT_CMD_WARNING; - } - parse_extract_token(buf, s, TOKEN_SPACE); - - /* ifdef KNOWN_SYMBOL or ifndef UNKNOWN_SYMBOL */ - if ((res && (data == 0)) || (!res && (data == 1))) - { - enum CommandResult rc = parse_rc_line(buf->data, err); - if (rc == MUTT_CMD_ERROR) - { - mutt_error(_("Error: %s"), buf_string(err)); - return MUTT_CMD_ERROR; - } - return rc; - } - return MUTT_CMD_SUCCESS; -} - -/** - * parse_ignore - Parse the 'ignore' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_ignore(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - do - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - remove_from_stailq(&UnIgnore, buf->data); - add_to_stailq(&Ignore, buf->data); - } while (MoreArgs(s)); - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_lists - Parse the 'lists' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_lists(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl); - - do - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - if (parse_grouplist(&gl, buf, s, err) == -1) - goto bail; - - mutt_regexlist_remove(&UnMailLists, buf->data); - - if (mutt_regexlist_add(&MailLists, buf->data, REG_ICASE, err) != 0) - goto bail; - - if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0) - goto bail; - } while (MoreArgs(s)); - - mutt_grouplist_destroy(&gl); - return MUTT_CMD_SUCCESS; - -bail: - mutt_grouplist_destroy(&gl); - return MUTT_CMD_ERROR; -} - -/** - * mailbox_add - Add a new Mailbox - * @param folder Path to use for '+' abbreviations - * @param mailbox Mailbox to add - * @param label Descriptive label - * @param poll Enable mailbox polling? - * @param notify Enable mailbox notification? - * @param err Buffer for error messages - * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS - */ -static enum CommandResult mailbox_add(const char *folder, const char *mailbox, - const char *label, enum TriBool poll, - enum TriBool notify, struct Buffer *err) -{ - mutt_debug(LL_DEBUG1, "Adding mailbox: '%s' label '%s', poll %s, notify %s\n", - mailbox, label ? label : "[NONE]", - (poll == TB_UNSET) ? "[UNSPECIFIED]" : - (poll == TB_TRUE) ? "true" : - "false", - (notify == TB_UNSET) ? "[UNSPECIFIED]" : - (notify == TB_TRUE) ? "true" : - "false"); - struct Mailbox *m = mailbox_new(); - - buf_strcpy(&m->pathbuf, mailbox); - /* int rc = */ mx_path_canon2(m, folder); - - if (m->type <= MUTT_UNKNOWN) - { - buf_printf(err, "Unknown Mailbox: %s", m->realpath); - mailbox_free(&m); - return MUTT_CMD_ERROR; - } - - bool new_account = false; - struct Account *a = mx_ac_find(m); - if (!a) - { - a = account_new(NULL, NeoMutt->sub); - a->type = m->type; - new_account = true; - } - - if (!new_account) - { - struct Mailbox *m_old = mx_mbox_find(a, m->realpath); - if (m_old) - { - if (!m_old->visible) - { - m_old->visible = true; - m_old->gen = mailbox_gen(); - } - - if (label) - mutt_str_replace(&m_old->name, label); - - if (notify != TB_UNSET) - m_old->notify_user = notify; - - if (poll != TB_UNSET) - m_old->poll_new_mail = poll; - - struct EventMailbox ev_m = { m_old }; - notify_send(m_old->notify, NT_MAILBOX, NT_MAILBOX_CHANGE, &ev_m); - - mailbox_free(&m); - return MUTT_CMD_SUCCESS; - } - } - - if (label) - m->name = mutt_str_dup(label); - - if (notify != TB_UNSET) - m->notify_user = notify; - - if (poll != TB_UNSET) - m->poll_new_mail = poll; - - if (!mx_ac_add(a, m)) - { - mailbox_free(&m); - if (new_account) - { - cs_subset_free(&a->sub); - FREE(&a->name); - notify_free(&a->notify); - FREE(&a); - } - return MUTT_CMD_SUCCESS; - } - - if (new_account) - { - neomutt_account_add(NeoMutt, a); - } - - // this is finally a visible mailbox in the sidebar and mailboxes list - m->visible = true; - -#ifdef USE_INOTIFY - mutt_monitor_add(m); -#endif - - return MUTT_CMD_SUCCESS; -} - -/** - * mailbox_add_simple - Add a new Mailbox - * @param mailbox Mailbox to add - * @param err Buffer for error messages - * @retval true Success - */ -bool mailbox_add_simple(const char *mailbox, struct Buffer *err) -{ - enum CommandResult rc = mailbox_add("", mailbox, NULL, TB_UNSET, TB_UNSET, err); - - return (rc == MUTT_CMD_SUCCESS); -} - -/** - * parse_mailboxes - Parse the 'mailboxes' command - Implements Command::parse() - @ingroup command_parse - * - * This is also used by 'virtual-mailboxes'. - */ -enum CommandResult parse_mailboxes(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - enum CommandResult rc = MUTT_CMD_WARNING; - - struct Buffer *label = buf_pool_get(); - struct Buffer *mailbox = buf_pool_get(); - - const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder"); - while (MoreArgs(s)) - { - bool label_set = false; - enum TriBool notify = TB_UNSET; - enum TriBool poll = TB_UNSET; - - do - { - // Start by handling the options - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - if (mutt_str_equal(buf_string(buf), "-label")) - { - if (!MoreArgs(s)) - { - buf_printf(err, _("%s: too few arguments"), "mailboxes -label"); - goto done; - } - - parse_extract_token(label, s, TOKEN_NO_FLAGS); - label_set = true; - } - else if (mutt_str_equal(buf_string(buf), "-nolabel")) - { - buf_reset(label); - label_set = true; - } - else if (mutt_str_equal(buf_string(buf), "-notify")) - { - notify = TB_TRUE; - } - else if (mutt_str_equal(buf_string(buf), "-nonotify")) - { - notify = TB_FALSE; - } - else if (mutt_str_equal(buf_string(buf), "-poll")) - { - poll = TB_TRUE; - } - else if (mutt_str_equal(buf_string(buf), "-nopoll")) - { - poll = TB_FALSE; - } - else if ((data & MUTT_NAMED) && !label_set) - { - if (!MoreArgs(s)) - { - buf_printf(err, _("%s: too few arguments"), "named-mailboxes"); - goto done; - } - - buf_copy(label, buf); - label_set = true; - } - else - { - buf_copy(mailbox, buf); - break; - } - } while (MoreArgs(s)); - - if (buf_is_empty(mailbox)) - { - buf_printf(err, _("%s: too few arguments"), "mailboxes"); - goto done; - } - - rc = mailbox_add(c_folder, buf_string(mailbox), - label_set ? buf_string(label) : NULL, poll, notify, err); - if (rc != MUTT_CMD_SUCCESS) - goto done; - - buf_reset(label); - buf_reset(mailbox); - } - - rc = MUTT_CMD_SUCCESS; - -done: - buf_pool_release(&label); - buf_pool_release(&mailbox); - return rc; -} - -/** - * parse_my_hdr - Parse the 'my_hdr' command - Implements Command::parse() - @ingroup command_parse - */ -enum CommandResult parse_my_hdr(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - parse_extract_token(buf, s, TOKEN_SPACE | TOKEN_QUOTE); - char *p = strpbrk(buf->data, ": \t"); - if (!p || (*p != ':')) - { - buf_strcpy(err, _("invalid header field")); - return MUTT_CMD_WARNING; - } - - struct EventHeader ev_h = { buf->data }; - struct ListNode *n = header_find(&UserHeader, buf->data); - - if (n) - { - header_update(n, buf->data); - mutt_debug(LL_NOTIFY, "NT_HEADER_CHANGE: %s\n", buf->data); - notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_CHANGE, &ev_h); - } - else - { - header_add(&UserHeader, buf->data); - mutt_debug(LL_NOTIFY, "NT_HEADER_ADD: %s\n", buf->data); - notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_ADD, &ev_h); - } - - return MUTT_CMD_SUCCESS; -} - -/** - * set_dump - Dump list of config variables into a file/pager - * @param flags Which config to dump, e.g. #GEL_CHANGED_CONFIG - * @param err Buffer for error message - * @return num See #CommandResult - * - * FIXME: Move me into parse/set.c. Note: this function currently depends on - * pager, which is the reason it is not included in the parse library. - */ -enum CommandResult set_dump(enum GetElemListFlags flags, struct Buffer *err) -{ - struct Buffer *tempfile = buf_pool_get(); - buf_mktemp(tempfile); - - FILE *fp_out = mutt_file_fopen(buf_string(tempfile), "w"); - if (!fp_out) - { - // L10N: '%s' is the file name of the temporary file - buf_printf(err, _("Could not create temporary file %s"), buf_string(tempfile)); - buf_pool_release(&tempfile); - return MUTT_CMD_ERROR; - } - - struct ConfigSet *cs = NeoMutt->sub->cs; - struct HashElemArray hea = get_elem_list(cs, flags); - dump_config(cs, &hea, CS_DUMP_NO_FLAGS, fp_out); - ARRAY_FREE(&hea); - - mutt_file_fclose(&fp_out); - - struct PagerData pdata = { 0 }; - struct PagerView pview = { &pdata }; - - pdata.fname = buf_string(tempfile); - - pview.banner = "set"; - pview.flags = MUTT_PAGER_NO_FLAGS; - pview.mode = PAGER_MODE_OTHER; - - mutt_do_pager(&pview, NULL); - buf_pool_release(&tempfile); - - return MUTT_CMD_SUCCESS; -} - -/** - * envlist_sort - Compare two environment strings - Implements ::sort_t - @ingroup sort_api - */ -static int envlist_sort(const void *a, const void *b, void *sdata) -{ - return strcmp(*(const char **) a, *(const char **) b); -} - -/** - * parse_setenv - Parse the 'setenv' and 'unsetenv' commands - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_setenv(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - char **envp = NeoMutt->env; - - bool query = false; - bool prefix = false; - bool unset = (data == MUTT_SET_UNSET); - - if (!MoreArgs(s)) - { - if (!StartupComplete) - { - buf_printf(err, _("%s: too few arguments"), "setenv"); - return MUTT_CMD_WARNING; - } - - struct Buffer *tempfile = buf_pool_get(); - buf_mktemp(tempfile); - - FILE *fp_out = mutt_file_fopen(buf_string(tempfile), "w"); - if (!fp_out) - { - // L10N: '%s' is the file name of the temporary file - buf_printf(err, _("Could not create temporary file %s"), buf_string(tempfile)); - buf_pool_release(&tempfile); - return MUTT_CMD_ERROR; - } - - int count = 0; - for (char **env = NeoMutt->env; *env; env++) - count++; - - mutt_qsort_r(NeoMutt->env, count, sizeof(char *), envlist_sort, NULL); - - for (char **env = NeoMutt->env; *env; env++) - fprintf(fp_out, "%s\n", *env); - - mutt_file_fclose(&fp_out); - - struct PagerData pdata = { 0 }; - struct PagerView pview = { &pdata }; - - pdata.fname = buf_string(tempfile); - - pview.banner = "setenv"; - pview.flags = MUTT_PAGER_NO_FLAGS; - pview.mode = PAGER_MODE_OTHER; - - mutt_do_pager(&pview, NULL); - buf_pool_release(&tempfile); - - return MUTT_CMD_SUCCESS; - } - - if (*s->dptr == '?') - { - query = true; - prefix = true; - - if (unset) - { - buf_printf(err, _("Can't query option with the '%s' command"), "unsetenv"); - return MUTT_CMD_WARNING; - } - - s->dptr++; - } - - /* get variable name */ - parse_extract_token(buf, s, TOKEN_EQUAL | TOKEN_QUESTION); - - if (*s->dptr == '?') - { - if (unset) - { - buf_printf(err, _("Can't query option with the '%s' command"), "unsetenv"); - return MUTT_CMD_WARNING; - } - - if (prefix) - { - buf_printf(err, _("Can't use a prefix when querying a variable")); - return MUTT_CMD_WARNING; - } - - query = true; - s->dptr++; - } - - if (query) - { - bool found = false; - while (envp && *envp) - { - /* This will display all matches for "^QUERY" */ - if (mutt_str_startswith(*envp, buf->data)) - { - if (!found) - { - mutt_endwin(); - found = true; - } - puts(*envp); - } - envp++; - } - - if (found) - { - mutt_any_key_to_continue(NULL); - return MUTT_CMD_SUCCESS; - } - - buf_printf(err, _("%s is unset"), buf->data); - return MUTT_CMD_WARNING; - } - - if (unset) - { - if (!envlist_unset(&NeoMutt->env, buf->data)) - { - buf_printf(err, _("%s is unset"), buf->data); - return MUTT_CMD_WARNING; - } - return MUTT_CMD_SUCCESS; - } - - /* set variable */ - - if (*s->dptr == '=') - { - s->dptr++; - SKIPWS(s->dptr); - } - - if (!MoreArgs(s)) - { - buf_printf(err, _("%s: too few arguments"), "setenv"); - return MUTT_CMD_WARNING; - } - - char *name = mutt_str_dup(buf->data); - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - envlist_set(&NeoMutt->env, name, buf->data, true); - FREE(&name); - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_source - Parse the 'source' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_source(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - struct Buffer *path = buf_pool_get(); - - do - { - if (parse_extract_token(buf, s, TOKEN_BACKTICK_VARS) != 0) - { - buf_printf(err, _("source: error at %s"), s->dptr); - buf_pool_release(&path); - return MUTT_CMD_ERROR; - } - buf_copy(path, buf); - buf_expand_path(path); - - if (source_rc(buf_string(path), err) < 0) - { - buf_printf(err, _("source: file %s could not be sourced"), buf_string(path)); - buf_pool_release(&path); - return MUTT_CMD_ERROR; - } - - } while (MoreArgs(s)); - - buf_pool_release(&path); - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_nospam - Parse the 'nospam' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_nospam(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - if (!MoreArgs(s)) - { - buf_printf(err, _("%s: too few arguments"), "nospam"); - return MUTT_CMD_ERROR; - } - - // Extract the first token, a regex or "*" - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - if (MoreArgs(s)) - { - buf_printf(err, _("%s: too many arguments"), "finish"); - return MUTT_CMD_ERROR; - } - - // "*" is special - clear both spam and nospam lists - if (mutt_str_equal(buf_string(buf), "*")) - { - mutt_replacelist_free(&SpamList); - mutt_regexlist_free(&NoSpamList); - return MUTT_CMD_SUCCESS; - } - - // If it's on the spam list, just remove it - if (mutt_replacelist_remove(&SpamList, buf_string(buf)) != 0) - return MUTT_CMD_SUCCESS; - - // Otherwise, add it to the nospam list - if (mutt_regexlist_add(&NoSpamList, buf_string(buf), REG_ICASE, err) != 0) - return MUTT_CMD_ERROR; - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_spam - Parse the 'spam' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_spam(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - if (!MoreArgs(s)) - { - buf_printf(err, _("%s: too few arguments"), "spam"); - return MUTT_CMD_ERROR; - } - - // Extract the first token, a regex - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - // If there's a second parameter, it's a template for the spam tag - if (MoreArgs(s)) - { - struct Buffer *templ = buf_pool_get(); - parse_extract_token(templ, s, TOKEN_NO_FLAGS); - - // Add to the spam list - int rc = mutt_replacelist_add(&SpamList, buf_string(buf), buf_string(templ), err); - buf_pool_release(&templ); - if (rc != 0) - return MUTT_CMD_ERROR; - } - else - { - // If not, try to remove from the nospam list - mutt_regexlist_remove(&NoSpamList, buf_string(buf)); - } - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_stailq - Parse a list command - Implements Command::parse() - @ingroup command_parse - * - * This is used by 'alternative_order', 'auto_view' and several others. - */ -static enum CommandResult parse_stailq(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - do - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - add_to_stailq((struct ListHead *) data, buf->data); - } while (MoreArgs(s)); - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_subscribe - Parse the 'subscribe' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_subscribe(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl); - - do - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - if (parse_grouplist(&gl, buf, s, err) == -1) - goto bail; - - mutt_regexlist_remove(&UnMailLists, buf->data); - mutt_regexlist_remove(&UnSubscribedLists, buf->data); - - if (mutt_regexlist_add(&MailLists, buf->data, REG_ICASE, err) != 0) - goto bail; - if (mutt_regexlist_add(&SubscribedLists, buf->data, REG_ICASE, err) != 0) - goto bail; - if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0) - goto bail; - } while (MoreArgs(s)); - - mutt_grouplist_destroy(&gl); - return MUTT_CMD_SUCCESS; - -bail: - mutt_grouplist_destroy(&gl); - return MUTT_CMD_ERROR; -} - -/** - * parse_subscribe_to - Parse the 'subscribe-to' command - Implements Command::parse() - @ingroup command_parse - * - * The 'subscribe-to' command allows to subscribe to an IMAP-Mailbox. - * Patterns are not supported. - * Use it as follows: subscribe-to =folder - */ -enum CommandResult parse_subscribe_to(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - if (!buf || !s || !err) - return MUTT_CMD_ERROR; - - buf_reset(err); - - if (MoreArgs(s)) - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - if (MoreArgs(s)) - { - buf_printf(err, _("%s: too many arguments"), "subscribe-to"); - return MUTT_CMD_WARNING; - } - - if (!buf_is_empty(buf)) - { - /* Expand and subscribe */ - buf_expand_path(buf); - if (imap_subscribe(buf_string(buf), true) == 0) - { - mutt_message(_("Subscribed to %s"), buf->data); - return MUTT_CMD_SUCCESS; - } - - buf_printf(err, _("Could not subscribe to %s"), buf->data); - return MUTT_CMD_ERROR; - } - - mutt_debug(LL_DEBUG1, "Corrupted buffer\n"); - return MUTT_CMD_ERROR; - } - - buf_addstr(err, _("No folder specified")); - return MUTT_CMD_WARNING; -} - -/** - * parse_tag_formats - Parse the 'tag-formats' command - Implements Command::parse() - @ingroup command_parse - * - * Parse config like: `tag-formats pgp GP` - * - * @note This maps format -> tag - */ -static enum CommandResult parse_tag_formats(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - if (!s) - return MUTT_CMD_ERROR; - - struct Buffer *tagbuf = buf_pool_get(); - struct Buffer *fmtbuf = buf_pool_get(); - - while (MoreArgs(s)) - { - parse_extract_token(tagbuf, s, TOKEN_NO_FLAGS); - const char *tag = buf_string(tagbuf); - if (*tag == '\0') - continue; - - parse_extract_token(fmtbuf, s, TOKEN_NO_FLAGS); - const char *fmt = buf_string(fmtbuf); - - /* avoid duplicates */ - const char *tmp = mutt_hash_find(TagFormats, fmt); - if (tmp) - { - mutt_warning(_("tag format '%s' already registered as '%s'"), fmt, tmp); - continue; - } - - mutt_hash_insert(TagFormats, fmt, mutt_str_dup(tag)); - } - - buf_pool_release(&tagbuf); - buf_pool_release(&fmtbuf); - return MUTT_CMD_SUCCESS; -} - -/** - * parse_tag_transforms - Parse the 'tag-transforms' command - Implements Command::parse() - @ingroup command_parse - * - * Parse config like: `tag-transforms pgp P` - * - * @note This maps tag -> transform - */ -static enum CommandResult parse_tag_transforms(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - if (!s) - return MUTT_CMD_ERROR; - - struct Buffer *tagbuf = buf_pool_get(); - struct Buffer *trnbuf = buf_pool_get(); - - while (MoreArgs(s)) - { - parse_extract_token(tagbuf, s, TOKEN_NO_FLAGS); - const char *tag = buf_string(tagbuf); - if (*tag == '\0') - continue; - - parse_extract_token(trnbuf, s, TOKEN_NO_FLAGS); - const char *trn = buf_string(trnbuf); - - /* avoid duplicates */ - const char *tmp = mutt_hash_find(TagTransforms, tag); - if (tmp) - { - mutt_warning(_("tag transform '%s' already registered as '%s'"), tag, tmp); - continue; - } - - mutt_hash_insert(TagTransforms, tag, mutt_str_dup(trn)); - } - - buf_pool_release(&tagbuf); - buf_pool_release(&trnbuf); - return MUTT_CMD_SUCCESS; -} - -/** - * parse_unignore - Parse the 'unignore' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_unignore(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - do - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - /* don't add "*" to the unignore list */ - if (!mutt_str_equal(buf->data, "*")) - add_to_stailq(&UnIgnore, buf->data); - - remove_from_stailq(&Ignore, buf->data); - } while (MoreArgs(s)); - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_unlists - Parse the 'unlists' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_unlists(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - mutt_hash_free(&AutoSubscribeCache); - do - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - mutt_regexlist_remove(&SubscribedLists, buf->data); - mutt_regexlist_remove(&MailLists, buf->data); - - if (!mutt_str_equal(buf->data, "*") && - (mutt_regexlist_add(&UnMailLists, buf->data, REG_ICASE, err) != 0)) - { - return MUTT_CMD_ERROR; - } - } while (MoreArgs(s)); - - return MUTT_CMD_SUCCESS; -} - -/** - * do_unmailboxes - Remove a Mailbox from the Sidebar/notifications - * @param m Mailbox to `unmailboxes` - */ -static void do_unmailboxes(struct Mailbox *m) -{ -#ifdef USE_INOTIFY - if (m->poll_new_mail) - mutt_monitor_remove(m); -#endif - m->visible = false; - m->gen = -1; - if (m->opened) - { - struct EventMailbox ev_m = { NULL }; - mutt_debug(LL_NOTIFY, "NT_MAILBOX_CHANGE: NULL\n"); - notify_send(NeoMutt->notify, NT_MAILBOX, NT_MAILBOX_CHANGE, &ev_m); - } - else - { - account_mailbox_remove(m->account, m); - mailbox_free(&m); - } -} - -/** - * do_unmailboxes_star - Remove all Mailboxes from the Sidebar/notifications - */ -static void do_unmailboxes_star(void) -{ - struct MailboxArray ma = neomutt_mailboxes_get(NeoMutt, MUTT_MAILBOX_ANY); - - struct Mailbox **mp = NULL; - ARRAY_FOREACH(mp, &ma) - { - do_unmailboxes(*mp); - } - ARRAY_FREE(&ma); // Clean up the ARRAY, but not the Mailboxes -} - -/** - * parse_unmailboxes - Parse the 'unmailboxes' command - Implements Command::parse() - @ingroup command_parse - * - * This is also used by 'unvirtual-mailboxes' - */ -enum CommandResult parse_unmailboxes(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - while (MoreArgs(s)) - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - if (mutt_str_equal(buf->data, "*")) - { - do_unmailboxes_star(); - return MUTT_CMD_SUCCESS; - } - - buf_expand_path(buf); - - struct Account **ap = NULL; - ARRAY_FOREACH(ap, &NeoMutt->accounts) - { - struct Mailbox *m = mx_mbox_find(*ap, buf_string(buf)); - if (m) - { - do_unmailboxes(m); - break; - } - } - } - return MUTT_CMD_SUCCESS; -} - -/** - * parse_unmy_hdr - Parse the 'unmy_hdr' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_unmy_hdr(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - struct ListNode *np = NULL, *tmp = NULL; - size_t l; - - do - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - if (mutt_str_equal("*", buf->data)) - { - /* Clear all headers, send a notification for each header */ - STAILQ_FOREACH(np, &UserHeader, entries) - { - mutt_debug(LL_NOTIFY, "NT_HEADER_DELETE: %s\n", np->data); - struct EventHeader ev_h = { np->data }; - notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_DELETE, &ev_h); - } - mutt_list_free(&UserHeader); - continue; - } - - l = mutt_str_len(buf->data); - if (buf_at(buf, l - 1) == ':') - l--; - - STAILQ_FOREACH_SAFE(np, &UserHeader, entries, tmp) - { - if (mutt_istrn_equal(buf->data, np->data, l) && (np->data[l] == ':')) - { - mutt_debug(LL_NOTIFY, "NT_HEADER_DELETE: %s\n", np->data); - struct EventHeader ev_h = { np->data }; - notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_DELETE, &ev_h); - - header_free(&UserHeader, np); - } - } - } while (MoreArgs(s)); - return MUTT_CMD_SUCCESS; -} - -/** - * parse_unstailq - Parse an unlist command - Implements Command::parse() - @ingroup command_parse - * - * This is used by 'unalternative_order', 'unauto_view' and several others. - */ -static enum CommandResult parse_unstailq(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - do - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - /* Check for deletion of entire list */ - if (mutt_str_equal(buf->data, "*")) - { - mutt_list_free((struct ListHead *) data); - break; - } - remove_from_stailq((struct ListHead *) data, buf->data); - } while (MoreArgs(s)); - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_unsubscribe - Parse the 'unsubscribe' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_unsubscribe(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - mutt_hash_free(&AutoSubscribeCache); - do - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - mutt_regexlist_remove(&SubscribedLists, buf->data); - - if (!mutt_str_equal(buf->data, "*") && - (mutt_regexlist_add(&UnSubscribedLists, buf->data, REG_ICASE, err) != 0)) - { - return MUTT_CMD_ERROR; - } - } while (MoreArgs(s)); - - return MUTT_CMD_SUCCESS; -} - -/** - * parse_unsubscribe_from - Parse the 'unsubscribe-from' command - Implements Command::parse() - @ingroup command_parse - * - * The 'unsubscribe-from' command allows to unsubscribe from an IMAP-Mailbox. - * Patterns are not supported. - * Use it as follows: unsubscribe-from =folder - */ -enum CommandResult parse_unsubscribe_from(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - if (!buf || !s || !err) - return MUTT_CMD_ERROR; - - if (MoreArgs(s)) - { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - if (MoreArgs(s)) - { - buf_printf(err, _("%s: too many arguments"), "unsubscribe-from"); - return MUTT_CMD_WARNING; - } - - if (buf->data && (*buf->data != '\0')) - { - /* Expand and subscribe */ - buf_expand_path(buf); - if (imap_subscribe(buf_string(buf), false) == 0) - { - mutt_message(_("Unsubscribed from %s"), buf->data); - return MUTT_CMD_SUCCESS; - } - - buf_printf(err, _("Could not unsubscribe from %s"), buf->data); - return MUTT_CMD_ERROR; - } - - mutt_debug(LL_DEBUG1, "Corrupted buffer\n"); - return MUTT_CMD_ERROR; - } - - buf_addstr(err, _("No folder specified")); - return MUTT_CMD_WARNING; -} - -/** - * parse_version - Parse the 'version' command - Implements Command::parse() - @ingroup command_parse - */ -static enum CommandResult parse_version(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) -{ - // silently ignore 'version' if it's in a config file - if (!StartupComplete) - return MUTT_CMD_SUCCESS; - - struct Buffer *tempfile = buf_pool_get(); - buf_mktemp(tempfile); - - FILE *fp_out = mutt_file_fopen(buf_string(tempfile), "w"); - if (!fp_out) - { - // L10N: '%s' is the file name of the temporary file - buf_printf(err, _("Could not create temporary file %s"), buf_string(tempfile)); - buf_pool_release(&tempfile); - return MUTT_CMD_ERROR; - } - - print_version(fp_out, false); - mutt_file_fclose(&fp_out); - - struct PagerData pdata = { 0 }; - struct PagerView pview = { &pdata }; - - pdata.fname = buf_string(tempfile); - - pview.banner = "version"; - pview.flags = MUTT_PAGER_NO_FLAGS; - pview.mode = PAGER_MODE_OTHER; - - mutt_do_pager(&pview, NULL); - buf_pool_release(&tempfile); - - return MUTT_CMD_SUCCESS; -} - -/** - * source_stack_cleanup - Free memory from the stack used for the source command - */ -void source_stack_cleanup(void) -{ - mutt_list_free(&MuttrcStack); -} - -/** - * MuttCommands - General NeoMutt Commands - */ -static const struct Command MuttCommands[] = { - // clang-format off - { "alias", parse_alias, 0 }, - { "alternates", parse_alternates, 0 }, - { "alternative_order", parse_stailq, IP &AlternativeOrderList }, - { "attachments", parse_attachments, 0 }, - { "auto_view", parse_stailq, IP &AutoViewList }, - { "cd", parse_cd, 0 }, - { "color", parse_color, 0 }, - { "echo", parse_echo, 0 }, - { "finish", parse_finish, 0 }, - { "group", parse_group, MUTT_GROUP }, - { "hdr_order", parse_stailq, IP &HeaderOrderList }, - { "ifdef", parse_ifdef, 0 }, - { "ifndef", parse_ifdef, 1 }, - { "ignore", parse_ignore, 0 }, - { "lists", parse_lists, 0 }, - { "mailboxes", parse_mailboxes, 0 }, - { "mailto_allow", parse_stailq, IP &MailToAllow }, - { "mime_lookup", parse_stailq, IP &MimeLookupList }, - { "mono", parse_mono, 0 }, - { "my_hdr", parse_my_hdr, 0 }, - { "named-mailboxes", parse_mailboxes, MUTT_NAMED }, - { "nospam", parse_nospam, 0 }, - { "reset", parse_set, MUTT_SET_RESET }, - { "score", parse_score, 0 }, - { "set", parse_set, MUTT_SET_SET }, - { "setenv", parse_setenv, MUTT_SET_SET }, - { "source", parse_source, 0 }, - { "spam", parse_spam, 0 }, - { "subjectrx", parse_subjectrx_list, 0 }, - { "subscribe", parse_subscribe, 0 }, - { "tag-formats", parse_tag_formats, 0 }, - { "tag-transforms", parse_tag_transforms, 0 }, - { "toggle", parse_set, MUTT_SET_INV }, - { "unalias", parse_unalias, 0 }, - { "unalternates", parse_unalternates, 0 }, - { "unalternative_order", parse_unstailq, IP &AlternativeOrderList }, - { "unattachments", parse_unattachments, 0 }, - { "unauto_view", parse_unstailq, IP &AutoViewList }, - { "uncolor", parse_uncolor, 0 }, - { "ungroup", parse_group, MUTT_UNGROUP }, - { "unhdr_order", parse_unstailq, IP &HeaderOrderList }, - { "unignore", parse_unignore, 0 }, - { "unlists", parse_unlists, 0 }, - { "unmailboxes", parse_unmailboxes, 0 }, - { "unmailto_allow", parse_unstailq, IP &MailToAllow }, - { "unmime_lookup", parse_unstailq, IP &MimeLookupList }, - { "unmono", parse_unmono, 0 }, - { "unmy_hdr", parse_unmy_hdr, 0 }, - { "unscore", parse_unscore, 0 }, - { "unset", parse_set, MUTT_SET_UNSET }, - { "unsetenv", parse_setenv, MUTT_SET_UNSET }, - { "unsubjectrx", parse_unsubjectrx_list, 0 }, - { "unsubscribe", parse_unsubscribe, 0 }, - { "version", parse_version, 0 }, - { NULL, NULL, 0 }, - // clang-format on -}; - -/** - * commands_init - Initialize commands array and register default commands - */ -bool commands_init(void) -{ - return commands_register(&NeoMutt->commands, MuttCommands); -} diff --git a/commands.h b/commands.h deleted file mode 100644 index e73610f9512..00000000000 --- a/commands.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @file - * Functions to parse commands in a config file - * - * @authors - * Copyright (C) 2023 Richard Russon - * - * @copyright - * This program is free software: 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 2 of the License, or (at your option) any later - * version. - * - * This program 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. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ - -#ifndef MUTT_COMMANDS_H -#define MUTT_COMMANDS_H - -#include "config.h" -#include -#include -#include "config/lib.h" -#include "core/lib.h" - -struct Buffer; -struct GroupList; - -/* parameter to parse_mailboxes */ -#define MUTT_NAMED (1 << 0) - -enum CommandResult parse_mailboxes (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_my_hdr (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_subjectrx_list (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_subscribe_to (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_unalternates (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_unmailboxes (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_unsubjectrx_list(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_unsubscribe_from(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); - -enum CommandResult parse_rc_line_cwd(const char *line, char *cwd, struct Buffer *err); -char *mutt_get_sourced_cwd(void); -bool mailbox_add_simple(const char *mailbox, struct Buffer *err); - -int parse_grouplist(struct GroupList *gl, struct Buffer *buf, struct Buffer *s, struct Buffer *err); -void source_stack_cleanup(void); -int source_rc(const char *rcfile_path, struct Buffer *err); - -enum CommandResult set_dump(enum GetElemListFlags flags, struct Buffer *err); - -#endif /* MUTT_COMMANDS_H */ diff --git a/alternates.c b/commands/alternates.c similarity index 58% rename from alternates.c rename to commands/alternates.c index 39249b50961..b4e09ae0d85 100644 --- a/alternates.c +++ b/commands/alternates.c @@ -1,9 +1,9 @@ /** * @file - * Alternate address handling + * Parse Alternate Commands * * @authors - * Copyright (C) 2021-2023 Richard Russon + * Copyright (C) 2021-2025 Richard Russon * * @copyright * This program is free software: you can redistribute it and/or modify it under @@ -21,23 +21,22 @@ */ /** - * @page neo_alternates Alternate address handling + * @page commands_alternates Parse Alternate Commands * - * Alternate address handling + * Parse Alternate Commands */ #include "config.h" #include -#include #include #include "mutt/lib.h" #include "address/lib.h" #include "email/lib.h" #include "core/lib.h" +#include "gui/lib.h" #include "alternates.h" #include "parse/lib.h" -#include "commands.h" -#include "mview.h" +#include "group.h" static struct RegexList Alternates = STAILQ_HEAD_INITIALIZER(Alternates); ///< List of regexes to match the user's alternate email addresses static struct RegexList UnAlternates = STAILQ_HEAD_INITIALIZER(UnAlternates); ///< List of regexes to exclude false matches in Alternates @@ -88,63 +87,93 @@ void mutt_alternates_reset(struct MailboxView *mv) /** * parse_alternates - Parse the 'alternates' command - Implements Command::parse() - @ingroup command_parse + * + * Parse: + * - `alternates [ -group ... ] [ ... ]` */ -enum CommandResult parse_alternates(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) +enum CommandResult parse_alternates(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, struct ParseError *pe) { + struct Buffer *err = pe->message; + + if (!MoreArgs(line)) + { + buf_printf(err, _("%s: too few arguments"), cmd->name); + return MUTT_CMD_WARNING; + } + struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl); + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_ERROR; do { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - - if (parse_grouplist(&gl, buf, s, err) == -1) - goto bail; + parse_extract_token(token, line, TOKEN_NO_FLAGS); - mutt_regexlist_remove(&UnAlternates, buf->data); + if (parse_grouplist(&gl, token, line, err, NeoMutt->groups) == -1) + goto done; - if (mutt_regexlist_add(&Alternates, buf->data, REG_ICASE, err) != 0) - goto bail; + mutt_regexlist_remove(&UnAlternates, buf_string(token)); - if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0) - goto bail; - } while (MoreArgs(s)); + if (mutt_regexlist_add(&Alternates, buf_string(token), REG_ICASE, err) != 0) + goto done; - mutt_grouplist_destroy(&gl); + if (grouplist_add_regex(&gl, buf_string(token), REG_ICASE, err) != 0) + goto done; + } while (MoreArgs(line)); - mutt_debug(LL_NOTIFY, "NT_ALTERN_ADD: %s\n", buf->data); + mutt_debug(LL_NOTIFY, "NT_ALTERN_ADD: %s\n", buf_string(token)); notify_send(AlternatesNotify, NT_ALTERN, NT_ALTERN_ADD, NULL); - return MUTT_CMD_SUCCESS; + rc = MUTT_CMD_SUCCESS; -bail: - mutt_grouplist_destroy(&gl); - return MUTT_CMD_ERROR; +done: + buf_pool_release(&token); + grouplist_destroy(&gl); + return rc; } /** * parse_unalternates - Parse the 'unalternates' command - Implements Command::parse() - @ingroup command_parse + * + * Parse: + * - `unalternates { * | ... }` */ -enum CommandResult parse_unalternates(struct Buffer *buf, struct Buffer *s, - intptr_t data, struct Buffer *err) +enum CommandResult parse_unalternates(const struct Command *cmd, struct Buffer *line, + const struct ParseContext *pc, struct ParseError *pe) { + struct Buffer *err = pe->message; + + if (!MoreArgs(line)) + { + buf_printf(err, _("%s: too few arguments"), cmd->name); + return MUTT_CMD_WARNING; + } + + struct Buffer *token = buf_pool_get(); + enum CommandResult rc = MUTT_CMD_ERROR; + do { - parse_extract_token(buf, s, TOKEN_NO_FLAGS); - mutt_regexlist_remove(&Alternates, buf->data); + parse_extract_token(token, line, TOKEN_NO_FLAGS); + mutt_regexlist_remove(&Alternates, buf_string(token)); - if (!mutt_str_equal(buf->data, "*") && - (mutt_regexlist_add(&UnAlternates, buf->data, REG_ICASE, err) != 0)) + if (!mutt_str_equal(buf_string(token), "*") && + (mutt_regexlist_add(&UnAlternates, buf_string(token), REG_ICASE, err) != 0)) { - return MUTT_CMD_ERROR; + goto done; } - } while (MoreArgs(s)); + } while (MoreArgs(line)); - mutt_debug(LL_NOTIFY, "NT_ALTERN_DELETE: %s\n", buf->data); + mutt_debug(LL_NOTIFY, "NT_ALTERN_DELETE: %s\n", buf_string(token)); notify_send(AlternatesNotify, NT_ALTERN, NT_ALTERN_DELETE, NULL); - return MUTT_CMD_SUCCESS; + rc = MUTT_CMD_SUCCESS; + +done: + buf_pool_release(&token); + return rc; } /** diff --git a/alternates.h b/commands/alternates.h similarity index 75% rename from alternates.h rename to commands/alternates.h index 0cb10b642ae..b3bca1d4a2d 100644 --- a/alternates.h +++ b/commands/alternates.h @@ -1,6 +1,6 @@ /** * @file - * Alternate address handling + * Parse Alternate Commands * * @authors * Copyright (C) 2021-2023 Richard Russon @@ -20,15 +20,16 @@ * this program. If not, see . */ -#ifndef MUTT_ALTERNATES_H -#define MUTT_ALTERNATES_H +#ifndef MUTT_COMMANDS_ALTERNATES_H +#define MUTT_COMMANDS_ALTERNATES_H #include -#include #include "core/lib.h" struct Buffer; struct MailboxView; +struct ParseContext; +struct ParseError; /** * enum NotifyAlternates - Alternates command notification types @@ -47,10 +48,10 @@ enum NotifyAlternates void alternates_init(void); void alternates_cleanup(void); -enum CommandResult parse_alternates (struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); -enum CommandResult parse_unalternates(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err); +enum CommandResult parse_alternates (const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); +enum CommandResult parse_unalternates(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe); bool mutt_alternates_match(const char *addr); void mutt_alternates_reset(struct MailboxView *mv); -#endif /* MUTT_ALTERNATES_H */ +#endif /* MUTT_COMMANDS_ALTERNATES_H */ diff --git a/commands/commands.c b/commands/commands.c new file mode 100644 index 00000000000..a0fe1a08ff6 --- /dev/null +++ b/commands/commands.c @@ -0,0 +1,359 @@ +/** + * @file + * Setup NeoMutt Commands + * + * @authors + * Copyright (C) 2025-2026 Richard Russon + * + * @copyright + * This program is free software: 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 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page commands_commands Setup NeoMutt Commands + * + * Setup NeoMutt Commands + */ + +#include "config.h" +#include +#include "mutt/lib.h" +#include "config/lib.h" +#include "email/lib.h" +#include "core/lib.h" +#include "alias/lib.h" +#include "attach/lib.h" +#include "color/lib.h" +#include "parse/lib.h" +#include "alternates.h" +#include "globals.h" +#include "group.h" +#include "ifdef.h" +#include "ignore.h" +#include "mailboxes.h" +#include "my_header.h" +#include "parse.h" +#include "score.h" +#include "setenv.h" +#include "source.h" +#include "spam.h" +#include "stailq.h" +#include "subjectrx.h" +#include "tags.h" + +/** + * CommandsCommands - General NeoMutt Commands + */ +const struct Command CommandsCommands[] = { + // clang-format off + { "alias", CMD_ALIAS, parse_alias, CMD_NO_DATA, + N_("Define an alias (name to email address)"), + N_("alias [ -group ... ]
[,...] [ # ]"), + "configuration.html#alias" }, + { "alternates", CMD_ALTERNATES, parse_alternates, CMD_NO_DATA, + N_("Define a list of alternate email addresses for the user"), + N_("alternates [ -group ... ] [ ... ]"), + "configuration.html#alternates" }, + { "alternative-order", CMD_ALTERNATIVE_ORDER, parse_stailq, IP &AlternativeOrderList, + N_("Set preference order for multipart alternatives"), + N_("alternative-order [/ ] [ ... ]"), + "mimesupport.html#alternative-order" }, + { "attachments", CMD_ATTACHMENTS, parse_attachments, CMD_NO_DATA, + N_("Set attachment counting rules"), + N_("attachments { + | - } [ ... ] | ?"), + "mimesupport.html#attachments" }, + { "auto-view", CMD_AUTO_VIEW, parse_stailq, IP &AutoViewList, + N_("Automatically display specified MIME types inline"), + N_("auto-view [/ ] [ ... ]"), + "mimesupport.html#auto-view" }, + { "cd", CMD_CD, parse_cd, CMD_NO_DATA, + N_("Change NeoMutt's current working directory"), + N_("cd [ ]"), + "configuration.html#cd" }, + { "color", CMD_COLOR, parse_color, CMD_NO_DATA, + N_("Define colors for the user interface"), + N_("color [ ... ] [ [ ]]"), + "configuration.html#color" }, + { "echo", CMD_ECHO, parse_echo, CMD_NO_DATA, + N_("Print a message to the status line"), + N_("echo "), + "advancedusage.html#echo" }, + { "finish", CMD_FINISH, parse_finish, CMD_NO_DATA, + N_("Stop reading current config file"), + N_("finish"), + "optionalfeatures.html#ifdef" }, + { "group", CMD_GROUP, parse_group, CMD_NO_DATA, + N_("Add addresses to an address group"), + N_("group [ -group ... ] { -rx ... | -addr
... }"), + "configuration.html#addrgroup" }, + { "header-order", CMD_HEADER_ORDER, parse_stailq, IP &HeaderOrderList, + N_("Define custom order of headers displayed"), + N_("header-order
[
... ]"), + "configuration.html#header-order" }, + { "ifdef", CMD_IFDEF, parse_ifdef, CMD_NO_DATA, + N_("Conditionally include config commands if symbol defined"), + N_("ifdef ' [ ... ]'"), + "optionalfeatures.html#ifdef" }, + { "ifndef", CMD_IFNDEF, parse_ifdef, CMD_NO_DATA, + N_("Conditionally include if symbol is not defined"), + N_("ifndef ' [ ... ]'"), + "optionalfeatures.html#ifdef" }, + { "ignore", CMD_IGNORE, parse_ignore, CMD_NO_DATA, + N_("Hide specified headers when displaying messages"), + N_("ignore [ ...]"), + "configuration.html#ignore" }, + { "lists", CMD_LISTS, parse_lists, CMD_NO_DATA, + N_("Add address to the list of mailing lists"), + N_("lists [ -group ... ] [ ... ]"), + "configuration.html#lists" }, + { "mailboxes", CMD_MAILBOXES, parse_mailboxes, CMD_NO_DATA, + N_("Define a list of mailboxes to watch"), + N_("mailboxes [ -label