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.
Files changed (51) hide show
  1. ledgerkit_editor-1.0.0/LICENSE +21 -0
  2. ledgerkit_editor-1.0.0/PKG-INFO +139 -0
  3. ledgerkit_editor-1.0.0/README.md +108 -0
  4. ledgerkit_editor-1.0.0/pyproject.toml +55 -0
  5. ledgerkit_editor-1.0.0/setup.cfg +4 -0
  6. ledgerkit_editor-1.0.0/src/ledgerkit_editor/__init__.py +5 -0
  7. ledgerkit_editor-1.0.0/src/ledgerkit_editor/__main__.py +6 -0
  8. ledgerkit_editor-1.0.0/src/ledgerkit_editor/app.py +220 -0
  9. ledgerkit_editor-1.0.0/src/ledgerkit_editor/commands/__init__.py +84 -0
  10. ledgerkit_editor-1.0.0/src/ledgerkit_editor/highlighting/__init__.py +17 -0
  11. ledgerkit_editor-1.0.0/src/ledgerkit_editor/highlighting/highlighter.py +407 -0
  12. ledgerkit_editor-1.0.0/src/ledgerkit_editor/highlighting/theme_bridge.py +98 -0
  13. ledgerkit_editor-1.0.0/src/ledgerkit_editor/highlighting/tokens.py +37 -0
  14. ledgerkit_editor-1.0.0/src/ledgerkit_editor/keybindings/__init__.py +14 -0
  15. ledgerkit_editor-1.0.0/src/ledgerkit_editor/keybindings/emacs_ledger.py +110 -0
  16. ledgerkit_editor-1.0.0/src/ledgerkit_editor/keybindings/office.py +84 -0
  17. ledgerkit_editor-1.0.0/src/ledgerkit_editor/themes/__init__.py +59 -0
  18. ledgerkit_editor-1.0.0/src/ledgerkit_editor/themes/monokai_pro.py +110 -0
  19. ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/__init__.py +7 -0
  20. ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/atomic_edit.py +51 -0
  21. ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/commodity_format.py +299 -0
  22. ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/date_parser.py +115 -0
  23. ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/file_resolver.py +51 -0
  24. ledgerkit_editor-1.0.0/src/ledgerkit_editor/utils/ledger_io.py +269 -0
  25. ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/__init__.py +7 -0
  26. ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/filter_popup.py +83 -0
  27. ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/ledger_textarea.py +272 -0
  28. ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/search_bar.py +270 -0
  29. ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/transaction_table.py +849 -0
  30. ledgerkit_editor-1.0.0/src/ledgerkit_editor/widgets/view_filter_bar.py +45 -0
  31. ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/PKG-INFO +139 -0
  32. ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/SOURCES.txt +49 -0
  33. ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/dependency_links.txt +1 -0
  34. ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/entry_points.txt +2 -0
  35. ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/requires.txt +6 -0
  36. ledgerkit_editor-1.0.0/src/ledgerkit_editor.egg-info/top_level.txt +1 -0
  37. ledgerkit_editor-1.0.0/tests/test_atomic_edit.py +77 -0
  38. ledgerkit_editor-1.0.0/tests/test_command_history.py +89 -0
  39. ledgerkit_editor-1.0.0/tests/test_commodity_format.py +220 -0
  40. ledgerkit_editor-1.0.0/tests/test_comprehensive_hledger.py +452 -0
  41. ledgerkit_editor-1.0.0/tests/test_date_parser.py +83 -0
  42. ledgerkit_editor-1.0.0/tests/test_date_shift.py +113 -0
  43. ledgerkit_editor-1.0.0/tests/test_file_resolver.py +46 -0
  44. ledgerkit_editor-1.0.0/tests/test_highlighting.py +533 -0
  45. ledgerkit_editor-1.0.0/tests/test_keybindings.py +112 -0
  46. ledgerkit_editor-1.0.0/tests/test_ledger_io.py +305 -0
  47. ledgerkit_editor-1.0.0/tests/test_parse_args.py +83 -0
  48. ledgerkit_editor-1.0.0/tests/test_themes.py +159 -0
  49. ledgerkit_editor-1.0.0/tests/test_transaction_table.py +675 -0
  50. ledgerkit_editor-1.0.0/tests/test_undo_redo.py +98 -0
  51. 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
+ [![CI](https://github.com/ctosullivan/ledgerkit-editor/actions/workflows/ci.yml/badge.svg)](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
+ [![CI](https://github.com/ctosullivan/ledgerkit-editor/actions/workflows/ci.yml/badge.svg)](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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """Ledger Editor — a terminal-based plain-text ledger editor built with Textual."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m ledgerkit_editor`."""
2
+
3
+ from ledgerkit_editor.app import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -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
+ ]