Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(`[metadata · key=value, …]`); TUI shows the same line and can expand it
to an inline key/value box with the new `m` key. A blank row follows the
summary for visual separation. Opt out entirely via `[metadata] show = false`
in `~/.termdown/config.toml`. See `docs/adr/0001-metadata-block-handling.md`.
in `~/.config/termdown/config.toml`. See `docs/adr/0001-metadata-block-handling.md`.

### Changed
- **Config location moved to the XDG path.** termdown now reads
`~/.config/termdown/config.toml` (or `$XDG_CONFIG_HOME/termdown/config.toml`)
instead of `~/.termdown/config.toml`. If a legacy `~/.termdown/config.toml`
is found while the new path is empty, termdown prints a one-line warning so
the old settings aren't dropped silently — move the file to the new location
to clear it. A documented `config.example.toml` with every default ships in
the repo root.

## [0.5.1] - 2026-05-26

Expand Down
2 changes: 1 addition & 1 deletion CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The two display states for a metadata block in TUI mode:
Cat mode has no "expanded" state — only the one-line summary or nothing.

### `[metadata] show`
The single config knob (in `~/.termdown/config.toml`) controlling whether
The single config knob (in `~/.config/termdown/config.toml`) controlling whether
frontmatter is visible at all. `show = true` (default) renders the [[metadata
one-line summary]] / expanded box; `show = false` hides the metadata block in
**both** cat and TUI. The pulldown-cmark metadata extensions are always
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ curl -fsSL https://raw.githubusercontent.com/rrbe/termdown/master/uninstall.sh |

```sh
rm $(which termdown)
rm -rf ~/.termdown
rm -rf ~/.config/termdown
```

</details>
Expand Down Expand Up @@ -139,7 +139,10 @@ TUI mode requires a file path; stdin input is not supported.

## Configuration

termdown reads configuration from `~/.termdown/config.toml`.
termdown reads configuration from `~/.config/termdown/config.toml` (or
`$XDG_CONFIG_HOME/termdown/config.toml` if `XDG_CONFIG_HOME` is set). All
settings are optional; see [`config.example.toml`](config.example.toml) for a
copy-pasteable file with every default.

```toml
# Theme: "auto" (default), "dark", or "light"
Expand Down
6 changes: 4 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ curl -fsSL https://raw.githubusercontent.com/rrbe/termdown/master/uninstall.sh |

```sh
rm $(which termdown)
rm -rf ~/.termdown
rm -rf ~/.config/termdown
```

</details>
Expand Down Expand Up @@ -131,7 +131,9 @@ TUI 模式需要指定文件路径,不支持从 stdin 读取。

## 配置

配置文件位于 `~/.termdown/config.toml`。
配置文件位于 `~/.config/termdown/config.toml`(若设置了 `XDG_CONFIG_HOME`,则为
`$XDG_CONFIG_HOME/termdown/config.toml`)。所有配置项均为可选;仓库根目录的
[`config.example.toml`](config.example.toml) 提供了一份包含全部默认值、可直接复制的示例。

```toml
# 主题:"auto"(默认)、"dark" 或 "light"
Expand Down
40 changes: 40 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# termdown configuration
#
# Copy this file to:
# ~/.config/termdown/config.toml
# (or $XDG_CONFIG_HOME/termdown/config.toml when XDG_CONFIG_HOME is set)
#
# Every value below is termdown's built-in default, so an empty or missing
# config behaves exactly like this file. Uncomment and edit only what you
# want to change.

# Color theme: "auto" (default), "dark", or "light".
# "auto" detects the terminal background via OSC 11.
theme = "auto"

# Vim-style edge bell: emit a terminal BEL when you scroll past the top or
# bottom of the document. The terminal emulator decides the visible effect
# (audible beep, title-bar 🔔, dock bounce, …). Default true.
# CLI override: --no-bell.
bell = true

