-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathhledger-mode.el
232 lines (206 loc) · 9.18 KB
/
hledger-mode.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
;;; hledger-mode.el --- A mode for writing journal entries for hledger.
;; Copyright (C) 2015-2016 Narendra Joshi
;; Author: Narendra Joshi <[email protected]>
;; URL: https://github.com/narendraj9/hledger-mode.git
;; Version: 0.1
;; Keywords: data
;; Package-Requires: ((emacs "24.4") (popup "0.5.3") (async "1.9") (htmlize "1.47"))
;;; Commentary:
;;
;; A major mode for writing hledger journal files. It generates some
;; useful reports along with some financial ratios that can help you
;; keep a check on your financial health. This is an attempt to
;; organize personal finances for Emacs users. If you don't like this,
;; try `ledger-mode'.
;;
;; Note: You must have hledger installed to be able to create the
;; reports: overall report, daily report, balancesheet, income
;; statement, etc.
;;
;;; Code:
(require 'hledger-defuns)
(require 'hledger-core)
(require 'hledger-navigate)
(require 'hledger-reports)
(require 'hledger-mail)
(require 'hledger-webservice)
(defgroup hledger nil
"Customization group hledger-mode."
:group 'data)
(defcustom hledger-mode-hook nil
"Normal hook for entering ‘hledger-mode’."
:type 'hook
:group 'hledger)
(defcustom hledger-date-face font-lock-string-face
"Face for date."
:type 'face
:group 'hledger)
(defcustom hledger-amount-face font-lock-constant-face
"Face for amount."
:type 'face
:group 'hledger)
(defcustom hledger-account-face font-lock-variable-name-face
"Face for accounts."
:type 'face
:group 'hledger)
(defcustom hledger-description-face nil
"Face for description text."
:type 'face
:group 'hledger)
(defcustom hledger-refresh-completions-idle-delay 1
"Update completions in file when Emacs has been idle for this many seconds.")
(defcustom hledger-invalidate-completions '()
"When to invalidate the data used for autocompletion. Apart
from `on-idle', these events do not refresh the data but only set
a flag for the next time completions are requested."
:type '(set (const :tag "When idle in the buffer" on-idle)
(const :tag "After saving" on-save)
(const :tag "After editing" on-edit))
:group 'hledger)
(defvar hledger-accounts-cache nil
"List of accounts cached for ac and company modes.")
(defvar hledger-must-update-accounts nil
"Flag indicating that the list of accounts has potentially changed
and must be recomputed. For internal use.")
(defvar hledger-ac-source
`((init . hledger-get-accounts)
(candidates . hledger-accounts-cache))
"A source for completing account names in a hledger buffer.")
;;;###autoload
(defun hledger-company (command &optional arg &rest ignored)
"Company backend for ‘hledger-mode’.
COMMAND, ARG and IGNORED the regular meanings."
(interactive (list 'interactive))
(pcase command
(`interactive (company-begin-backend 'hledger-company))
(`prefix (and (eq major-mode 'hledger-mode)
(company-grab-symbol)))
(`candidates
(delq nil
(mapcar (lambda (c)
(and (string-prefix-p arg c) c))
hledger-accounts-cache)))))
;;;###autoload
(defvar hledger-mode-map
(let ((map (make-keymap)))
(define-key map (kbd "C-c C-i") 'hledger-append-clipboard-to-journal)
(define-key map (kbd "C-c C-d") 'hledger-reschedule)
(define-key map (kbd "C-c C-b") 'hledger-edit-amount)
(define-key map (kbd "C-c C-p") 'hledger-backward-entry)
(define-key map (kbd "C-c C-n") 'hledger-next-or-new-entry)
(define-key map (kbd "RET") 'hledger-ret-command)
(define-key map (kbd "<backtab>") 'hledger-backtab-command)
map))
(defvar hledger-view-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-q") 'hledger-kill-reporting-window)
(define-key map (kbd "e") 'hledger-jentry)
(define-key map (kbd "g") 'hledger-redo)
(define-key map (kbd "q") 'bury-buffer)
(define-key map (kbd "h") 'hledger-show-view-mode-help)
(define-key map (kbd "w") 'hledger-copy-to-clipboard)
(define-key map (kbd "j") 'hledger-run-command)
(define-key map (kbd "t") 'hledger-report-ending-today)
(define-key map (kbd "w") 'hledger-widen-results-for-register)
(define-key map (kbd "<") 'hledger-prev-report)
(define-key map (kbd ">") 'hledger-next-report)
(define-key map (kbd "D") 'hledger-report-at-day)
(define-key map (kbd ".") 'hledger-present-report)
(define-key map (kbd "o") (hledger-as-command hledger-overall-report*
"overall"))
(define-key map (kbd "i") (hledger-as-command hledger-incomestatement*
"incomestatement"))
(define-key map (kbd "d") (hledger-as-command hledger-daily-report*
"daily"))
(define-key map (kbd "b") (hledger-as-command hledger-balancesheet*
"balancesheet"))
(define-key map (kbd "<tab>") 'hledger-expand-account)
(define-key map (kbd "n") 'next-line)
(define-key map (kbd "p") 'previous-line)
(define-key map (kbd "%") 'hledger-display-percentages)
map))
(defun hledger-font-lock-keywords-1 ()
"Minimal highlighting expressions for hledger mode."
(list
`(,(concat "^ +" hledger-account-regex) 1 hledger-account-face)
`(,(concat "^account " hledger-account-regex "$") 1 hledger-account-face)
`(,(concat "^alias \\([^[:space:]=;\n]+\\) = " hledger-account-regex "$")
(1 hledger-account-face)
(2 hledger-account-face))
`(,(concat "^payee " hledger-account-regex "$") 1 hledger-account-face)
`(,hledger-date-regex . hledger-date-face)
`(,(hledger-amount-regex) . hledger-amount-face)))
(defun hledger-font-lock-defaults ()
"Default highlighting expressions for hledger mode."
(list (hledger-font-lock-keywords-1)))
(defvar hledger-mode-syntax-table (let ((st (make-syntax-table)))
(modify-syntax-entry ?: "_" st)
(modify-syntax-entry ?\; "<" st)
(modify-syntax-entry ?\n ">" st)
st)
"Syntax table for hledger mode.")
(defun hledger-mode-init ()
"Function that does initial setup in the \"major-mode\" function."
(setq font-lock-defaults (hledger-font-lock-defaults))
(setq-local indent-line-function 'hledger-indent-line)
(setq-local indent-region-function 'hledger-indent-region-function)
(setq-local comment-start "; ")
(setq-local comment-end "")
(setq require-final-newline t)
(electric-indent-local-mode -1)
;; Make an overlay for current entry if enabled
(when hledger-enable-current-overlay
(add-hook 'post-command-hook 'hledger-update-current-entry-overlay))
(hledger-update-accounts)
(when (memq 'on-idle hledger-invalidate-completions)
(setq-local hledger-update-accounts-timer
(run-with-idle-timer hledger-refresh-completions-idle-delay t
'hledger-update-accounts (current-buffer)))
(add-hook 'kill-buffer-hook
(lambda () (cancel-timer hledger-update-accounts-timer))
nil
t))
(when (memq 'on-edit hledger-invalidate-completions)
(add-hook 'post-command-hook 'hledger-maybe-update-accounts nil t))
(when (memq 'on-save hledger-invalidate-completions)
(add-hook 'after-save-hook 'hledger-must-update-accounts nil t))
(add-to-list (make-local-variable 'completion-at-point-functions)
'hledger-completion-at-point))
;;;###autoload
(define-derived-mode hledger-mode fundamental-mode "HLedger" ()
"Major mode for editing journal files."
:syntax-table hledger-mode-syntax-table
(hledger-mode-init)
(hledger-init-thing-at-point))
;;;###autoload
(define-derived-mode hledger-view-mode special-mode "HLedger View" ()
"Major mode for viewing hledger reports. I have a separate major mode
so that the key bindings are not shared between buffers that are used for
viewing reports and the journal file. I require the same kind of syntax
highlighting in both kinds of buffers."
:syntax-table hledger-mode-syntax-table
(setq font-lock-defaults (hledger-font-lock-defaults))
;; Populate accounts cache if not already.
(unless hledger-accounts-cache
(hledger-update-accounts))
;; Setting up font-lock for partial account names. This is only to
;; make sure they have the right face in a tree-type report. Why?
;; Why not!?
(let* ((account-words (apply 'append
(mapcar (lambda (s)
(split-string s ":" t))
hledger-accounts-cache)))
(font-lock-acc-string (concat "\\<\\("
(mapconcat 'identity
(delete-dups account-words)
"\\|")
"\\)\\>")))
;; Do this only in view mode
(font-lock-add-keywords 'hledger-view-mode
`((,font-lock-acc-string . hledger-account-face)
(":" . hledger-account-face))))
;; Avoid wrapping lines in reports
(setq truncate-lines t)
(hledger-init-thing-at-point))
(provide 'hledger-mode)
;;; hledger-mode.el ends here