diff --git a/core/core-popup.el b/core/core-popup.el index 07282012f..ec6e9218d 100644 --- a/core/core-popup.el +++ b/core/core-popup.el @@ -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 "" '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 diff --git a/init.el b/init.el index 1330d10cf..81c6de554 100644 --- a/init.el +++ b/init.el @@ -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 diff --git a/modules/defuns/defuns-org-notes.el b/modules/defuns/defuns-org-notes.el deleted file mode 100644 index 642125b94..000000000 --- a/modules/defuns/defuns-org-notes.el +++ /dev/null @@ -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 "") - (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 diff --git a/modules/defuns/defuns-org.el b/modules/defuns/defuns-org.el index 87b193cab..a963f497e 100644 --- a/modules/defuns/defuns-org.el +++ b/modules/defuns/defuns-org.el @@ -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 "") - (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 "") + (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 diff --git a/modules/module-org-crm.el b/modules/module-org-crm.el deleted file mode 100644 index 907e5cc24..000000000 --- a/modules/module-org-crm.el +++ /dev/null @@ -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 diff --git a/modules/module-org-notes.el b/modules/module-org-notes.el deleted file mode 100644 index ff3a5608e..000000000 --- a/modules/module-org-notes.el +++ /dev/null @@ -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 diff --git a/modules/module-org.el b/modules/module-org.el index e560909b0..0539a3b8f 100644 --- a/modules/module-org.el +++ b/modules/module-org.el @@ -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 "" '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 "" (λ! (doom/org-insert-item 'below)) :ni "" (λ! (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