From 477ef168d4a72397035c3785613470374817b7c3 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Tue, 1 Mar 2016 01:59:36 -0500 Subject: [PATCH] Org-mode config is back baby! With a vengeance! --- Cask | 15 ++ core/core-evil.el | 1 + core/core-file-templates.el | 6 + core/core-os-osx.el | 8 +- core/core-project.el | 8 + core/core-ui.el | 4 + init.el | 8 +- modules/contrib/helm-deft.el | 297 ----------------------- modules/contrib/openwith.el | 120 ++++++++++ modules/defuns/defuns-org-notebook.el | 113 +++++++++ modules/defuns/defuns-org.el | 300 ++++++++++++++++++++++++ modules/defuns/macros-org.el | 45 ++++ modules/module-org-crm.el | 22 ++ modules/module-org-notebook.el | 107 +++++++++ modules/module-org.el | 326 ++++++++++++++++++++++++++ modules/module-write.el | 135 +++++++++++ private/my-bindings.el | 12 +- private/my-commands.el | 3 + private/templates/org-mode/__ | 6 + 19 files changed, 1227 insertions(+), 309 deletions(-) delete mode 100644 modules/contrib/helm-deft.el create mode 100644 modules/contrib/openwith.el create mode 100644 modules/defuns/defuns-org-notebook.el create mode 100644 modules/defuns/defuns-org.el create mode 100644 modules/defuns/macros-org.el create mode 100644 modules/module-org-crm.el create mode 100644 modules/module-org-notebook.el create mode 100644 modules/module-org.el create mode 100644 modules/module-write.el create mode 100644 private/templates/org-mode/__ diff --git a/Cask b/Cask index d95e91745..b1125d7d4 100644 --- a/Cask +++ b/Cask @@ -209,6 +209,21 @@ (depends-on "jaded-mode" :git "https://github.com/hlissner/jaded-mode") +;;;; ORGANIZATIONAL/WRITING ;;;;;;;;;;;; +;; Org -- modules/module-org.el +(depends-on "org-plus-contrib") +(depends-on "org-bullets") +(depends-on "ob-go" :git "https://github.com/pope/ob-go") +(depends-on "ob-http") + +;; Org Notebook -- modules/module-org-notebook.el +(depends-on "org-download") +(depends-on "ox-pandoc") + +;; Writing -- modules/module-write.el +(depends-on "helm-bibtex") + + ;;;; EXTRA TOOLS ;;;;;;;;;;;;;;;;;;;;;;; ;; Demo --- module-demo.el (depends-on "impatient-mode") diff --git a/core/core-evil.el b/core/core-evil.el index 3e5bd6fc7..80d7c3033 100644 --- a/core/core-evil.el +++ b/core/core-evil.el @@ -68,6 +68,7 @@ (Man-mode . emacs) (grep-mode . emacs) (image-mode . normal) + (doc-view-mode . normal) )) (evil-set-initial-state `,(car mode-map) `,(cdr mode-map))) diff --git a/core/core-file-templates.el b/core/core-file-templates.el index c97c42e78..fbaaf29c5 100644 --- a/core/core-file-templates.el +++ b/core/core-file-templates.el @@ -19,6 +19,12 @@ ("/Dockerfile$" "__" dockerfile-mode) ("/docker-compose.yml$" "__" yaml-mode) + ;; Org-mode + ("\\.org$" "__" org-mode) + ("/Work/.+\\.org$" "__project.org" org-mode) + ("/Invoices/.+\\.org$" "__invoice.org" org-mode) + ("/Contacts/.+\\.org$" "__contact.org" org-mode) + ;; C/C++ ("/Makefile$" "__" makefile-gmake-mode) ("/main\\.\\(cc\\|cpp\\)$" "__main.cpp" c++-mode) diff --git a/core/core-os-osx.el b/core/core-os-osx.el index 250244aad..7c3403368 100644 --- a/core/core-os-osx.el +++ b/core/core-os-osx.el @@ -98,10 +98,10 @@ (narf:send-to-tmux (format "cd %s" (shell-quote-argument default-directory))) (narf-switch-to-iterm)) -(defun narf-org-init-for-osx () - ;; Reveal files in finder - (setq org-file-apps '(("\\.org$" . emacs) - (t . "open -R \"%s\"")))) +;; Open with external programs +(require 'openwith) +(openwith-mode t) +(setq openwith-associations '(("\\.\\(pdf\\|jpe?g\\|gif\\|docx?\\|pptx?\\|xlsx?\\|zip\\|tar\\(\\.gz\\)?\\|rar\\)$" "open" (file)))) (provide 'core-os-osx) ;;; core-os-osx.el ends here diff --git a/core/core-project.el b/core/core-project.el index b5eada6e7..b9369b34e 100644 --- a/core/core-project.el +++ b/core/core-project.el @@ -114,6 +114,14 @@ (after! projectile (setq projectile-switch-project-action 'neotree-projectile-action)) + ;; Shorter file names for org files + (defun narf*neo-path--file-short-name (orig-fun &rest args) + (let ((file (car args))) + (if (f-ext? file "org") + (s-replace "-" " " (f-base file)) + (apply orig-fun args)))) + (advice-add 'neo-path--file-short-name :around 'narf*neo-path--file-short-name) + ;; A custom and simple theme for neotree (advice-add 'neo-buffer--insert-fold-symbol :override 'narf*neo-buffer-fold-symbol)) diff --git a/core/core-ui.el b/core/core-ui.el index 3efe0a757..ed61f48df 100644 --- a/core/core-ui.el +++ b/core/core-ui.el @@ -66,6 +66,10 @@ (set-fontset-font "fontset-default" `(,x . ,x) (font-spec :name "DejaVu Sans" :size 10) nil)) '(?➊ ?➋ ?➌ ?➍ ?➎ ?❻ ?➐ ?➑ ?➒ ?➓ ?λ)) +(mapc (lambda (x) + (set-fontset-font "fontset-default" `(,x . ,x) + (font-spec :name "FontAwesome" :size 13) nil)) + '(? ? ? ? ? ? ? ? ?)) (blink-cursor-mode 1) ; do blink cursor (tooltip-mode -1) ; show tooltips in echo area diff --git a/init.el b/init.el index 2ac52be10..64ee58d5a 100644 --- a/init.el +++ b/init.el @@ -88,9 +88,15 @@ ;;module-nim ; look out Abraham ;;module-pony ; Dear Princess Compiler + ;; Organizational/Notes + module-org ; for organized fearless leader + module-org-crm ; org-mode, for keeping tabs on my victims + module-org-notebook ; making org-mode into a modern note-taking solution + ;; Extra Tools module-demo ; allow me to demonstrate... - module-ansible + module-ansible ; + module-write ; yes, I write papers and fiction in Emacs ;; Key bindings & ex commands my-bindings diff --git a/modules/contrib/helm-deft.el b/modules/contrib/helm-deft.el deleted file mode 100644 index 6149bd1f1..000000000 --- a/modules/contrib/helm-deft.el +++ /dev/null @@ -1,297 +0,0 @@ -;;; helm-deft.el --- helm module for grepping note files over directories - -;; Copyright (C) 2014 Derek Feichtinger - -;; Author: Derek Feichtinger -;; Keywords: convenience -;; Homepage: https://github.com/dfeich/helm-deft -;; Version: TODO -;; Package-Requires: ((helm "1.7.7") (f "0.17.0") (cl-lib "0.5")) - -;; This file is not part of GNU Emacs. - -;; This program is free software; you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation; either version 3, or (at your option) -;; any later version. -;; -;; This program is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. -;; -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see . - -;;; Commentary: -;; Helm command to find files fast based on contents and filename. Inspired -;; by the great emacs deft package. It allows defining a list of input directories -;; that can be defined and that are searched recursively. -;; -;; helm-deft is composed of three search sources -;; - file names: simple match of pattern vs. file name -;; - file match: shows file names of files where all of the individual patterns -;; match anywhere in the file -;; - file contents: show the lines where the last word in the search patterns -;; matches - -;;; Code: - -(require 'helm) -(require 'helm-grep) -(require 'helm-files) -(require 'f) -(require 'cl-lib) -(require 'subr-x) - -(defgroup helm-deft nil - "customization group for the helm-deft utility" :group 'helm :version 24.3) - -(defcustom helm-deft-dir-list - '("~/Documents") - "List of directories in which to search recursively for candidate files." - :group 'helm-deft - ) - -(defcustom helm-deft-extension "org" - "Defines file extension for identifying candidate files to be searched for.") - -(defvar helm-deft-file-list nil - "Variable to store the list of candidate files. -This is constant over the invocation of one helm-deft.") - -(defvar helm-deft-matching-files '() - "Used for building the list of filenames that the grep matched.") - -(defvar helm-source-deft-fn - '((name . "File Names") - (header-line . "C-r: rotate pattern C-s/C-d: set/delete (marked) candidates from list") - (init . (lambda () - (progn (unless helm-deft-file-list - (setq helm-deft-file-list (helm-deft-fname-search))) - (with-current-buffer (helm-candidate-buffer 'local) - (insert (mapconcat 'identity - helm-deft-file-list "\n")))))) - (candidates-in-buffer) - ;; matching is done in the buffer when candidates-in-buffer is used - ;; We only want against the basename and not the full path - (match-part . (lambda (c) (helm-basename c))) - ;;(type . file) - (action . helm-find-files-actions) - ;; We want the file list sorted. helm-highlight-files also will - ;; transform a filename to a (basename . filename) cons - (candidate-transformer . (lambda (c) (sort (helm-highlight-files c) - (lambda (a b) - (string< (downcase (car a)) - (downcase (car b))))))) - (cleanup . (lambda () (setq helm-deft-file-list nil))) - ) - "Source definition for matching filenames of the `helm-deft' utility.") - -(defun helm-deft-fname-search () - "Search all preconfigured directories for matching files. -Returns the filenames as a list." - (assert helm-deft-extension nil "No file extension defined for helm-deft") - (assert helm-deft-dir-list nil "No directories defined for helm-deft") - (cl-loop for dir in helm-deft-dir-list - do (assert (file-exists-p dir) nil - (format "Directory %s does not exist. Check helm-deft-dir-list" dir)) - collect (f--files dir (equal (f-ext it) helm-deft-extension) t) - into reslst - finally (return (apply #'append reslst))) - ) - -(defvar helm-source-deft-filegrep - '((name . "File Contents") - (candidates-process . helm-deft-fgrep-search) - ;; We use the action from the helm-grep module - (action . helm-grep-action) - (requires-pattern) - (pattern-transformer . (lambda (pattern) - (cl-loop for ptr in (split-string pattern " *" t) - if (string-prefix-p "w:" ptr) - collect (string-remove-prefix "w:" ptr) into cptr - else collect ptr into cptr - finally return (mapconcat 'identity cptr " ")))) - (filter-one-by-one . (lambda (candidate) - ;; we abuse the filter-one-by-one function - ;; for building the candidates list for the - ;; matching-files source - (helm-deft-matching-files-search candidate) - ;; we borrow the helm-grep filter function - (helm-grep-filter-one-by-one candidate))) - (cleanup . (lambda () (when (get-buffer "*helm-deft-proc*") - (let ((kill-buffer-query-functions nil)) - (kill-buffer "*helm-deft-proc*"))))) - ) - "Source definition for matching against file contents for the `helm-deft' utility.") - -(defun helm-deft-build-cmd (ptrnstr filelst) - "Builds a grep command based on the patterns and file list. -PTRNSTR may contain multiple search patterns separated by -spaces. The first pattern will be used to retrieve matching -lines. All other patterns will be used to pre-select files with -matching lines. FILELST is a list of file paths" - (let* ((ptrnlst (reverse (split-string ptrnstr " *" t))) - (firstp (pop ptrnlst)) - (firstaddflag (if (string-prefix-p "w:" firstp) - (progn - (setq firstp (string-remove-prefix "w:" firstp)) - "-w") - "")) - (filelst (mapconcat 'identity filelst " ")) - (innercmd (if ptrnlst - (cl-labels ((build-inner-cmd - (ptrnlst filelst) - (let* ((pattern (pop ptrnlst)) - (addflags - (if (string-prefix-p "w:" pattern) - (progn - (setq pattern - (string-remove-prefix - "w:" pattern)) - "-w") - ""))) - (if ptrnlst - (format "$(grep %s -Elie '%s' %s)" - addflags pattern - (build-inner-cmd ptrnlst filelst)) - (format "$(grep %s -Elie '%s' %s)" - addflags pattern filelst))))) - (build-inner-cmd ptrnlst filelst)) - filelst))) - (format "grep %s -EHine '%s' %s" firstaddflag firstp innercmd)) - ) - -(defun helm-deft-fgrep-search () - "Greps for the helm search pattern in the configuration defined file list." - (setq helm-deft-matching-files '()) - ;; need to pass helm-input (the real input line) to the build - ;; function since helm-pattern is already cleaned by the - ;; pattern-transformer function of helm-source-deft-filegrep - (let* ((shcmd (helm-deft-build-cmd helm-input helm-deft-file-list))) - (helm-log "grep command: %s" shcmd) - ;; the function must return the process object - (prog1 - (start-process-shell-command "helm-deft-proc" "*helm-deft-proc*" - shcmd) - (set-process-sentinel - (get-process "helm-deft-proc") - (lambda (process event) - (cond - ((string= event "finished\n") - (with-helm-window - (setq mode-line-format - '(" " mode-line-buffer-identification " " - (:eval (format "L%s" (helm-candidate-number-at-point))) " " - (:eval (propertize - ;; TODO: The count is wrong since it counts all sources - (format - "[Grep process finished - (%s results)] " - (max (1- (count-lines - (point-min) - (point-max))) - 0)) - 'face 'helm-grep-finish)))) - (force-mode-line-update)) - ;; must NOT DO a targeted update here. Seems to call also this source - ;; and we end in an infinite loop - ;; (helm-update nil helm-source-deft-matching-files) - ) - ;; Catch error output in log. - (t (helm-log - "Error: Grep %s" - (replace-regexp-in-string "\n" "" event)))) - )) - ) - )) - -(defvar helm-source-deft-matching-files - '((name . "Matching Files") - (candidates . helm-deft-matching-files) - ;;(type . file) - ;; introducing the delayed value to always have it scheduled after - ;; the async grep process that produces the basis for this source - (delayed . 0.5) - (action . helm-find-files-actions) - ;; need to override the file type's match settings - (match . (lambda (candidate) t)) - (candidate-transformer . (lambda (c) (sort (helm-highlight-files c) - (lambda (a b) - (string< (downcase (car a)) - (downcase (car b))))))) - (requires-pattern) - (volatile) - ) - "Source definition for showing matching files from the grep buffer of the `helm-deft' utility.") - -(defun helm-deft-matching-files-search (candidate) - "Add entry to helm-deft-matching-files list from a grep CANDIDATE." - (when (string-match "\\([^:]+\\):[0-9]+:" candidate) - (pushnew (match-string 1 candidate) helm-deft-matching-files :test #'equal))) - -;; (defun helm-deft-matching-files-search () -;; (when (get-buffer "*helm-deft-proc*") -;; (with-current-buffer "*helm-deft-proc*" -;; (beginning-of-buffer) -;; (while (and -;; (looking-at "^\\([^:]+\\):[0-9]+:") -;; (not (equal (forward-line) 1))) -;; (push (match-string 1) helm-deft-matching-files))) -;; (cl-remove-duplicates helm-deft-matching-files :test #'equal)) -;; ) - -(defun helm-deft-rotate-searchkeys () - "Rotate the words of the search pattern in the helm minibuffer." - (interactive) - (helm-log "Executing helm-deft-rotate-searchkeys") - (let ((patlst (split-string helm-pattern " *"))) - (when (and (>= (length patlst) 1) - (> (length (car patlst)) 0)) - (delete-minibuffer-contents) - (insert (mapconcat #'identity - (append (cdr patlst) (list (car patlst))) - " ")) - (helm-update))) - ) - -(defun helm-deft-remove-candidate-file () - "Remove the file under point from the list of candidates." - (interactive) - ;; helm-get-selection returns current item under point - ;; helm-marked-candidates returns all marked candidates or the item under point - (dolist (selection (helm-marked-candidates)) - (when (string-match "\\([^:]+\\):[0-9]+:" selection) - (setq selection (match-string 1 selection))) - (setq helm-deft-file-list (delete selection helm-deft-file-list))) - (helm-unmark-all) - (helm-force-update)) - -(defun helm-deft-set-to-marked () - "Set the filelist to the marked files." - (interactive) - (setq helm-deft-file-list (helm-marked-candidates)) - (helm-unmark-all) - (helm-force-update)) - -(defvar helm-deft-map - (let ((map (make-sparse-keymap))) - (set-keymap-parent map helm-map) - (define-key map (kbd "C-r") 'helm-deft-rotate-searchkeys) - (define-key map (kbd "C-d") 'helm-deft-remove-candidate-file) - (define-key map (kbd "C-s") 'helm-deft-set-to-marked) - (delq nil map)) - "Helm keymap used for helm deft sources.") - -;;;###autoload -(defun helm-deft () - "Preconfigured `helm' module for locating matching files. -Either the filename or the file contents must match the query -string. Inspired by the Emacs `deft' extension" - (interactive) - (helm :sources '(helm-source-deft-fn helm-source-deft-matching-files - helm-source-deft-filegrep) - :keymap helm-deft-map)) - -(provide 'helm-deft) -;;; helm-deft.el ends here diff --git a/modules/contrib/openwith.el b/modules/contrib/openwith.el new file mode 100644 index 000000000..3157a944c --- /dev/null +++ b/modules/contrib/openwith.el @@ -0,0 +1,120 @@ +;;; openwith.el --- Open files with external programs + +;; Copyright (C) 2007, 2013 Markus Triska + +;; Author: Markus Triska +;; Keywords: files, processes + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; This lets you associate external applications with files so that +;; you can open them via C-x C-f, with RET in dired, etc. + +;; Copy openwith.el to your load-path and add to your .emacs: + +;; (require 'openwith) +;; (openwith-mode t) + +;; To customize associations etc., use: +;; +;; M-x customize-group RET openwith RET +;; +;; To prevent openwith-mode from interfering with attachments when +;; writing a message in Gnus, add the following to your .emacs: +;; +;; (require 'mm-util) +;; (add-to-list 'mm-inhibit-file-name-handlers 'openwith-file-handler) + +;;; Code: + +(defconst openwith-version "0.8g") + +(defgroup openwith nil + "Associate external applications with file name patterns." + :group 'files + :group 'processes) + +(defcustom openwith-associations + '(("\\.pdf\\'" "acroread" (file)) + ("\\.mp3\\'" "xmms" (file)) + ("\\.\\(?:mpe?g\\|avi\\|wmv\\)\\'" "mplayer" ("-idx" file)) + ("\\.\\(?:jp?g\\|png\\)\\'" "display" (file))) + "Associations of file patterns to external programs. +File pattern is a regular expression describing the files to +associate with a program. The program arguments are a list of +strings and symbols and are passed to the program on invocation, +where the symbol 'file' is replaced by the file to be opened." + :group 'openwith + :type '(repeat (list (regexp :tag "Files") + (string :tag "Program") + (sexp :tag "Parameters")))) + +(defcustom openwith-confirm-invocation nil + "Ask for confirmation before invoking external programs." + :group 'openwith + :type 'boolean) + +(defun openwith-file-handler (operation &rest args) + "Open file with external program, if an association is configured." + (when (and openwith-mode (not (buffer-modified-p)) (zerop (buffer-size))) + (let ((assocs openwith-associations) + (file (car args)) + oa) + ;; do not use `dolist' here, since some packages (like cl) + ;; temporarily unbind it + (while assocs + (setq oa (car assocs) + assocs (cdr assocs)) + (when (save-match-data (string-match (car oa) file)) + (let ((params (mapcar (lambda (x) (if (eq x 'file) file x)) + (nth 2 oa)))) + (when (or (not openwith-confirm-invocation) + (y-or-n-p (format "%s %s? " (cadr oa) + (mapconcat #'identity params " ")))) + (apply #'start-process "openwith-process" nil (cadr oa) params) + (kill-buffer nil) + ;; inhibit actions that would follow the regular + ;; insertion of file contents + (let (debug-on-error) + (error "Opened %s in external program" + (file-name-nondirectory file))))))))) + ;; when no association was found, relay the operation to other handlers + (let ((inhibit-file-name-handlers + (cons 'openwith-file-handler + (and (eq inhibit-file-name-operation operation) + inhibit-file-name-handlers))) + (inhibit-file-name-operation operation)) + (apply operation args))) + +;;;###autoload +(define-minor-mode openwith-mode + "Automatically open files with external programs." + :lighter "" + :global t + (if openwith-mode + (progn + ;; register `openwith-file-handler' for all files + (put 'openwith-file-handler 'safe-magic t) + (put 'openwith-file-handler 'operations '(insert-file-contents)) + (add-to-list 'file-name-handler-alist '("" . openwith-file-handler))) + (setq file-name-handler-alist + (delete '("" . openwith-file-handler) file-name-handler-alist)))) + +(provide 'openwith) + +;;; openwith.el ends here \ No newline at end of file diff --git a/modules/defuns/defuns-org-notebook.el b/modules/defuns/defuns-org-notebook.el new file mode 100644 index 000000000..9f13f2a56 --- /dev/null +++ b/modules/defuns/defuns-org-notebook.el @@ -0,0 +1,113 @@ +;;; defuns-org-notebook.el + +;; Keep track of attachments +(defvar narf-org-attachments-list '() "A list of attachments for the current buffer") +(make-variable-buffer-local 'narf-org-attachments-list) + +;;;###autoload +(defun narf/org-start () + (interactive) + (narf:workgroup-new nil "*ORG*" t) + (cd org-directory) + (let ((helm-full-frame t)) + (helm-find-files nil)) + (save-excursion + (neotree-show))) + +;;;###autoload +(defun narf/org-notebook-new () + (interactive) + (projectile-invalidate-cache nil) + (let* ((default-directory org-directory) + (dir (projectile-complete-dir)) + (narf-org-quicknote-dir dir)) + (when dir + (narf/org-notebook-quick-note)))) + +;;;###autoload +(defun narf/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 narf-org-quicknote-dir) + (erase-buffer) + (insert text))) + +;;;###autoload +(defun narf/org-download-dnd (uri action) + (if (and (eq major-mode 'org-mode) + (not (image-type-from-file-name uri))) + (narf:org-attach uri) + (let ((dnd-protocol-alist + (rassq-delete-all 'narf/org-download-dnd (copy-alist dnd-protocol-alist)))) + (dnd-handle-one-url nil action uri)))) + +;;;###autoload (autoload 'narf:org-attach "defuns-org-notebook" nil t) +(evil-define-command narf:org-attach (&optional uri) + (interactive "") + (if uri + (let* ((rel-path (org-download--fullname uri)) + (new-path (f-expand rel-path))) + (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)) + " " (narf/org-attach-icon rel-path))) + (insert (format "%s [[./%s][%s]]" + (narf/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-toggle-inline-images))) + (let ((attachments (narf-org-attachments))) + (unless attachments + (user-error "No attachments in this file")) + (helm :sources (helm-build-sync-source "Attachments" :candidates attachments))))) + +;;;###autoload +(defun narf/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" ?) + (t ?)))) + +;;;###autoload +(defun narf/org-attachments () + (let ((attachments '()) + element + file) + (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 narf/org-cleanup-attachments () + (let* ((attachments (narf/org-attachments)) + (to-delete (-difference narf-org-attachments-list attachments))) + (mapc (lambda (f) + (message "Deleting attachment: %s" f) + (delete-file f t)) + to-delete) + (setq narf-org-attachments-list attachments))) + +(provide 'defuns-org-notebook) +;;; defuns-org-notebook.el ends here diff --git a/modules/defuns/defuns-org.el b/modules/defuns/defuns-org.el new file mode 100644 index 000000000..3f73e9298 --- /dev/null +++ b/modules/defuns/defuns-org.el @@ -0,0 +1,300 @@ +;;; defuns-org.el + +;;;###autoload +(defun narf/org-find-file-in-notes () + (interactive) + (in! (f-slash org-directory) + (helm-projectile-find-file))) + +;;;###autoload +(defun narf/org-find-file () + (interactive) + (in! (f-slash org-directory) + (helm-find-files nil))) + +;;;###autoload +(defun narf/org-find-exported-file () + (interactive) + (in! (f-slash narf-org-export-directory) + (helm-find-files nil))) + +;;;###autoload +(defun narf/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 narf/org-insert-item (direction) + "Inserts a new heading or item, depending on the context." + (interactive) + (let* ((context (org-element-lineage + (org-element-context) + '(table table-row headline inlinetask item plain-list) + t)) + (type (org-element-type context))) + (cond ((eq type 'item) + (cl-case direction + ('below + (org-end-of-line) + (org-insert-heading)) + ('above + (evil-first-non-blank) + (org-insert-heading))) + (when (org-element-property :checkbox context) + (insert "[ ] "))) + ((memq type '(table table-row)) + (cl-case direction + ('below + (org-table-insert-row)) + ('above + (narf/org-table-prepend-row-or-shift-up)))) + (t + (cl-case direction + ('below + (org-insert-heading-after-current)) + ('above + (org-back-to-heading) + (org-insert-heading))) + (when (org-element-property :todo-type context) + (org-todo 'todo)))) + (evil-append-line 1))) + +;;;###autoload +(defun narf/org-toggle-checkbox () + (interactive) + (let ((context (org-element-lineage (org-element-context) '(item) t))) + (when context + (org-end-of-line) + (org-beginning-of-line) + (if (org-element-property :checkbox context) + (when (search-backward-regexp "\\[[ +-]\\]" (line-beginning-position) t) + (delete-char 4)) + (insert "[ ] "))))) + +;;;###autoload +(defun narf/org-dwim-at-point () + (interactive) + (let* ((scroll-pt (window-start)) + (context (org-element-context)) + (type (org-element-type context)) + (value (org-element-property :value context))) + (cond + ((memq type '(table table-row)) + (if (org-element-property :tblfm (org-element-property :parent context)) + (org-table-recalculate t) + (org-table-align))) + + ((and (memq type '(item)) + (org-element-property :checkbox context)) + (org-toggle-checkbox)) + + ((and (memq type '(headline)) + (org-element-property :todo-type context)) + (org-todo + (if (eq (org-element-property :todo-type context) 'done) 'todo 'done))) + + ((and (memq type '(headline)) + (string= "ARCHIVE" (car-safe (org-get-tags)))) + (org-force-cycle-archived)) + + ((memq type '(headline)) + (org-remove-latex-fragment-image-overlays) + (org-preview-latex-fragment '(4))) + + ((memq type '(babel-call)) + (org-babel-lob-execute-maybe)) + + ((memq type '(src-block inline-src-block)) + (org-babel-execute-src-block)) + + ((memq type '(latex-fragment latex-environment)) + (org-preview-latex-fragment)) + + ((memq type '(link)) + (let ((path (org-element-property :path (org-element-lineage (org-element-context) '(link) t)))) + (if (and path (image-type-from-file-name path)) + (narf/org-refresh-inline-images) + (org-open-at-point)))) + + (t (narf/org-refresh-inline-images))) + (set-window-start nil scroll-pt))) + +;;;###autoload +(defun narf/org-refresh-inline-images () + (interactive) + (if (> (length org-inline-image-overlays) 0) + (org-remove-inline-images) + (org-display-inline-images + t t + (if (org-before-first-heading-p) + (line-beginning-position) + (save-excursion (org-back-to-heading) (point))) + (if (org-before-first-heading-p) + (line-end-position) + (save-excursion (org-end-of-subtree) (point)))))) + +;; Formatting shortcuts +;;;###autoload +(defun narf/org-surround (delim) + (if (region-active-p) + (save-excursion + (goto-char (region-beginning)) + (insert delim) + (goto-char (region-end)) + (insert delim)) + (insert delim) + (save-excursion (insert delim)))) + +;;;###autoload +(defun narf/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 narf/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)))) + +;;;###autoload +(defun narf/org-table-next-row () + (interactive) + (if (org-at-table-p) (org-table-next-row) (org-down-element))) + +;;;###autoload +(defun narf/org-table-previous-row () + (interactive) + (if (org-at-table-p) (narf--org-table-previous-row) (org-up-element))) + +;;;###autoload +(defun narf/org-table-next-field () + (interactive) + (if (org-at-table-p) (org-table-next-field) (org-end-of-line))) + +;;;###autoload +(defun narf/org-table-previous-field () + (interactive) + (if (org-at-table-p) (org-table-previous-field) (org-beginning-of-line))) + +;;;###autoload +(defun narf/org-table-append-field-or-shift-right () + (interactive) + (org-shiftmetaright) + (when (org-at-table-p) (org-metaright))) + +;;;###autoload +(defun narf/org-table-prepend-field-or-shift-left () + (interactive) + (if (org-at-table-p) + (org-shiftmetaright) + (org-shiftmetaleft))) + +;;;###autoload +(defun narf/org-table-append-row-or-shift-down () + (interactive) + (org-shiftmetadown) + (when (org-at-table-p) (org-metadown))) + +;;;###autoload +(defun narf/org-table-prepend-row-or-shift-up () + (interactive) + (if (org-at-table-p) + (org-shiftmetadown) + (org-shiftmetaup))) + +(defun narf--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)))) + +(provide 'defuns-org) +;;; defuns-org.el ends here diff --git a/modules/defuns/macros-org.el b/modules/defuns/macros-org.el new file mode 100644 index 000000000..474122b37 --- /dev/null +++ b/modules/defuns/macros-org.el @@ -0,0 +1,45 @@ +;;; macros-org.el + +;;;###autoload +(defmacro define-org-section! (type directory &optional id-func link-glob) + (setq directory (f-slash directory)) + (let* ((type-str (symbol-name type)) + (link-sym (intern (format "narf/org-link-%s" type-str))) + (dir-var (intern (format "org-directory-%s" type-str)))) + `(progn + ;; Variable containing directory: `org-directory-%s' + (defvar ,dir-var ,(expand-file-name directory org-directory)) + + ;; Open helm in directory: `narf/helm-org-%s' + (evil-define-command ,(intern (format "narf:org-search-%s" type-str)) () + (interactive) + (let ((default-directory (concat ,dir-var "/" (format-time-string "%Y") "/"))) + (helm-find-files nil))) + + ;; Open helm in directory: `narf/helm-org-%s' + (defun ,(intern (format "narf/helm-org-find-file-in-%s" type-str)) () + (interactive) + (helm-do-ag (f-slash ,dir-var))) + + ;; Org link handler + (defun ,link-sym (id) + (let ((path (f-glob (format (or ,link-glob "%s.org") id) ,dir-var))) + (unless path + (error "ID not found: %s" id)) + (org-open-file (car path) t))) + (org-add-link-type ,type-str ',link-sym) + + ;; Org completion handler + (defun ,(intern (format "org-%s-complete-link" type-str)) () + (let* ((default-directory (f-slash ,dir-var)) + (file (org-iread-file-name "> ")) + (relpath (f-relative file ,dir-var))) + (when (and (not (file-exists-p file)) + (y-or-n-p (format "Create %s?" relpath))) + (write-region "" nil file) + (message "Created %s" file)) + (format "%s:%s" ,type-str ,(if id-func `(funcall ,id-func relpath) '(f-no-ext relpath))) + ))))) + +(provide 'macros-org) +;;; macros-org.el ends here diff --git a/modules/module-org-crm.el b/modules/module-org-crm.el new file mode 100644 index 000000000..e27e7e711 --- /dev/null +++ b/modules/module-org-crm.el @@ -0,0 +1,22 @@ +;;; module-org-crm.el + +(defun narf|org-crm-init () + (define-org-section! cproject "Work") + (define-org-section! project "Projects") + (define-org-section! contact "Contacts") + ) + +(defun narf/org-crm-new (type name) + ) + +(defun narf/org-crm-visit-project (&optional name) + ) + +(defun narf/org-crm-visit-contact (&optional name) + ) + +(defun narf/org-crm-visit-invoice (&optional name) + ) + +(provide 'module-org-crm) +;;; module-org-crm.el ends here diff --git a/modules/module-org-notebook.el b/modules/module-org-notebook.el new file mode 100644 index 000000000..7a53bfca4 --- /dev/null +++ b/modules/module-org-notebook.el @@ -0,0 +1,107 @@ +;;; module-org-notebook.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 support for drag-and-drop attachments into org. +;; + A simpler, pandoc-powered, export system. + +(add-hook 'org-load-hook 'narf|org-notebook-init t) +(add-hook 'org-load-hook 'narf|org-attach-init t) +(add-hook 'org-load-hook 'narf|org-export-init t) + +(defvar narf-org-export-directory (concat org-directory ".export")) +(defvar narf-org-quicknote-dir (concat org-directory "Inbox/")) +(defvar org-attach-directory ".attach/") + +;; Tell helm to ignore these directories +(mapc (lambda (r) (add-to-list 'helm-boring-file-regexp-list r)) + (list "\\.attach$" "\\.export$")) + + +;; +(defun narf|org-notebook-init () + (define-org-section! course "Classes" + (lambda (path) (substring path 0 (s-index-of " " path))) "%s*.org") + + (exmap "ocl[ass]" 'narf:org-search-course)) + + +;; +(defun narf|org-attach-init () + ;; Drag-and-drop support + (require 'org-download) + (setq-default org-download-image-dir org-attach-directory + 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 narf*org-download--fullname (path) + (f-relative path (f-dirname (buffer-file-name)))) + (advice-add 'org-download--fullname :filter-return 'narf*org-download--fullname) + + ;; Add another drag-and-drop handler that will handle anything but image files + (setq dnd-protocol-alist `(("^\\(https?\\|ftp\\|file\\|nfs\\):\\(//\\)?" . narf/org-download-dnd) + ,@dnd-protocol-alist)) + + ;; ...auto-delete attachments once all references to it have been deleted. + (add-hook 'org-mode-hook 'narf|org-attach-track-init) + (defun narf|org-attach-track-init () + (setq narf-org-attachments-list (narf/org-attachments)) + (add-hook 'after-save-hook 'narf/org-cleanup-attachments nil t))) + + +;; +(defun narf|org-export-init () + (setq org-export-backends '(ascii html latex md opml) + org-export-with-toc t + org-export-with-author t) + + (use-package 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 narf-org-export-directory) + (mkdir narf-org-export-directory)) + (defun narf*org-export-output-file-name (args) + (unless (nth 2 args) + (setq args (append args (list narf-org-export-directory)))) + args) + (advice-add 'org-export-output-file-name :filter-args 'narf*org-export-output-file-name)) + + +;; +;; (defvar narf-org-tags '()) +;; (defun narf|org-tag-init () +;; (async-start +;; `(lambda () +;; (let ((default-directory (narf/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 (_) +;; (load (concat php-extras-eldoc-functions-file ".el")) +;; (message "PHP eldoc updated!"))) +;; ) + +;; +(provide 'module-org-notebook) +;;; module-org-notebook.el ends here diff --git a/modules/module-org.el b/modules/module-org.el new file mode 100644 index 000000000..d2eece70b --- /dev/null +++ b/modules/module-org.el @@ -0,0 +1,326 @@ +;;; module-org.el + +(add-hook 'org-load-hook 'narf|org-init t) +(add-hook 'org-load-hook 'narf|org-keybinds t) +(add-hook 'org-mode-hook 'narf|org-hook) + +(defvar org-directory (expand-file-name "~/Dropbox/docs/")) + +(define-minor-mode evil-org-mode + "Evil-mode bindings for org-mode." + :init-value nil + :lighter " !" + :keymap (make-sparse-keymap) ; defines evil-org-mode-map + :group 'evil-org) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun narf|org-hook () + (evil-org-mode +1) + (org-indent-mode +1) + (setq line-spacing 1) + + ;; Highlight plaintext links + (highlight-regexp org-any-link-re 'org-link) + + ;; If saveplace places the point in a folded position, unfold it on load + (when (outline-invisible-p) + (ignore-errors + (save-excursion + (outline-previous-visible-heading 1) + (org-show-subtree)))) + + (defun narf|org-update () + (when (file-exists-p buffer-file-name) + (org-update-statistics-cookies t) + (org-align-all-tags))) + + (add-hook 'before-save-hook 'narf|org-update nil t) + (add-hook 'evil-insert-state-exit-hook 'narf|org-update nil t)) + +(defun narf|org-init () + (setq-default + org-agenda-files + (f-entries org-directory (lambda (path) (string-suffix-p ".org" path))) + + ;; Appearance + org-indent-mode-turns-on-hiding-stars t + org-adapt-indentation nil + org-blank-before-new-entry '((heading . nil) (plain-list-item . auto)) + org-bullets-bullet-list '("•" "◦" "•" "◦" "•" "◦") + org-cycle-separator-lines 1 + org-ellipsis 'hs-face + org-entities-user '(("flat" "\\flat" nil "" "" "266D" "♭") + ("sharp" "\\sharp" nil "" "" "266F" "♯")) + org-fontify-done-headline t + org-fontify-quote-and-verse-blocks t + org-fontify-whole-heading-line t + org-footnote-auto-label 'plain + org-hide-emphasis-markers t + org-hide-leading-stars nil + org-image-actual-width nil + org-indent-indentation-per-level 2 + org-pretty-entities t + org-pretty-entities-include-sub-superscripts nil + org-startup-folded t + org-startup-indented nil + org-startup-with-inline-images nil + org-tags-column 70 + org-use-sub-superscripts '{} + + ;; Behavior + org-catch-invisible-edits nil + org-checkbox-hierarchical-statistics nil + org-completion-use-ido nil ; Use helm for refiling + org-confirm-elisp-link-function nil + org-default-priority ?C + org-hidden-keywords '(title) + org-hierarchical-todo-statistics t + org-log-done t + org-loop-over-headlines-in-active-region t + org-outline-path-complete-in-steps nil + org-refile-use-outline-path t + org-special-ctrl-a/e t + + ;; Sorting/refiling + org-archive-location (concat org-directory "/Archived/%s::") + org-refile-targets '((nil . (:maxlevel . 2))) ; display full path in refile completion + + ;; Agenda + org-agenda-restore-windows-after-quit t + org-agenda-skip-unavailable-files t + org-agenda-window-setup 'other-window + org-todo-keywords '((sequence "TODO(t)" "|" "DONE(d)") + (sequence "IDEA(i)" "NEXT(n)" "ACTIVE(a)" "WAITING(w)" "LATER(l)" "|" "CANCELLED(c)")) + + ;; Babel + org-confirm-babel-evaluate nil ; you don't need my permission + org-src-fontify-natively t ; make code pretty + org-src-preserve-indentation t + org-src-tab-acts-natively t + org-src-window-setup 'current-window + + ;; Latex + org-format-latex-options (plist-put org-format-latex-options :scale 1.3) + org-highlight-latex-and-related '(latex) + org-latex-create-formula-image-program 'dvipng + org-latex-image-default-width nil + org-latex-preview-ltxpng-directory (concat narf-temp-dir "ltxpng/") + org-latex-remove-logfiles nil + org-startup-with-latex-preview nil + ;; org-latex-packages-alist + ;; '(("" "gauss" t) + ;; ("" "physics" t) TODO Install this) + + org-capture-templates + '(("c" "Changelog" entry + (file+headline (concat (narf/project-root) "CHANGELOG.org") "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"))) + + (org-babel-do-load-languages + 'org-babel-load-languages + '((python . t) (ruby . t) (sh . t) (js . t) (css . t) + (plantuml . t) (emacs-lisp . t) (matlab . t) + (latex . t) (calc . t) (lisp . t) (lilypond . t) + (go . t) (http . t) + (rust . t))) + + (let ((ext-regexp (regexp-opt '("GIF" "JPG" "JPEG" "SVG" "TIF" "TIFF" "BMP" "XPM" + "gif" "jpg" "jpeg" "svg" "tif" "tiff" "bmp" "xpm")))) + (setq iimage-mode-image-regex-alist + `((,(concat "\\(`?file://\\|\\[\\[\\|<\\|`\\)?\\([-+./_0-9a-zA-Z]+\\." + ext-regexp "\\)\\(\\]\\]\\|>\\|'\\)?") . 2) + (,(concat "<\\(http://.+\\." ext-regexp "\\)>") . 1)))) + + ;; Don't open separate windows + (add-to-list 'org-link-frame-setup '(file . find-file)) + + ;; Reveal files in finder + (setq org-file-apps '(("\\.org$" . emacs) (t . "open -R \"%s\""))) + + ;; Custom faces + (defface org-list-bullet '((t ())) "Face for list bullets") + (defvar narf-org-font-lock-keywords + `(("^ *\\([-+]\\|[0-9]+[).]\\) " + (1 'org-list-bullet)) + ("^ *\\(-----+\\)$" + (1 'org-meta-line)))) + (font-lock-add-keywords 'org-mode narf-org-font-lock-keywords) + + ;; Enable encryption + (require 'epa-file) + (epa-file-enable) + (require 'org-crypt) + (org-crypt-use-before-save-magic) + (setq org-tags-exclude-from-inheritance '("crypt") + org-crypt-key user-mail-address + epa-file-encrypt-to user-mail-address) + + ;; Don't track attachments + (push (format "/%s.+$" (regexp-quote org-attach-directory)) recentf-exclude) + ;; 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) + + ;; Evil integration + (progn + (advice-add 'evil-force-normal-state :before 'org-remove-occur-highlights) + ;; Add element delimiter text-objects so we can use evil-surround to + ;; manipulate them. + (define-text-object! "$" "\\$" "\\$") + (define-text-object! "*" "\\*" "\\*") + (define-text-object! "/" "/" "/") + (define-text-object! "_" "_" "_") + (define-text-object! "=" "=" "=") + (define-text-object! "~" "~" "~"))) + +(defun narf|org-keybinds () + (define-key org-mode-map (kbd "RET") nil) + (define-key org-mode-map (kbd "C-j") nil) + (define-key org-mode-map (kbd "C-k") nil) + (map! (:map org-mode-map + :i [remap narf/inflate-space-maybe] 'org-self-insert-command + :i "RET" 'org-return-indent) + + (:map evil-org-mode-map + :ni "A-l" 'org-metaright + :ni "A-h" 'org-metaleft + :ni "A-k" 'org-metaup + :ni "A-j" 'org-metadown + ;; Expand tables (or shiftmeta move) + :ni "A-L" 'narf/org-table-append-field-or-shift-right + :ni "A-H" 'narf/org-table-prepend-field-or-shift-left + :ni "A-K" 'narf/org-table-prepend-row-or-shift-up + :ni "A-J" 'narf/org-table-append-row-or-shift-down + + :i "C-L" 'narf/org-table-next-field + :i "C-H" 'narf/org-table-previous-field + :i "C-K" 'narf/org-table-previous-row + :i "C-J" 'narf/org-table-next-row + + :i "C-e" 'org-end-of-line + :i "C-a" 'org-beginning-of-line + + :nv "j" 'evil-next-visual-line + :nv "k" 'evil-previous-visual-line + :v "" 'narf/yas-insert-snippet + + :i "M-a" (λ! (evil-visual-state) (org-mark-element)) + :n "M-a" 'org-mark-element + :v "M-a" 'mark-whole-buffer + + :ni "" (λ! (narf/org-insert-item 'below)) + :ni "" (λ! (narf/org-insert-item 'above)) + + :i "M-b" (λ! (narf/org-surround "*")) ; bold + :i "M-u" (λ! (narf/org-surround "_")) ; underline + :i "M-i" (λ! (narf/org-surround "/")) ; italics + :i "M-`" (λ! (narf/org-surround "+")) ; strikethrough + + :v "M-b" "S*" + :v "M-u" "S_" + :v "M-i" "S/" + :v "M-`" "S+" + + (:leader + :n ";" 'helm-org-in-buffer-headings + :n "oa" 'narf/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" 'narf/org-remove-link + ;; :n "w" 'writing-mode + :n "v" 'variable-pitch-mode + :n "SPC" 'narf/org-toggle-checkbox + :n "RET" 'org-archive-subtree + + :n "a" 'org-agenda + :n "A" 'narf:org-attachment-list + + :n "d" 'org-time-stamp + :n "D" 'org-time-stamp-inactive + :n "i" 'narf/org-toggle-inline-images-at-point + :n "t" (λ! (org-todo (if (org-entry-is-todo-p) 'none 'todo))) + :n "T" 'org-todo + :n "s" 'org-schedule + :n "r" 'org-refile + :n "R" (λ! (org-metaleft) (org-archive-to-archive-sibling)) ; archive to parent sibling + ) + + ;; 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))) + + :m "]]" (λ! (call-interactively 'org-forward-heading-same-level) (org-beginning-of-line)) + :m "[[" (λ! (call-interactively 'org-backward-heading-same-level) (org-beginning-of-line)) + :m "]l" 'org-next-link + :m "[l" 'org-previous-link + + :n "RET" 'narf/org-dwim-at-point + + :m "gh" 'outline-up-heading + :m "gj" 'org-forward-heading-same-level + :m "gk" 'org-backward-heading-same-level + :m "gl" (λ! (call-interactively 'outline-next-visible-heading) (show-children)) + + :n "go" 'org-open-at-point + :n "gO" (λ! (let ((org-link-frame-setup (append '((file . find-file-other-window)) org-link-frame-setup)) + (org-file-apps '(("\\.org$" . emacs) + (t . "open \"%s\"")))) + (call-interactively 'org-open-at-point))) + + :n "gQ" 'org-fill-paragraph + :m "$" 'org-end-of-line + :m "^" 'org-beginning-of-line + :n "<" 'org-metaleft + :n ">" 'org-metaright + :v "<" (λ! (org-metaleft) (evil-visual-restore)) + :v ">" (λ! (org-metaright) (evil-visual-restore)) + :n "-" 'org-cycle-list-bullet + :n [tab] 'org-cycle) + + (:map org-src-mode-map + :n "" (λ! (message "Exited") (org-edit-src-exit))) + + (:after org-agenda + (:map org-agenda-mode-map + :e "" 'org-agenda-Quit + :e "C-j" 'org-agenda-next-item + :e "C-k" 'org-agenda-previous-item + :e "C-n" 'org-agenda-next-item + :e "C-p" 'org-agenda-previous-item)))) + +(provide 'module-org) +;;; module-org.el ends here diff --git a/modules/module-write.el b/modules/module-write.el new file mode 100644 index 000000000..14d2cf6e4 --- /dev/null +++ b/modules/module-write.el @@ -0,0 +1,135 @@ +;; module-write.el + +;; This library offers the following: +;; + Write-mode: a mode that turns Emacs into an app for writing notes, papers, or +;; fiction: it adds eye-candy to org-mode, switches to a light color theme and +;; to a more readable font. +;; + Bibtex integration + +;; Write-mode settings +(defconst write-mode nil) +(defconst write-mode-font (font-spec :family "Hack" :size 12)) +(defconst write-mode-biblio-dir "~/Dropbox/docs/biblio") + +(defconst write-mode--last-mode-line mode-line-format) +(defconst write-mode--last-line-spacing line-spacing) + +;; (defvar write-mode-org-font-lock-keywords +;; `(("[-+*] \\[X\\] \\([^$\n\r]+\\)" +;; (1 'org-headline-done)) +;; ("^ *\\([-+]\\|[0-9]+[).]\\)\\( \\)+[^$\n\r]" +;; (1 'org-list-bullet)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq-default visual-fill-column-center-text nil + visual-fill-column-width 80) + +(defun write-mode|org-hook () + "A hook that runs everytime an org-mode buffer is visited/created while `write-mode' is +active." + ;; (font-lock-add-keywords nil write-mode-org-font-lock-keywords) + ) + +(defun write-mode-toggle () + "Enable write-mode, this is not a [global] minor mode because it mixes some frame-local +functionality with buffer-local ones, which can be buggy in a minor-mode." + (interactive) + (let* ((mode-p write-mode) + (on-off (if mode-p -1 +1))) + ;; (scroll-bar-mode on-off) + (narf/load-font (if mode-p narf-default-font write-mode-font)) + (when (featurep 'volatile-highlights) + (volatile-highlights-mode (not on-off))) + (when IS-MAC + (setq mouse-wheel-scroll-amount + (if mode-p '(5 ((shift) . 2)) '(3 ((shift) . 2))))) + (if write-mode + (remove-hook 'org-mode-hook 'write-mode|org-hook) + (add-hook 'org-mode-hook 'write-mode|org-hook)) + (mapc (lambda (b) + (with-current-buffer b + (setq line-spacing (if mode-p write-mode--last-line-spacing '2)) + (when (eq major-mode 'org-mode) + (unless write-mode + ;; (font-lock-remove-keywords nil write-mode-org-font-lock-keywords) + (write-mode|org-hook)) + (org-bullets-mode on-off)))) + (narf/get-buffers-in-modes '(org-mode markdown-mode))) + (setq write-mode (not write-mode)))) + +(evil-define-command narf:set-columns (&optional bang columns) + "Adjusts visual-fill-column-width on the fly." + (interactive "") + (if (or (= (length columns) 0) bang) + (progn + (setq visual-fill-column-width 80) + (when visual-fill-column-mode + (visual-fill-column-mode -1))) + (setq columns (string-to-number columns)) + (when (> columns 30) + (setq visual-fill-column-width columns))) + (if visual-fill-column-mode + (visual-fill-column--adjust-window) + (visual-fill-column-mode 1))) + +(when (>= emacs-major-version 25) + ;; From + ;; Splitting windows while visual-fill-column makes Emacs go crazy. This prevents that + ;; by simply disabled VFC before splitting. + (after! visual-fill-column + (advice-add 'split-window :around #'visual-fill-column--disable-on-split-window)) + (defun visual-fill-column--disable-on-split-window (fn window &rest args) + "Undo the effects of `visual-fill-column-mode' for splitting window." + (if (and (or (not window) (window-live-p window)) + (buffer-local-value 'visual-fill-column-mode + (window-buffer (or window (selected-window))))) + (let ((inhibit-redisplay t)) + (set-window-fringes (or window (selected-window)) nil) + (set-window-margins (or window (selected-window)) 0 0) + (unwind-protect (apply fn window args) + (save-selected-window + (when window (select-window window 'norecord)) + (visual-fill-column--adjust-window)))) + (apply fn window args)))) + +;;; LaTeX +(setq TeX-auto-save t) +(setq TeX-parse-self t) +(setq bibtex-dialect 'biblatex) +(setq bibtex-align-at-equal-sign t) +(setq bibtex-text-indentation 20) +(add-hook! bibtex-mode + (local-set-key (kbd "C-c \\") 'bibtex-fill-entry) + (setq fill-column 140)) +(add-hook! latex-mode 'turn-on-auto-fill) +(add-hook! LaTeX-mode 'turn-on-auto-fill) + +(use-package reftex + :config + (add-hook 'latex-mode-hook 'turn-on-reftex) + (add-hook 'LaTeX-mode-hook 'turn-on-reftex) + (setq reftex-plug-into-AUCTeX t + reftex-ref-style-default-list '("Cleveref" "Hyperref" "Fancyref") + reftex-default-bibliography + `(,(expand-file-name "phys.bib" write-mode-biblio-dir)))) + +;;; Bibtex +;; NOTE: http://bibdesk.sourceforge.net/ +(use-package helm-bibtex + :defer t + :config + (setq helm-bibtex-bibliography + `(,(expand-file-name "phys.bib" write-mode-biblio-dir)) + + helm-bibtex-library-path + `(,(expand-file-name "phys-pdf" write-mode-biblio-dir)) + + helm-bibtex-notes-path (expand-file-name "notes" write-mode-biblio-dir) + helm-bibtex-notes-extension ".org" + + helm-bibtex-pdf-open-function + (lambda (fpath) (async-start-process "open-pdf" "/usr/bin/open" nil fpath)))) + +(provide 'module-write) +;;; module-write.el ends here diff --git a/private/my-bindings.el b/private/my-bindings.el index 6db250305..43405a704 100644 --- a/private/my-bindings.el +++ b/private/my-bindings.el @@ -161,14 +161,12 @@ :n "T" 'os-switch-to-term-and-cd) ;; Org notes + :n "X" 'narf/org-start (:prefix "x" - :n "." 'narf/helm-org-find-files - :n "/" 'narf/helm-org - :n "x" 'narf/org-open-notes - :n "p" 'narf/helm-org-crm-projects - :n "c" 'narf/helm-org-crm-contacts - :n "i" 'narf/helm-org-crm-invoices - :n "w" 'narf/helm-org-writing)) + :n "." 'narf/org-find-file + :n "/" 'narf/org-find-file-in-notes + :n "e" 'narf/org-find-exported-file + )) (:localleader :n "k" 'narf/helm-descbinds-localleader diff --git a/private/my-commands.el b/private/my-commands.el index 7263c78ac..36d4ed3ba 100644 --- a/private/my-commands.el +++ b/private/my-commands.el @@ -72,6 +72,9 @@ (exmap "tabp[rev]" 'narf:switch-to-tab-left) (exmap "tabl[ast]" 'narf:switch-to-tab-last) +;; Org-mode +(exmap "att[ach]" 'narf:org-attach) + ;; Plugins (after! flycheck (exmap "er[rors]" (λ! (flycheck-buffer) (flycheck-list-errors)))) diff --git a/private/templates/org-mode/__ b/private/templates/org-mode/__ new file mode 100644 index 000000000..c0f70fd52 --- /dev/null +++ b/private/templates/org-mode/__ @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Org template +# -- +#+TITLE:${1:`(f-base buffer-file-name)`} + +$0 \ No newline at end of file