[metadata]
# Render YAML (`---`) / TOML (`+++`) frontmatter metadata blocks. When true
# (default), --cat and the TUI show a dim one-line summary, and the TUI `m`
# key expands it to a key/value box. When false, metadata is hidden entirely
# (it is still parsed, so it never leaks into body content).
show = true

[font.heading]
# Fonts for image-rendered headings (H1–H3). When unset, termdown uses
# platform defaults and falls back to an embedded SourceSerif4 font. Body
# text always uses your terminal's own font, not these.

# Latin / ASCII heading font (sans-serif recommended).
# latin = "Inter"

# CJK heading font.
# cjk = "LXGW WenKai"

# Emoji / symbol fallback font for headings.
# emoji = "Apple Color Emoji"
4 changes: 2 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
```
src/
├── main.rs CLI entry point, arg parsing, terminal state management
├── config.rs ~/.termdown/config.toml loading (serde)
├── config.rs ~/.config/termdown/config.toml loading (serde)
├── font.rs Font resolution, caching, CJK/Latin detection
├── style.rs HeadingStyle, ANSI constants, display width utilities
├── render.rs Text measurement, glyph drawing, PNG encoding, Kitty protocol
Expand Down Expand Up @@ -145,7 +145,7 @@ On UNIX, `main.rs` temporarily disables terminal echo before rendering and resto

## Configuration

`~/.termdown/config.toml` is deserialized via serde:
`~/.config/termdown/config.toml` is deserialized via serde:

```
Config
Expand Down
2 changes: 1 addition & 1 deletion docs/MARKDOWN_FEATURE_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Audit of `src/markdown.rs` against pulldown-cmark 0.13 and common Markdown exten
| Horizontal rule | ✓ | |
| HTML blocks | ✓ | Rendered verbatim as a dim preformatted block; HTML comments dropped |
| Inline HTML | ⚠ | Format tags (`b`/`strong`, `i`/`em`, `u`, `s`/`del`/`strike`, `code`/`kbd`) map to ANSI; `<br/>` / `<hr/>` handled; comments dropped; unknown tags stripped but their content is preserved. Attributes (e.g. `style="color:red"`, `href`) are not interpreted. |
| YAML / TOML frontmatter | ✓ | Parsed via pulldown-cmark's metadata-block extensions. Rendered as a dim one-line summary (`[metadata · key=value, …]`) in `--cat`; foldable inline box in TUI (toggle with `m`). Heuristic key/value extraction. See `docs/adr/0001-metadata-block-handling.md`. Opt out via `[metadata] show = false` in `~/.termdown/config.toml`. |
| YAML / TOML frontmatter | ✓ | Parsed via pulldown-cmark's metadata-block extensions. Rendered as a dim one-line summary (`[metadata · key=value, …]`) in `--cat`; foldable inline box in TUI (toggle with `m`). Heuristic key/value extraction. See `docs/adr/0001-metadata-block-handling.md`. Opt out via `[metadata] show = false` in `~/.config/termdown/config.toml`. |

## Enabled GFM extensions

Expand Down
2 changes: 1 addition & 1 deletion docs/TUI_MODE_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ if you want the "why this stack"; this doc is the "what we're building".
- Future evolution (documented but not implemented in v1):
- Automatic mode when output is a TTY and the rendered document
exceeds terminal height (git-log-style).
- `[tui]` section in `~/.termdown/config.toml` to opt into automatic
- `[tui]` section in `~/.config/termdown/config.toml` to opt into automatic
mode or override defaults.

Single binary, no cargo feature flag. TUI code is always compiled in;
Expand Down
7 changes: 4 additions & 3 deletions docs/adr/0001-metadata-block-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ as body content and not as completely invisible noise:
own row. The box is part of the scrolling content (not pinned), not part
of search, and not part of the Table of Contents. Default state is folded.
6. **Config gate**: A single boolean knob `[metadata] show` (default `true`)
in `~/.termdown/config.toml`. When `false`, frontmatter is **completely
in `~/.config/termdown/config.toml`. When `false`, frontmatter is **completely
hidden** in both cat and TUI; `m` becomes a no-op. The pulldown extensions
remain enabled — `show` gates rendering only, never parsing, so frontmatter
never leaks back into body regardless of config.
Expand Down Expand Up @@ -128,7 +128,8 @@ as body content and not as completely invisible noise:

