Metadata-Version: 2.4
Name: soonish
Version: 0.1.1
Summary: A mouse-aware terminal todo app with natural-language dates
Author-email: moonshadow <moonshadow--pypi-soonish@toothycat.net>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Keywords: calendar,tasks,terminal,todo,tui
Classifier: Environment :: Console
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Scheduling
Requires-Python: >=3.10
Requires-Dist: dateparser
Provides-Extra: watch
Requires-Dist: inotify; (sys_platform == 'linux') and extra == 'watch'
Description-Content-Type: text/markdown

# soonish

A small mouse-aware terminal todo list, written in plain Python with no
framework — raw-mode input decoding, ANSI rendering, and persistence are all
implemented in-tree. The name is how it treats time: entry dates display
with just enough precision to answer "what's happening soonish?".

You type entries into a prompt at the bottom of the screen; they appear above
it, split into undated **todos** and date-scheduled **tasks**. Clicking an
entry completes it.

## Installation and usage

```sh
pip install soonish          # or: pip install soonish[watch]
soonish [--debug-events | --no-debug-events] [--discard-unknown-fields]
```

`python -m soonish` works too. From a source checkout, `pip install -e .`
gives the same entry points.

Requires Python 3.10+ (uses `match` statements);
[`dateparser`](https://pypi.org/project/dateparser/) is installed as a
dependency, and the `[watch]` extra adds
[`inotify`](https://pypi.org/project/inotify/) for live reload
when another instance or editor changes the data file.
Must be run in a real terminal; mouse support
needs a terminal that speaks SGR mouse reporting (any modern one does).
Requires a terminal of at least 5 columns × 3 rows; below that the app displays
a frowning face (☹) until the window is resized.

**Options:**
- `--debug-events` / `--no-debug-events` — show / silently ignore unhandled
  input events on the status line (useful for development); when neither is
  given, the `debug_events` setting (F4; default on) decides
- `--discard-unknown-fields` — load a data file written by a *newer* version of
  the software, dropping the fields this version does not understand (by
  default such a file is refused, so no data is silently lost)

### Adding entries

Type at the prompt and press Enter:

| Input | Effect |
|---|---|
| `todo: buy milk` | Add an undated todo (if the line starts with `todo:`, the rest is the text) |
| `tomorrow 9am: standup` | Add a task due at that time (the date is everything before the last `": "` in the line, falling back to the last `:` if there is no colon-space — so times like `17:30: dentist` work) |
| `next friday: review` | `next <weekday>`/`last <weekday>` resolve to the nearest such day strictly in the future/past (never today, at most a week away); `next week`/`next month` mean the same day next week/month. `last ...` and other past dates are allowed — the task just shows as already due |
| `todo: [high] pay rent` | Tags: a todo starting with `[high]` sorts above untagged todos, `[low]` below (tags are case-insensitive, shown as typed; only leading `[...]` words count) |
| `/query` | Live search: filters the current view as you type. A date or range (`/friday`, `/june`, `/next week`, `/2026-06-12`) filters tasks by due time — the range is as wide as what you typed (a day, a week, a month, a year); plain text filters both panes case-insensitively; `/friday: dentist` applies both; `/todo: milk` searches only todos. Matching tasks show full-precision dates. ESC cancels; Enter ends the search (and records it in input history) |
| `done: milk` | (ACTIVE view only; prefix case-insensitive) live search like `/`, but when Enter is hit with exactly one entry matching, that entry is marked complete — the keyboard alternative to a left click |

Dates are understood in English plus your locale's language (from
`$LANGUAGE`/`$LANG` — under `LANG=fr_FR.UTF-8`, `demain: x` works).
Letting `dateparser` guess the language of every input instead is far
too slow for the live search, which parses the query on each keystroke;
the `dateparser_languages` setting (F4) adds languages your locale
doesn't mention, or selects them all (leave it empty), at that price.

### Keys and mouse

- **F1 / F2 / F3** — switch between ACTIVE, COMPLETED, and CALENDAR views; clicking a mode label in the heading does the same. The current view's label is highlighted bright+bold (or `[bracketed]` on colourless terminals)
- **F4** — the settings view: every tunable (date language list, layout thresholds, ASCII markers, linger time, colour off, …) as a row. Click a tickbox (or Enter on the Up/Down-selected row) to toggle a boolean; click any other row to edit its value on the prompt, Enter to apply. Changes apply immediately and persist to a `settings.tsv` next to the data file. F4 or ESC leaves
- **CALENDAR view** — as many month grids as fit the window; dates colour-coded (red = overdue tasks, green = upcoming, grey = nothing scheduled, plain = all done; today bold); PgUp/PgDn page months; clicking a date jumps to ACTIVE/COMPLETED with a `/` search for that date applied
- **Left click** an entry — mark complete (it lingers ~15 s, then leaves the ACTIVE view); the keyboard alternative is a `done:` search (see above)
- **Right click** an entry — un-complete it (in the COMPLETED view it lingers ~15 s before leaving, for the same mid-glance reasons)
- **Middle click** an entry — edit it: the prompt is pre-filled with its `date: text` form, the entry mirrors your typing live (in bold), and Enter replaces it (creation time is kept). ESC or an empty Enter cancels. Ending a `/` search with Enter when exactly one entry matches also opens it for editing. Committing an edit of a *completed* entry un-completes it — the keyboard alternative to a right click (find it with `/`, Enter to edit, Enter to commit)
- **Up/Down arrows or scroll wheel** — input history
- **Shift+Left/Right** — horizontally scroll truncated entry text (half a screen per press; markers and dates stay put; resets on mode switch, Enter or ESC)
- **PgUp/PgDn** — scroll the todo/task panes by half a pane per press; click the whitespace after an entry's text to make that pane *active* (rendered bold) and scroll it alone — click again or press ESC to deactivate
- **Enter** on an empty prompt — clears the status line
- Usual line-editing keys at the prompt: Home/End, Ctrl+Left/Right (by word), Insert (overwrite mode), ESC (clear line)
- **Ctrl-C** — quit

On terminals that render unicode (probed at startup) and are at least
80×25, the whole UI is framed with box-drawing borders — mode labels and
clock embedded in the top border, `…` indicators where a pane has more
entries than fit. Smaller or glyph-less terminals keep the plain layout.
At 160 columns or wider, todos and tasks display side by side (todos
left, 50/50 split) instead of stacked.

On colour-capable terminals (set `NO_COLOR` to opt out), the ACTIVE view
colours tasks green when due within 30 minutes and red when overdue;
just-completed entries linger in green; a leading `[high]` tag is shown in
high intensity.

## Storage

Entries are kept in a single TSV file at `$XDG_DATA_HOME/soonish/soonish.tsv`
(default `~/.local/share/soonish/soonish.tsv`). The app was previously named
`todo`; an existing `~/.local/share/todo/todo.tsv` is adopted (moved over)
automatically on first start, unless a soonish data file already exists.
Writes are atomic (temp file + rename),
fsync'd to protect against power loss, and preserve the file's permissions.
The file is human-readable: a `name:type` header row describing the columns,
then one line per entry:

```
created:t	due:t	complete:t	uid:s	modified:t	text:s
<created>\t<due|None>\t<complete|None>\t<uid>\t<modified>\t<text>
```

The header makes the format forward-compatible: old headerless files are
read and silently upgraded on the next change (a `.bak` copy is kept), and
fields a future version adds will default sensibly when missing.

Two running instances (e.g. two users with write access to the same file)
can share a task list: writes take a lock and merge concurrent changes by
entry uid (the newer change wins), and a running instance notices external
changes to the file via inotify and folds them in within half a second.
See [`doc/database.md`](doc/database.md) for the details.

## Code layout

The package lives in `src/soonish/`:

| Module | Role |
|---|---|
| `main.py` | `cli()` entry point, event loop, command dispatch |
| `events.py` | Raw terminal input → typed events; `TextBuffer` line editor and its `History` |
| `rendering.py` | `View`: screen layout, modes, status line |
| `calendar_view.py` | The CALENDAR mode's month grids and paging |
| `database.py` | `Entry` records and the TSV-backed `Database` |
| `search.py` | Date parsing (`parse_when`) and live `/` search queries |
| `tags.py` | `[tag]` semantics (`[high]`/`[low]` priority bands) |
| `caps.py` | Terminal capability detection (colour, cursor control, glyph width) |
| `ansi.py` | ANSI escape-sequence constants/builders |
| `config.py` | The defaults for every user-tunable value |
| `settings.py` | Settings schema and `settings.tsv` persistence (the F4 view's backend) |

Per-module documentation lives in [`doc/`](doc/). Tests (`tests/`, pytest)
run against the source tree directly: `python3 -m pytest -q`.

## License

GPLv3 (see `LICENSE`).

## Status

Working but in progress; pending small features are marked with `todo:`
comments in the source.
