org-mode: major rewrite + refactor

This commit is contained in:
Henrik Lissner 2017-01-03 22:50:52 -05:00
parent 77b1a93231
commit dbacc26e11
7 changed files with 387 additions and 516 deletions

View file

@ -105,5 +105,43 @@
(goto-char (cdr location))
(message "Unable to find location in file"))))))
(add-hook! org-load
;; Ensures org-src-edit yields control of its buffer to shackle.
(defun org-src-switch-to-buffer (buffer context)
(pop-to-buffer buffer))
;; And these for org-todo, org-link and org-agenda
(defun org-pop-to-buffer-same-window (&optional buffer-or-name norecord label)
"Pop to buffer specified by BUFFER-OR-NAME in the selected window."
(display-buffer buffer-or-name))
(defun org-switch-to-buffer-other-window (&rest args)
(car-safe
(mapc (lambda (b)
(let ((buf (if (stringp b) (get-buffer-create b) b)))
(pop-to-buffer buf t t)))
args)))
(defun doom/org-agenda-quit ()
"Necessary to finagle org-agenda into shackle popups and behave properly on quit."
(interactive)
(if org-agenda-columns-active
(org-columns-quit)
(let ((buf (current-buffer)))
(and (not (eq org-agenda-window-setup 'current-window))
(not (one-window-p))
(delete-window))
(kill-buffer buf)
(setq org-agenda-archives-mode nil
org-agenda-buffer nil))))
(after! org-agenda
(map! :map org-agenda-mode-map
:e "<escape>" 'doom/org-agenda-quit
:e "ESC" 'doom/org-agenda-quit
:e [escape] 'doom/org-agenda-quit
"q" 'doom/org-agenda-quit
"Q" 'doom/org-agenda-quit)))
(provide 'core-popup)
;;; core-popup.el ends here

View file

@ -79,8 +79,6 @@
;;; Org
module-org ; for organized fearless leader
module-org-notes ; org-mode as a (modern?) note-taking platform
module-org-crm ; org-mode for business management
;;; Custom/experimental modules
custom-db ; emacs as a db browser/client

View file

@ -1,112 +0,0 @@
;;; defuns-org-notes.el
;;;###autoload
(defun doom/org ()
(interactive)
(find-file (f-expand "inbox.org" org-directory)))
;;;###autoload
(defun doom/org-notebook-new ()
(interactive)
(projectile-invalidate-cache nil)
(let* ((default-directory org-directory)
(dir (projectile-complete-dir))
(doom-org-quicknote-dir dir))
(when dir
(doom/org-notebook-quick-note))))
;;;###autoload
(defun doom/org-notebook-quick-note ()
(interactive)
(let (text)
(when (evil-visual-state-p)
(setq text (buffer-substring-no-properties evil-visual-beginning evil-visual-end)))
(switch-to-buffer (generate-new-buffer "*quick-note*"))
(setq default-directory doom-org-quicknote-dir)
(erase-buffer)
(insert text)))
;;;###autoload
(defun doom/org-download-dnd (uri action)
(if (eq major-mode 'org-mode)
(doom:org-attach uri)
(let ((dnd-protocol-alist
(rassq-delete-all 'doom/org-download-dnd (copy-alist dnd-protocol-alist))))
(dnd-handle-one-url nil action uri))))
;;;###autoload (autoload 'doom:org-attach "defuns-org-notes" nil t)
(evil-define-command doom:org-attach (&optional uri)
(interactive "<a>")
(unless (eq major-mode 'org-mode)
(user-error "Not in an org-mode buffer"))
(if uri
(let* ((rel-path (org-download--fullname uri))
(new-path (f-expand rel-path))
(image-p (image-type-from-file-name uri)))
(cond ((string-match-p (concat "^" (regexp-opt '("http" "https" "nfs" "ftp" "file")) ":/") uri)
(url-copy-file uri new-path))
(t (copy-file uri new-path)))
(unless new-path
(user-error "No file was provided"))
(if (evil-visual-state-p)
(org-insert-link nil (format "./%s" rel-path)
(concat (buffer-substring-no-properties (region-beginning) (region-end))
" " (doom--org-attach-icon rel-path)))
(insert (if image-p
(format "[[./%s]] " rel-path)
(format "%s [[./%s][%s]] "
(doom--org-attach-icon rel-path)
rel-path (f-filename rel-path)))))
(when (string-match-p (regexp-opt '("jpg" "jpeg" "gif" "png")) (f-ext rel-path))
(org-redisplay-inline-images)))
(let ((default-directory ".attach/"))
(if (file-exists-p default-directory)
(call-interactively 'find-file)
(user-error "No attachments")))))
(defun doom--org-attach-icon (path)
(char-to-string (pcase (downcase (f-ext path))
("jpg" ?) ("jpeg" ?) ("png" ?) ("gif" ?)
("pdf" ?)
("ppt" ?) ("pptx" ?)
("xls" ?) ("xlsx" ?)
("doc" ?) ("docx" ?)
("ogg" ?) ("mp3" ?) ("wav" ?)
("mp4" ?) ("mov" ?) ("avi" ?)
("zip" ?) ("gz" ?) ("tar" ?) ("7z" ?) ("rar" ?)
(_ ?))))
;;;###autoload
(defun doom/org-cleanup-attachments ()
;; "Deletes any attachments that are no longer present in the org-mode buffer."
(let* ((attachments-local (doom-org-attachments))
(attachments (f-entries org-attach-directory))
(to-delete (-difference attachments-local attachments)))
;; TODO
to-delete))
(defun doom-org-attachments ()
(unless (eq major-mode 'org-mode)
(user-error "Not an org buffer"))
(org-save-outline-visibility nil
(let ((attachments '())
element
file)
(when (and (f-dir? org-attach-directory)
(> (length (f-glob (concat (f-slash org-attach-directory) "*"))) 0))
(save-excursion
(goto-char (point-min))
(while (progn (org-next-link) (not org-link-search-failed))
(setq element (org-element-lineage (org-element-context) '(link) t))
(when element
(setq file (expand-file-name (org-element-property :path element)))
(when (and (string= (org-element-property :type element) "file")
(string= (concat (f-base (f-dirname file)) "/") org-attach-directory)
(file-exists-p file))
(push file attachments))))))
(-distinct attachments))))
(provide 'defuns-org-notes)
;;; defuns-org-notes.el ends here

View file

@ -1,31 +1,5 @@
;;; defuns-org.el
;;;###autoload
(defun doom/org-find-file-in-notes ()
(interactive)
(let ((default-directory (f-slash org-directory)))
(projectile-find-file)))
;;;###autoload
(defun doom/org-find-file ()
(interactive)
(let ((default-directory (f-slash org-directory)))
(counsel-find-file)))
;;;###autoload
(defun doom/org-find-exported-file ()
(interactive)
(let ((default-directory (f-slash doom-org-export-directory)))
(counsel-find-file)))
;;;###autoload
(defun doom/org-get-property (name)
(interactive)
(save-excursion
(goto-char 1)
(re-search-forward (format "^#\\+%s:[ \t]*\\([^\n]+\\)" (upcase name)) nil t)
(buffer-substring-no-properties (match-beginning 1) (match-end 1))))
;;;###autoload
(defun doom/org-indent ()
"Indent the current item (header or item). Otherwise, forward to
@ -63,7 +37,8 @@
;;;###autoload
(defun doom/org-dedent-or-prev-field ()
"Depending on the context either dedent the current item or go the previous table field."
"Depending on the context either dedent the current item or go the previous
table field."
(interactive)
(call-interactively (if (org-at-table-p) 'org-table-previous-field 'doom/org-dedent)))
@ -139,6 +114,9 @@ wrong places)."
;;;###autoload
(defun doom/org-dwim-at-point ()
"Do-what-I-mean at point. This includes following timestamp links, aligning
tables, toggling checkboxes/todos, executing babel blocks, previewing latex
fragments, opening links, or refreshing images."
(interactive)
(let* ((scroll-pt (window-start))
(context (org-element-context))
@ -190,6 +168,7 @@ wrong places)."
;;;###autoload
(defun doom/org-refresh-inline-images ()
"Refresh image previews in the current heading/tree."
(interactive)
(if (> (length org-inline-image-overlays) 0)
(org-remove-inline-images)
@ -202,9 +181,9 @@ wrong places)."
(line-end-position)
(save-excursion (org-end-of-subtree) (point))))))
;; Formatting shortcuts
;;;###autoload
(defun doom/org-surround (delim)
"Surround the cursor (or selected region) with DELIM."
(if (region-active-p)
(save-excursion
(goto-char (region-beginning))
@ -214,89 +193,10 @@ wrong places)."
(insert delim)
(save-excursion (insert delim))))
;;;###autoload
(defun doom/org-word-count (beg end &optional count-footnotes?)
"Report the number of words in the Org mode buffer or selected region.
Ignores:
- comments
- tables
- source code blocks (#+BEGIN_SRC ... #+END_SRC, and inline blocks)
- hyperlinks (but does count words in hyperlink descriptions)
- tags, priorities, and TODO keywords in headers
- sections tagged as 'not for export'.
The text of footnote definitions is ignored, unless the optional argument
COUNT-FOOTNOTES? is non-nil."
(interactive "r")
(unless mark-active
(setf beg (point-min)
end (point-max)))
(let ((wc 0))
(save-excursion
(goto-char beg)
(while (< (point) end)
(cond
;; Ignore comments.
((or (org-at-comment-p) (org-at-table-p))
nil)
;; Ignore hyperlinks. But if link has a description, count
;; the words within the description.
((looking-at org-bracket-link-analytic-regexp)
(when (match-string-no-properties 5)
(let ((desc (match-string-no-properties 5)))
(save-match-data
(incf wc (length (remove "" (org-split-string
desc "\\W")))))))
(goto-char (match-end 0)))
((looking-at org-any-link-re)
(goto-char (match-end 0)))
;; Ignore source code blocks.
((org-between-regexps-p "^#\\+BEGIN_SRC\\W" "^#\\+END_SRC\\W")
nil)
;; Ignore inline source blocks, counting them as 1 word.
((save-excursion
(backward-char)
(looking-at org-babel-inline-src-block-regexp))
(goto-char (match-end 0))
(setf wc (+ 2 wc)))
;; Ignore footnotes.
((and (not count-footnotes?)
(or (org-footnote-at-definition-p)
(org-footnote-at-reference-p)))
nil)
(t
(let ((contexts (org-context)))
(cond
;; Ignore tags and TODO keywords, etc.
((or (assoc :todo-keyword contexts)
(assoc :priority contexts)
(assoc :keyword contexts)
(assoc :checkbox contexts))
nil)
;; Ignore sections marked with tags that are
;; excluded from export.
((assoc :tags contexts)
(if (intersection (org-get-tags-at) org-export-exclude-tags
:test 'equal)
(org-forward-same-level 1)
nil))
(t
(incf wc))))))
(re-search-forward "\\w+\\W*")))
(message (format "%d words in %s." wc
(if mark-active "region" "buffer")))))
;;;###autoload
(defun doom/org-remove-link ()
"Replace an org link by its description or if empty its address"
(interactive)
(if (org-in-regexp org-bracket-link-regexp 1)
(let ((remove (list (match-beginning 0) (match-end 0)))
(description (if (match-end 3)
(org-match-string-no-properties 3)
(org-match-string-no-properties 1))))
(apply 'delete-region remove)
(insert description))))
;;
;; tables
;;
;;;###autoload
(defun doom/org-table-next-row ()
@ -305,8 +205,25 @@ COUNT-FOOTNOTES? is non-nil."
;;;###autoload
(defun doom/org-table-previous-row ()
"Go to the previous row (same column) in the current table. Before doing so,
re-align the table if necessary. (Necessary because org-mode has a
`org-table-next-row', but not `org-table-previous-row')"
(interactive)
(if (org-at-table-p) (doom--org-table-previous-row) (org-up-element)))
(if (org-at-table-p)
(progn
(org-table-maybe-eval-formula)
(org-table-maybe-recalculate-line)
(if (and org-table-automatic-realign
org-table-may-need-update)
(org-table-align))
(let ((col (org-table-current-column)))
(beginning-of-line 0)
(when (or (not (org-at-table-p)) (org-at-table-hline-p))
(beginning-of-line))
(org-table-goto-column col)
(skip-chars-backward "^|\n\r")
(when (org-looking-at-p " ") (forward-char))))
(org-up-element)))
;;;###autoload
(defun doom/org-table-next-field ()
@ -327,9 +244,7 @@ COUNT-FOOTNOTES? is non-nil."
;;;###autoload
(defun doom/org-table-prepend-field-or-shift-left ()
(interactive)
(if (org-at-table-p)
(org-shiftmetaright)
(org-shiftmetaleft)))
(if (org-at-table-p) (org-shiftmetaright) (org-shiftmetaleft)))
;;;###autoload
(defun doom/org-table-append-row-or-shift-down ()
@ -344,23 +259,10 @@ COUNT-FOOTNOTES? is non-nil."
(org-shiftmetadown)
(org-shiftmetaup)))
(defun doom--org-table-previous-row ()
"Go to the previous row (same column) in the current table. Before doing so,
re-align the table if necessary. (Necessary because org-mode has a
`org-table-next-row', but not `org-table-previous-row')"
(interactive)
(org-table-maybe-eval-formula)
(org-table-maybe-recalculate-line)
(if (and org-table-automatic-realign
org-table-may-need-update)
(org-table-align))
(let ((col (org-table-current-column)))
(beginning-of-line 0)
(when (or (not (org-at-table-p)) (org-at-table-hline-p))
(beginning-of-line))
(org-table-goto-column col)
(skip-chars-backward "^|\n\r")
(when (org-looking-at-p " ") (forward-char))))
;;
;; Links
;;
;;;###autoload (autoload 'doom:org-link "defuns-org" nil t)
(evil-define-command doom:org-link (link)
@ -373,22 +275,134 @@ re-align the table if necessary. (Necessary because org-mode has a
(org-insert-link nil link (when (and beg end) (buffer-substring-no-properties beg end)))))
;;;###autoload
(defun doom/sp-org-skip-asterisk (ms mb me)
(or (and (= (line-beginning-position) mb)
(eq 32 (char-after (1+ mb))))
(and (= (1+ (line-beginning-position)) me)
(eq 32 (char-after me)))))
(defun doom/org-remove-link ()
"Replace an org link by its description or if empty its address"
(interactive)
(if (org-in-regexp org-bracket-link-regexp 1)
(let ((remove (list (match-beginning 0) (match-end 0)))
(description (if (match-end 3)
(org-match-string-no-properties 3)
(org-match-string-no-properties 1))))
(apply 'delete-region remove)
(insert description))))
;;
;; org-capture
;;
;;;###autoload
(defun doom/org-capture (&optional template string)
"Run `org-capture' in a new, disposable popup frame."
(interactive)
(let ((org-capture-entry (org-capture-select-template template)))
(cond ((equal org-capture-entry "C")
(find-file (expand-file-name "module-org-notes.el" doom-modules-dir))
(re-search-forward "^\\s-+(setq org-capture-templates" (point-max) t)
(recenter))
((not (equal org-capture-entry "q"))
(let ((frame (make-frame '((name . "org-capture") (height . 15) (width . 80)))))
(with-selected-frame frame
(if string
(org-capture-string string)
(org-capture))))))))
;;;###autoload (autoload 'doom:org-capture "defuns-org" nil t)
(evil-define-operator doom:org-capture (&optional beg end bang)
"Send a selection to `org-capture'."
"Send a selection to `doom/org-capture'."
:move-point nil
:type inclusive
(interactive "<r><!>")
(org-capture-string
(doom/org-capture
(if (and (evil-visual-state-p) beg end)
(buffer-substring beg end)
"")))
;;
;; attachments
;;
;;;###autoload (autoload 'doom:org-attach "defuns-org-notes" nil t)
(evil-define-command doom:org-attach (&optional uri)
(interactive "<a>")
(unless (eq major-mode 'org-mode)
(user-error "Not in an org-mode buffer"))
(if uri
(let* ((rel-path (org-download--fullname uri))
(new-path (f-expand rel-path))
(image-p (image-type-from-file-name uri)))
(cond ((string-match-p (concat "^" (regexp-opt '("http" "https" "nfs" "ftp" "file")) ":/") uri)
(url-copy-file uri new-path))
(t (copy-file uri new-path)))
(unless new-path
(user-error "No file was provided"))
(if (evil-visual-state-p)
(org-insert-link nil (format "./%s" rel-path)
(concat (buffer-substring-no-properties (region-beginning) (region-end))
" " (doom--org-attach-icon rel-path)))
(insert (if image-p
(format "[[./%s]] " rel-path)
(format "%s [[./%s][%s]] "
(doom--org-attach-icon rel-path)
rel-path (f-filename rel-path)))))
(when (string-match-p (regexp-opt '("jpg" "jpeg" "gif" "png")) (f-ext rel-path))
(org-redisplay-inline-images)))
(let ((default-directory ".attach/"))
(if (file-exists-p default-directory)
(call-interactively 'find-file)
(user-error "No attachments")))))
(defun doom--org-attach-icon (path)
(char-to-string (pcase (downcase (f-ext path))
("jpg" ?) ("jpeg" ?) ("png" ?) ("gif" ?)
("pdf" ?)
("ppt" ?) ("pptx" ?)
("xls" ?) ("xlsx" ?)
("doc" ?) ("docx" ?)
("ogg" ?) ("mp3" ?) ("wav" ?)
("mp4" ?) ("mov" ?) ("avi" ?)
("zip" ?) ("gz" ?) ("tar" ?) ("7z" ?) ("rar" ?)
(_ ?))))
;;;###autoload
(defun doom/org-cleanup-attachments ()
;; "Deletes any attachments that are no longer present in the org-mode buffer."
(let* ((attachments-local (doom-org-attachments))
(attachments (f-entries org-attach-directory))
(to-delete (-difference attachments-local attachments)))
;; TODO
to-delete))
(defun doom-org-attachments ()
(unless (eq major-mode 'org-mode)
(user-error "Not an org buffer"))
(org-save-outline-visibility nil
(let ((attachments '())
element
file)
(when (and (f-dir? org-attach-directory)
(> (length (f-glob (concat (f-slash org-attach-directory) "*"))) 0))
(save-excursion
(goto-char (point-min))
(while (progn (org-next-link) (not org-link-search-failed))
(setq element (org-element-lineage (org-element-context) '(link) t))
(when element
(setq file (expand-file-name (org-element-property :path element)))
(when (and (string= (org-element-property :type element) "file")
(string= (concat (f-base (f-dirname file)) "/") org-attach-directory)
(file-exists-p file))
(push file attachments))))))
(-distinct attachments))))
;;;###autoload
(defun doom/org-download-dnd (uri action)
(if (eq major-mode 'org-mode)
(doom:org-attach uri)
(let ((dnd-protocol-alist
(rassq-delete-all 'doom/org-download-dnd (copy-alist dnd-protocol-alist))))
(dnd-handle-one-url nil action uri))))
(provide 'defuns-org)
;;; defuns-org.el ends here

View file

@ -1,28 +0,0 @@
;;; module-org-crm.el
(add-hook 'org-load-hook 'doom|org-crm-init)
(defvar org-directory-crm (expand-file-name "/crm/" doom-org-dir)
"")
(defun doom|org-crm-init ()
;; (define-org-section! work "Work")
;; (define-org-section! project "Projects")
;; (define-org-section! contact "Contacts")
)
;;
(defun doom/org-crm-new (type name)
)
(defun doom/org-crm-visit-project (&optional name)
)
(defun doom/org-crm-visit-contact (&optional name)
)
(defun doom/org-crm-visit-invoice (&optional name)
)
(provide 'module-org-crm)
;;; module-org-crm.el ends here

View file

@ -1,139 +0,0 @@
;;; module-org-notes.el
;; This transforms Emacs+org-mode into a notebook application with:
;; + Custom links for class notes
;; + Shortcuts for searching org files
;; + Shortcuts for creating new notes (there's org-capture, but this is suited to my
;; workflow)
;; + A simpler attachment system (with auto-deleting support) and drag-and-drop
;; for images and documents into org files
;; + A pandoc-powered export system
(add-hook 'org-load-hook 'doom|org-notebook-init t)
(add-hook 'org-load-hook 'doom|org-attach-init t)
(add-hook 'org-load-hook 'doom|org-export-init t)
(defvar doom-org-notes-dir (f-expand "notes" doom-org-dir)
"The directory where the notes are kept")
(defvar doom-org-quicknote-dir (f-expand "inbox" doom-org-notes-dir)
"")
(defvar doom-org-attachment-dir ".attach/"
"Where to store attachments (relative to current org file).")
;; Keep track of attachments
(defvar-local doom-org-attachments-list '()
"A list of attachments for the current buffer")
;; Tell helm to ignore these directories
(after! helm
(mapc (lambda (r) (add-to-list 'helm-boring-file-regexp-list r))
(list "\\.attach$" "\\.export$")))
;;
(defun doom|org-notebook-init ()
(setq org-default-notes-file (f-expand "inbox.org" doom-org-notes-dir)
org-attach-directory doom-org-attachment-dir
org-export-directory (f-expand ".export" org-directory)
org-capture-templates
'(;; TODO: New Note (note)
;; TODO: New Task (todo)
;; TODO: New vocabulary word
("c" "Changelog" entry
(file+headline (f-expand "CHANGELOG.org" (doom/project-root)) "Unreleased")
"* %?")
;; ("p" "Project Notes" entry
;; (file+headline org-default-notes-file "Inbox")
;; "* %u %?\n%i" :prepend t)
;; ("m" "Major-mode Notes" entry
;; (file+headline org-default-notes-file "Inbox")
;; "* %u %?\n%i" :prepend t)
;; ("n" "Notes" entry
;; (file+headline org-default-notes-file "Inbox")
;; "* %u %?\n%i" :prepend t)
;; ("v" "Vocab" entry
;; (file+headline (concat org-directory "topics/vocab.org") "Unsorted")
;; "** %i%?\n")
)))
;; I don't like Org's attachment system. So I replaced it with my own, which stores
;; attachments in a global org .attach directory. It also implements drag-and-drop
;; file support and attachment icons. It also treats images specially.
;;
;; To clean up unreferenced attachments, call `doom/org-cleanup-attachments'
(defun doom|org-attach-init ()
;; FIXME Use all-the-icons
;; (doom-fix-unicode '("FontAwesome" 13) ? ? ? ? ? ? ? ?)
;; Drag-and-drop support
(require 'org-download)
(setq-default org-download-image-dir doom-org-attachment-dir
org-download-heading-lvl nil
org-download-timestamp "_%Y%m%d_%H%M%S")
(when IS-MAC
(setq org-download-screenshot-method "screencapture -i %s"))
;; Write download paths relative to current file
(defun org-download--dir-2 () nil)
(defun doom*org-download--fullname (path)
(f-relative path (f-dirname (buffer-file-name))))
(advice-add 'org-download--fullname :filter-return 'doom*org-download--fullname)
;; Add another drag-and-drop handler that will handle anything but image files
(setq dnd-protocol-alist `(("^\\(https?\\|ftp\\|file\\|nfs\\):\\(//\\)?" . doom/org-download-dnd)
,@dnd-protocol-alist)))
;;
(defun doom|org-export-init ()
"Set up my own exporting system."
(setq org-export-backends '(ascii html latex md)
org-export-with-toc t
org-export-with-author t)
;; (require 'ox-pandoc)
;; (setq org-pandoc-options '((standalone . t) (mathjax . t) (parse-raw . t)))
;; Export to a central directory (why isn't this easier?)
(unless (file-directory-p org-export-directory)
(mkdir org-export-directory))
(defun doom*org-export-output-file-name (args)
(unless (nth 2 args)
(setq args (append args (list org-export-directory))))
args)
(advice-add 'org-export-output-file-name :filter-args 'doom*org-export-output-file-name))
;; TODO
;; (defvar doom-org-tags '())
;; (defun doom|org-tag-init ()
;; (async-start
;; `(lambda ()
;; (let* ((default-directory (doom/project-root))
;; (data (s-trim (shell-command-to-string "ag --nocolor --nonumbers '^#\\+TAGS:'")))
;; (alist '()))
;; (unless (zerop (length data))
;; (mapc (lambda (l)
;; (let* ((parts (s-split ":" l))
;; (file (car parts))
;; (tags (s-trim (nth 2 parts))))
;; (mapc (lambda (tag)
;; (setq tag (substring tag 1))
;; (unless (assoc tag alist)
;; (push (cons tag (list)) alist))
;; (push file (cdr (assoc tag alist))))
;; (s-split " " tags))))
;; (s-lines data))
;; alist)))
;; (lambda (_)
;; )))
;;
(provide 'module-org-notes)
;;; module-org-notes.el ends here

View file

@ -1,5 +1,15 @@
;;; module-org.el --- -*- no-byte-compile: t; -*-
;; A few things you can expect
;; + `org-capture' in a popup frame (can be invoked from outside emacs too)
;; + A simpler attachment system (with auto-deleting support) and
;; drag-and-drop for images and documents into org files
;; + Exported files are put in a centralized location (see
;; `org-export-directory')
;; + TODO Custom links for class notes
;; + TODO An org-mode based CRM (including invoicing and pdf exporting) (see custom-crm)
;; + TODO A tag-based file browser reminiscient of Evernote and Quiver (there's neotree too!)
(define-minor-mode evil-org-mode
"Evil-mode bindings for org-mode."
:init-value nil
@ -8,14 +18,32 @@
:group 'evil-org)
(add-hook 'org-load-hook 'doom|org-init t)
(add-hook 'org-load-hook 'doom|org-keybinds t)
(add-hook 'org-load-hook 'doom|org-init-attach t)
(add-hook 'org-load-hook 'doom|org-init-export t)
(add-hook 'org-load-hook 'doom|org-init-capture t)
(add-hook 'org-load-hook 'doom|org-hacks t)
(add-hook 'org-mode-hook 'doom|org-hook)
;; Custom variables
(defvaralias 'org-directory 'doom-org-dir)
(defvar doom-org-notes-dir (f-expand "notes" doom-org-dir)
"The directory where the notes are kept")
(defvar doom-org-quicknote-dir (f-expand "inbox" doom-org-dir)
"The directory to store quick notes produced by `doom:org-capture' (individual org files)")
(defvar doom-org-attachment-dir ".attach/"
"Where to store attachments (relative to current org file).")
(defvar-local doom-org-attachments-list '()
"A list of attachments for the current buffer. This is so my custom attachment
system can keep track of each buffer's attachments.")
;;
(defun doom|org-hook ()
"Run everytime `org-mode' is enabled."
(evil-org-mode +1)
(visual-line-mode +1)
(setq line-spacing 1)
@ -34,7 +62,9 @@
(add-hook 'before-save-hook 'doom|org-update nil t)
(add-hook 'evil-insert-state-exit-hook 'doom|org-update nil t))
(defun doom|org-init ()
"Initializes org core."
(def-popup! " *Agenda Commands*" :align below :size 30)
(def-popup! " *Org todo*" :align below :size 5 :noselect t)
(def-popup! "*Calendar*" :align below :size 0.4)
@ -167,73 +197,9 @@
(sp-local-pair "{" nil))
;; bullets
(use-package org-bullets :commands org-bullets-mode))
(use-package org-bullets :commands org-bullets-mode)
(defun doom|org-hacks ()
;; Don't open separate windows
(push '(file . find-file) org-link-frame-setup)
;; Reveal files in finder
(setq org-file-apps '(("\\.org$" . emacs) (t . "open -R \"%s\"")))
;; Don't clobber recentf with agenda files
(defun org-is-agenda-file (filename)
(find (file-truename filename) org-agenda-files :key 'file-truename
:test 'equal))
(pushnew 'org-is-agenda-file recentf-exclude)
;; Don't track attachments
(push (format "/%s.+$" (regexp-quote doom-org-attachment-dir)) recentf-exclude)
(push ".attach" projectile-globally-ignored-file-suffixes)
;; Remove highlights on ESC
(defun doom*org-remove-occur-highlights (&rest args)
(when (eq major-mode 'org-mode) (org-remove-occur-highlights)))
(advice-add 'evil-force-normal-state :before 'doom*org-remove-occur-highlights)
;; Don't reset org-hide!
(advice-add 'org-find-invisible-foreground :override 'ignore)
;; Tame org-mode popups
;; Ensures org-src-edit yields control of its buffer to shackle.
(defun org-src-switch-to-buffer (buffer context)
(pop-to-buffer buffer))
;; And these for org-todo, org-link and org-agenda
(defun org-pop-to-buffer-same-window (&optional buffer-or-name norecord label)
"Pop to buffer specified by BUFFER-OR-NAME in the selected window."
(display-buffer buffer-or-name))
(defun org-switch-to-buffer-other-window (&rest args)
(car-safe
(mapc (lambda (b)
(let ((buf (if (stringp b) (get-buffer-create b) b)))
(pop-to-buffer buf t t)))
args)))
;; Taming Org-agenda!
(defun doom/org-agenda-quit ()
"Necessary to finagle org-agenda into shackle popups and behave properly on quit."
(interactive)
(if org-agenda-columns-active
(org-columns-quit)
(let ((buf (current-buffer)))
(and (not (eq org-agenda-window-setup 'current-window))
(not (one-window-p))
(delete-window))
(kill-buffer buf)
(setq org-agenda-archives-mode nil
org-agenda-buffer nil))))
(after! org-agenda
(map! :map org-agenda-mode-map
:e "<escape>" 'doom/org-agenda-quit
:e "ESC" 'doom/org-agenda-quit
:e [escape] 'doom/org-agenda-quit
"q" 'doom/org-agenda-quit
"Q" 'doom/org-agenda-quit)))
(defun doom|org-keybinds ()
;; Keybinds
(map! (:map org-mode-map
"RET" nil
"C-j" nil
@ -283,6 +249,7 @@
:ni "<M-return>" (λ! (doom/org-insert-item 'below))
:ni "<S-M-return>" (λ! (doom/org-insert-item 'above))
;; Formatting shortcuts
:i "M-b" (λ! (doom/org-surround "*")) ; bold
:i "M-u" (λ! (doom/org-surround "_")) ; underline
:i "M-i" (λ! (doom/org-surround "/")) ; italics
@ -293,47 +260,40 @@
:v "M-i" "S/"
:v "M-`" "S+"
(:leader
:n "oa" 'doom/org-attachment-reveal)
(:localleader
:n "/" 'org-sparse-tree
:n "?" 'org-tags-view
:n "n" (λ! (if (buffer-narrowed-p) (widen) (org-narrow-to-subtree)))
:n "e" 'org-edit-special
:n "=" 'org-align-all-tags
:nv "l" 'org-insert-link
:n "L" 'org-store-link
:n "x" 'doom/org-remove-link
;; :n "w" 'writing-mode
:n "v" 'variable-pitch-mode
:n "SPC" 'doom/org-toggle-checkbox
:n "RET" 'org-archive-subtree
:n "a" 'org-agenda
:n "A" 'doom:org-attachment-list
:n "d" 'org-time-stamp
:n "SPC" 'doom/org-toggle-checkbox
:n "/" 'org-sparse-tree
:n "=" 'org-align-all-tags
:n "?" 'org-tags-view
:n "D" 'org-deadline
:n "i" 'doom/org-toggle-inline-images-at-point
:n "t" (λ! (org-todo (if (org-entry-is-todo-p) 'none 'todo)))
:v "t" (λ! (evil-ex-normal evil-visual-beginning evil-visual-end "\\t"))
:n "T" 'org-todo
:n "s" 'org-schedule
:n "r" 'org-refile
:n "L" 'org-store-link
:n "R" (λ! (org-metaleft) (org-archive-to-archive-sibling))) ; archive to parent sibling
:n "T" 'org-todo
:n "a" 'org-agenda
:n "d" 'org-time-stamp
:n "e" 'org-edit-special
:n "i" 'doom/org-toggle-inline-images-at-point
:nv "l" 'org-insert-link
:n "n" (λ! (if (buffer-narrowed-p) (widen) (org-narrow-to-subtree)))
:n "r" 'org-refile
:n "s" 'org-schedule
:n "t" (λ! (org-todo (if (org-entry-is-todo-p) 'none 'todo)))
:v "t" (λ! (evil-ex-normal evil-visual-beginning evil-visual-end "\\t"))
:n "v" 'variable-pitch-mode
;; :n "w" 'writing-mode
:n "x" 'doom/org-remove-link
;; TODO Improve folding bindings
:n "za" 'org-cycle
:n "zA" 'org-shifttab
:n "zm" (λ! (outline-hide-sublevels 1))
:n "zr" 'outline-show-all
:n "zo" 'outline-show-subtree
:n "zO" 'outline-show-all
:n "zc" 'outline-hide-subtree
:n "zC" (λ! (outline-hide-sublevels 1))
:n "zd" (lambda (&optional arg) (interactive "p") (outline-hide-sublevels (or arg 3)))
:n "zm" (λ! (outline-hide-sublevels 1))
:n "zo" 'outline-show-subtree
:n "zO" 'outline-show-all
:n "zr" 'outline-show-all
:m "]]" (λ! (call-interactively 'org-forward-heading-same-level) (org-beginning-of-line))
:m "[[" (λ! (call-interactively 'org-backward-heading-same-level) (org-beginning-of-line))
@ -373,5 +333,145 @@
:e "C-n" 'org-agenda-next-item
:e "C-p" 'org-agenda-previous-item))))
;; FIXME
;; Initializes my own org-mode attachment system. I didn't like Org's native
;; one. Mine stores attachments in a global org .attach directory. It also
;; implements drag-and-drop file support and attachment icons. It also treats
;; images specially.
;;
;; To clean up unreferenced attachments, call `doom/org-cleanup-attachments'
(defun doom|org-init-attach ()
(setq org-attach-directory doom-org-attachment-dir)
;; Don't track attachments in recentf or projectile
(push (format "/%s.+$" (regexp-quote doom-org-attachment-dir)) recentf-exclude)
(push ".attach" projectile-globally-ignored-file-suffixes)
;; FIXME Use all-the-icons
;; (doom-fix-unicode '("FontAwesome" 13) ? ? ? ? ? ? ? ?)
;; Drag-and-drop support
(require 'org-download)
(setq-default org-download-image-dir doom-org-attachment-dir
org-download-heading-lvl nil
org-download-timestamp "_%Y%m%d_%H%M%S")
(setq org-download-screenshot-method
(cond (IS-MAC "screencapture -i %s")
(IS-LINUX "maim --opengl -s %s")))
;; Write download paths relative to current file
(defun org-download--dir-2 () nil)
(defun doom*org-download--fullname (path)
(f-relative path (f-dirname (buffer-file-name))))
(advice-add 'org-download--fullname :filter-return 'doom*org-download--fullname)
;; Add another drag-and-drop handler that will handle anything but image files
(setq dnd-protocol-alist `(("^\\(https?\\|ftp\\|file\\|nfs\\):\\(//\\)?" . doom/org-download-dnd)
,@dnd-protocol-alist))
;; keybinds
(map! (:leader
:n "oa" (@find-file-in doom-org-attachment-dir))))
;; My own, centralized exporting system as well.
(defun doom|org-init-export ()
(setq org-export-directory (f-expand ".export" org-directory)
org-export-backends '(ascii html latex md)
org-export-with-toc t
org-export-with-author t)
;; Export to a central directory (why isn't this easier?)
(unless (file-directory-p org-export-directory)
(mkdir org-export-directory))
(defun doom*org-export-output-file-name (args)
(unless (nth 2 args)
(setq args (append args (list org-export-directory))))
args)
(advice-add 'org-export-output-file-name :filter-args 'doom*org-export-output-file-name)
;; (require 'ox-pandoc)
;; (setq org-pandoc-options '((standalone . t) (mathjax . t) (parse-raw . t)))
;; keybinds
(map! (:leader
:n "oe" (@find-file-in org-export-directory))))
;; Sets up a sane `org-capture' workflow, wherein the org-capture buffer is
;; opened in a popup frame, and can be invoked from outside Emacs as well.
;;
;; See `doom/org-capture'
(defun doom|org-init-capture ()
"Set up a sane `org-capture' workflow."
(setq org-default-notes-file (f-expand "notes.org" doom-org-dir))
(require 'org-capture)
(require 'org-protocol)
(def-popup! "*Org Select*" :align below :size 0.4)
(defadvice org-capture (after make-full-window-frame activate)
"If org-capture creates a new frame, this initializes it properly, by
deleting other windows and blanking out the mode-line."
(when (equal "org-capture" (frame-parameter nil 'name))
(setq mode-line-format nil)
(delete-other-windows)))
(defadvice org-capture-finalize (after delete-capture-frame activate)
"Closes the frame once org-capture is done."
(when (equal "org-capture" (frame-parameter nil 'name))
(delete-frame)))
(setq org-capture-templates
'(;; TODO: New Task (todo)
;; TODO: New vocabulary word
("c" "Changelog" entry
(file+headline (f-expand "CHANGELOG.org" (doom/project-root)) "Unreleased")
"* %?")
;; ("p" "Project Notes" entry
;; (file+headline org-default-notes-file "Inbox")
;; "* %u %?\n%i" :prepend t)
;; ("m" "Major-mode Notes" entry
;; (file+headline org-default-notes-file "Inbox")
;; "* %u %?\n%i" :prepend t)
("n" "Notes" entry
(file+headline org-default-notes-file "Inbox")
"* %u %?\n%i" :prepend t)
;; ("v" "Vocab" entry
;; (file+headline (concat org-directory "topics/vocab.org") "Unsorted")
;; "** %i%?\n")
)))
;; Getting org to behave
(defun doom|org-hacks ()
;; Don't open separate windows
(push '(file . find-file) org-link-frame-setup)
;; Let OS decide what to do with files when opened
(setq org-file-apps
`(("\\.org$" . emacs)
(t . ,(cond (IS-MAC "open -R \"%s\"")
(IS-LINUX "xdg-open \"%s\"")))))
;; Don't clobber recentf with agenda files
(defun org-is-agenda-file (filename)
(find (file-truename filename) org-agenda-files :key 'file-truename
:test 'equal))
(pushnew 'org-is-agenda-file recentf-exclude)
;; Remove highlights on ESC
(defun doom*org-remove-occur-highlights (&rest args)
(when (eq major-mode 'org-mode) (org-remove-occur-highlights)))
(advice-add 'evil-force-normal-state :before 'doom*org-remove-occur-highlights)
;; Don't reset org-hide!
(advice-add 'org-find-invisible-foreground :override 'ignore))
(provide 'module-org)
;;; module-org.el ends here