## Open follow-ups

- Migration of config dir from `~/.termdown/` to XDG `~/.config/termdown/`
is unrelated and explicitly out of scope here.
- Migration of the config dir from `~/.termdown/` to XDG `~/.config/termdown/`
is handled alongside this change (same branch); see `config.example.toml`
and the `## Configuration` section of the README.
- "Use `title` field in TUI title bar" — sketched as a future enhancement,
not committed.
63 changes: 60 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};

use serde::Deserialize;

Expand Down Expand Up @@ -61,8 +61,17 @@ pub struct HeadingFontConfig {
}

fn config_dir() -> Option<PathBuf> {
// XDG Base Directory spec: prefer an absolute $XDG_CONFIG_HOME, otherwise
// fall back to ~/.config (USERPROFILE covers Windows, where HOME is often
// unset). The config file itself lives at `<dir>/termdown/config.toml`.
if let Some(xdg) = std::env::var_os("XDG_CONFIG_HOME") {
let xdg = PathBuf::from(xdg);
if xdg.is_absolute() {
return Some(xdg.join("termdown"));
}
}
let home = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE"))?;
Some(PathBuf::from(home).join(".termdown"))
Some(PathBuf::from(home).join(".config").join("termdown"))
}

pub fn load() -> Config {
Expand All @@ -76,6 +85,54 @@ pub fn load() -> Config {
eprintln!("Warning: invalid config at {}: {}", path.display(), e);
Config::default()
}),
Err(_) => Config::default(),
Err(_) => {
warn_if_legacy_config(&path);
Config::default()
}
}
}

/// Nudge users upgrading from the pre-XDG layout: if nothing was found at the
/// new path but a config still sits at the old `~/.termdown/config.toml`, warn
/// so the settings aren't silently dropped. Stays silent on a clean install
/// (no legacy file) and matches `uninstall.sh`'s legacy-cleanup awareness.
fn warn_if_legacy_config(new_path: &Path) {
let home = match std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")) {
Some(home) => home,
None => return,
};
let legacy = PathBuf::from(home).join(".termdown").join("config.toml");
if legacy.is_file() {
eprintln!(
"Warning: ignoring legacy config at {}; move it to {}",
legacy.display(),
new_path.display()
);
}
}

#[cfg(test)]
mod tests {
use super::*;

/// `config.example.toml` is the user-facing source of truth for defaults,
/// so guard it against bit-rot: it must always parse, and its explicit
/// values must match what termdown treats as the built-in defaults.
#[test]
fn example_config_parses_and_matches_defaults() {
let example = include_str!("../config.example.toml");
let parsed: Config =
toml::from_str(example).expect("config.example.toml must remain valid TOML");

// The example spells out the *effective* defaults explicitly.
assert_eq!(parsed.theme.as_deref(), Some("auto"));
assert_eq!(parsed.bell, Some(true));
// `metadata.show` is a real struct default — assert the example tracks it.
assert_eq!(parsed.metadata.show, Config::default().metadata.show);
assert!(parsed.metadata.show);
// Font overrides are commented out, so they must parse as unset.
assert!(parsed.font.heading.latin.is_none());
assert!(parsed.font.heading.cjk.is_none());
assert!(parsed.font.heading.emoji.is_none());
}
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn main() {
println!("By default, passing FILE opens it in the interactive TUI.");
println!("Piped/redirected stdout and stdin input automatically use cat mode.");
println!();
println!("Config: ~/.termdown/config.toml");
println!("Config: ~/.config/termdown/config.toml");
return;
}

Expand Down
Loading
Loading