From 22b0a6b56ffe8c423047ee25440dce79a990610c Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Thu, 12 Jan 2023 16:39:38 -0600 Subject: Make it work for ... $work --- lisp/+org-capture.el | 49 +++++++ lisp/acdw-mail.el | 233 +++++++++++++++++++++++++++++++ lisp/acdw-org.el | 377 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 659 insertions(+) create mode 100644 lisp/+org-capture.el create mode 100644 lisp/acdw-mail.el create mode 100644 lisp/acdw-org.el (limited to 'lisp') diff --git a/lisp/+org-capture.el b/lisp/+org-capture.el new file mode 100644 index 0000000..2f7bf6a --- /dev/null +++ b/lisp/+org-capture.el @@ -0,0 +1,49 @@ +;;; +org-capture.el -*- lexical-binding: t; -*- + +;;; Code: + +(require 'cl-lib) +;; Don't /require/ `org-capture', since that'll pull in all of `org' and that'll +;; take a minute. Just let the compiler know that this variable exists. +(defvar org-capture-templates nil) + +;; https://github.com/cadadr/configuration/blob/39813a771286e542af3aa333172858532c3bb257/emacs.d/gk/gk-org.el#L1573 +(defun +org-capture-template-define (description &rest args) + "Define a capture template. +Creates a list and adds it to `org-capture-templates', if it's +not already there. ARGS is a plist, which in addition to the +additional options `org-capture-templates' accepts (which see), +takes the following and puts them in the right spot: `:keys', +`:description', `:type', `:target', and `:template'." + (declare (indent 1)) + (let* ((keys (plist-get args :keys)) + (type (plist-get args :type)) + (target (plist-get args :target)) + (template (plist-get args :template)) + (template-value (append + (list description) + (when (or type target template) + (list (or type 'entry) target template)) + (cl-loop for i from 0 below (length args) by 2 + unless (member (nth i args) + '(:keys :description :type + :target :template)) + append (list (nth i args) + (plist-get args (nth i + args))))))) + ;; The only way I know how to do this properly (add a value to the end of + ;; the list, if it exists; otherwise update it) is to do this weird if-setf + ;; dance. + (if (seq-find (lambda (el) (equal (car el) keys)) + org-capture-templates) + (setf (alist-get keys org-capture-templates nil nil #'equal) + template-value) + (setf org-capture-templates + (append org-capture-templates + (list (cons keys template-value))))) + ;; Regardless of what we do, return the new value of + ;; `org-capture-templates'. + org-capture-templates)) + +(provide '+org-capture) +;;; +org-capture.el diff --git a/lisp/acdw-mail.el b/lisp/acdw-mail.el new file mode 100644 index 0000000..d0ee28e --- /dev/null +++ b/lisp/acdw-mail.el @@ -0,0 +1,233 @@ +;;; acdw-mail.el --- My email configuration -*- lexical-binding: t; -*- + +;;; Code: + +(require 'cl-lib) + +;;; Variables + +(defcustom +message-send-dispatch-rules nil + "Alist to set variables based on the current from address." + :group 'message + :type '(alist :key-type (string :tag "From address") + :value-type (alist :tag "Rules" + :key-type (symbol :tag "Variable") + :value-type (sexp :tag "Value")))) + +(defcustom +notmuch-spam-tags '("+spam") + "List of tag changes to apply when marking a thread as spam." + :group 'notmuch + :type '(repeat string)) + +;;; Functions + +(defun +message-send-set-variables () + "Set variables for `message-send' depending on the From: header. +Useful in `message-send-hook'." + (let ((from (message-fetch-field "from"))) + (cl-loop for (var . val) in (cl-loop for (address . bindings) + in +message-send-dispatch-rules + if (string-match-p address from) + return bindings) + do (set (make-local-variable var) val)))) + +;; Thanks to Alex Schroeder! +;; https://www.emacswiki.org/emacs/Change_Signature_Dynamically +(defun +message-check-for-signature-change (&rest ignore) + "Check for a change in the To: or Cc: fields" + (when (and (message--in-tocc-p) + (not (buffer-narrowed-p))) + (save-excursion + (goto-char (point-max)) + (let ((end (point))) + (when (re-search-backward message-signature-separator nil t) + (delete-region (1- (match-beginning 0)) end))) + (message-insert-signature)))) + +(defun +message-signature-setup () + (make-local-variable 'after-change-functions) + (push '+message-check-for-signature-change after-change-functions)) + +(defun +notmuch-field-match-p (field regexp) + "Return whether message FIELD matches REGEXP." + (string-match-p regexp (or (message-fetch-field field) ""))) + +(defun +notmuch-query-concat (&rest queries) + "Concatenate `notmuch' QUERIES with AND." + (mapconcat #'identity queries " AND ")) + +(defun +notmuch-goto (&optional prefix) + "Perform a saved `notmuch' search. +Without a PREFIX argument, perform the first search in +`notmuch-saved-searches'. With a single PREFIX argument +(\\[universal-argument]), prompt the user as to which saved +search to perform. With two PREFIX arguments, prompt the user +for a free-form search. With any other PREFIX argument, open +`notmuch-hello'." + (interactive "P") + (pcase prefix + ('nil (notmuch-search (plist-get (car notmuch-saved-searches) :query))) + ('(4) (notmuch-search + (plist-get (cl-find (completing-read "Saved search: " + (mapcar (lambda (elt) + (plist-get elt :name)) + notmuch-saved-searches)) + :key (lambda (elt) (plist-get elt :name)) + :test #'equal) + :query))) + ('(16) (notmuch-search)) + (_ (notmuch-hello)))) + +(defun +notmuch-search-mark-spam (&optional ham start end) + "Mark the current thread or region as spam. +That is, add the tags in `+notmuch-spam-tags' to the message. +With an optional HAM argument (interactively, +\\[universal-argument]), mark the message as not-spam, or ham, by +reversing the tag changes." + (interactive (cons current-prefix-arg (notmuch-interactive-region))) + (when +notmuch-spam-tags + (notmuch-search-tag (notmuch-tag-change-list +notmuch-spam-tags ham) + start end)) + (when (eq start end) + (notmuch-search-next-thread))) + +(defun +notmuch-tree-mark-spam (&optional ham) + "Mark the current message as spam. +That is, add the tags in `+notmuch-spam-tags' to the message. +With an optional HAM argument (interactively, +\\[universal-argument]), mark the message as not-spam, or ham, by +reversing the tag changes." + (interactive (cons current-prefix-arg (notmuch-interactive-region))) + (when +notmuch-spam-tags + (notmuch-tree-tag (notmuch-tag-change-list +notmuch-spam-tags ham))) + (notmuch-tree-next-matching-message)) + +(defun +notmuch-define-saved-search (name key search-type &rest queries) + "Wrapper to ease `notmuch-saved-searches' defining. +NAME, KEY, and SEARCH-TYPE are paired with the corresponding keywords in +`notmuch-saved-searches', which see. QUERIES are all concatenated together with +AND. If QUERIES is prepended with more keyword arguments, those are added to +the saved search as well." + (declare (indent 3)) + (let (extra-keywords) + (while (keywordp (car queries)) + (push (cadr queries) extra-keywords) + (push (car queries) extra-keywords) + (setf queries (cddr queries))) + (add-to-list 'notmuch-saved-searches + (append (list :name name + :key key + :search-type search-type + :query (apply #'+notmuch-query-concat queries)) + (reverse extra-keywords)) + :append + (lambda (a b) + (equal (plist-get a :name) + (plist-get b :name)))))) + +;;; Packages + +(use-package bbdb + :ensure t + :config + (setopt bbdb-complete-mail-allow-cycling t + bbdb-file (private/ "bbdb")) + (add-hook 'custom-allowed-after-load-hook + (defun bbdb@after-custom () + (require 'bbdb) + (require 'bbdb-message) + (bbdb-initialize 'message)))) + +(use-package bbdb-vcard + :ensure t + :after bbdb) + +(use-package notmuch + :when (executable-find "notmuch") + :load-path "~/usr/share/emacs/site-lisp/" + :defer t + :commands (notmuch-mua-new-mail + notmuch-search + notmuch-hello) + :preface (defdir notmuch/ (sync/ "emacs/notmuch/") + "Notmuch configuration directory." + :makedir) + :config + ;; Options + (setopt notmuch-init-file (notmuch/ "notmuch-init.el" t) + notmuch-address-save-filename (notmuch/ "addresses" t) + notmuch-address-use-company (featurep 'company) + notmuch-search-oldest-first nil + notmuch-archive-tags '("-inbox" "-unread") + notmuch-draft-tags '("+draft" "-inbox" "-unread") + +notmuch-spam-tags '("+spam" "+Spam") + mail-user-agent 'notmuch-user-agent + message-mail-user-agent t + notmuch-show-indent-content nil + message-kill-buffer-on-exit t + message-auto-save-directory nil + send-mail-function #'sendmail-send-it + mail-specify-envelope-from t + message-sendmail-envelope-from 'header + message-envelope-from 'header + notmuch-saved-searches nil) + ;; Key bindings + (keymap-global-set "C-c m" #'nomtuch-mua-new-mail) + (keymap-global-set "C-c n" #'+notmuch-goto) + (keymap-set notmuch-search-mode-map "!" #'+notmuch-search-mark-spam) + (keymap-set notmuch-search-mode-map "RET" #'notmuch-search-show-thread) + (keymap-set notmuch-search-mode-map "M-RET" #'notmuch-tree-from-search-thread) + (keymap-set notmuch-tree-mode-map "!" #'+notmuch-tree-mark-spam) + ;; Saved searches + (+notmuch-define-saved-search "inbox+unread" "m" 'tree + "tag:inbox" "tag:unread" "NOT tag:Spam") + (+notmuch-define-saved-search "inbox" "i" 'tree + "tag:inbox" "NOT tag:Spam") + (+notmuch-define-saved-search "lists+unread" "l" 'tree + "tag:/List/" "tag:unread") + (+notmuch-define-saved-search "lists" "L" 'tree + "tag:/List/") + (+notmuch-define-saved-search "unread" "u" 'tree + "tag:unread" "NOT tag:Spam") + (+notmuch-define-saved-search "flagged" "f" 'tree + "tag:flagged") + (+notmuch-define-saved-search "sent" "t" 'tree + "tag:sent") + (+notmuch-define-saved-search "drafts" "d" 'tree + "tag:draft") + (+notmuch-define-saved-search "all mail" "a" 'tree "*") + ;; Hooks and advice + (add-hook 'message-send-hook #'+message-send-dispatch-rules) + (add-hook 'message-setup-hook #'+message-signature-setup) + (autoload 'visual-fill-column-mode "visual-fill-column" nil t) + (add-hook 'notmuch-message-mode-hook #'visual-fill-column-mode) + (add-hook 'notmuch-show-mode-hook #'visual-fill-column-mode) + + (define-advice notmuch-address-selection-function + (:override (prompt collection _) no-initial-input) + "Call `completing-read' with `notmuch-address-history'. +This version doesn't add any initial-input." + (completing-read prompt collection nil nil nil 'notmuch-address-history)) + + (add-to-list 'notmuch-message-headers "List-Post" :append #'equal) + (define-advice notmuch-mua-new-reply (:around (orig &rest r) list-aware) + "Make `notmuch-mua-new-reply' list-aware." + (let ((ml (notmuch-show-get-header :List-Post))) + (apply orig r) + (when ml + (with-buffer-modified-unmodified + (message-remove-header "To") + (message-add-header + (format "To: %s" (replace-regexp-in-string "" "\\1" + ml))) + (messgage-goto-body))))) + + (define-advice notmuch-tag (:filter-args (args) trim) + "Trim whitespace from ends of tags." + (list (car args) (mapcar #'string-trim (cadr args)))) + ;; Load init file + ;; (load notmuch-init-file 'noerror) + ) + +(provide 'acdw-mail) +;;; acdw-mail.el ends here diff --git a/lisp/acdw-org.el b/lisp/acdw-org.el new file mode 100644 index 0000000..8a63d04 --- /dev/null +++ b/lisp/acdw-org.el @@ -0,0 +1,377 @@ +;;; acdw-org.el --- My org customizations -*- lexical-binding: t; -*- + +;;; Code: + +(require 'cl-lib) + +;;; Variables + +(defcustom org-agenda-skip-file-regexp nil + "Files matching this regexp are removed from `org-agenda-files'." + :group 'org-agenda + :type 'regexp) + +;;; Functions + + +;;; DWIM + +;; https://github.com/alphapapa/unpackaged.el, +;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/ +(defun +org-return-dwim (&optional arg) + "A helpful replacement for `org-return'. +When called interactively with \\[universal-argument], call `org-return' +itself. Other values of ARG will call `newline' with that ARG." + (interactive "P") + ;; Auto-fill if enabled + (when auto-fill-function + (dolist (func (ensure-list auto-fill-function)) + (funcall func))) + (cl-letf* ((el (org-element-at-point)) + ((symbol-function 'el-child-of) + (lambda (&rest types) + (org-element-lineage el types t)))) + (cond ; Figure out what we're going to do + (arg ; Handle prefix ARG + (pcase arg + ('(4) (org-return t nil t)) + (_ (newline arg t)))) + ((and org-return-follows-link ; Open a link + (el-child-of 'link)) + (org-open-at-point-global)) + ((org-at-heading-p) ; Open a paragraph after a heading + (let ((heading-start (org-entry-beginning-position))) + (goto-char (org-entry-end-position)) + (cond ((and (org-at-heading-p) ; Entry is only a heading + (= heading-start (org-entry-beginning-position))) + (end-of-line) + (newline 2)) + (:else ; Entry is more than a heading + (forward-line -1) + (end-of-line) + (when (org-at-heading-p) + ;; Open a paragraph + (forward-line) + (newline) + (forward-line -1)) + (while (not (looking-back "\\(?:[[:blank:]]?\n\\)\\{3\\}" nil)) + (newline)) + (forward-line -1))))) + ((org-at-item-checkbox-p) ; Insert a new checkbox item + (end-of-line) + (org-insert-todo-heading nil)) + ((org-in-item-p) ; Insert a new list item + (let* ((context (org-element-context el)) + (first-item-p (eq 'plain-list (car context))) + (itemp (eq 'item (car context))) + (emptyp (or + ;; This (regular) list item is empty + (eq (org-element-property :contents-begin context) + (org-element-property :contents-end context)) + ;; This (definition) list item is empty + (looking-at " *::"))) + (item-child-p (el-child-of 'item))) + (cond ((and itemp emptyp) + ;; This test has to be here even though it's the same as the + ;; :else clause, because an item that's empty will also satisfy + ;; the next clause. + (delete-region (line-beginning-position) (line-end-position)) + (newline)) + ((or first-item-p + (and itemp (not emptyp)) + item-child-p) + (org-end-of-item) + (org-insert-item)) + (:else + (delete-region (line-beginning-position) (line-end-position)) + (newline))))) + ((and (fboundp 'org-inlinetask-in-task-p) ; Just return for inline tasks + (org-inlinetask-in-task-p)) + (org-return)) + ((org-at-table-p) ; Insert a new table row + (cond ((save-excursion ; Empty row: end the table + (beginning-of-line) + (cl-loop with end = (line-end-position) + for cell = (org-element-table-cell-parser) + always (eq (org-element-property :contents-begin cell) + (org-element-property :contents-end cell)) + while (re-search-forward "|" end t))) + (delete-region (line-beginning-position) (line-end-position)) + (org-return)) + (:else ; Non-empty row + (org-return)))) + (:else ; Something else + (org-return))))) + +(defun +org-table-copy-down|+org-return-dwim (&optional n) + "Call `org-table-copy-down' or `+org-return' depending on context." + (interactive "P") + (if (org-table-check-inside-data-field 'noerror) + (org-table-copy-down (or n 1)) + (+org-return-dwim n))) + + +;;; Buffer view cleanup + +(defun +org-hide-drawers-except-point () + "Hide all drawers except for the one point is in." + ;; Most of this bit is taken from `org-fold--hide-drawers'. + (let ((pt (point)) + (begin (point-min)) + (end (point-max))) + (save-excursion + (goto-char begin) + (while (and (< (point) end) + (re-search-forward org-drawer-regexp end t)) + (if (org-fold-folded-p nil 'drawer) + (goto-char (org-fold-next-folding-state-change 'drawer nil end)) + (let* ((drawer (org-element-at-point)) + (type (org-element-type drawer)) + (el-begin (org-element-property :begin drawer)) + (el-end (org-element-property :end drawer))) + (when (memq type '(drawer property-drawer)) + (org-fold-hide-drawer-toggle + (if (< el-begin pt el-end) 'off 'on) + nil drawer) + (goto-char el-end)))))))) + + +;;; Copy rich text to the keyboard +;; Thanks to Oleh Krehel: +;; https://emacs.stackexchange.com/questions/54292/copy-results-of-org-export-directly-to-clipboard +;; So. Emacs can't do this itself because it doesn't support sending clipboard +;; or selection contents as text/html. We have to use xclip instead. +(defun org-to-html-to-clipboard (&rest org-export-args) + "Export current org buffer to HTML, then copy it to the clipboard. +ORG-EXPORT-ARGS are passed to `org-export-to-file'." + (let ((f (make-temp-file "org-html-export"))) + (apply #'org-export-to-file 'html f org-export-args) + (start-process "xclip" " *xclip*" + "xclip" "-verbose" "-i" f + "-t" "text/html" "-selection" "clipboard") + (message "HTML pasted to clipboard."))) + +(defun org-subtree-to-html-to-clipboard () + "Export current subtree to HTML." + (interactive) + (org-to-html-to-clipboard nil :subtree)) + + +;;; Prompting + +(defun +org-prompt-for-property (property &optional clipboardp insert list) + "Prompt for PROPERTY and return a properly-formatted string. +Pre-fill the input with clipboard contents if they match CLIPBOARDP. If +CLIPBOARDP is nil or missing, don't pre-fill. + +If INSERT is non-nil, insert the property into the property +drawer of the current org tree. + +If LIST is non-nil, return the result as a list instead of a string." + (let* ((kill (current-kill 0)) + (value (read-string (concat property ": ") + (when (and clipboardp + (or (eq clipboardp t) + (funcall clipboardp kill))) + kill)))) + (when insert + (org-set-property property value)) + (if list + (list property value) + (format ":%s: %s" property value)))) + +(defun +org-prompt-tags (&optional prompt global) + (let* ((buffer (org-capture-get :buffer)) + (file (buffer-file-name (or (buffer-base-buffer buffer) buffer))) + (org-last-tags-completion-table + (org-global-tags-completion-table + (if global (org-agenda-files) (list file)))) + (org-add-colon-after-tag-completion t) + (ins (mapconcat + #'identity + (let ((crm-separator "[ \t]*:[ \t]*")) + (completing-read-multiple + (or prompt "Tags: ") + org-last-tags-completion-table nil nil nil + 'org-tags-history)) + ":"))) + (when (org-string-nw-p ins) + (prog1 (concat + (unless (eq (char-before) ?:) ":") + ins + (unless (eq (char-after) ?:) ":")) + (when (org-at-heading-p) (org-align-tags)))))) + + +;;; Faces + +(defface org-bold '((t (:weight bold))) + "Bold face in `org-mode' documents.") + +(defface org-italic '((t (:slant italic))) + "Italic face in `org-mode' documents.") + +(defface org-underline '((t (:underline t))) + "Underline face in `org-mode' documents.") + +(defface org-strikethrough '((t (:strike-through t))) + "Strike-through face for `org-mode' documents.") + + +;;; Packages + +(use-package org + :defer t + :config + ;; Options + (setopt org-adapt-indentation nil + org-auto-align-tags t + org-archive-mark-done t + org-fold-catch-invisible-edits 'show-and-error + org-clock-clocked-in-display 'mode-line + org-clock-string-limit 7 ; just the clock bit + org-clock-persist nil + org-confirm-babel-evaluate nil + org-cycle-separator-lines 0 + org-deadline-warning-days 0 + org-directory (sync/ "org/" t) + org-ellipsis (or (bound-and-true-p truncate-string-ellipsis) "…") + org-emphasis-alist '(("*" org-bold) + ("/" org-italic) + ("_" org-underline) + ("=" org-verbatim) + ("~" org-code) + ("+" org-strikethrough)) + org-fontify-done-headline t + org-fontify-quote-and-verse-blocks t + org-fontify-whole-heading-line t + org-hide-emphasis-markers t + org-html-coding-system 'utf-8-unix + org-image-actual-width (list (* (window-font-width) + (- fill-column 8))) + org-imenu-depth 3 + org-indent-indentation-per-level 0 + org-indent-mode-turns-on-hiding-stars nil + org-insert-heading-respect-content t + org-list-demote-modify-bullet '(("-" . "+") + ("+" . "-")) + org-log-done 'time + org-log-into-drawer t + org-num-skip-commented t + org-num-skip-unnumbered t + org-num-skip-footnotes t + org-outline-path-complete-in-steps nil + org-pretty-entities t + org-pretty-entities-include-sub-superscripts nil + org-refile-targets '((nil . (:maxlevel . 2)) + (org-agenda-files . (:maxlevel . 1))) + org-refile-use-outline-path 'file + org-special-ctrl-a/e t + org-special-ctrl-k t + org-src-fontify-natively t + org-src-tab-acts-natively t + org-src-window-setup 'current-window + org-startup-truncated nil + org-startup-with-inline-images t + org-tags-column 0 + org-todo-keywords '((sequence "TODO(t)" "WAIT(w@/!)" "ONGOING(o@)" + "|" "DONE(d!)" "ASSIGNED(a@/!)") + (sequence "|" "CANCELED(k@)") + (sequence "MEETING(m)")) + org-use-fast-todo-selection 'auto + org-use-speed-commands t) + ;; Keys + (keymap-set org-mode-map "C-M-k" #'kill-paragraph) + (keymap-set org-mode-map "C-M-t" #'transpose-paragraphs) + (keymap-set org-mode-map "RET" #'+org-return-dwim) + (keymap-set org-mode-map "S-" #'+org-table-copy-down|+org-return-dwim) + ;; Hooks + (add-hook 'org-mode-hook #'variable-pitch-mode) + (autoload 'visual-fill-column-mode "visual-fill-column" nil t) + (add-hook 'org-mode-hook #'visual-fill-column-mode) + (add-hook 'org-mode-hook #'turn-off-auto-fill) + (add-hook 'org-mode-hook #'org-indent-mode) + (add-hook 'org-mode-hook #'abbrev-mode) + (add-hook 'org-mode-hook (defun before-save@org-mode () + (org-align-tags 'all) + (+org-hide-drawers-except-point)))) + +(use-package org-agenda + :bind (("C-c a" . org-agenda)) + :config + (setopt org-agenda-skip-deadline-if-done t + org-agenda-skip-scheduled-if-done t + org-agenda-span 10 + org-agenda-block-separator ?─ + org-agenda-time-grid + '((daily today require-timed) + (800 1000 1200 1400 1600 1800 2000) + " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄") + org-agenda-current-time-string + "← now ─────────────────────────────────────────────────" + org-agenda-include-diary nil ; I use the org-diary features + org-agenda-todo-ignore-deadlines 'near + org-agenda-todo-ignore-scheduled 'future + org-agenda-include-deadlines t + org-deadline-warning-days 0 + org-agenda-show-future-repeats 'next + org-agenda-window-setup 'current-window + org-agenda-skip-file-regexp "sync-conflict") + ;; Hooks and advice + (add-hook 'org-agenda-mode-hook #'truncate-lines-local-mode) + (add-hook 'org-agenda-mode-hook #'hl-line-mode) + (add-hook 'org-agenda-after-show-hook #'org-narrow-to-subtree) + (define-advice org-agenda-files (:filter-return (files) skip-regexp) + "Filter some files from `org-agenda'." + (when org-agenda-skip-file-regexp + (setq files + (cl-remove-if (lambda (file) + (string-match-p org-agenda-skip-file-regexp + file)) + files))) + files)) + +(use-package org-capture + :bind (("C-c c" . org-capture))) + +(use-package ol ; org-link + :after org + :preface + (defmacro +org-link-define-type (type args &rest body) + "Define an org link TYPE. +A function named `+org-link-TYPE-open' will be created, with ARGS +as its arguments and BODY as its body. BODY can be blank, in +which case the user will be messaged (This is a good do-nothing +effect for exporting link types)." + (declare (indent 2) + (doc-string 3) + (debug (sexp sexp def-body))) + (let ((fn (intern (format "+org-link-%s-open" type))) + (body (or body `((message ,(format "%S: %%S" type) + ,(car args))))) + (type-string (format "%S" type))) + `(prog1 + (defun ,fn ,args ,@body) + (org-link-set-parameters ,type-string :follow #',fn)))) + :config + (+org-link-define-type sms (number _)) + (+org-link-define-type tel (number _))) + +(use-package ox ; org-export + :after org + :config + (require 'ox-md) + (setopt org-export-coding-system 'utf-8-unix + org-export-headline-levels 8 + org-export-with-drawers nil + org-export-with-section-numbers nil + org-export-with-smart-quotes t + org-export-with-sub-superscripts t + org-export-with-toc nil)) + +(use-package org-word-count + :load-path "~/src/emacs/org-word-count/" + :hook org-mode-hook) + +(provide 'acdw-org) +;;; acdw-org.el ends here -- cgit 1.4.1-21-gabe81