diff --git a/Cask b/Cask index e4643c371..6bb383846 100644 --- a/Cask +++ b/Cask @@ -219,7 +219,7 @@ (depends-on "org-download") (depends-on "ox-opml" :git "https://github.com/edavis/org-opml") (depends-on "ox-pandoc") -; (depends-on "org-plus-contrib") +(depends-on "org-plus-contrib") ;; Writing -- modules/lib-writing.el (depends-on "helm-bibtex") diff --git a/core/defuns/defuns-helm.el b/core/defuns/defuns-helm.el index 9ff8ab7ea..f50f3ab66 100644 --- a/core/defuns/defuns-helm.el +++ b/core/defuns/defuns-helm.el @@ -93,8 +93,7 @@ buffers." (interactive) (in! org-directory (let ((helm-ff-skip-boring-files t)) - (helm-find-files-1 org-directory)))) - + (helm-find-files-1 (concat org-directory "/"))))) (provide 'defuns-helm) ;;; defuns-helm.el ends here diff --git a/modules/contrib/helm-deft.el b/modules/contrib/helm-deft.el index b4ac332de..6149bd1f1 100644 --- a/modules/contrib/helm-deft.el +++ b/modules/contrib/helm-deft.el @@ -1,8 +1,8 @@ ;;; helm-deft.el --- helm module for grepping note files over directories ;; Copyright (C) 2014 Derek Feichtinger - -;; Author: Derek Feichtinger + +;; Author: Derek Feichtinger ;; Keywords: convenience ;; Homepage: https://github.com/dfeich/helm-deft ;; Version: TODO @@ -23,6 +23,18 @@ ;; 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) @@ -30,45 +42,64 @@ (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" + "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") + "Defines file extension for identifying candidate files to be searched for.") -(defvar helm-deft-file-list "" - "variable to store the list of candidate files") +(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 (setq helm-deft-file-list (helm-deft-fname-search)) + (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")))))) + (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) - ;; Note: We override the transformer that the file type brings. We - ;; want the file list sorted + ;;(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))))))) - ;; (action . (("open file" . (lambda (candidate) - ;; (find-file candidate))))) - ;;(persistent-help . "show name") + (cleanup . (lambda () (setq helm-deft-file-list nil))) ) - "Source definition for matching filenames of the `helm-deft' utility") + "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") @@ -76,16 +107,142 @@ ;; We use the action from the helm-grep module (action . helm-grep-action) (requires-pattern) - (filter-one-by-one . helm-grep-filter-one-by-one) + (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") + "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" + "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 " *"))) @@ -98,64 +255,42 @@ (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") - -(defun helm-deft-fname-search () - "search all preconfigured directories for matching files and return the -filenames as a list" - (cl-assert helm-deft-extension nil "No file extension defined for helm-deft") - (cl-assert helm-deft-dir-list nil "No directories defined for helm-deft") - (cl-loop for dir in helm-deft-dir-list - do (cl-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))) - ) - -(defun helm-deft-build-cmd (ptrnstr filelst) - "Builds a grep command where 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 (remove "" (reverse (split-string ptrnstr " *")))) - (firstp (pop ptrnlst)) - (filelst (mapconcat 'identity filelst " ")) - (innercmd (if ptrnlst - (cl-labels ((build-inner-cmd - (ptrnlst filelst) - (let ((pattern (pop ptrnlst))) - (if ptrnlst - (format "$(grep -Elie \"%s\" %s)" pattern - (build-inner-cmd ptrnlst filelst)) - (format "$(grep -Elie \"%s\" %s)" - pattern filelst))))) - (build-inner-cmd ptrnlst filelst)) - filelst))) - (format "grep -EHine \"%s\" %s" firstp innercmd)) - ) - -(defun helm-deft-fgrep-search () - "greps for the helm search pattern in the configuration defined -file list" - (let* ((shcmd (helm-deft-build-cmd helm-pattern helm-deft-file-list))) - (helm-log "grep command: %s" shcmd) - (start-process-shell-command "helm-deft-proc" "*helm-deft-proc*" - shcmd)) - ) + "Helm keymap used for helm deft sources.") ;;;###autoload (defun helm-deft () - "Preconfigured `helm' module for locating note files where either the -filename or the file contents match the query string. Inspired by the -emacs `deft' extension" + "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-filegrep) + (helm :sources '(helm-source-deft-fn helm-source-deft-matching-files + helm-source-deft-filegrep) :keymap helm-deft-map)) (provide 'helm-deft) diff --git a/modules/defuns/defuns-org-crm.el b/modules/defuns/defuns-org-crm.el index bdd228d51..66b804e17 100644 --- a/modules/defuns/defuns-org-crm.el +++ b/modules/defuns/defuns-org-crm.el @@ -1,5 +1,7 @@ ;;; defuns-org-crm.el --- for my custom org-based CRM +(require 'helm-deft) + (defun narf--helm-org (&optional directory) (let ((helm-deft-dir-list `(,(or directory default-directory)))) (helm-deft))) @@ -30,33 +32,7 @@ (let ((narf--helm-org-params '())) (narf--helm-org (expand-file-name "writing/" org-directory)))) -;;;###autoload -(defun narf/org-crm-link-contact (id) - (org-open-file (narf--org-crm-id-to-path id 'contact) t)) -;;;###autoload -(defun narf/org-crm-link-project (id) - (org-open-file (narf--org-crm-id-to-path id 'project) t)) -;;;###autoload -(defun narf/org-crm-link-invoice (id) - (org-open-file (narf--org-crm-id-to-path id 'invoice) t)) - -(defun narf--org-complete (type) - (let ((default-directory (symbol-value (intern (format "org-directory-%ss" type))))) - (let* ((file (org-iread-file-name ">>> ")) - (match (s-match "^\\([0-9]+\\)[-.]" (f-filename file)))) - (unless (file-exists-p file) - (message "Created %s" file) - (write-region "" nil file)) - (unless match - (user-error "Invalid file ID")) - (format "%s:%s" type (cadr match))))) - -;;;###autoload -(defun org-contact-complete-link () (narf--org-complete 'contact)) -;;;###autoload -(defun org-project-complete-link () (narf--org-complete 'project)) -;;;###autoload -(defun org-invoice-complete-link () (narf--org-complete 'invoice)) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun narf--org-crm-assert-type (type) (unless (memq type '(project contact invoice)) @@ -67,7 +43,7 @@ (let* ((prefix (replace-regexp-in-string "/+$" "" (symbol-value (intern (format "org-directory-%ss" type))))) - (last-file (car-safe (sort (f-glob "*.org" prefix) 'string>)))) + (last-file (car-safe (sort (f-glob "*.org" prefix) 'org-string>)))) (when last-file (let* ((old-id (narf--org-crm-path-to-id last-file type)) (new-id (format "%04X" (1+ old-id)))) @@ -91,7 +67,12 @@ (replace-regexp-in-string "/+$" "" (symbol-value (intern (format "org-directory-%ss" type)))))) (car-safe - (f-glob (format (if (eq type 'invoice) "*-%04X.org" "%04X*.org") + (f-glob (format (cond ((eq type 'invoice) + "*-%04X.org") + ((eq type 'courses) + "%s*.org") + (t + "%04X*.org")) (string-to-number id 16)) prefix)))) diff --git a/modules/defuns/defuns-org.el b/modules/defuns/defuns-org.el index 52ce438e2..c1282c658 100644 --- a/modules/defuns/defuns-org.el +++ b/modules/defuns/defuns-org.el @@ -1,5 +1,13 @@ ;;; defuns-org.el +;;;###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-open-notes () (interactive) diff --git a/modules/defuns/macros-org.el b/modules/defuns/macros-org.el new file mode 100644 index 000000000..268079673 --- /dev/null +++ b/modules/defuns/macros-org.el @@ -0,0 +1,45 @@ +;;; macros-org.el + +;;;###autoload +(defmacro define-org-link! (type directory &optional id-func) + (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 + (defvar ,dir-var ,(expand-file-name directory org-directory)) + + (defun ,(intern (format "narf/helm-org-%s" type-str)) () + (interactive) + (let ((default-directory ,directory)) + (helm-deft))) + + (defun ,link-sym (id) + (let ((path (f-glob (format "%s*.org" id) ,directory))) + (unless path + (error "ID not found: %s" id)) + (org-open-file (car path) t))) + (org-add-link-type ,type-str ',link-sym) + + (defun ,(intern (format "narf/org-%s-at-pt" type-str)) () + (interactive) + (let* ((id (or (narf/org-get-property ,type-str) + (thing-at-point 'word t))) + (path (f-glob (format "%s*.org" id) ,dir-var))) + (unless path + (user-error "Couldn't find anything with %s (%s in %s)" id path ,directory)) + (org-open-file (car path) t))) + + (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) 'relpath)) + ))))) + +(provide 'macros-org) +;;; macros-org.el ends here diff --git a/modules/module-lisp.el b/modules/module-lisp.el index edf1c734e..155779710 100644 --- a/modules/module-lisp.el +++ b/modules/module-lisp.el @@ -58,7 +58,7 @@ "add-hook" "associate" "open-with" "define-repl" "define-builder" "narf-space-setup" "define-env-command" "define-text-object" "add-yas-minor-mode" - "define-company-backend")) + "define-org-link!" "define-company-backend")) "!\\)") (1 font-lock-keyword-face append)))) diff --git a/modules/module-org.el b/modules/module-org.el index 055cedb89..d95f7e344 100644 --- a/modules/module-org.el +++ b/modules/module-org.el @@ -7,13 +7,13 @@ :keymap (make-sparse-keymap) ; defines evil-org-mode-map :group 'evil-org) -(defvar org-directory (expand-file-name "org/" narf-dropbox-dir)) -(defvar org-directory-contacts (expand-file-name "work/contacts/" org-directory)) -(defvar org-directory-projects (expand-file-name "work/projects/" org-directory)) -(defvar org-directory-invoices (expand-file-name "work/invoices/" org-directory)) +(defvar org-directory (expand-file-name write-mode-dir)) +(defvar org-directory-contacts (expand-file-name "Work/Contacts/" org-directory)) +(defvar org-directory-projects (expand-file-name "Work/Projects/" org-directory)) +(defvar org-directory-invoices (expand-file-name "Work/Invoices/" org-directory)) +(defvar org-directory-courses (expand-file-name "Courses/" org-directory)) -(defvar org-default-notes-file (concat org-directory "notes.org")) -(defvar org-default-todo-file (concat org-directory "todo.org")) +(defvar org-default-notes-file (concat org-directory "/Notes.org")) (add-hook! org-load 'narf|org-init) @@ -21,7 +21,7 @@ (defun narf@org-vars () (setq org-agenda-files - (f-entries org-directory (lambda (path) (string-suffix-p ".org" path)) t) + (f-entries org-directory (lambda (path) (string-suffix-p ".org" path))) org-archive-location (concat org-directory "/archive/%s::") org-attach-directory ".attach/" @@ -46,7 +46,7 @@ org-log-done t org-agenda-window-setup 'other-window org-agenda-skip-unavailable-files t - org-startup-folded 'content + org-startup-folded t org-todo-keywords '((sequence "TODO(t)" "|" "DONE(d)") (sequence "IDEA(i)" "NEXT(n)" "ACTIVE(a)" "WAITING(w)" "LATER(l)" "|" "CANCELLED(c)") (sequence "UNSENT(u)" "UNPAID(U)" "|" "PAID(p)")) @@ -58,33 +58,18 @@ ("@projects" . ?r)) org-capture-templates - '(("t" "TODO" entry - (file+headline org-default-todo-file "Inbox") - "*** TODO %? %u") - - ;; TODO Select file from org files - ;; ("T" "Specific TODO" entry - ;; (function narf/-org-capture-choose) - ;; "** TODO %?\n%i" :prepend t) - - ;; ("c" "Changelog" entry - ;; (function narf/-org-capture-changelog) - ;; "** %<%H:%M>: %? :unsorted:\n%i" :prepend t) + '(("c" "Changelog" entry + (file+headline (concat (narf/project-root) "CHANGELOG.org") "Unreleased") + "* %?") ("j" "Journal" entry (file+datetree (concat org-directory "journal.org")) "** %<%H:%M>: %?\n%i" :prepend t) - ;; TODO Select file from notes folder ("n" "Notes" entry (file+headline org-default-notes-file "Inbox") "* %u %?\n%i" :prepend t) - ("s" "Writing Scraps" entry - (file+headline (concat org-directory "writing/scraps.org") "Unsorted") - "* %t %?\n%i" :prepend t) - - ;; TODO Sort word under correct header ("v" "Vocab" entry (file+headline (concat org-directory "topics/vocab.org") "Unsorted") "** %i%?\n") @@ -117,7 +102,11 @@ (defun narf@org-export () (defvar narf-org-export-directory (concat org-directory ".export")) - (require 'ox-pandoc) + + (unless (featurep 'ox-opml) + (load-library "org-opml")) + (use-package ox-pandoc) + (setq org-export-backends '(ascii html latex md opml) org-export-with-toc nil) @@ -135,20 +124,21 @@ org-src-preserve-indentation t org-src-tab-acts-natively t) - (defun narf-refresh-babel-lob () - (let ((files (f-entries org-directory (lambda (path) (f-ext? path "org")) t))) - (async-start - `(lambda () - ,(async-inject-variables "\\`\\(org-directory\\|load-path$\\)") - (require 'org) - (setq org-babel-library-of-babel nil) - (mapc (lambda (f) (org-babel-lob-ingest f)) (list ,@files)) - org-babel-library-of-babel) - (lambda (lib) - ;; (persistent-soft-store 'org-babel-library lib "org") - (message "Library of babel updated!") - (setq org-babel-library-of-babel lib))))) - (setq org-babel-library-of-babel (narf-refresh-babel-lob)) + ;; (defun narf-refresh-babel-lob () + ;; (let ((files (f-entries org-directory (lambda (path) (f-ext? path "org")) t))) + ;; (async-start + ;; `(lambda () + ;; ,(async-inject-variables "\\`\\(org-directory\\|load-path$\\)") + ;; (require 'org) + ;; (setq org-babel-library-of-babel nil) + ;; (mapc (lambda (f) (org-babel-lob-ingest f)) (list ,@files)) + ;; org-babel-library-of-babel) + ;; (lambda (lib) + ;; ;; (persistent-soft-store 'org-babel-library lib "org") + ;; (message "Library of babel updated!") + ;; (setq org-babel-library-of-babel lib))))) + ;; (setq org-babel-library-of-babel (narf-refresh-babel-lob)) + (add-hook! org-mode (add-hook 'after-save-hook (lambda () @@ -163,7 +153,9 @@ '((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) - (http . t) (rust . t) (go . t))) + (go . t) + (rust . t))) + ;; (http . t) (setq org-babel-lilypond-gen-png t) ;; Ensure lilypond doesn't print out entire pages for previews @@ -207,117 +199,94 @@ will function properly." (defun narf@org-latex () (setq-default org-latex-preview-ltxpng-directory (concat narf-temp-dir "ltxpng/") - org-latex-remove-logfiles t + org-latex-remove-logfiles nil org-latex-create-formula-image-program 'dvipng org-startup-with-latex-preview nil org-highlight-latex-and-related '(latex) - org-format-latex-options (plist-put org-format-latex-options :scale 1.4) + org-format-latex-options (plist-put org-format-latex-options :scale 1.2) org-latex-image-default-width nil org-latex-packages-alist - '(("" "gauss" t) + '(;; ("" "gauss" t) ;; ("" "physics" t) TODO Install this ))) (defun narf@org-looks () - (setq org-image-actual-width nil - org-startup-with-inline-images nil - org-startup-indented t - org-pretty-entities t - org-pretty-entities-include-sub-superscripts t - org-use-sub-superscripts '{} - org-fontify-whole-heading-line nil - org-fontify-done-headline t - org-fontify-quote-and-verse-blocks t - org-ellipsis 'hs-face - org-indent-indentation-per-level 2 - org-cycle-separator-lines 2 - org-hide-emphasis-markers t - org-hide-leading-stars t - org-bullets-bullet-list '("•" "◦" "•" "◦" "•" "◦") - org-entities-user - '(("flat" "\\flat" nil "" "" "266D" "♭") - ("sharp" "\\sharp" nil "" "" "266F" "♯")) - - org-priority-faces - '((?A . org-todo-vhigh) - (?B . org-todo-high) - (?C . org-todo))) + (setq-default + org-image-actual-width nil + org-startup-with-inline-images nil + org-startup-indented t + org-pretty-entities nil + org-pretty-entities-include-sub-superscripts t + org-use-sub-superscripts '{} + org-fontify-whole-heading-line nil + org-fontify-done-headline t + org-fontify-quote-and-verse-blocks t + org-ellipsis 'hs-face + org-indent-indentation-per-level 2 + org-cycle-separator-lines 2 + org-hide-emphasis-markers t + org-hide-leading-stars t + org-bullets-bullet-list '("•" "◦" "•" "◦" "•" "◦") + org-entities-user + '(("flat" "\\flat" nil "" "" "266D" "♭") + ("sharp" "\\sharp" nil "" "" "266F" "♯"))) (add-hook! org-mode (highlight-regexp org-any-link-re 'org-link)) ;; Restore org-block-background face (removed in official org) - (defface org-block-background '((t ())) - "Face used for the source block background.") - (defun narf--adjoin-to-list-or-symbol (element list-or-symbol) - (let ((list (if (not (listp list-or-symbol)) - (list list-or-symbol) - list-or-symbol))) - (require 'cl-lib) - (cl-adjoin element list))) - (set-face-attribute 'org-block-background nil :inherit - (narf--adjoin-to-list-or-symbol - 'fixed-pitch - (face-attribute 'org-block-background :inherit))) + ;; (defface org-block-background '((t ())) "Face used for the source block background.") + ;; (defun narf--adjoin-to-list-or-symbol (element list-or-symbol) + ;; (let ((list (if (not (listp list-or-symbol)) + ;; (list list-or-symbol) + ;; list-or-symbol))) + ;; (require 'cl-lib) + ;; (cl-adjoin element list))) + ;; (set-face-attribute 'org-block-background nil :inherit + ;; (narf--adjoin-to-list-or-symbol + ;; 'fixed-pitch + ;; (face-attribute 'org-block-background :inherit))) ;; Prettify symbols, blocks and TODOs (defface org-headline-todo '((t ())) "Face for todo headlines") + (defface org-headline-done '((t ())) "Face for todo headlines") (defface org-todo-high '((t ())) "Face for high-priority todo") (defface org-todo-vhigh '((t ())) "Face for very high-priority todo") ;; (defface org-whitespace '((t ())) "Face for spaces") (defface org-list-bullet '((t ())) "Face for list bullets") (defface org-todo-checkbox '((t ())) "Face for list bullets") - (font-lock-add-keywords - 'org-mode `(("^ *\\(#\\+begin_src\\>\\)" - (1 (narf/show-as ?#))) - ("^ *\\(#\\+end_src\\>\\)" - (1 (narf/show-as ?#))) - ("^ *\\(#\\+begin_quote\\>\\)" - (1 (narf/show-as ?>))) - ("^ *\\(#\\+end_quote\\>\\)" - (1 (narf/show-as ?>))) - ;; Hide TODO tags - ("^\\**\\(\\* DONE\\) \\([^$\n\r]+\\)" - (1 (narf/show-as ?☑)) - (2 'org-headline-done)) - ("^\\**\\(\\* \\(?:TODO\\|PAID\\)\\) " - (1 (narf/show-as ?☐))) - - ("[-+*] \\[X\\] \\([^$\n\r]+\\)" - (1 'org-headline-done)) - - ;; Show checkbox for other todo states (but don't hide the label) - (,(concat - "\\(\\*\\) " - (regexp-opt '("IDEA" "NEXT" "ACTIVE" "WAITING" "LATER" "CANCELLED" "UNPAID" "UNSENT") t) - " ") - (1 (narf/show-as ?☐))) - - ("^ *\\([-+]\\|[0-9]+[).]\\)\\( \\)+[^$\n\r]" - (1 'org-list-bullet)) - ))) + (defvar narf-org-font-lock-keywords + '(("^ *\\([-+]\\|[0-9]+[).]\\)[^$\n\r]" + (1 'org-list-bullet)) + ;; Crossed out finished items + ("\\* \\(?:DONE\\|PAID\\) \\([^$\n\r]+\\)" + (1 'org-headline-done)) + ("^ *\\(?:[-+]\\|[0-9]+[).]\\) \\[X\\] \\([^$\n\r]+\\)" + (1 'org-headline-done)))) + (font-lock-add-keywords 'org-mode narf-org-font-lock-keywords)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun narf|org-hook () (evil-org-mode +1) - (org-bullets-mode +1) (org-indent-mode +1) - ;; (text-scale-set 1) + (setq line-spacing '1) + + (setq header-line-format mode-line-format + mode-line-format '("%e")) + + (visual-line-mode +1) + (visual-fill-column-mode +1) + + (when write-mode + (org-bullets-mode +1)) ;;; OS-Specific (cond (IS-MAC (narf-org-init-for-osx)) (IS-LINUX nil) (IS-WINDOWS nil)) - ;; Org-specific font. See `narf-writing-font' - (setq buffer-face-mode-face `(:family ,(symbol-name (font-get narf-writing-font :family)))) - (buffer-face-mode +1) - - (setq truncate-lines nil) - (setq line-spacing '2) - (defun narf|org-update-statistics-cookies () (when (file-exists-p buffer-file-name) (org-update-statistics-cookies t))) @@ -365,9 +334,12 @@ will function properly." (setq epa-file-encrypt-to user-mail-address) ;; Custom links - (org-add-link-type "contact" 'narf/org-crm-link-contact) - (org-add-link-type "project" 'narf/org-crm-link-project) - (org-add-link-type "invoice" 'narf/org-crm-link-invoice) + (define-org-link! project "Work/Projects" 'f-no-ext) + (define-org-link! invoice "Work/Invoices" 'f-no-ext) + (define-org-link! contact "Work/Contacts" 'f-no-ext) + (define-org-link! dev "Personal/Dev") + (define-org-link! course "Courses" + (lambda (path) (substring path 0 (s-index-of " " path)))) (after! helm (mapc (lambda (r) (add-to-list 'helm-boring-file-regexp-list r)) @@ -411,6 +383,8 @@ will function properly." (f-relative path (f-dirname (buffer-file-name)))) (advice-add 'org-download--fullname :filter-return 'narf*org-download--fullname) + ;; Let org-download also handle PDFs + ;;; Auto-completion (after! company (require 'company-math) @@ -472,9 +446,8 @@ will function properly." :v "M-`" "S+" (:leader - :n ";" 'helm-org-in-buffer-headings - :n "oa" 'narf/org-attachment-reveal - ) + :n ";" 'helm-org-in-buffer-headings + :n "oa" 'narf/org-attachment-reveal) (:localleader :n "/" 'org-sparse-tree @@ -486,7 +459,7 @@ will function properly." :nv "l" 'org-insert-link :n "L" 'org-store-link :n "x" 'narf/org-remove-link - :n "w" 'writing-mode + ;; :n "w" 'writing-mode :n "v" 'variable-pitch-mode :n "SPC" 'narf/org-toggle-checkbox :n "RET" 'org-archive-subtree @@ -558,111 +531,112 @@ will function properly." :e "C-n" 'org-agenda-next-item :e "C-p" 'org-agenda-previous-item))) - (progn ;; Org hacks - (defun org-fontify-meta-lines-and-blocks-1 (limit) - "Fontify #+ lines and blocks." - (let ((case-fold-search t)) - (if (re-search-forward - "^\\([ \t]*#\\(\\(\\+[a-zA-Z]+:?\\| \\|$\\)\\(_\\([a-zA-Z]+\\)\\)?\\)[ \t]*\\(\\([^ \t\n]*\\)[ \t]*\\(.*\\)\\)\\)" - limit t) - (let ((beg (match-beginning 0)) - (block-start (match-end 0)) - (block-end nil) - (lang (match-string 7)) - (beg1 (line-beginning-position 2)) - (dc1 (downcase (match-string 2))) - (dc3 (downcase (match-string 3))) - end end1 quoting block-type ovl) - (cond - ((and (match-end 4) (equal dc3 "+begin")) - ;; Truly a block - (setq block-type (downcase (match-string 5)) - quoting (member block-type org-protecting-blocks)) - (when (re-search-forward - (concat "^[ \t]*#\\+end" (match-string 4) "\\>.*") - nil t) ;; on purpose, we look further than LIMIT - (setq end (min (point-max) (match-end 0)) - end1 (min (point-max) (1- (match-beginning 0)))) - (setq block-end (match-beginning 0)) - (when quoting - (org-remove-flyspell-overlays-in beg1 end1) - (remove-text-properties beg end - '(display t invisible t intangible t))) - (add-text-properties - beg end '(font-lock-fontified t font-lock-multiline t)) - (add-text-properties beg beg1 '(face org-meta-line)) - (org-remove-flyspell-overlays-in beg beg1) - (add-text-properties ; For end_src - end1 (min (point-max) (1+ end)) '(face org-meta-line)) - (org-remove-flyspell-overlays-in end1 end) - (cond - ((and lang (not (string= lang "")) org-src-fontify-natively) - (org-src-font-lock-fontify-block lang block-start block-end) - ;;;;;;; EDIT - ;; remove old background overlays - (mapc (lambda (ov) - (if (eq (overlay-get ov 'face) 'org-block-background) - (delete-overlay ov))) - (overlays-at (/ (+ beg1 block-end) 2))) - ;; add a background overlay - (setq ovl (make-overlay beg1 block-end)) - (overlay-put ovl 'face 'org-block-background) - (overlay-put ovl 'evaporate t)) ; make it go away when empty - ;; (add-text-properties beg1 block-end '(src-block t))) - ;;;;;;; /EDIT - (quoting - (add-text-properties beg1 (min (point-max) (1+ end1)) - '(face org-block))) ; end of source block - ((not org-fontify-quote-and-verse-blocks)) - ((string= block-type "quote") - (add-text-properties beg1 (min (point-max) (1+ end1)) '(face org-quote))) - ((string= block-type "verse") - (add-text-properties beg1 (min (point-max) (1+ end1)) '(face org-verse)))) - (add-text-properties beg beg1 '(face org-block-begin-line)) - (add-text-properties (min (point-max) (1+ end)) (min (point-max) (1+ end1)) - '(face org-block-end-line)) - t)) - ((string-match-p - (format "^\\+%s+:$" - (regexp-opt '("title" "author" "email" "date" "address" "location" "contact" - "project" "country" "city" "created" "issued" "paid" "currency"))) - dc1) - ;; (member dc1 '("+title:" "+author:" "+email:" "+date:" "+address:" "+location:" "+contact:" "+project:")) - (org-remove-flyspell-overlays-in - (match-beginning 0) - (if (equal "+title:" dc1) (match-end 2) (match-end 0))) - (add-text-properties - beg (match-end 3) - (if (member (intern (substring dc1 1 -1)) org-hidden-keywords) - '(font-lock-fontified t invisible t) - '(font-lock-fontified t face org-document-info-keyword))) - (add-text-properties - (match-beginning 6) (min (point-max) (1+ (match-end 6))) - (if (string-equal dc1 "+title:") - '(font-lock-fontified t face org-document-title) - '(font-lock-fontified t face org-document-info)))) - ((equal dc1 "+caption:") - (org-remove-flyspell-overlays-in (match-end 2) (match-end 0)) - (remove-text-properties (match-beginning 0) (match-end 0) - '(display t invisible t intangible t)) - (add-text-properties (match-beginning 1) (match-end 3) - '(font-lock-fontified t face org-meta-line)) - (add-text-properties (match-beginning 6) (+ (match-end 6) 1) - '(font-lock-fontified t face org-block)) - t) - ((member dc3 '(" " "")) - (org-remove-flyspell-overlays-in beg (match-end 0)) - (add-text-properties - beg (match-end 0) - '(font-lock-fontified t face font-lock-comment-face))) - (t ;; just any other in-buffer setting, but not indented - (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) - (remove-text-properties (match-beginning 0) (match-end 0) - '(display t invisible t intangible t)) - (add-text-properties beg (match-end 0) - '(font-lock-fontified t face org-meta-line)) - t)))))) - )) + (progn ;; Org hacks + (defun org-fontify-meta-lines-and-blocks-1 (limit) + "Fontify #+ lines and blocks." + (let ((case-fold-search t)) + (if (re-search-forward + "^\\([ \t]*#\\(\\(\\+[a-zA-Z]+:?\\| \\|$\\)\\(_\\([a-zA-Z]+\\)\\)?\\)[ \t]*\\(\\([^ \t\n]*\\)[ \t]*\\(.*\\)\\)\\)" + limit t) + (let ((beg (match-beginning 0)) + (block-start (match-end 0)) + (block-end nil) + (lang (match-string 7)) + (beg1 (line-beginning-position 2)) + (dc1 (downcase (match-string 2))) + (dc3 (downcase (match-string 3))) + end end1 quoting block-type ovl) + (cond + ((and (match-end 4) (equal dc3 "+begin")) + ;; Truly a block + (setq block-type (downcase (match-string 5)) + quoting (member block-type org-protecting-blocks)) + (when (re-search-forward + (concat "^[ \t]*#\\+end" (match-string 4) "\\>.*") + nil t) ;; on purpose, we look further than LIMIT + (setq end (min (point-max) (match-end 0)) + end1 (min (point-max) (1- (match-beginning 0)))) + (setq block-end (match-beginning 0)) + (when quoting + (org-remove-flyspell-overlays-in beg1 end1) + (remove-text-properties beg end + '(display t invisible t intangible t))) + (add-text-properties + beg end '(font-lock-fontified t font-lock-multiline t)) + (add-text-properties beg beg1 '(face org-meta-line)) + (org-remove-flyspell-overlays-in beg beg1) + (add-text-properties ; For end_src + end1 (min (point-max) (1+ end)) '(face org-meta-line)) + (org-remove-flyspell-overlays-in end1 end) + (cond + ((and lang (not (string= lang "")) org-src-fontify-natively) + (org-src-font-lock-fontify-block lang block-start block-end) + ;;;;;;; EDIT + ;; remove old background overlays + (mapc (lambda (ov) + (if (eq (overlay-get ov 'face) 'org-block-background) + (delete-overlay ov))) + (overlays-at (/ (+ beg1 block-end) 2))) + ;; add a background overlay + (setq ovl (make-overlay beg1 block-end)) + (overlay-put ovl 'face 'org-block-background) + (overlay-put ovl 'evaporate t)) ; make it go away when empty + ;; (add-text-properties beg1 block-end '(src-block t))) + ;;;;;;; /EDIT + (quoting + (add-text-properties beg1 (min (point-max) (1+ end1)) + '(face org-block))) ; end of source block + ((not org-fontify-quote-and-verse-blocks)) + ((string= block-type "quote") + (add-text-properties beg1 (min (point-max) (1+ end1)) '(face org-quote))) + ((string= block-type "verse") + (add-text-properties beg1 (min (point-max) (1+ end1)) '(face org-verse)))) + (add-text-properties beg beg1 '(face org-block-begin-line)) + (add-text-properties (min (point-max) (1+ end)) (min (point-max) (1+ end1)) + '(face org-block-end-line)) + t)) + ((string-match-p + (format "^\\+%s+:$" + (regexp-opt '("title" "author" "email" "date" "address" "location" "contact" + "project" "country" "city" "created" "issued" "paid" "currency"))) + dc1) + ;; (member dc1 '("+title:" "+author:" "+email:" "+date:" "+address:" "+location:" "+contact:" "+project:")) + (org-remove-flyspell-overlays-in + (match-beginning 0) + (if (equal "+title:" dc1) (match-end 2) (match-end 0))) + (add-text-properties + beg (match-end 3) + (if (member (intern (substring dc1 1 -1)) org-hidden-keywords) + '(font-lock-fontified t invisible t) + '(font-lock-fontified t face org-document-info-keyword))) + (add-text-properties + (match-beginning 6) (min (point-max) (1+ (match-end 6))) + (if (string-equal dc1 "+title:") + '(font-lock-fontified t face org-document-title) + '(font-lock-fontified t face org-document-info)))) + ((equal dc1 "+caption:") + (org-remove-flyspell-overlays-in (match-end 2) (match-end 0)) + (remove-text-properties (match-beginning 0) (match-end 0) + '(display t invisible t intangible t)) + (add-text-properties (match-beginning 1) (match-end 3) + '(font-lock-fontified t face org-meta-line)) + (add-text-properties (match-beginning 6) (+ (match-end 6) 1) + '(font-lock-fontified t face org-block)) + t) + ((member dc3 '(" " "")) + (org-remove-flyspell-overlays-in beg (match-end 0)) + (add-text-properties + beg (match-end 0) + '(font-lock-fontified t face font-lock-comment-face))) + (t ;; just any other in-buffer setting, but not indented + (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) + (remove-text-properties (match-beginning 0) (match-end 0) + '(display t invisible t intangible t)) + (add-text-properties beg (match-end 0) + '(font-lock-fontified t face org-meta-line)) + t)))))) + ) +) (provide 'module-org) ;;; module-org.el ends here