ledgerkit-editor 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ledgerkit_editor-1.0.0/LICENSE +21 -0
- ledgerkit_editor-1.0.0/PKG-INFO +139 -0
- ledgerkit_editor-1.0.0/README.md +108 -0
- ledgerkit_editor-1.0.0/pyproject.toml +55 -0
- ledgerkit_editor-1.0.0/setup.cfg +4 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/__init__.py +5 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/__main__.py +6 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/app.py +220 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/commands/__init__.py +84 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/highlighting/__init__.py +17 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/highlighting/highlighter.py +407 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/highlighting/theme_bridge.py +98 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/highlighting/tokens.py +37 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/keybindings/__init__.py +14 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/keybindings/emacs_ledger.py +110 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/keybindings/office.py +84 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/themes/__init__.py +59 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/themes/monokai_pro.py +110 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/__init__.py +7 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/atomic_edit.py +51 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/commodity_format.py +299 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/date_parser.py +115 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/file_resolver.py +51 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/ledger_io.py +269 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/__init__.py +7 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/filter_popup.py +83 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/ledger_textarea.py +272 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/search_bar.py +270 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/transaction_table.py +849 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/view_filter_bar.py +45 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/PKG-INFO +139 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/SOURCES.txt +49 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/dependency_links.txt +1 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/entry_points.txt +2 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/requires.txt +6 -0
- ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/top_level.txt +1 -0
- ledgerkit_editor-1.0.0/tests/test_atomic_edit.py +77 -0
- ledgerkit_editor-1.0.0/tests/test_command_history.py +89 -0
- ledgerkit_editor-1.0.0/tests/test_commodity_format.py +220 -0
- ledgerkit_editor-1.0.0/tests/test_comprehensive_hledger.py +452 -0
- ledgerkit_editor-1.0.0/tests/test_date_parser.py +83 -0
- ledgerkit_editor-1.0.0/tests/test_date_shift.py +113 -0
- ledgerkit_editor-1.0.0/tests/test_file_resolver.py +46 -0
- ledgerkit_editor-1.0.0/tests/test_highlighting.py +533 -0
- ledgerkit_editor-1.0.0/tests/test_keybindings.py +112 -0
- ledgerkit_editor-1.0.0/tests/test_ledger_io.py +305 -0
- ledgerkit_editor-1.0.0/tests/test_parse_args.py +83 -0
- ledgerkit_editor-1.0.0/tests/test_themes.py +159 -0
- ledgerkit_editor-1.0.0/tests/test_transaction_table.py +675 -0
- ledgerkit_editor-1.0.0/tests/test_undo_redo.py +98 -0
- ledgerkit_editor-1.0.0/tests/test_view_filter.py +376 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cormac O'Sullivan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ledgerkit-editor
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A keyboard-driven terminal editor for hledger plain-text accounting journals
|
|
5
|
+
Author: Cormac O'Sullivan
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ctosullivan/ledgerkit-editor
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/ctosullivan/ledgerkit-editor/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/ctosullivan/ledgerkit-editor/blob/master/CHANGELOG.md
|
|
10
|
+
Keywords: hledger,ledger,accounting,tui,textual,plaintext-accounting
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: textual==8.2.5
|
|
26
|
+
Requires-Dist: ledgerkit==1.0.0.dev1
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# ledgerkit-editor
|
|
33
|
+
|
|
34
|
+
[](https://github.com/ctosullivan/ledgerkit-editor/actions/workflows/ci.yml)
|
|
35
|
+
|
|
36
|
+
A keyboard-driven terminal editor for hledger plain-text accounting journals.
|
|
37
|
+
|
|
38
|
+
> **Early beta — use with caution.** This software is under active development and
|
|
39
|
+
> **may corrupt your journal files**. Always keep a backup before editing. Do not
|
|
40
|
+
> use on irreplaceable data without a recovery plan.
|
|
41
|
+
|
|
42
|
+
Built with [Textual](https://textual.textualize.io/) and
|
|
43
|
+
[ledgerkit](https://github.com/ctosullivan/ledgerkit).
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- Full-screen text editor with hledger syntax highlighting (dates, payees, accounts,
|
|
50
|
+
amounts, commodities, comments, directives)
|
|
51
|
+
- Monokai Pro default theme; runtime theme switching supported
|
|
52
|
+
- Incremental search with match highlighting (`Ctrl+F`, `Shift+PgUp/Down`)
|
|
53
|
+
- View filter — show all / cleared-only / unreconciled-only transactions (`Ctrl+L`)
|
|
54
|
+
- Transaction block selection and duplication (`Ctrl+T`, `Ctrl+G`)
|
|
55
|
+
- Cleared status toggle — single transaction (3-state cycle) and bulk selection (`Ctrl+R`)
|
|
56
|
+
- Insert today's date at cursor (`Ctrl+D`)
|
|
57
|
+
- Date shifting with `Shift+Up` / `Shift+Down` (cursor-position-aware)
|
|
58
|
+
- Save with date-sort and whitespace re-alignment (`Ctrl+S`)
|
|
59
|
+
- Undo / Redo (`Ctrl+Z` / `Ctrl+Y`)
|
|
60
|
+
- Command palette (`Ctrl+P`)
|
|
61
|
+
|
|
62
|
+
### Planned / In Progress
|
|
63
|
+
|
|
64
|
+
- Transaction filter popup (`Ctrl+O`) — UI stub only; criteria filtering not yet implemented
|
|
65
|
+
- BalanceSidebar — account balance tree (wired up, threaded refresh on save)
|
|
66
|
+
- RegisterPanel — account transaction register (wired up, last 500 rows)
|
|
67
|
+
- Reconciliation mode
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
### From PyPI
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install ledgerkit-editor
|
|
77
|
+
ledgerkit-editor path/to/my.journal
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Development setup
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git clone https://github.com/ctosullivan/ledgerkit-editor.git
|
|
84
|
+
cd ledgerkit-editor
|
|
85
|
+
|
|
86
|
+
python -m venv .venv
|
|
87
|
+
.venv\Scripts\activate # Windows
|
|
88
|
+
# source .venv/bin/activate # macOS / Linux
|
|
89
|
+
|
|
90
|
+
pip install --upgrade pip
|
|
91
|
+
pip install -e ".[dev]"
|
|
92
|
+
|
|
93
|
+
# Run tests
|
|
94
|
+
pytest --tb=short
|
|
95
|
+
|
|
96
|
+
# Launch
|
|
97
|
+
ledgerkit-editor path/to/my.journal
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Journal File Resolution
|
|
103
|
+
|
|
104
|
+
The editor resolves the journal file in this order:
|
|
105
|
+
|
|
106
|
+
1. Path passed as a command-line argument
|
|
107
|
+
2. `$LEDGER_FILE` environment variable
|
|
108
|
+
3. `~/.hledger.journal` default
|
|
109
|
+
4. Interactive prompt on launch (if none of the above resolve)
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Keyboard Shortcuts
|
|
114
|
+
|
|
115
|
+
See [docs/shortcuts.md](docs/shortcuts.md) for the full reference.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Requirements
|
|
120
|
+
|
|
121
|
+
- Python 3.9+
|
|
122
|
+
- textual 8.2.5
|
|
123
|
+
- ledgerkit 1.0.0.dev1
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions and the
|
|
130
|
+
ledgerkit dependency update workflow.
|
|
131
|
+
|
|
132
|
+
## Roadmap
|
|
133
|
+
|
|
134
|
+
See [ROADMAP.md](ROADMAP.md) for planned features and current development status.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
> This repository was previously named `PyLedger-editor`. GitHub redirects
|
|
139
|
+
> old URLs automatically.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# ledgerkit-editor
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ctosullivan/ledgerkit-editor/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A keyboard-driven terminal editor for hledger plain-text accounting journals.
|
|
6
|
+
|
|
7
|
+
> **Early beta — use with caution.** This software is under active development and
|
|
8
|
+
> **may corrupt your journal files**. Always keep a backup before editing. Do not
|
|
9
|
+
> use on irreplaceable data without a recovery plan.
|
|
10
|
+
|
|
11
|
+
Built with [Textual](https://textual.textualize.io/) and
|
|
12
|
+
[ledgerkit](https://github.com/ctosullivan/ledgerkit).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- Full-screen text editor with hledger syntax highlighting (dates, payees, accounts,
|
|
19
|
+
amounts, commodities, comments, directives)
|
|
20
|
+
- Monokai Pro default theme; runtime theme switching supported
|
|
21
|
+
- Incremental search with match highlighting (`Ctrl+F`, `Shift+PgUp/Down`)
|
|
22
|
+
- View filter — show all / cleared-only / unreconciled-only transactions (`Ctrl+L`)
|
|
23
|
+
- Transaction block selection and duplication (`Ctrl+T`, `Ctrl+G`)
|
|
24
|
+
- Cleared status toggle — single transaction (3-state cycle) and bulk selection (`Ctrl+R`)
|
|
25
|
+
- Insert today's date at cursor (`Ctrl+D`)
|
|
26
|
+
- Date shifting with `Shift+Up` / `Shift+Down` (cursor-position-aware)
|
|
27
|
+
- Save with date-sort and whitespace re-alignment (`Ctrl+S`)
|
|
28
|
+
- Undo / Redo (`Ctrl+Z` / `Ctrl+Y`)
|
|
29
|
+
- Command palette (`Ctrl+P`)
|
|
30
|
+
|
|
31
|
+
### Planned / In Progress
|
|
32
|
+
|
|
33
|
+
- Transaction filter popup (`Ctrl+O`) — UI stub only; criteria filtering not yet implemented
|
|
34
|
+
- BalanceSidebar — account balance tree (wired up, threaded refresh on save)
|
|
35
|
+
- RegisterPanel — account transaction register (wired up, last 500 rows)
|
|
36
|
+
- Reconciliation mode
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
### From PyPI
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install ledgerkit-editor
|
|
46
|
+
ledgerkit-editor path/to/my.journal
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Development setup
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/ctosullivan/ledgerkit-editor.git
|
|
53
|
+
cd ledgerkit-editor
|
|
54
|
+
|
|
55
|
+
python -m venv .venv
|
|
56
|
+
.venv\Scripts\activate # Windows
|
|
57
|
+
# source .venv/bin/activate # macOS / Linux
|
|
58
|
+
|
|
59
|
+
pip install --upgrade pip
|
|
60
|
+
pip install -e ".[dev]"
|
|
61
|
+
|
|
62
|
+
# Run tests
|
|
63
|
+
pytest --tb=short
|
|
64
|
+
|
|
65
|
+
# Launch
|
|
66
|
+
ledgerkit-editor path/to/my.journal
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Journal File Resolution
|
|
72
|
+
|
|
73
|
+
The editor resolves the journal file in this order:
|
|
74
|
+
|
|
75
|
+
1. Path passed as a command-line argument
|
|
76
|
+
2. `$LEDGER_FILE` environment variable
|
|
77
|
+
3. `~/.hledger.journal` default
|
|
78
|
+
4. Interactive prompt on launch (if none of the above resolve)
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Keyboard Shortcuts
|
|
83
|
+
|
|
84
|
+
See [docs/shortcuts.md](docs/shortcuts.md) for the full reference.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Requirements
|
|
89
|
+
|
|
90
|
+
- Python 3.9+
|
|
91
|
+
- textual 8.2.5
|
|
92
|
+
- ledgerkit 1.0.0.dev1
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions and the
|
|
99
|
+
ledgerkit dependency update workflow.
|
|
100
|
+
|
|
101
|
+
## Roadmap
|
|
102
|
+
|
|
103
|
+
See [ROADMAP.md](ROADMAP.md) for planned features and current development status.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
> This repository was previously named `PyLedger-editor`. GitHub redirects
|
|
108
|
+
> old URLs automatically.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ledgerkit-editor"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "A keyboard-driven terminal editor for hledger plain-text accounting journals"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Cormac O'Sullivan" }]
|
|
12
|
+
keywords = ["hledger", "ledger", "accounting", "tui", "textual", "plaintext-accounting"]
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"textual==8.2.5",
|
|
16
|
+
"ledgerkit==1.0.0.dev1",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
"Intended Audience :: End Users/Desktop",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Topic :: Office/Business :: Financial :: Accounting",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/ctosullivan/ledgerkit-editor"
|
|
34
|
+
"Bug Tracker" = "https://github.com/ctosullivan/ledgerkit-editor/issues"
|
|
35
|
+
Changelog = "https://github.com/ctosullivan/ledgerkit-editor/blob/master/CHANGELOG.md"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
ledgerkit-editor = "ledgerkit_editor.app:main"
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest",
|
|
43
|
+
"pytest-asyncio",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
where = ["src"]
|
|
48
|
+
|
|
49
|
+
[tool.black]
|
|
50
|
+
line-length = 88
|
|
51
|
+
target-version = ["py39"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
asyncio_mode = "auto"
|
|
55
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Root Textual application for the ledger editor.
|
|
2
|
+
|
|
3
|
+
Composes the main layout: a file-path bar below the Header, then JournalEditor
|
|
4
|
+
(full-width editing surface). FilterPopup overlays on demand via Ctrl+Shift+P.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import ClassVar
|
|
15
|
+
|
|
16
|
+
from textual.app import App, ComposeResult
|
|
17
|
+
from textual.binding import Binding
|
|
18
|
+
from textual.css.query import NoMatches
|
|
19
|
+
from textual.widgets import Footer, Header, Static
|
|
20
|
+
|
|
21
|
+
from ledgerkit_editor.themes import VALID_THEMES, register_all
|
|
22
|
+
from ledgerkit_editor.themes.monokai_pro import THEME_NAME
|
|
23
|
+
from ledgerkit_editor.utils.file_resolver import resolve_journal_file
|
|
24
|
+
from ledgerkit_editor.widgets.filter_popup import FilterPopup
|
|
25
|
+
from ledgerkit_editor.widgets.transaction_table import JournalEditor
|
|
26
|
+
|
|
27
|
+
__all__ = ["LedgerApp", "main"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LedgerApp(App[None]):
|
|
31
|
+
"""Top-level Textual application for the plain-text ledger editor."""
|
|
32
|
+
|
|
33
|
+
TITLE = "Ledger Editor"
|
|
34
|
+
COMMAND_PALETTE_DISPLAY: ClassVar[str] = "Ctrl+P"
|
|
35
|
+
CSS_PATH = ["themes/monokai_pro.tcss"]
|
|
36
|
+
BINDINGS = [
|
|
37
|
+
Binding("ctrl+o", "toggle_filter", "Filter transactions",
|
|
38
|
+
priority=True, key_display="Ctrl+O"),
|
|
39
|
+
]
|
|
40
|
+
CSS = """
|
|
41
|
+
Screen {
|
|
42
|
+
layout: horizontal;
|
|
43
|
+
}
|
|
44
|
+
#file-path-bar {
|
|
45
|
+
dock: top;
|
|
46
|
+
height: 1;
|
|
47
|
+
background: $surface;
|
|
48
|
+
color: $text-muted;
|
|
49
|
+
padding: 0 1;
|
|
50
|
+
}
|
|
51
|
+
JournalEditor {
|
|
52
|
+
width: 1fr;
|
|
53
|
+
layout: vertical;
|
|
54
|
+
}
|
|
55
|
+
ViewFilterBar {
|
|
56
|
+
dock: top;
|
|
57
|
+
height: 1;
|
|
58
|
+
background: $surface;
|
|
59
|
+
color: $text-muted;
|
|
60
|
+
padding: 0 1;
|
|
61
|
+
}
|
|
62
|
+
SearchBar {
|
|
63
|
+
dock: bottom;
|
|
64
|
+
height: 3;
|
|
65
|
+
background: $surface;
|
|
66
|
+
border-top: solid $primary;
|
|
67
|
+
display: none;
|
|
68
|
+
layout: horizontal;
|
|
69
|
+
padding: 0 1;
|
|
70
|
+
}
|
|
71
|
+
SearchBar Input {
|
|
72
|
+
width: 1fr;
|
|
73
|
+
height: 1;
|
|
74
|
+
background: $panel;
|
|
75
|
+
color: $text;
|
|
76
|
+
border: none;
|
|
77
|
+
}
|
|
78
|
+
SearchBar #match-counter {
|
|
79
|
+
width: auto;
|
|
80
|
+
padding: 0 1;
|
|
81
|
+
color: $text-muted;
|
|
82
|
+
}
|
|
83
|
+
SearchBar Button {
|
|
84
|
+
width: auto;
|
|
85
|
+
min-width: 3;
|
|
86
|
+
height: 1;
|
|
87
|
+
border: none;
|
|
88
|
+
padding: 0 1;
|
|
89
|
+
}
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
journal_path: Path,
|
|
95
|
+
start_line: int | None = None,
|
|
96
|
+
theme_name: str | None = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Initialise the app with a resolved journal file path.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
journal_path: Resolved path to the .journal or .ledger file.
|
|
102
|
+
start_line: 1-indexed line to place the cursor on after open.
|
|
103
|
+
theme_name: Registered theme name to activate; defaults to monokai-pro.
|
|
104
|
+
"""
|
|
105
|
+
super().__init__()
|
|
106
|
+
self.journal_path = journal_path
|
|
107
|
+
self._start_line = start_line
|
|
108
|
+
self._theme_name = theme_name
|
|
109
|
+
|
|
110
|
+
def on_mount(self) -> None:
|
|
111
|
+
"""Register bundled themes, activate Monokai Pro, and populate file-path bar."""
|
|
112
|
+
from ledgerkit_editor.widgets.ledger_textarea import LedgerTextArea # noqa: PLC0415
|
|
113
|
+
|
|
114
|
+
editor = self.query_one(JournalEditor)
|
|
115
|
+
ledger_textarea = editor.query_one("#journal_textarea", LedgerTextArea)
|
|
116
|
+
register_all(self, ledger_textarea)
|
|
117
|
+
if os.environ.get("TEXTUAL_THEME") is None:
|
|
118
|
+
self.theme = self._theme_name or THEME_NAME
|
|
119
|
+
self._update_file_path_bar(modified=False)
|
|
120
|
+
|
|
121
|
+
def compose(self) -> ComposeResult:
|
|
122
|
+
"""Build the initial widget tree."""
|
|
123
|
+
yield Header()
|
|
124
|
+
yield Static("", id="file-path-bar")
|
|
125
|
+
yield JournalEditor(self.journal_path, start_line=self._start_line)
|
|
126
|
+
yield Footer()
|
|
127
|
+
|
|
128
|
+
def _update_file_path_bar(self, modified: bool) -> None:
|
|
129
|
+
"""Refresh the path bar label, appending '· modified' when unsaved."""
|
|
130
|
+
path_str = str(Path(self.journal_path).resolve())
|
|
131
|
+
text = f"{path_str} · modified" if modified else path_str
|
|
132
|
+
try:
|
|
133
|
+
self.query_one("#file-path-bar", Static).update(text)
|
|
134
|
+
except NoMatches:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
# ------------------------------------------------------------------
|
|
138
|
+
# Message handlers
|
|
139
|
+
# ------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
def on_journal_editor_file_modified_changed(
|
|
142
|
+
self, event: JournalEditor.FileModifiedChanged
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Update the file-path bar with the modified indicator."""
|
|
145
|
+
self._update_file_path_bar(modified=event.modified)
|
|
146
|
+
|
|
147
|
+
def action_toggle_filter(self) -> None:
|
|
148
|
+
"""Open or close the transaction filter popup (Ctrl+Shift+P)."""
|
|
149
|
+
existing = self.query(FilterPopup)
|
|
150
|
+
if existing:
|
|
151
|
+
existing.first().remove()
|
|
152
|
+
else:
|
|
153
|
+
self.mount(FilterPopup())
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
157
|
+
"""Parse CLI arguments for the ledger-editor entry point."""
|
|
158
|
+
raw = list(sys.argv[1:] if argv is None else argv)
|
|
159
|
+
|
|
160
|
+
# Extract +N tokens before argparse; it does not handle the bare + prefix.
|
|
161
|
+
# +N is the traditional $EDITOR convention (vi, emacs) used by shell
|
|
162
|
+
# scripts and tools that invoke editors via: $EDITOR +N file.
|
|
163
|
+
#
|
|
164
|
+
# Purpose: match a bare "+<digits>" token anywhere in argv.
|
|
165
|
+
# Group 1: the decimal line number.
|
|
166
|
+
# Edge cases: "+0" matches (clamped to row 0 at use-site); "+N file"
|
|
167
|
+
# adjacent to a filename leaves the filename intact.
|
|
168
|
+
plus_line: int | None = None
|
|
169
|
+
filtered: list[str] = []
|
|
170
|
+
for token in raw:
|
|
171
|
+
m = re.fullmatch(r"\+(\d+)", token)
|
|
172
|
+
if m:
|
|
173
|
+
plus_line = int(m.group(1))
|
|
174
|
+
else:
|
|
175
|
+
filtered.append(token)
|
|
176
|
+
|
|
177
|
+
parser = argparse.ArgumentParser(
|
|
178
|
+
prog="ledger-editor",
|
|
179
|
+
description="Terminal-based plain-text ledger editor (hledger format).",
|
|
180
|
+
)
|
|
181
|
+
parser.add_argument(
|
|
182
|
+
"file",
|
|
183
|
+
nargs="?",
|
|
184
|
+
metavar="FILE",
|
|
185
|
+
help="Path to the .journal or .ledger file to edit.",
|
|
186
|
+
)
|
|
187
|
+
parser.add_argument(
|
|
188
|
+
"--line",
|
|
189
|
+
type=int,
|
|
190
|
+
metavar="N",
|
|
191
|
+
default=None,
|
|
192
|
+
help="Open with cursor at line N (1-indexed). Also accepts +N positional form.",
|
|
193
|
+
)
|
|
194
|
+
parser.add_argument(
|
|
195
|
+
"--theme",
|
|
196
|
+
metavar="THEME",
|
|
197
|
+
default=None,
|
|
198
|
+
choices=sorted(VALID_THEMES),
|
|
199
|
+
help="Color theme to activate at startup (default: monokai-pro).",
|
|
200
|
+
)
|
|
201
|
+
args = parser.parse_args(filtered)
|
|
202
|
+
# --line takes precedence; +N is the fallback when --line is absent.
|
|
203
|
+
if args.line is None and plus_line is not None:
|
|
204
|
+
args.line = plus_line
|
|
205
|
+
return args
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def main(argv: list[str] | None = None) -> None:
|
|
209
|
+
"""Entry point wired up in pyproject.toml [project.scripts]."""
|
|
210
|
+
args = _parse_args(argv)
|
|
211
|
+
journal_path = resolve_journal_file(args.file)
|
|
212
|
+
if journal_path is None:
|
|
213
|
+
print(
|
|
214
|
+
"No journal file found. Pass a path as an argument, set $LEDGER_FILE, "
|
|
215
|
+
"or create ~/.hledger.journal.",
|
|
216
|
+
file=sys.stderr,
|
|
217
|
+
)
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
app = LedgerApp(journal_path, start_line=args.line, theme_name=args.theme)
|
|
220
|
+
app.run()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Command palette stubs and application-level undo/redo infrastructure."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Callable, Optional
|
|
7
|
+
|
|
8
|
+
__all__ = ["Command", "CommandHistory"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Command:
|
|
13
|
+
"""An invertible operation on application or model state.
|
|
14
|
+
|
|
15
|
+
Both ``execute`` and ``undo`` must be synchronous callables that complete
|
|
16
|
+
quickly (< 16 ms). Heavy work (disk I/O, journal re-parse) must be
|
|
17
|
+
dispatched via Textual workers by the caller, not inside these callables.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
execute: Callable[[], None]
|
|
21
|
+
undo: Callable[[], None]
|
|
22
|
+
description: str
|
|
23
|
+
"""Short human-readable label shown in notify() on undo/redo."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class CommandHistory:
|
|
28
|
+
"""Application-level undo/redo stack for operations that affect both buffer
|
|
29
|
+
text and in-memory model objects.
|
|
30
|
+
|
|
31
|
+
This is separate from TextArea's native EditHistory (_undo_stack). The two
|
|
32
|
+
stacks are kept in sync by JournalEditor: action_undo / action_redo consult
|
|
33
|
+
CommandHistory first and fall through to TextArea native undo if it has no
|
|
34
|
+
entries.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_max_size: int = 100
|
|
38
|
+
_done: list[Command] = field(default_factory=list)
|
|
39
|
+
_undone: list[Command] = field(default_factory=list)
|
|
40
|
+
|
|
41
|
+
def execute(self, cmd: Command) -> None:
|
|
42
|
+
"""Execute a command and push it onto the undo stack."""
|
|
43
|
+
cmd.execute()
|
|
44
|
+
self._done.append(cmd)
|
|
45
|
+
self._undone.clear()
|
|
46
|
+
if len(self._done) > self._max_size:
|
|
47
|
+
self._done.pop(0)
|
|
48
|
+
|
|
49
|
+
def undo(self) -> Optional[Command]:
|
|
50
|
+
"""Undo the most recent command and move it to the redo stack."""
|
|
51
|
+
if not self._done:
|
|
52
|
+
return None
|
|
53
|
+
cmd = self._done.pop()
|
|
54
|
+
cmd.undo()
|
|
55
|
+
self._undone.append(cmd)
|
|
56
|
+
return cmd
|
|
57
|
+
|
|
58
|
+
def redo(self) -> Optional[Command]:
|
|
59
|
+
"""Redo the most recently undone command."""
|
|
60
|
+
if not self._undone:
|
|
61
|
+
return None
|
|
62
|
+
cmd = self._undone.pop()
|
|
63
|
+
cmd.execute()
|
|
64
|
+
self._done.append(cmd)
|
|
65
|
+
return cmd
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def can_undo(self) -> bool:
|
|
69
|
+
"""True if there is at least one command on the undo stack."""
|
|
70
|
+
return bool(self._done)
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def can_redo(self) -> bool:
|
|
74
|
+
"""True if there is at least one command on the redo stack."""
|
|
75
|
+
return bool(self._undone)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# TODO: register command palette providers via Textual's CommandPalette API
|
|
79
|
+
# Examples of planned commands:
|
|
80
|
+
# "Open file" — resolve_journal_file, reload EditorDocument
|
|
81
|
+
# "Save" — trigger action_save
|
|
82
|
+
# "Filter..." — open FilterPopup
|
|
83
|
+
# "Go to account" — jump cursor to first posting for an account
|
|
84
|
+
# "Toggle cleared" — action_toggle_cleared on selection
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Syntax highlighting for hledger journal files.
|
|
2
|
+
|
|
3
|
+
Provides the LedgerHighlighter (pure-Python scanner), build_textarea_theme
|
|
4
|
+
(CSS-variable bridge), and the token name constants in tokens.py.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ledgerkit_editor.highlighting.highlighter import LedgerHighlighter, LineInfo, LineKind
|
|
8
|
+
from ledgerkit_editor.highlighting.theme_bridge import build_textarea_theme
|
|
9
|
+
from ledgerkit_editor.highlighting import tokens
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"LedgerHighlighter",
|
|
13
|
+
"LineInfo",
|
|
14
|
+
"LineKind",
|
|
15
|
+
"build_textarea_theme",
|
|
16
|
+
"tokens",
|
|
17
|
+
]
|