diff options
36 files changed, 2155 insertions, 0 deletions
@@ -0,0 +1,48 @@ +;; Load up the general config + +;; Added by Package.el. This must come before configurations of +;; installed packages. Don't delete this line. If you don't want it, +;; just comment it out by adding a semicolon to the start of the line. +;; You may delete these explanatory comments. +(package-initialize) + +(load-file "~/.emacs.local.d/general.el") + +;; Modes +;;(load-file "~/.emacs.local.d/modes/auto-complete.el") +;;(load-file "~/.emacs.local.d/modes/ecb.el") + +;;(load-file "~/.emacs.local.d/modes/linum.el") +;;(load-file "~/.emacs.local.d/modes/notmuch.el") + +(load-file "~/.emacs.local.d/modes/anzu.el") +(load-file "~/.emacs.local.d/modes/auto-fill.el") +(load-file "~/.emacs.local.d/modes/company.el") +(load-file "~/.emacs.local.d/modes/dashboard.el") +(load-file "~/.emacs.local.d/modes/doom-modeline.el") +(load-file "~/.emacs.local.d/modes/eshell.el") +(load-file "~/.emacs.local.d/modes/flyspell.el") +(load-file "~/.emacs.local.d/modes/go-lang.el") +(load-file "~/.emacs.local.d/modes/ibuffer.el") +(load-file "~/.emacs.local.d/modes/helm.el") +(load-file "~/.emacs.local.d/modes/ido.el") +(load-file "~/.emacs.local.d/modes/imenu.el") +(load-file "~/.emacs.local.d/modes/latex.el") +(load-file "~/.emacs.local.d/modes/mail-mode.el") +(load-file "~/.emacs.local.d/modes/markdown.el") +(load-file "~/.emacs.local.d/modes/magit.el") +(load-file "~/.emacs.local.d/modes/org.el") +(load-file "~/.emacs.local.d/modes/package.el") +(load-file "~/.emacs.local.d/modes/projectile.el") +(load-file "~/.emacs.local.d/modes/smex.el") +(load-file "~/.emacs.local.d/modes/sml.el") +(load-file "~/.emacs.local.d/modes/sublimity.el") +(load-file "~/.emacs.local.d/modes/whitespace.el") +(load-file "~/.emacs.local.d/modes/writeroom.el") + +(load-file "~/.emacs.local.d/modes/themes.el") + +;; Extra Debian packages: elpa-yaml-mode +;; Extra packages to install from Elpa: puppet-mode + +(provide '.emacs) diff --git a/.emacs.local.d/elisp/beancount.el b/.emacs.local.d/elisp/beancount.el new file mode 100644 index 0000000..7ca2a02 --- /dev/null +++ b/.emacs.local.d/elisp/beancount.el @@ -0,0 +1,653 @@ +;;; beancount.el --- A minor mode that can be used to edit beancount input files. + +;; Copyright (C) 2013 Martin Blais <blais@furius.ca> +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; Version: 0 +;; Author: Martin Blais <blais@furius.ca> +;; Author: Stefan Monnier <monnier@iro.umontreal.ca> + +;; This file is not part of GNU Emacs. + +;; This package is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This package is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this package. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; TODO: Add a flymake rule, using bean-check + +;;; Code: + +(autoload 'ido-completing-read "ido") +(require 'font-lock) + +(defgroup beancount () + "Editing mode for Beancount files." + :group 'beancount) + +(defconst beancount-timestamped-directive-names + '("balance" + "open" + "close" + "pad" + "document" + "note" + ;; The ones below are not followed by an account name. + "event" + "price" + "commodity" + "query" + "txn") + "Directive names that can appear after a date.") + +(defconst beancount-nontimestamped-directive-names + '("pushtag" + "poptag" + "option" + "include" + "plugin") + "Directive names that can appear after a date.") + +(defvar beancount-directive-names + (append beancount-nontimestamped-directive-names + beancount-timestamped-directive-names) + "A list of the directive names.") + +(defconst beancount-tag-chars "[:alnum:]-_/.") + +(defconst beancount-account-categories + '("Assets" "Liabilities" "Equity" "Income" "Expenses")) + +(defconst beancount-account-chars "[:alnum:]-_:") + +(defconst beancount-option-names + ;; This list is kept in sync with the options defined in + ;; beancount/parser/options.py. + ;; Note: We should eventually build a tool that spits out the current list + ;; automatically. + '("title" + "name_assets" + "name_liabilities" + "name_equity" + "name_income" + "name_expenses" + "bookin_algorithm" + "bookin_method" + "account_previous_balances" + "account_previous_earnings" + "account_previous_conversions" + "account_current_earnings" + "account_current_conversions" + "account_rounding" + "conversion_currency" + "inferred_tolerance_default" + "inferred_tolerance_multiplier" + "infer_tolerance_from_cost" + "documents" + "operating_currency" + "render_commas" + "plugin_processing_mode" + "plugin" + "long_string_maxlines" + )) + +(defvar beancount-font-lock-keywords + `(;; Reserved keywords + (,(regexp-opt beancount-directive-names) . font-lock-keyword-face) + + ;; Tags & Links + ("[#\\^][A-Za-z0-9\-_/.]+" . font-lock-type-face) + + ;; Date + ("[0-9][0-9][0-9][0-9][-/][0-9][0-9][-/][0-9][0-9]" . font-lock-constant-face) + + ;; Account + ("\\([A-Z][A-Za-z0-9\-]+:\\)+\\([A-Z][A-Za-z0-9\-]+\\)" . font-lock-builtin-face) + + ;; Txn Flags + ("! " . font-lock-warning-face) + + ;; Number + Currency + ;;; ("\\([\\-+]?[0-9,]+\\(\\.[0-9]+\\)?\\)\\s-+\\([A-Z][A-Z0-9'\.]\\{1,10\\}\\)" . ) + )) + + +(defvar beancount-mode-map-prefix [(control c)] + "The prefix key used to bind Beancount commands in Emacs") + +(defvar beancount-mode-map + (let ((map (make-sparse-keymap)) + (p beancount-mode-map-prefix)) + (define-key map (vconcat p [(\')]) #'beancount-insert-account) + (define-key map (vconcat p [(control g)]) #'beancount-transaction-set-flag) + (define-key map (vconcat p [(r)]) #'beancount-init-accounts) + (define-key map (vconcat p [(l)]) #'beancount-check) + (define-key map (vconcat p [(q)]) #'beancount-query) + (define-key map (vconcat p [(x)]) #'beancount-context) + (define-key map (vconcat p [(k)]) #'beancount-linked) + (define-key map (vconcat p [(p)]) #'beancount-insert-prices) + (define-key map (vconcat p [(\;)]) #'beancount-align-to-previous-number) + (define-key map (vconcat p [(\:)]) #'beancount-align-numbers) + + ;; FIXME: Binding TAB breaks expected org-mode behavior to fold/unfold. We + ;; need to find a better solution. + ;;(define-key map [?\t] #'beancount-tab) + map)) + +(defvar beancount-mode-syntax-table + (let ((st (make-syntax-table))) + (modify-syntax-entry ?\" "\"\"" st) + (modify-syntax-entry ?\; "<" st) + (modify-syntax-entry ?\n ">" st) + st)) + +(defun beancount--goto-bob () (goto-char (point-min))) + +;;;###autoload +(define-minor-mode beancount-mode + "A minor mode to help editing Beancount files. +This can be used within other text modes, in particular, org-mode +is great for sectioning large files with many transactions. + +\\{beancount-mode-map}" + :init-value nil + :lighter " Beancount" + :group 'beancount + + ;; The following is mostly lifted from lisp-mode. + + (set (make-local-variable 'paragraph-ignore-fill-prefix) t) + (set (make-local-variable 'fill-paragraph-function) #'lisp-fill-paragraph) + + (set (make-local-variable 'comment-start) ";; ") + + ;; Look within the line for a ; following an even number of backslashes + ;; after either a non-backslash or the line beginning. + (set (make-local-variable 'comment-start-skip) + "\\(\\(^\\|[^\\\\\n]\\)\\(\\\\\\\\\\)*\\);+ *") + ;; Font lock mode uses this only when it KNOWS a comment is starting. + ;; FIXME: Why bother? + (set (make-local-variable 'font-lock-comment-start-skip) ";+ *") + ;; Default to `;;' in comment-region. + (set (make-local-variable 'comment-add) 1) + + ;; Org-mode sets both of these to `org-comment-or-uncomment-region', + ;; which doesn't know about our ";" comments. + (kill-local-variable 'comment-region-function) + (kill-local-variable 'uncomment-region-function) + + ;; No tabs by default. + (set (make-local-variable 'indent-tabs-mode) nil) + + (add-hook 'completion-at-point-functions + #'beancount-completion-at-point nil t) + (set (make-local-variable 'completion-ignore-case) t) + + ;; Customize font-lock for beancount. + ;; + (set-syntax-table beancount-mode-syntax-table) + (when (fboundp 'syntax-ppss-flush-cache) + (syntax-ppss-flush-cache (point-min)) + (set (make-local-variable 'syntax-begin-function) #'beancount--goto-bob)) + ;; Force font-lock to use the syntax-table to find strings-and-comments, + ;; regardless of what the "host major mode" decided. + (set (make-local-variable 'font-lock-keywords-only) nil) + ;; Important: you have to use 'nil for the mode here because in certain major + ;; modes (e.g. org-mode) the font-lock-keywords is a buffer-local variable. + (if beancount-mode + (font-lock-add-keywords nil beancount-font-lock-keywords) + (font-lock-remove-keywords nil beancount-font-lock-keywords)) + (if (fboundp 'font-lock-flush) + (font-lock-flush) + (with-no-warnings (font-lock-fontify-buffer))) + + (when beancount-mode + (beancount-init-accounts)) + ) + +(defvar beancount-accounts nil + "A list of the accounts available in this buffer. +This is a cache of the value computed by `beancount-get-accounts'.") +(make-variable-buffer-local 'beancount-accounts) + +(defun beancount-init-accounts () + "Initialize or reset the list of accounts." + (interactive) + (setq beancount-accounts (beancount-get-accounts)) + (message "Accounts updated.")) + +(defvar beancount-date-regexp "[0-9][0-9][0-9][0-9][-/][0-9][0-9][-/][0-9][0-9]" + "A regular expression to match dates.") + +(defvar beancount-account-regexp + (concat (regexp-opt beancount-account-categories) + "\\(?::[[:upper:]][" beancount-account-chars "]+\\)") + "A regular expression to match account names.") + +(defvar beancount-number-regexp "[-+]?[0-9,]+\\(?:\\.[0-9]*\\)?" + "A regular expression to match decimal numbers in beancount.") + +(defvar beancount-currency-regexp "[A-Z][A-Z-_'.]*" + "A regular expression to match currencies in beancount.") + +(defun beancount-tab () + "Try to use the right meaning of TAB." + (interactive) + (let ((cdata (beancount-completion-at-point))) + (if cdata + ;; There's beancount-specific completion at point. + (call-interactively #'completion-at-point) + (let* ((beancount-mode nil) + (fallback (key-binding (this-command-keys)))) + (if (commandp fallback) + (command-execute fallback)))))) + +(defun beancount-tags (prefix) + "Return list of all tags starting with PREFIX in current buffer. +Excludes tags appearing on the current line." + (unless (string-match "\\`[#^]" prefix) + (error "Unexpected prefix to search tags: %S" prefix)) + (let ((found ()) + (re (concat prefix "[" beancount-tag-chars "]*"))) + (save-excursion + (forward-line 0) + (while (re-search-backward re nil t) + (push (match-string 0) found))) + ;; Ignore tags on current line. + (save-excursion + (forward-line 1) + (while (re-search-forward re nil t) + (push (match-string 0) found))) + (delete-dups found))) + +(defconst beancount-txn-regexp + ;; For the full definition of a flag, see the rule that emits FLAG in + ;; beancount/parser/lexer.l. For this, let's assume that it's a single char + ;; that's neither a space nor a lower-case letter. This should be updated as + ;; the parser is improved. + "^[0-9-/]+ +\\(?:txn +\\)?[^ [:lower:]]\\($\\| \\)") + +(defun beancount-inside-txn-p () + ;; FIXME: The doc doesn't actually say how the legs of a transaction can be + ;; layed out. We assume that they all start with some space on the line. + (save-excursion + (forward-line 0) + (while (and (looking-at "[ \t]") (not (bobp))) + (forward-line -1)) + (looking-at beancount-txn-regexp))) + +(defun beancount-completion-at-point () + "Return the completion data relevant for the text at point." + (let ((bp (buffer-substring (line-beginning-position) (point)))) + (cond + ((string-match "\\`[a-z]*\\'" bp) + ;; A directive starting at BOL (hence non-timestamped). + (list (line-beginning-position) + (save-excursion (skip-chars-forward "a-z") (point)) + '("pushtag" "poptag"))) + + ((string-match + (concat "\\`option +\\(\"[a-z_]*\\)?\\'") + bp) + (list (- (point) + (if (match-end 1) (- (match-end 1) (match-beginning 1)) 0)) + (save-excursion (skip-chars-forward "a-z_") + (if (looking-at "\"") (forward-char 1)) + (point)) + (mapcar (lambda (s) (concat "\"" s "\"")) beancount-option-names))) + + ((string-match + (concat "\\`poptag +\\(#[" beancount-tag-chars "]*\\)?\\'") + bp) + (list (- (point) + (if (match-end 1) (- (match-end 1) (match-beginning 1)) 0)) + (save-excursion (skip-chars-forward beancount-tag-chars) (point)) + (save-excursion + (let ((opened ())) + (while (re-search-backward + (concat "^pushtag +\\(#[" beancount-tag-chars "]+\\)") + nil t) + (push (match-string 1) opened)) + opened)))) + + ((string-match "\\`[0-9-/]+ +\\([[:alpha:]]*\\'\\)" bp) + ;; A timestamped directive. + (list (- (point) (- (match-end 1) (match-beginning 1))) + (save-excursion (skip-chars-forward "[:alpha:]") (point)) + beancount-timestamped-directive-names)) + + ((and (beancount-inside-txn-p) + (string-match (concat "\\`[ \t]+\\([" + beancount-account-chars "]*\\)\\'") + bp)) + ;; Hopefully, an account name. We don't force the partially-written + ;; account name to start with a capital, so that it's possible to use + ;; substring completion and also so we can rely on completion to put the + ;; right capitalization (thanks to completion-ignore-case). + (list (- (point) (- (match-end 1) (match-beginning 1))) + (save-excursion (skip-chars-forward beancount-account-chars) + (point)) + #'beancount-account-completion-table)) + + ((string-match (concat "\\`[0-9-/]+ +\\(" + (regexp-opt beancount-timestamped-directive-names) + "\\) +\\([" beancount-account-chars "]*\\'\\)") + bp) + (list (- (point) (- (match-end 2) (match-beginning 2))) + (save-excursion (skip-chars-forward beancount-account-chars) + (point)) + (if (equal (match-string 1 bp) "open") + (append + (mapcar (lambda (c) (concat c ":")) beancount-account-categories) + beancount-accounts) + #'beancount-account-completion-table))) + + ((string-match (concat "[#^][" beancount-tag-chars "]*\\'") bp) + (list (- (point) (- (match-end 0) (match-beginning 0))) + (save-excursion (skip-chars-forward beancount-tag-chars) (point)) + (completion-table-dynamic #'beancount-tags)))))) + +(defun beancount-hash-keys (hashtable) + "Extract all the keys of the given hashtable. Return a sorted list." + (let (rlist) + (maphash (lambda (k _v) (push k rlist)) hashtable) + (sort rlist 'string<))) + +(defun beancount-get-accounts () + "Heuristically obtain a list of all the accounts used in all the postings. +We ignore patterns seen the line 'exclude-line'. If ALL is non-nil, look +for account names in postings as well (default is to look at the @defaccount +declarations only." + (let ((accounts (make-hash-table :test 'equal))) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward beancount-account-regexp nil t) + (puthash (match-string-no-properties 0) nil accounts))) + (sort (beancount-hash-keys accounts) 'string<))) + +(defcustom beancount-use-ido t + "If non-nil, use ido-style completion rather than the standard completion." + :type 'boolean) + +(defun beancount-account-completion-table (string pred action) + (if (eq action 'metadata) + '(metadata (category . beancount-account)) + (complete-with-action action beancount-accounts string pred))) + +;; Default to substring completion for beancount accounts. +(defconst beancount--completion-overrides + '(beancount-account (styles basic partial-completion substring))) +(cond + ((boundp 'completion-category-defaults) + (add-to-list 'completion-category-defaults beancount--completion-overrides)) + ((and (boundp 'completion-category-overrides) + (not (assq 'beancount-account completion-category-overrides))) + (push beancount--completion-overrides completion-category-overrides))) + +(defun beancount-insert-account (account-name) + "Insert one of the valid account names in this file. +Uses ido niceness according to `beancount-use-ido'." + (interactive + (list + (if beancount-use-ido + ;; `ido-completing-read' is too dumb to understand functional + ;; completion tables! + (ido-completing-read "Account: " beancount-accounts + nil nil (thing-at-point 'word)) + (completing-read "Account: " #'beancount-account-completion-table + nil t (thing-at-point 'word))))) + (let ((bounds (bounds-of-thing-at-point 'word))) + (when bounds + (delete-region (car bounds) (cdr bounds)))) + (insert account-name)) + + +(defun beancount-transaction-set-flag () + (interactive) + (save-excursion + (backward-paragraph 1) + (forward-line 1) + (while (search-forward "!" (line-end-position) t) + (replace-match "*")))) + + +(defmacro beancount-for-line-in-region (begin end &rest exprs) + "Iterate over each line in region until an empty line is encountered." + `(save-excursion + (let ((end-marker (copy-marker ,end))) + (goto-char ,begin) + (beginning-of-line) + (while (and (not (eobp)) (< (point) end-marker)) + (beginning-of-line) + (progn ,@exprs) + (forward-line 1) + )))) + + +(defun beancount-max-accounts-width (begin end) + "Return the minimum widths of a list of account names on a list +of lines. Initial whitespace is ignored." + (let* (widths) + (beancount-for-line-in-region + begin end + (let* ((line (thing-at-point 'line))) + (when (string-match + (concat "^[ \t]*\\(" beancount-account-regexp "\\)") line) + (push (length (match-string 1 line)) widths)))) + (apply 'max widths))) + + +(defun beancount-align-numbers (begin end &optional requested-currency-column) + "Align all numbers in the given region. CURRENCY-COLUMN is the character +at which to align the beginning of the amount's currency. If not specified, use +the smallest columns that will align all the numbers. With a prefix argument, +align with the fill-column." + (interactive "r") + + ;; With a prefix argument, align with the fill-column. + (when current-prefix-arg + (setq requested-currency-column fill-column)) + + ;; Loop once in the region to find the length of the longest string before the + ;; number. + (let (prefix-widths + number-widths + (number-padding " ")) + (beancount-for-line-in-region + begin end + (let ((line (thing-at-point 'line))) + (when (string-match (concat "\\(.*?\\)" + "[ \t]+" + "\\(" beancount-number-regexp "\\)" + "[ \t]+" + beancount-currency-regexp) + line) + (push (length (match-string 1 line)) prefix-widths) + (push (length (match-string 2 line)) number-widths) + ))) + + (when prefix-widths + ;; Loop again to make the adjustments to the numbers. + (let* ((number-width (apply 'max number-widths)) + (number-format (format "%%%ss" number-width)) + ;; Compute rightmost column of prefix. + (max-prefix-width (apply 'max prefix-widths)) + (max-prefix-width + (if requested-currency-column + (max (- requested-currency-column (length number-padding) number-width 1) + max-prefix-width) + max-prefix-width)) + (prefix-format (format "%%-%ss" max-prefix-width)) + ) + + (beancount-for-line-in-region + begin end + (let ((line (thing-at-point 'line))) + (when (string-match (concat "^\\([^\"]*?\\)" + "[ \t]+" + "\\(" beancount-number-regexp "\\)" + "[ \t]+" + "\\(.*\\)$") + line) + (delete-region (line-beginning-position) (line-end-position)) + (let* ((prefix (match-string 1 line)) + (number (match-string 2 line)) + (rest (match-string 3 line)) ) + (insert (format prefix-format prefix)) + (insert number-padding) + (insert (format number-format number)) + (insert " ") + (insert rest))))))))) + + +(defun beancount-align-to-previous-number () + "Align postings under the point's paragraph. +This function looks for a posting in the previous transaction to +determine the column at which to align the transaction, or otherwise +the fill column, and align all the postings of this transaction to +this column." + (interactive) + (let* ((begin (save-excursion + (beancount-beginning-of-directive) + (point))) + (end (save-excursion + (goto-char begin) + (forward-paragraph 1) + (point))) + (currency-column (or (beancount-find-previous-alignment-column) + fill-column))) + (beancount-align-numbers begin end currency-column))) + + +(defun beancount-beginning-of-directive () + "Move point to the beginning of the enclosed or preceding directive." + (beginning-of-line) + (while (and (> (point) (point-min)) + (not (looking-at + "[0-9][0-9][0-9][0-9][\-/][0-9][0-9][\-/][0-9][0-9]"))) + (forward-line -1))) + + +(defun beancount-find-previous-alignment-column () + "Find the preceding column to align amounts with. +This is used to align transactions at the same column as that of +the previous transaction in the file. This function merely finds +what that column is and returns it (an integer)." + ;; Go hunting for the last column with a suitable posting. + (let (column) + (save-excursion + ;; Go to the beginning of the enclosing directive. + (beancount-beginning-of-directive) + (forward-line -1) + + ;; Find the last posting with an amount and a currency on it. + (let ((posting-regexp (concat + "\\s-+" + beancount-account-regexp "\\s-+" + beancount-number-regexp "\\s-+" + "\\(" beancount-currency-regexp "\\)")) + (balance-regexp (concat + beancount-date-regexp "\\s-+" + "balance" "\\s-+" + beancount-account-regexp "\\s-+" + beancount-number-regexp "\\s-+" + "\\(" beancount-currency-regexp "\\)"))) + (while (and (> (point) (point-min)) + (not (or (looking-at posting-regexp) + (looking-at balance-regexp)))) + (forward-line -1)) + (when (or (looking-at posting-regexp) + (looking-at balance-regexp)) + (setq column (- (match-beginning 1) (point)))) + )) + column)) + +(defvar beancount-install-dir nil + "Directory in which Beancount's source is located. +Only useful if you have not installed Beancount properly in your PATH.") + +(defvar beancount-check-program "bean-check" + "Program to run to run just the parser and validator on an + input file.") + +(defvar compilation-read-command) + +(defun beancount--run (prog &rest args) + (let ((process-environment + (if beancount-install-dir + `(,(concat "PYTHONPATH=" beancount-install-dir) + ,(concat "PATH=" + (expand-file-name "bin" beancount-install-dir) + ":" + (getenv "PATH")) + ,@process-environment) + process-environment)) + (compile-command (mapconcat (lambda (arg) + (if (stringp arg) + (shell-quote-argument arg) "")) + (cons prog args) + " "))) + (call-interactively 'compile))) + +(defun beancount-check () + "Run `beancount-check-program'." + (interactive) + (let ((compilation-read-command nil)) + (beancount--run beancount-check-program + (file-relative-name buffer-file-name)))) + +(defvar beancount-query-program "bean-query" + "Program to run to run just the parser and validator on an + input file.") + +(defun beancount-query () + "Run bean-query." + (interactive) + ;; Don't let-bind compilation-read-command this time, since the default + ;; command is incomplete. + (beancount--run beancount-query-program + (file-relative-name buffer-file-name) t)) + + +(defvar beancount-doctor-program "bean-doctor" + "Program to run the doctor commands.") + +(defun beancount-context () + "Get the \"context\" from `beancount-doctor-program'." + (interactive) + (let ((compilation-read-command nil)) + (beancount--run beancount-doctor-program "context" + (file-relative-name buffer-file-name) + (number-to-string (line-number-at-pos))))) + + +(defun beancount-linked () + "Get the \"linked\" info from `beancount-doctor-program'." + (interactive) + (let ((compilation-read-command nil)) + (beancount--run beancount-doctor-program "linked" + (file-relative-name buffer-file-name) + (number-to-string (line-number-at-pos))))) + +(defvar beancount-price-program "bean-price" + "Program to run the price fetching commands.") + +(defun beancount-insert-prices () + "Run bean-price on the current file and insert the output inline." + (interactive) + (call-process beancount-price-program nil t nil + (file-relative-name buffer-file-name))) + + +(provide 'beancount) +;;; beancount.el ends here diff --git a/.emacs.local.d/general.el b/.emacs.local.d/general.el new file mode 100644 index 0000000..558490b --- /dev/null +++ b/.emacs.local.d/general.el @@ -0,0 +1,165 @@ +(message nil);; Debian packages: elpa-use-package elpa-fill-column-indicator + +;; This is only needed once, near the top of the file +(eval-when-compile + (require 'use-package)) + +;; Added by Package.el. This must come before configurations of +;; installed packages. Don't delete this line. If you don't want it, +;; just comment it out by adding a semicolon to the start of the line. +;; You may delete these explanatory comments. +(package-initialize) + +(add-to-list 'package-archives + '("melpa-stable" . "https://stable.melpa.org/packages/") t) + +(add-to-list 'load-path "~/.emacs.local.d/elisp") + +;; ------ +;; Require misc stuff +;; ------ +(require 'fill-column-indicator) + +;; Place backups in ~/.backups/ directory, like a civilized program. +;; ------ +(if (file-directory-p "~/.backup") + (setq backup-directory-alist '(("." . "~/.backup"))) + (message "Directory does not exist: ~/.backup")) + +(filesets-init) + +(setq backup-by-copying t ; Don't delink hardlinks + delete-old-versions t ; Clean up the backups + version-control t ; Use version numbers on backups, + kept-new-versions 3 ; keep some new versions + kept-old-versions 2) ; and some old ones, too + +;;; backup/autosave - old studd, just in case +;; (defvar backup-dir (expand-file-name "~/.emacs.d/backup/")) +;; (defvar autosave-dir (expand-file-name "~/.emacs.d/autosave/")) +;; (setq backup-directory-alist (list (cons ".*" backup-dir))) +;; (setq auto-save-list-file-prefix autosave-dir) +;; (setq auto-save-file-name-transforms `((".*" ,autosave-dir t))) + + +;; --------- +;; Generic keybindings +;; --------- +(global-set-key (kbd "C-c d") 'diff-buffer-with-file) +(global-set-key (kbd "C-c R") 'revert-buffer) + +(global-set-key (kbd "C-<next>") 'next-buffer) +(global-set-key (kbd "C-<prior>") 'previous-buffer) + +;; compile +(global-set-key [f12] 'compile) + +;; ------ +;; General config BS +;; ------ + +(setq fill-column 79) + +;; Make sure that pressing middle mouse button pastes right at point, +;; not where the mouse cursor is. +(setq mouse-yank-at-point t) +(setq column-number-mode 1) +(setq line-number-mode 1) +(setq-default indent-tabs-mode nil) +(setq-default tab-width 4) + +(setq-default c-basic-offset 4) + +;; ------ +;; Initialize some things +;; ------ + + +(setenv "TMPDIR" (concat (getenv "HOME") "/tmp")) +(server-start) + +;; ------ +;; Terminal / window specific stuff +;; ------ + +;; Don't minimize my emacs! Honestly wtf +(when window-system + (progn + (global-unset-key (kbd "C-z")) + (setq scroll-bar-mode nil) + (tool-bar-mode nil) + (menu-bar-mode nil))) + +;; ------ +;; Helper for compilation. +;; ------ +;; Close the compilation window if there was no error at all. +(defun compilation-exit-autoclose (status code msg) + ;; If M-x compile exists with a 0 + (when (and (eq status 'exit) (zerop code)) + ;; then bury the *compilation* buffer, so that C-x b doesn't go there + (bury-buffer) + ;; and delete the *compilation* window + (delete-window (get-buffer-window (get-buffer "*compilation*")))) + ;; Always return the anticipated result of compilation-exit-message-function + (cons msg code)) +;; Specify my function (maybe I should have done a lambda function) +(setq compilation-exit-message-function 'compilation-exit-autoclose) +(setq compilation-read-command nil) + +;; Themes +(add-to-list 'custom-theme-load-path "~/.emacs.d/themes/") + +;; Remove scratch message +(setq initial-scratch-message "") + +;; Turn of scroll bar +(when (featurep 'scroll-bar) (scroll-bar-mode -1)) + +;; Ask y or n instead of yes or no +(defalias 'yes-or-no-p 'y-or-n-p) + +;; Fancier buffer selection +(global-set-key (kbd "C-x C-b") 'bs-show) + +;;; Stefan Monnier <foo at acm.org>. It is the opposite of fill-paragraph +(defun unfill-paragraph (&optional region) + "Takes a multi-line paragraph and makes it into a single line of text." + (interactive (progn (barf-if-buffer-read-only) '(t))) + (let ((fill-column (point-max)) + ;; This would override `fill-column' if it's an integer. + (emacs-lisp-docstring-fill-column t)) + (fill-paragraph nil region))) + +;; Handy key definition +(define-key global-map "\M-Q" 'unfill-paragraph) + +(setq custom-file "~/.emacs.d/custom.el") +(load custom-file 'noerror) + +(menu-bar-mode -1) +(tool-bar-mode -1) + + +;; Start maximized +(add-to-list 'default-frame-alist '(fullscreen . maximized)) + +;; Only flash the mode line +(setq ring-bell-function + (lambda () + (let ((orig-fg (face-foreground 'mode-line))) + (set-face-foreground 'mode-line "#F2804F") + (run-with-idle-timer 0.1 nil + (lambda (fg) (set-face-foreground 'mode-line fg)) + orig-fg)))) + +;; Highlight parens +(setq show-paren-delay 0) +(show-paren-mode 1) + +;; Use default brwoser +;;(setq browse-url-browser-function 'browse-url-generic browse-url-generic-program "chromium") + +;; Mutt support. +(setq auto-mode-alist (append '((".*tmp/mutt.*" . mail-mode)) auto-mode-alist)) +(setq auto-mode-alist (append '((".*tmp/neomutt.*" . mail-mode)) auto-mode-alist)) diff --git a/.emacs.local.d/modes/anzu.el b/.emacs.local.d/modes/anzu.el new file mode 100644 index 0000000..5b1cc64 --- /dev/null +++ b/.emacs.local.d/modes/anzu.el @@ -0,0 +1,8 @@ +;; Debian packages: elpa-anzu + +(use-package anzu + :ensure t + :bind (("M-%" . anzu-query-replace) + ("C-M-%" . anzu-query-replace-regexp)) + :config + (global-anzu-mode)) diff --git a/.emacs.local.d/modes/auto-complete.el b/.emacs.local.d/modes/auto-complete.el new file mode 100644 index 0000000..c536b37 --- /dev/null +++ b/.emacs.local.d/modes/auto-complete.el @@ -0,0 +1,13 @@ +;; auto-complete +(require 'auto-complete) +(require 'auto-complete-config) + +;; Debian packages: auto-complete-el + +(add-to-list 'ac-dictionary-directories "/usr/share/auto-complete/dict/") + +(ac-config-default) +(defun auto-complete-mode-maybe () + "No maybe for you. Only AC!" + (unless (minibufferp (current-buffer)) + (auto-complete-mode 1))) diff --git a/.emacs.local.d/modes/auto-fill.el b/.emacs.local.d/modes/auto-fill.el new file mode 100644 index 0000000..dad2831 --- /dev/null +++ b/.emacs.local.d/modes/auto-fill.el @@ -0,0 +1,4 @@ +;; auto-fill mode +(add-hook 'text-mode-hook 'turn-on-auto-fill) +(global-set-key (kbd "C-c q") 'auto-fill-mode) + diff --git a/.emacs.local.d/modes/beancount.el b/.emacs.local.d/modes/beancount.el new file mode 100644 index 0000000..ca5c7c6 --- /dev/null +++ b/.emacs.local.d/modes/beancount.el @@ -0,0 +1,5 @@ +(require 'beancount) + +;; Automatically open .beancount files in beancount-mode. +(add-to-list 'auto-mode-alist '("\\.beancount$" . beancount-mode)) + diff --git a/.emacs.local.d/modes/company.el b/.emacs.local.d/modes/company.el new file mode 100644 index 0000000..dc80b33 --- /dev/null +++ b/.emacs.local.d/modes/company.el @@ -0,0 +1,29 @@ +;; Debian packages: elpa-company +;; Elpa packages: company-quickhelp + +(use-package company + :ensure t + :defer t + :init (global-company-mode) + :config + (progn + ;; Use Company for completion + (bind-key [remap completion-at-point] #'company-complete company-mode-map) + + (setq company-tooltip-align-annotations t + ;; Easy navigation to candidates with M-<n> + company-show-numbers t) + (setq company-dabbrev-downcase nil)) + :diminish company-mode) + +(use-package company-quickhelp ; Documentation popups for Company + :ensure t + :defer t + :init (add-hook 'global-company-mode-hook #'company-quickhelp-mode)) + +(use-package company-go + :ensure t + :defer t + :init + (with-eval-after-load 'company + (add-to-list 'company-backends 'company-go))) diff --git a/.emacs.local.d/modes/dashboard.el b/.emacs.local.d/modes/dashboard.el new file mode 100644 index 0000000..1dc81d3 --- /dev/null +++ b/.emacs.local.d/modes/dashboard.el @@ -0,0 +1,11 @@ +;; Elpa packages: dashboard + +(use-package dashboard + :ensure t + :diminish dashboard-mode + :config + (setq dashboard-banner-logo-title "Only the educated are free.") + (setq dashboard-items '((recents . 10) + (projects . 5) + (bookmarks . 10))) + (dashboard-setup-startup-hook)) diff --git a/.emacs.local.d/modes/doom-modeline.el b/.emacs.local.d/modes/doom-modeline.el new file mode 100644 index 0000000..c3b6cc2 --- /dev/null +++ b/.emacs.local.d/modes/doom-modeline.el @@ -0,0 +1,5 @@ +;; Elpa packages: doom-modeline + +(use-package doom-modeline + :ensure t + :hook (after-init . doom-modeline-mode)) diff --git a/.emacs.local.d/modes/ecb.el b/.emacs.local.d/modes/ecb.el new file mode 100644 index 0000000..146a75f --- /dev/null +++ b/.emacs.local.d/modes/ecb.el @@ -0,0 +1,21 @@ +;;; activate ecb +(require 'ecb) +(require 'ecb-autoloads) + +(setq ecb-layout-name "left13") +(setq ecb-show-sources-in-directories-buffer 'always) + +;;; activate and deactivate ecb +(global-set-key (kbd "C-x C-;") 'ecb-activate) +(global-set-key (kbd "C-x C-'") 'ecb-deactivate) +;;; show/hide ecb window +(global-set-key (kbd "C-;") 'ecb-show-ecb-windows) +(global-set-key (kbd "C-'") 'ecb-hide-ecb-windows) +;;; quick navigation between ecb windows +(global-set-key (kbd "C-)") 'ecb-goto-window-edit1) +(global-set-key (kbd "C-!") 'ecb-goto-window-directories) +(global-set-key (kbd "C-@") 'ecb-goto-window-sources) +(global-set-key (kbd "C-#") 'ecb-goto-window-methods) +(global-set-key (kbd "C-$") 'ecb-goto-window-compilation) + + diff --git a/.emacs.local.d/modes/eshell.el b/.emacs.local.d/modes/eshell.el new file mode 100644 index 0000000..c9be093 --- /dev/null +++ b/.emacs.local.d/modes/eshell.el @@ -0,0 +1,85 @@ +(setq eshell-history-size 512) +(setq eshell-prompt-regexp "^.*> ") + +(require 'em-hist) ; So the history vars are defined +(if (boundp 'eshell-save-history-on-exit) + (setq eshell-save-history-on-exit t)) ; Don't ask, just save +(if (boundp 'eshell-ask-to-save-history) + (setq eshell-ask-to-save-history 'always)) ; For older(?) version + +(defun eshell/ef (fname-regexp &optional dir) + (ef fname-regexp (or dir default-directory))) + + +;;; ---- path manipulation + +(defun pwd-repl-home (pwd) + (interactive) + (let* ((home (expand-file-name (getenv "HOME"))) + (home-len (length home))) + (if (and + (>= (length pwd) home-len) + (equal home (substring pwd 0 home-len))) + (concat "~" (substring pwd home-len)) + pwd))) + +(defun curr-dir-git-branch-string (pwd) + "Returns current git branch as a string, or the empty string if +PWD is not in a git repo (or the git command is not found)." + (interactive) + (when (and (eshell-search-path "git") + (locate-dominating-file pwd ".git")) + (let ((git-output (shell-command-to-string (concat "git branch | grep '\\*' | sed -e 's/^\\* //'")))) + (concat "[g:" + (if (> (length git-output) 0) + (substring git-output 0 -1) + "(no branch)") + "] ")))) + +(defun curr-dir-svn-string (pwd) + (interactive) + (when (and (eshell-search-path "svn") + (locate-dominating-file pwd ".svn")) + (concat "[s:" + (cond ((string-match-p "/trunk\\(/.*\\)?" pwd) + "trunk") + ((string-match "/branches/\\([^/]+\\)\\(/.*\\)?" pwd) + (match-string 1 pwd)) + (t + "(no branch)")) + "] "))) + +(setq eshell-prompt-function + (lambda () + (concat + (or (curr-dir-git-branch-string (eshell/pwd)) + (curr-dir-svn-string (eshell/pwd))) + ((lambda (p-lst) + (if (> (length p-lst) 3) + (concat + (mapconcat (lambda (elm) (if (zerop (length elm)) "" + (substring elm 0 1))) + (butlast p-lst 3) + "/") + "/" + (mapconcat (lambda (elm) elm) + (last p-lst 3) + "/")) + (mapconcat (lambda (elm) elm) + p-lst + "/"))) + (split-string (pwd-repl-home (eshell/pwd)) "/")) + "> "))) + +;; ; From http://www.emacswiki.org/cgi-bin/wiki.pl/EshellWThirtyTwo +;; ; Return nil, otherwise you'll see the return from w32-shell-execute +;; (defun eshell/open (file) +;; "Invoke (w32-shell-execute \"Open\" FILE) and substitute slashes for +;; backslashes" +;; (w32-shell-execute "Open" (substitute ?\\ ?/ (expand-file-name file))) +;; nil) + +(add-hook 'eshell-mode-hook + (lambda () + (local-set-key "\C-c\C-q" 'eshell-kill-process) + (local-set-key "\C-c\C-k" 'compile))) diff --git a/.emacs.local.d/modes/flycheck.el b/.emacs.local.d/modes/flycheck.el new file mode 100644 index 0000000..45571e2 --- /dev/null +++ b/.emacs.local.d/modes/flycheck.el @@ -0,0 +1,12 @@ +;; Debian-packages: elpa-flycheck python3-proselint + +(flycheck-define-checker proselint + "A linter for prose." + :command ("proselint" source-inplace) + :error-patterns + ((warning line-start (file-name) ":" line ":" column ": " + (id (one-or-more (not (any " ")))) + (message) line-end)) + :modes (text-mode markdown-mode gfm-mode org-mode)) + +(add-to-list 'flycheck-checkers 'proselint) diff --git a/.emacs.local.d/modes/flyspell.el b/.emacs.local.d/modes/flyspell.el new file mode 100644 index 0000000..8cf27b8 --- /dev/null +++ b/.emacs.local.d/modes/flyspell.el @@ -0,0 +1,12 @@ +(defcustom flyspell-delayed-commands nil + "List of commands that are \"delayed\" for Flyspell mode. +After these commands, Flyspell checking is delayed for a short time, +whose length is specified by `flyspell-delay'." + :group 'flyspell + :type '(repeat (symbol))) + +(setq ispell-dictionary "en") +(setq flyspell-default-dictionary "en") + +(setq flyspell-issue-welcome-flag nil) +(setq-default ispell-list-command "list") diff --git a/.emacs.local.d/modes/go-lang.el b/.emacs.local.d/modes/go-lang.el new file mode 100644 index 0000000..5d43e16 --- /dev/null +++ b/.emacs.local.d/modes/go-lang.el @@ -0,0 +1,23 @@ +;; Debian packages: elpa-go-mode +;; Elpa packages: go-eldoc + +(use-package go-mode + :ensure t + :init + (progn + (setq gofmt-command "goimports") + (add-hook 'before-save-hook 'gofmt-before-save) + (bind-key [remap find-tag] #'godef-jump)) + :config + (add-hook 'go-mode-hook 'electric-pair-mode) + (add-hook 'go-mode-hook 'my-go-mode-hook)) + +(use-package go-eldoc + :ensure t + :defer + :init + (add-hook 'go-mode-hook 'go-eldoc-setup)) + +;; Define function to call when go-mode loads +(defun my-go-mode-hook () + (set 'compile-command "go build -v && go test -v && go vet")) diff --git a/.emacs.local.d/modes/helm.el b/.emacs.local.d/modes/helm.el new file mode 100644 index 0000000..55a4c19 --- /dev/null +++ b/.emacs.local.d/modes/helm.el @@ -0,0 +1,22 @@ +(use-package helm + :bind (("M-x" . helm-M-x) + ("M-<f5>" . helm-find-files) + ([f10] . helm-buffers-list) + ([S-f10] . helm-recentf)) + :config + (setq helm-mode-fuzzy-match t) + (setq helm-completion-in-region-fuzzy-match t) + ) + + +(setq helm-M-x-fuzzy-match t + helm-bookmark-show-location t + helm-buffers-fuzzy-matching t + helm-completion-in-region-fuzzy-match t + helm-file-cache-fuzzy-match t + helm-imenu-fuzzy-match t + helm-mode-fuzzy-match t + helm-locate-fuzzy-match t + helm-quick-update t + helm-recentf-fuzzy-match t + helm-semantic-fuzzy-match t) diff --git a/.emacs.local.d/modes/ibuffer.el b/.emacs.local.d/modes/ibuffer.el new file mode 100644 index 0000000..d5198d8 --- /dev/null +++ b/.emacs.local.d/modes/ibuffer.el @@ -0,0 +1,35 @@ +;; Debian packages: elpa-ibuffer-vc + +(use-package ibuffer ; Better buffer list + :bind (([remap list-buffers] . ibuffer)) + ;; Show VC Status in ibuffer + :config (setq ibuffer-formats + '((mark modified read-only vc-status-mini " " + (name 18 18 :left :elide) + " " + (size 9 -1 :right) + " " + (mode 16 16 :left :elide) + " " + (vc-status 16 16 :left) + " " + filename-and-process) + (mark modified read-only " " + (name 18 18 :left :elide) + " " + (size 9 -1 :right) + " " + (mode 16 16 :left :elide) + " " filename-and-process) + (mark " " + (name 16 -1) + " " filename)))) + +(use-package ibuffer-vc ; Group buffers by VC project and status + :ensure t + :defer t + :init (add-hook 'ibuffer-hook + (lambda () + (ibuffer-vc-set-filter-groups-by-vc-root) + (unless (eq ibuffer-sorting-mode 'alphabetic) + (ibuffer-do-sort-by-alphabetic))))) diff --git a/.emacs.local.d/modes/ido.el b/.emacs.local.d/modes/ido.el new file mode 100644 index 0000000..8b475f9 --- /dev/null +++ b/.emacs.local.d/modes/ido.el @@ -0,0 +1,19 @@ +;; Debian packages: elpa-flx-ido elpa-ido-vertical-mode elpa-ido-completing-read+ + +(require 'ido) +(ido-mode t) + +(require 'flx-ido) +(ido-mode 1) +(ido-everywhere 1) +(flx-ido-mode 1) +;; disable ido faces to see flx highlights. +(setq ido-enable-flex-matching t) +(setq ido-use-faces nil) + +(require 'ido-vertical-mode) +(ido-vertical-mode 1) +(setq ido-vertical-define-keys 'C-n-and-C-p-only) + +(require 'ido-completing-read+) + diff --git a/.emacs.local.d/modes/imenu.el b/.emacs.local.d/modes/imenu.el new file mode 100644 index 0000000..1a2b29b --- /dev/null +++ b/.emacs.local.d/modes/imenu.el @@ -0,0 +1,12 @@ +;; Debian packages: elpa-imenu-list +(use-package imenu-list + :ensure t + :bind ("C-." . imenu-list-minor-mode) + :config + (setq imenu-list-focus-after-activation t) + (setq imenu-list-size 0.2) + (setq imenu-list-position 'left) + (add-hook 'go-mode-hook #'imenu-list-minor-mode)) + + + diff --git a/.emacs.local.d/modes/latex.el b/.emacs.local.d/modes/latex.el new file mode 100644 index 0000000..de4de1f --- /dev/null +++ b/.emacs.local.d/modes/latex.el @@ -0,0 +1,9 @@ +(add-hook 'latex-mode-hook 'flyspell-mode) +(setq TeX-PDF-mode t) + +(defun pdfevince () + (add-to-list 'TeX-output-view-style + '("^pdf$" "." "evince %o %(outpage)"))) + +(add-hook 'LaTeX-mode-hook 'pdfevince t) ; AUCTeX LaTeX mode + diff --git a/.emacs.local.d/modes/linum.el b/.emacs.local.d/modes/linum.el new file mode 100644 index 0000000..8df9986 --- /dev/null +++ b/.emacs.local.d/modes/linum.el @@ -0,0 +1,3 @@ +;(require 'linum-relative) + +(add-hook 'go-mode-hook #'linum-mode) diff --git a/.emacs.local.d/modes/magit.el b/.emacs.local.d/modes/magit.el new file mode 100644 index 0000000..e1ca63f --- /dev/null +++ b/.emacs.local.d/modes/magit.el @@ -0,0 +1,15 @@ +;; Debian packages: elpa-magit elpa-magithub + +(use-package magit + :ensure t + :defer t + :bind (("C-x g" . magit-status)) + :config + (progn + (defun inkel/magit-log-edit-mode-hook () + (flyspell-mode t) + (turn-on-auto-fill)) + (defadvice magit-status (around magit-fullscreen activate) + (window-configuration-to-register :magit-fullscreen) + ad-do-it + (delete-other-windows)))) diff --git a/.emacs.local.d/modes/mail-mode.el b/.emacs.local.d/modes/mail-mode.el new file mode 100644 index 0000000..c10cd81 --- /dev/null +++ b/.emacs.local.d/modes/mail-mode.el @@ -0,0 +1,10 @@ +(add-to-list 'auto-mode-alist '("/mutt" . mail-mode)) +(add-hook 'mail-mode-hook + (lambda () + (font-lock-add-keywords nil + '(("^[ \t]*>[ \t]*>[ \t]*>.*$" + (0 'compilation-error)) + ("^[ \t]*>[ \t]*>.*$" + (0 'compilation-column-number)) + ("^[ \t]*>.*$" + (0 'comint-highlight-prompt)))))) diff --git a/.emacs.local.d/modes/markdown.el b/.emacs.local.d/modes/markdown.el new file mode 100644 index 0000000..f035509 --- /dev/null +++ b/.emacs.local.d/modes/markdown.el @@ -0,0 +1,5 @@ +(autoload 'markdown-mode "markdown-mode.el" + "Major mode for editing Markdown files" t) + +(setq auto-mode-alist + (cons '("\\.mdwn" . markdown-mode) auto-mode-alist)) diff --git a/.emacs.local.d/modes/mu4e.el b/.emacs.local.d/modes/mu4e.el new file mode 100644 index 0000000..da8b7c8 --- /dev/null +++ b/.emacs.local.d/modes/mu4e.el @@ -0,0 +1,61 @@ +(require 'mu4e) + +;; sending mail +(setq message-send-mail-function 'message-send-mail-with-sendmail + sendmail-program "/home/lur/bin/te-msmtp" + user-mail-address "raul@thousandeyes.com" + user-full-name "Raúl Benencia") + +(setq mu4e-user-mail-address-list (list "raul@thousandeyes.com")) + +(setq message-kill-buffer-on-exit t) +;; Use fancy chars +(setq mu4e-use-fancy-chars t) +;; don't save message to Sent Messages, Gmail/IMAP takes care of this +(setq mu4e-sent-messages-behavior 'delete) +(setq mu4e-update-interval 60) ;; update every 5 minutes + +;; use 'fancy' non-ascii characters in various places in mu4e +;;(setq mu4e-use-fancy-chars t) + +(setq relevant-maildirs " (maildir:/INBOX OR maildir:/jira OR maildir:/news OR maildir:/git)") +(mu4e-alert-enable-notifications) +(mu4e-alert-set-default-style 'libnotify) +(setq mu4e-alert-interesting-mail-query + (concat "flag:unread" + " AND NOT flag:trashed" + " AND" relevant-maildirs)) + +(mu4e-alert-set-default-style 'libnotify) +;;(add-hook 'after-init-hook #'mu4e-alert-enable-notifications) +(add-hook 'after-init-hook #'mu4e-alert-enable-mode-line-display) + +(setq mu4e-bookmarks + `(,(make-mu4e-bookmark + :name "INBOX" + :query "maildir:/INBOX" + :key ?i) + ,(make-mu4e-bookmark + :name "Unread messages" + :query (concat "flag:unread AND NOT flag:trashed AND" relevant-maildirs) + :key ?u) + ,(make-mu4e-bookmark + :name "Today's messages" + :query (concat "date:today..now AND" relevant-maildirs) + :key ?t) + ,(make-mu4e-bookmark + :name "Last 7 days" + :query (concat "date:7d..now AND" relevant-maildirs) + :key ?w) + ,(make-mu4e-bookmark + :name "Today's unread logs " + :query (concat "date:today..now flag:unread AND NOT" relevant-maildirs) + :key ?l) + ,(make-mu4e-bookmark + :name "Today's logs " + :query (concat "date:today..now AND NOT maildir:/fim AND NOT" relevant-maildirs) + :key ?l)) +) + +;; (require 'mu4e-maildirs-extension) +;; (mu4e-maildirs-extension) diff --git a/.emacs.local.d/modes/notmuch.el b/.emacs.local.d/modes/notmuch.el new file mode 100644 index 0000000..f5096c8 --- /dev/null +++ b/.emacs.local.d/modes/notmuch.el @@ -0,0 +1,105 @@ +;; -------- +;; notmuch mode +;; -------- +(require 'notmuch) + +;; This should be upstream +(require 'notmuch-show) +(require 'notmuch-tag) +(defun notmuch-tree-show-message-in () + "Show the current message (in split-pane)." + (interactive) + (let ((id (notmuch-tree-get-message-id)) + (inhibit-read-only t) + buffer) + (when id + ;; We close and reopen the window to kill off un-needed buffers + ;; this might cause flickering but seems ok. + (notmuch-tree-close-message-window) + (setq notmuch-tree-message-window + (split-window-vertically (/ (window-height) 4))) + (with-selected-window notmuch-tree-message-window + ;; Since we are only displaying one message do not indent. + (let ((notmuch-show-indent-messages-width 0) + (notmuch-show-only-matching-messages t)) + (setq buffer (notmuch-show id)))) + ;; We need the `let' as notmuch-tree-message-window is buffer local. + (let ((window notmuch-tree-message-window)) + (with-current-buffer buffer + (setq notmuch-tree-message-window window) + (add-hook 'kill-buffer-hook 'notmuch-tree-message-window-kill-hook))) + (when notmuch-show-mark-read-tags + (notmuch-tree-tag-update-display notmuch-show-mark-read-tags) + (notmuch-tree-tag notmuch-show-mark-read-tags)) + (setq notmuch-tree-message-buffer buffer)))) +;; End upstream + +(define-key notmuch-show-mode-map "S" + (lambda () + "mark message as spam" + (interactive) + (notmuch-show-tag (list "+spam" "-inbox" "-unread")))) + +(define-key notmuch-search-mode-map "S" + (lambda (&optional beg end) + "mark thread as spam" + (interactive (notmuch-search-interactive-region)) + (notmuch-search-tag (list "+spam" "-inbox" "-unread") beg end))) + +(define-key notmuch-search-mode-map "R" + (lambda (&optional beg end) + "mark thread as read" + (interactive (notmuch-search-interactive-region)) + (notmuch-search-tag (list "-unread") beg end) + (notmuch-search-next-thread))) + +(define-key notmuch-search-mode-map (kbd "RET") + (lambda () + "Show the selected thread with notmuch-tree if it has more +than one email. Use notmuch-show otherwise." + (interactive) + (if (= (plist-get (notmuch-search-get-result) :total) 1) + (notmuch-search-show-thread) + (notmuch-tree (notmuch-search-find-thread-id) + notmuch-search-query-string + nil + (notmuch-prettify-subject (notmuch-search-find-subject)))))) + +(setq notmuch-folders '(("inbox" . "tag:inbox") + ("debian-announce" . "tag:inbox AND tag:debian-announce") + ("debian-devel" . "tag:inbox AND tag:debian-devel") + ("debian-haskell" . "tag:inbox AND tag:debian-haskell") + )) + +(defun color-inbox-if-unread () (interactive) + (save-excursion + (goto-char (point-min)) + (let ((cnt (car (process-lines "notmuch" "count" "tag:inbox and tag:unread")))) + (when (> (string-to-number cnt) 0) + (save-excursion + (when (search-forward "inbox" (point-max) t) + (let* ((overlays (overlays-in (match-beginning 0) (match-end 0))) + (overlay (car overlays))) + (when overlay + (overlay-put overlay 'face '((:inherit bold) (:foreground "green"))))))))))) +(add-hook 'notmuch-hello-refresh-hook 'color-inbox-if-unread) + +(defun my-notmuch-show-view-as-patch () + "View the the current message as a patch." + (interactive) + (let* ((id (notmuch-show-get-message-id)) + (subject (concat "Subject: " (notmuch-show-get-subject) "\n")) + (diff-default-read-only t) + (buf (get-buffer-create (concat "*notmuch-patch-" id "*"))) + (map (make-sparse-keymap))) + (define-key map "q" 'notmuch-kill-this-buffer) + (switch-to-buffer buf) + (let ((inhibit-read-only t)) + (erase-buffer) + (insert subject) + (insert (notmuch-get-bodypart-internal id 1 nil))) + (set-buffer-modified-p nil) + (diff-mode) + (lexical-let ((new-ro-bind (cons 'buffer-read-only map))) + (add-to-list 'minor-mode-overriding-map-alist new-ro-bind)) + (goto-char (point-min)))) diff --git a/.emacs.local.d/modes/org.el b/.emacs.local.d/modes/org.el new file mode 100644 index 0000000..f01718d --- /dev/null +++ b/.emacs.local.d/modes/org.el @@ -0,0 +1,676 @@ +;; Debian packages: elpa-org elpa-org-bullets + +(require 'org-capture) +(require 'org-protocol) +(require 'org-habit) +(require 'org-bullets) + +;; -------- +;; Org mode +;; -------- + +(require 'org) + +(setq org-agenda-files '("~/src/git/org/")) +(setq org-cycle-separator-lines 0) +(setq org-startup-indented t) +(setq org-hide-leading-stars nil) + +(setq org-startup-indented t + org-bullets-bullet-list '(" ") ;; no bullets, needs org-bullets package + org-ellipsis " " ;; folding symbol + org-pretty-entities t + org-hide-emphasis-markers t + ;; show actually italicized text instead of /italicized text/ + org-agenda-block-separator "" + org-fontify-whole-heading-line t + org-fontify-done-headline t + org-fontify-quote-and-verse-blocks t) + + +;; ORG BINDINGS ;; +(global-set-key (kbd "<f12>") 'org-agenda) +(global-set-key (kbd "<f5>") 'bh/org-todo) +(global-set-key (kbd "<f8>") 'org-clock-goto) + +(global-set-key (kbd "<f9> I") 'bh/punch-in) +(global-set-key (kbd "<f9> O") 'bh/punch-out) + + +;; ORG STATES ;; +(setq org-todo-keywords + (quote ((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)") + (sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)" "PHONE" "MEETING")))) + + +(setq org-use-fast-todo-selection t) + +(setq org-todo-state-tags-triggers + (quote (("CANCELLED" ("CANCELLED" . t)) + ("WAITING" ("WAITING" . t)) + ("HOLD" ("WAITING") ("HOLD" . t)) + (done ("WAITING") ("HOLD")) + ("TODO" ("WAITING") ("CANCELLED") ("HOLD")) + ("NEXT" ("WAITING") ("CANCELLED") ("HOLD")) + ("DONE" ("WAITING") ("CANCELLED") ("HOLD"))))) + +(setq org-enforce-todo-dependencies t) +(setq org-log-done (quote time)) +(setq org-log-redeadline (quote time)) +(setq org-log-reschedule (quote time)) + +;; CAPTURE ;; +(setq org-default-notes-file "~/src/git/org/refile.org") + +;; I use C-c c to start capture mode +(global-set-key (kbd "C-c c") 'org-capture) + +;; Capture templates for: TODO tasks, Notes, appointments, phone calls, meetings, and org-protocol +(setq org-capture-templates + (quote (("t" "todo" entry (file "~/src/git/org/refile.org") + "* TODO %?\n%U\n%a\n" :clock-in t :clock-resume t) + ("r" "respond" entry (file "~/src/git/org/refile.org") + "* NEXT Respond to %:from on %:subject\nSCHEDULED: %t\n%U\n%a\n" :clock-in t :clock-resume t :immediate-finish t) + ("n" "note" entry (file "~/src/git/org/refile.org") + "* %? :NOTE:\n%U\n%a\n" :clock-in t :clock-resume t) + ("j" "Journal" entry (file+datetree "~/src/git/org/diary.org") + "* %?\n%U\n" :clock-in t :clock-resume t) + ("w" "org-protocol" entry (file "~/src/git/org/refile.org") + "* TODO Review %c\n%U\n" :immediate-finish t) + ("m" "Meeting" entry (file "~/src/git/org/refile.org") + "* MEETING with %? :MEETING:\n%U" :clock-in t :clock-resume t) + ("p" "Phone call" entry (file "~/src/git/org/refile.org") + "* PHONE %? :PHONE:\n%U" :clock-in t :clock-resume t) + ("h" "Habit" entry (file "~/src/git/org/refile.org") + "* NEXT %?\n%U\n%a\nSCHEDULED: %(format-time-string \"%<<%Y-%m-%d %a .+1d/3d>>\")\n:PROPERTIES:\n:STYLE: habit\n:REPEAT_TO_STATE: NEXT\n:END:\n")))) + +;; Remove empty LOGBOOK drawers on clock out +(defun bh/remove-empty-drawer-on-clock-out () + (interactive) + (save-excursion + (beginning-of-line 0) + (org-remove-empty-drawer-at "LOGBOOK" (point)))) + +(add-hook 'org-clock-out-hook 'bh/remove-empty-drawer-on-clock-out 'append) + +(add-hook 'org-capture-mode-hook 'delete-other-windows) +(setq my-org-protocol-flag nil) +(defadvice org-capture-finalize (after delete-frame-at-end activate) + "Delete frame at remember finalization" + (progn (if my-org-protocol-flag (delete-frame)) + (setq my-org-protocol-flag nil))) + +(defadvice org-capture-kill (after delete-frame-at-end activate) + "Delete frame at remember abort" + (progn (if my-org-protocol-flag (delete-frame)) + (setq my-org-protocol-flag nil))) + +(defadvice org-protocol-capture (before set-org-protocol-flag activate) + (setq my-org-protocol-flag t)) + + +;; REFILE ;; + +; Targets include this file and any file contributing to the agenda - up to 9 levels deep +(setq org-refile-targets (quote ((nil :maxlevel . 9) + (org-agenda-files :maxlevel . 9)))) + +; Exclude DONE state tasks from refile targets +(defun bh/verify-refile-target () + "Exclude todo keywords with a done state from refile targets" + (not (member (nth 2 (org-heading-components)) org-done-keywords))) + +(setq org-refile-target-verify-function 'bh/verify-refile-target) + +; Targets complete directly with IDO +(setq org-outline-path-complete-in-steps nil) + +; Allow refile to create parent tasks with confirmation +(setq org-refile-allow-creating-parent-nodes (quote confirm)) + +;; AGENDA VIEW ;; + + +;; Do not dim blocked tasks +(setq org-agenda-dim-blocked-tasks nil) + +;; Compact the block agenda view +(setq org-agenda-compact-blocks t) + +;; Custom agenda command definitions +(setq org-agenda-custom-commands + (quote (("N" "Notes" tags "NOTE" + ((org-agenda-overriding-header "Notes") + (org-tags-match-list-sublevels t))) + ("h" "Habits" tags-todo "STYLE=\"habit\"" + ((org-agenda-overriding-header "Habits") + (org-agenda-sorting-strategy + '(todo-state-down effort-up category-keep)))) + (" " "Agenda" + ((agenda "" nil) + (tags "REFILE" + ((org-agenda-overriding-header "Tasks to Refile") + (org-tags-match-list-sublevels nil))) + (tags-todo "-CANCELLED/!" + ((org-agenda-overriding-header "Stuck Projects") + (org-agenda-skip-function 'bh/skip-non-stuck-projects) + (org-agenda-sorting-strategy + '(category-keep)))) + (tags-todo "-HOLD-CANCELLED/!" + ((org-agenda-overriding-header "Projects") + (org-agenda-skip-function 'bh/skip-non-projects) + (org-tags-match-list-sublevels 'indented) + (org-agenda-sorting-strategy + '(category-keep)))) + (tags-todo "-CANCELLED/!NEXT" + ((org-agenda-overriding-header (concat "Project Next Tasks" + (if bh/hide-scheduled-and-waiting-next-tasks + "" + " (including WAITING and SCHEDULED tasks)"))) + (org-agenda-skip-function 'bh/skip-projects-and-habits-and-single-tasks) + (org-tags-match-list-sublevels t) + (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks) + (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks) + (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks) + (org-agenda-sorting-strategy + '(todo-state-down effort-up category-keep)))) + (tags-todo "-REFILE-CANCELLED-WAITING-HOLD/!" + ((org-agenda-overriding-header (concat "Project Subtasks" + (if bh/hide-scheduled-and-waiting-next-tasks + "" + " (including WAITING and SCHEDULED tasks)"))) + (org-agenda-skip-function 'bh/skip-non-project-tasks) + (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks) + (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks) + (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks) + (org-agenda-sorting-strategy + '(category-keep)))) + (tags-todo "-REFILE-CANCELLED-WAITING-HOLD/!" + ((org-agenda-overriding-header (concat "Standalone Tasks" + (if bh/hide-scheduled-and-waiting-next-tasks + "" + " (including WAITING and SCHEDULED tasks)"))) + (org-agenda-skip-function 'bh/skip-project-tasks) + (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks) + (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks) + (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks) + (org-agenda-sorting-strategy + '(category-keep)))) + (tags-todo "-CANCELLED+WAITING|HOLD/!" + ((org-agenda-overriding-header (concat "Waiting and Postponed Tasks" + (if bh/hide-scheduled-and-waiting-next-tasks + "" + " (including WAITING and SCHEDULED tasks)"))) + (org-agenda-skip-function 'bh/skip-non-tasks) + (org-tags-match-list-sublevels nil) + (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks) + (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks))) + (tags "-REFILE/" + ((org-agenda-overriding-header "Tasks to Archive") + (org-agenda-skip-function 'bh/skip-non-archivable-tasks) + (org-tags-match-list-sublevels nil)))) + nil)))) + +(defun bh/skip-non-archivable-tasks () + "Skip trees that are not available for archiving" + (save-restriction + (widen) + ;; Consider only tasks with done todo headings as archivable candidates + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))) + (subtree-end (save-excursion (org-end-of-subtree t)))) + (if (member (org-get-todo-state) org-todo-keywords-1) + (if (member (org-get-todo-state) org-done-keywords) + nil + (or subtree-end (point-max))) + next-headline)))) + +(defun bh/org-auto-exclude-function (tag) + "Automatic task exclusion in the agenda with / RET" + (and (cond + ((string= tag "hold") + t)) + (concat "-" tag))) + +(setq org-agenda-auto-exclude-function 'bh/org-auto-exclude-function) +(setq org-agenda-span 'day) +(setq org-deadline-warning-days 30) + + +(defun bh/is-project-p () + "Any task with a todo keyword subtask" + (save-restriction + (widen) + (let ((has-subtask) + (subtree-end (save-excursion (org-end-of-subtree t))) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (forward-line 1) + (while (and (not has-subtask) + (< (point) subtree-end) + (re-search-forward "^\*+ " subtree-end t)) + (when (member (org-get-todo-state) org-todo-keywords-1) + (setq has-subtask t)))) + (and is-a-task has-subtask)))) + +(defun bh/is-project-subtree-p () + "Any task with a todo keyword that is in a project subtree. +Callers of this function already widen the buffer view." + (let ((task (save-excursion (org-back-to-heading 'invisible-ok) + (point)))) + (save-excursion + (bh/find-project-task) + (if (equal (point) task) + nil + t)))) + +(defun bh/is-task-p () + "Any task with a todo keyword and no subtask" + (save-restriction + (widen) + (let ((has-subtask) + (subtree-end (save-excursion (org-end-of-subtree t))) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (forward-line 1) + (while (and (not has-subtask) + (< (point) subtree-end) + (re-search-forward "^\*+ " subtree-end t)) + (when (member (org-get-todo-state) org-todo-keywords-1) + (setq has-subtask t)))) + (and is-a-task (not has-subtask))))) + +(defun bh/is-subproject-p () + "Any task which is a subtask of another project" + (let ((is-subproject) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (while (and (not is-subproject) (org-up-heading-safe)) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq is-subproject t)))) + (and is-a-task is-subproject))) + +(defun bh/list-sublevels-for-projects-indented () + "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks. + This is normally used by skipping functions where this variable is already local to the agenda." + (if (marker-buffer org-agenda-restrict-begin) + (setq org-tags-match-list-sublevels 'indented) + (setq org-tags-match-list-sublevels nil)) + nil) + +(defun bh/list-sublevels-for-projects () + "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks. + This is normally used by skipping functions where this variable is already local to the agenda." + (if (marker-buffer org-agenda-restrict-begin) + (setq org-tags-match-list-sublevels t) + (setq org-tags-match-list-sublevels nil)) + nil) + +(defvar bh/hide-scheduled-and-waiting-next-tasks t) + +(defun bh/toggle-next-task-display () + (interactive) + (setq bh/hide-scheduled-and-waiting-next-tasks (not bh/hide-scheduled-and-waiting-next-tasks)) + (when (equal major-mode 'org-agenda-mode) + (org-agenda-redo)) + (message "%s WAITING and SCHEDULED NEXT Tasks" (if bh/hide-scheduled-and-waiting-next-tasks "Hide" "Show"))) + +(defun bh/skip-stuck-projects () + "Skip trees that are not stuck projects" + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (if (bh/is-project-p) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (has-next )) + (save-excursion + (forward-line 1) + (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t)) + (unless (member "WAITING" (org-get-tags-at)) + (setq has-next t)))) + (if has-next + nil + next-headline)) ; a stuck project, has subtasks but no next task + nil)))) + +(defun bh/skip-non-stuck-projects () + "Skip trees that are not stuck projects" + ;; (bh/list-sublevels-for-projects-indented) + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (if (bh/is-project-p) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (has-next )) + (save-excursion + (forward-line 1) + (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t)) + (unless (member "WAITING" (org-get-tags-at)) + (setq has-next t)))) + (if has-next + next-headline + nil)) ; a stuck project, has subtasks but no next task + next-headline)))) + +(defun bh/skip-non-projects () + "Skip trees that are not projects" + ;; (bh/list-sublevels-for-projects-indented) + (if (save-excursion (bh/skip-non-stuck-projects)) + (save-restriction + (widen) + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + nil) + ((and (bh/is-project-subtree-p) (not (bh/is-task-p))) + nil) + (t + subtree-end)))) + (save-excursion (org-end-of-subtree t)))) + +(defun bh/skip-non-tasks () + "Show non-project tasks. +Skip project and sub-project tasks, habits, and project related tasks." + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (cond + ((bh/is-task-p) + nil) + (t + next-headline))))) + +(defun bh/skip-project-trees-and-habits () + "Skip trees that are projects" + (save-restriction + (widen) + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + subtree-end) + ((org-is-habit-p) + subtree-end) + (t + nil))))) + +(defun bh/skip-projects-and-habits-and-single-tasks () + "Skip trees that are projects, tasks that are habits, single non-project tasks" + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (cond + ((org-is-habit-p) + next-headline) + ((and bh/hide-scheduled-and-waiting-next-tasks + (member "WAITING" (org-get-tags-at))) + next-headline) + ((bh/is-project-p) + next-headline) + ((and (bh/is-task-p) (not (bh/is-project-subtree-p))) + next-headline) + (t + nil))))) + +(defun bh/skip-project-tasks-maybe () + "Show tasks related to the current restriction. +When restricted to a project, skip project and sub project tasks, habits, NEXT tasks, and loose tasks. +When not restricted, skip project and sub-project tasks, habits, and project related tasks." + (save-restriction + (widen) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (next-headline (save-excursion (or (outline-next-heading) (point-max)))) + (limit-to-project (marker-buffer org-agenda-restrict-begin))) + (cond + ((bh/is-project-p) + next-headline) + ((org-is-habit-p) + subtree-end) + ((and (not limit-to-project) + (bh/is-project-subtree-p)) + subtree-end) + ((and limit-to-project + (bh/is-project-subtree-p) + (member (org-get-todo-state) (list "NEXT"))) + subtree-end) + (t + nil))))) + +(defun bh/skip-project-tasks () + "Show non-project tasks. +Skip project and sub-project tasks, habits, and project related tasks." + (save-restriction + (widen) + (let* ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + subtree-end) + ((org-is-habit-p) + subtree-end) + ((bh/is-project-subtree-p) + subtree-end) + (t + nil))))) + +(defun bh/skip-non-project-tasks () + "Show project tasks. +Skip project and sub-project tasks, habits, and loose non-project tasks." + (save-restriction + (widen) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (cond + ((bh/is-project-p) + next-headline) + ((org-is-habit-p) + subtree-end) + ((and (bh/is-project-subtree-p) + (member (org-get-todo-state) (list "NEXT"))) + subtree-end) + ((not (bh/is-project-subtree-p)) + subtree-end) + (t + nil))))) + +(defun bh/skip-projects-and-habits () + "Skip trees that are projects and tasks that are habits" + (save-restriction + (widen) + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + subtree-end) + ((org-is-habit-p) + subtree-end) + (t + nil))))) + +(defun bh/skip-non-subprojects () + "Skip trees that are not projects" + (let ((next-headline (save-excursion (outline-next-heading)))) + (if (bh/is-subproject-p) + nil + next-headline))) + +;; CLOCKING ;; +;; Resume clocking task when emacs is restarted +(org-clock-persistence-insinuate) +;; +;; Show lot of clocking history so it's easy to pick items off the C-F11 list +(setq org-clock-history-length 23) +;; Resume clocking task on clock-in if the clock is open +(setq org-clock-in-resume t) +;; Change tasks to NEXT when clocking in +(setq org-clock-in-switch-to-state 'bh/clock-in-to-next) +;; Separate drawers for clocking and logs +(setq org-drawers (quote ("PROPERTIES" "LOGBOOK"))) +;; Save clock data and state changes and notes in the LOGBOOK drawer +(setq org-clock-into-drawer t) +;; Sometimes I change tasks I'm clocking quickly - this removes clocked tasks with 0:00 duration +(setq org-clock-out-remove-zero-time-clocks t) +;; Clock out when moving task to a done state +(setq org-clock-out-when-done t) +;; Save the running clock and all clock history when exiting Emacs, load it on startup +(setq org-clock-persist t) +;; Do not prompt to resume an active clock +(setq org-clock-persist-query-resume nil) +;; Enable auto clock resolution for finding open clocks +(setq org-clock-auto-clock-resolution (quote when-no-clock-is-running)) +;; Include current clocking task in clock reports +(setq org-clock-report-include-clocking-task t) + + +(setq bh/keep-clock-running nil) + +(defun bh/clock-in-to-next (kw) + "Switch a task from TODO to NEXT when clocking in. +Skips capture tasks, projects, and subprojects. +Switch projects and subprojects from NEXT back to TODO" + (when (not (and (boundp 'org-capture-mode) org-capture-mode)) + (cond + ((and (member (org-get-todo-state) (list "TODO")) + (bh/is-task-p)) + "NEXT") + ((and (member (org-get-todo-state) (list "NEXT")) + (bh/is-project-p)) + "TODO")))) + +(defun bh/find-project-task () + "Move point to the parent (project) task if any" + (save-restriction + (widen) + (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point)))) + (while (org-up-heading-safe) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq parent-task (point)))) + (goto-char parent-task) + parent-task))) + +(defun bh/punch-in (arg) + "Start continuous clocking and set the default task to the +selected task. If no task is selected set the Organization task +as the default task." + (interactive "p") + (setq bh/keep-clock-running t) + (if (equal major-mode 'org-agenda-mode) + ;; + ;; We're in the agenda + ;; + (let* ((marker (org-get-at-bol 'org-hd-marker)) + (tags (org-with-point-at marker (org-get-tags-at)))) + (if (and (eq arg 4) tags) + (org-agenda-clock-in '(16)) + (bh/clock-in-organization-task-as-default))) + ;; + ;; We are not in the agenda + ;; + (save-restriction + (widen) + ; Find the tags on the current task + (if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4)) + (org-clock-in '(16)) + (bh/clock-in-organization-task-as-default))))) + +(defun bh/punch-out () + (interactive) + (setq bh/keep-clock-running nil) + (when (org-clock-is-active) + (org-clock-out)) + (org-agenda-remove-restriction-lock)) + +(defun bh/clock-in-default-task () + (save-excursion + (org-with-point-at org-clock-default-task + (org-clock-in)))) + +(defun bh/clock-in-parent-task () + "Move point to the parent (project) task if any and clock in" + (let ((parent-task)) + (save-excursion + (save-restriction + (widen) + (while (and (not parent-task) (org-up-heading-safe)) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq parent-task (point)))) + (if parent-task + (org-with-point-at parent-task + (org-clock-in)) + (when bh/keep-clock-running + (bh/clock-in-default-task))))))) + +(defvar bh/organization-task-id "eb155a82-92b2-4f25-a3c6-0304591af2f9") + +(defun bh/clock-in-organization-task-as-default () + (interactive) + (org-with-point-at (org-id-find bh/organization-task-id 'marker) + (org-clock-in '(16)))) + +(defun bh/clock-out-maybe () + (when (and bh/keep-clock-running + (not org-clock-clocking-in) + (marker-buffer org-clock-default-task) + (not org-clock-resolving-clocks-due-to-idleness)) + (bh/clock-in-parent-task))) + +(add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append) + + +;; ORG REPORTS ;; +; Set default column view headings: Task Effort Clock_Summary +(setq org-columns-default-format "%80ITEM(Task) %10Effort(Effort){:} %10CLOCKSUM") + +(defun my-org-clocktable-indent-string (level) + (if (= level 1) + "" + (let ((str "^")) + (while (> level 2) + (setq level (1- level) + str (concat str "--"))) + (concat str "-> ")))) + +(advice-add 'org-clocktable-indent-string :override #'my-org-clocktable-indent-string) + +(setq org-clock-clocktable-default-properties '(:maxlevel 4 :scope file :formula %)) + +; global Effort estimate values +; global STYLE property values for completion +(setq org-global-properties (quote (("Effort_ALL" . "0:15 0:30 0:45 1:00 2:00 3:00 4:00 5:00 6:00 0:00") + ("STYLE_ALL" . "habit")))) + +;; Agenda log mode items to display (closed and state changes by default) +(setq org-agenda-log-mode-items (quote (closed state))) + +;; TAGS ;; +; Tags with fast selection keys +(setq org-tag-alist (quote ((:startgroup) + ("@errand" . ?e) + ("@office" . ?o) + ("@home" . ?H) + (:endgroup) + ("WAITING" . ?w) + ("HOLD" . ?h) + ("PERSONAL" . ?P) + ("WORK" . ?W) + ("ORG" . ?O) + ("NOTE" . ?n) + ("CANCELLED" . ?c) + ("FLAGGED" . ??)))) + +; Allow setting single tags without the menu +(setq org-fast-tag-selection-single-key (quote expert)) + +; For tag searches ignore tasks with scheduled and deadline dates +(setq org-agenda-tags-todo-honor-ignore-options t) + +(require 'ox-extra) +(ox-extras-activate '(ignore-headlines)) + +(add-to-list 'org-latex-classes + '("memoir-article" + "\\documentclass[11pt,oneside,article]{memoir} + [PACKAGES] + \\usepackage{memoir-article-style} + [NO-DEFAULT-PACKAGES]" + ("\\section{%s}" . "\\section*{%s}") + ("\\subsection{%s}" . "\\subsection*{%s}") + ("\\subsubsection{%s}" . "\\subsubsection*{%s}") + ("\\paragraph{%s}" . "\\paragraph*{%s}") + ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))) diff --git a/.emacs.local.d/modes/package.el b/.emacs.local.d/modes/package.el new file mode 100644 index 0000000..acbfc21 --- /dev/null +++ b/.emacs.local.d/modes/package.el @@ -0,0 +1,7 @@ +(require 'package) ;; You might already have this line + +(add-to-list 'package-archives + '("melpa-stable" . "https://stable.melpa.org/packages/") t) + +(package-initialize) ;; You might already have this line + diff --git a/.emacs.local.d/modes/projectile.el b/.emacs.local.d/modes/projectile.el new file mode 100644 index 0000000..f5f8097 --- /dev/null +++ b/.emacs.local.d/modes/projectile.el @@ -0,0 +1,3 @@ +;; Debian packages: elpa-projectile + +(projectile-global-mode) diff --git a/.emacs.local.d/modes/smex.el b/.emacs.local.d/modes/smex.el new file mode 100644 index 0000000..d724452 --- /dev/null +++ b/.emacs.local.d/modes/smex.el @@ -0,0 +1,15 @@ +;; Debian packages: elpa-smex +;; Elpa packages: helm-smex + +(require 'smex) ; Not needed if you use package.el +(smex-initialize) ; Can be omitted. This might cause a (minimal) delay + ; when Smex is auto-initialized on its first run. + +(require 'helm-smex) +(global-set-key [remap execute-extended-command] #'helm-smex) +(global-set-key (kbd "M-X") #'helm-smex-major-mode-commands) + +;; (global-set-key (kbd "M-x") 'smex) +;; (global-set-key (kbd "M-X") 'smex-major-mode-commands) +;; This is your old M-x. +(global-set-key (kbd "C-c C-c M-x") 'execute-extended-command) diff --git a/.emacs.local.d/modes/sml.el b/.emacs.local.d/modes/sml.el new file mode 100644 index 0000000..247d9b2 --- /dev/null +++ b/.emacs.local.d/modes/sml.el @@ -0,0 +1,7 @@ +;; Debian packages: elpa-smart-mode-line elpa-smart-mode-line-powerline-theme + +(use-package smart-mode-line + :ensure t + :config + (setq sml/theme 'respectful) + (sml/setup)) diff --git a/.emacs.local.d/modes/sublimity.el b/.emacs.local.d/modes/sublimity.el new file mode 100644 index 0000000..a702a22 --- /dev/null +++ b/.emacs.local.d/modes/sublimity.el @@ -0,0 +1,8 @@ +;; Elpa packages: sublimity + +(require 'sublimity) +(require 'sublimity-scroll) +;(require 'sublimity-map) +;(require 'sublimity-attractive) + +(sublimity-mode 1) diff --git a/.emacs.local.d/modes/themes.el b/.emacs.local.d/modes/themes.el new file mode 100644 index 0000000..c6e9bc2 --- /dev/null +++ b/.emacs.local.d/modes/themes.el @@ -0,0 +1,6 @@ +;; Debian packages: elpa-clues-theme elpa-monokai-theme elpa-smart-mode-line-powerline-theme elpa-solarized-theme elpa-zenburn-theme +;; Elpa packages: atom-one-dark + +(load-theme 'clues t) + + diff --git a/.emacs.local.d/modes/whitespace.el b/.emacs.local.d/modes/whitespace.el new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.emacs.local.d/modes/whitespace.el @@ -0,0 +1 @@ + diff --git a/.emacs.local.d/modes/writeroom.el b/.emacs.local.d/modes/writeroom.el new file mode 100644 index 0000000..ba700a1 --- /dev/null +++ b/.emacs.local.d/modes/writeroom.el @@ -0,0 +1,27 @@ +(use-package writeroom-mode + :defer t + :config + (setq writeroom-width 140 + writeroom-mode-line nil + writeroom-global-effects '(writeroom-set-bottom-divider-width + writeroom-set-internal-border-width + (lambda (arg) + (let ((langs '("python" + "emacs-lisp" + "common-lisp" + "js" + "ruby"))) + (cond + ((= arg 1) + (progn + (setq org-src-block-faces + (mapcar (lambda (lang) (list lang '(:family "Source Code Pro" :height 0.8))) langs)) + (normal-mode) + (variable-pitch-mode))) + ((= arg -1) + (progn + (setq org-src-block-faces + (mapcar (lambda (lang) (list lang '(:family "Source Code Pro" :height 1.0))) langs)) + (normal-mode) + (variable-pitch-mode) +(variable-pitch-mode))))))))) diff --git a/README.md b/README.md new file mode 100644 index 0000000..e13418a --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Installing Emacs dependencies + +Install the following dependencies before attempting to run my emacs +configuration. It should work for Debian 10 or later: + + sudo apt-get install $(grep -ri 'Debian packages:' .emacs .emacs.local.d/ | awk -F: '{print $3}' | tr '\n' ' ') + +If Debian packages aren't available, we use Elpa packages. At some +point I should create Debian packages for these. + + emacs-pkg-install $(grep -ri 'Elpa packages:' .emacs .emacs.local.d/ | awk -F: '{print $3}' | tr '\n' ' ') + +The first time you open emacs after installing these packages, a bunch +of warnings will be printed. You can dismiss them; they'll only appear +on the first run. |