diff --git a/init.example.el b/init.example.el index 9289e42fc..b4e9df284 100644 --- a/init.example.el +++ b/init.example.el @@ -125,13 +125,10 @@ ;;nix ; I hereby declare "nix geht mehr!" ;;ocaml ; an objective camel (org ; organize your plain life in plain text - +attach ; custom attachment system - +babel ; running code in org - +capture ; org-capture in and outside of Emacs - +export ; Exporting org to whatever you want - +habit ; Keep track of your habits - +present ; Emacs for presentations - +protocol) ; Support for org-protocol:// links + +dragndrop ; file drag & drop support + +ipython ; ipython support for babel + +pandoc ; pandoc integration into org's exporter + +present) ; using Emacs for presentations ;;perl ; write code no one else can comprehend ;;php ; perl's insecure younger brother ;;plantuml ; diagrams for confusing people more diff --git a/modules/lang/org/+attach.el b/modules/lang/org/+attach.el deleted file mode 100644 index bade75ea5..000000000 --- a/modules/lang/org/+attach.el +++ /dev/null @@ -1,92 +0,0 @@ -;;; lang/org/+attach.el -*- lexical-binding: t; -*- - -;; I believe Org's native attachment system is over-complicated and litters -;; files with metadata I don't want. So I wrote my own, which: -;; -;; + Places attachments in a centralized location (`+org-attach-dir' in -;; `org-directory'), using an attach:* link abbreviation. -;; + Use `+org-attach/sync' to index all attachments in `org-directory' that use -;; the attach:* abbreviation and delete orphaned ones that are no longer -;; referenced. -;; + Adds drag-and-drop support for images (with inline image preview) -;; + Adds drag-and-drop support for media files (pdfs, zips, etc) with a -;; filetype icon and short link. - -;; Some commands of interest: -;; + `org-download-screenshot' -;; + `+org-attach/file' -;; + `+org-attach/url' -;; + `+org-attach/sync' - -(defvar +org-attach-dir ".attach/" - "Where to store attachments relative to `org-directory'.") - - -;; -;;; Bootstrap - -(setq org-attach-directory (expand-file-name +org-attach-dir org-directory) - org-download-image-dir org-attach-directory - org-download-heading-lvl nil - org-download-timestamp "_%Y%m%d_%H%M%S") - -;; A shorter link to attachments -(add-to-list 'org-link-abbrev-alist (cons "attach" (abbreviate-file-name org-attach-directory))) - -(org-link-set-parameters - "attach" - :follow (lambda (link) (find-file (expand-file-name link org-attach-directory))) - :complete (lambda (&optional _arg) - (+org--relpath (+org-link-read-file "attach" org-attach-directory) - org-attach-directory)) - :face (lambda (link) - (if (file-exists-p (expand-file-name link org-attach-directory)) - 'org-link - 'error))) - -(after! projectile - (add-to-list 'projectile-globally-ignored-directories - (car (last (split-string +org-attach-dir "/" t))))) - -(after! recentf - (add-to-list 'recentf-exclude (format "%s.+$" (regexp-quote org-attach-directory)))) - - -;; -;;; Packages - -(def-package! org-download - :commands (org-download-dnd org-download-dnd-base64) - :init - ;; Add these manually so that org-download is lazy-loaded... - (add-to-list 'dnd-protocol-alist '("^\\(https?\\|ftp\\|file\\|nfs\\):" . +org-attach-download-dnd)) - (add-to-list 'dnd-protocol-alist '("^data:" . org-download-dnd-base64)) - - (advice-add #'org-download-enable :override #'ignore) - :config - - (setq org-download-screenshot-method - (cond (IS-MAC "screencapture -i %s") - (IS-LINUX - (cond ((executable-find "maim") "maim -s %s") - ((executable-find "scrot") "scrot -s %s"))))) - - ;; Handle non-image files a little differently. Images should be inserted - ;; as-is, as image previews. Other files, like pdfs or zips, should be linked - ;; to, with an icon indicating the type of file. - (advice-add #'org-download-insert-link :override #'+org-attach*insert-link) - - (defun +org-attach*download-subdir () - (when (file-in-directory-p buffer-file-name org-directory) - (file-relative-name buffer-file-name org-directory))) - - (defun +org-attach*download-fullname (path) - "Write PATH relative to current file." - (let ((dir (or (if buffer-file-name (file-name-directory buffer-file-name)) - default-directory))) - (if (file-in-directory-p dir org-directory) - (file-relative-name path dir) - path))) - (advice-add #'org-download--dir-2 :override #'ignore) - (advice-add #'org-download--fullname - :filter-return #'+org-attach*download-fullname)) diff --git a/modules/lang/org/+babel.el b/modules/lang/org/+babel.el deleted file mode 100644 index 14fa91882..000000000 --- a/modules/lang/org/+babel.el +++ /dev/null @@ -1,103 +0,0 @@ -;;; lang/org/+babel.el -*- lexical-binding: t; -*- - -(defvar +org-babel-mode-alist - '((cpp . C) - (C++ . C) - (D . C) - (sh . shell) - (bash . shell) - (matlab . octave)) - "An alist mapping languages to babel libraries. This is necessary for babel -libraries (ob-*.el) that don't match the name of the language. - -For example, with (fish . shell) will cause #+BEGIN_SRC fish to load ob-shell.el -when executed.") - -(defvar +org-babel-load-functions () - "A list of functions executed to load the current executing src block. They -take one argument (the language specified in the src block, as a string). Stops -at the first function to return non-nil.") - - -;; -;;; Bootstrap - -(setq org-src-fontify-natively t ; make code pretty - org-src-preserve-indentation t ; use native major-mode indentation - org-src-tab-acts-natively t - org-src-window-setup 'current-window - org-confirm-babel-evaluate nil) ; you don't need my permission - -;; Use major-mode native TAB indentation in SRC blocks -(advice-add #'org-return-indent :after #'+org*fix-newline-and-indent-in-src-blocks) - -(defun +org*babel-lazy-load-library (info) - "Load babel libraries lazily when babel blocks are executed." - (let* ((lang (nth 0 info)) - (lang (if (symbolp lang) lang (intern lang))) - (lang (or (cdr (assq lang +org-babel-mode-alist)) - lang))) - (when (and (not (cdr (assq lang org-babel-load-languages))) - (or (run-hook-with-args-until-success '+org-babel-load-functions lang) - (require (intern (format "ob-%s" lang)) nil t))) - (when (assq :async (nth 2 info)) - ;; ob-async has its own agenda for lazy loading packages (in the - ;; child process), so we only need to make sure it's loaded. - (require 'ob-async nil t)) - (add-to-list 'org-babel-load-languages (cons lang t))) - t)) -(advice-add #'org-babel-confirm-evaluate :after-while #'+org*babel-lazy-load-library) - -;; I prefer C-c C-c over C-c ' (more consistent) -(define-key org-src-mode-map (kbd "C-c C-c") #'org-edit-src-exit) - -;; `org-babel-get-header' was removed from org in 9.0. Quite a few babel -;; plugins use it, so until those plugins update, this polyfill will do: -(defun org-babel-get-header (params key &optional others) - (cl-loop with fn = (if others #'not #'identity) - for p in params - if (funcall fn (eq (car p) key)) - collect p)) - - -;; -;;; Packages - -(def-package! ob-ipython - :when (featurep! +ipython) - :defer t - :init - (defvar +ob-ipython-local-runtime-dir nil - "TODO") - - (setq ob-ipython-resources-dir ".ob-ipython-resrc") - - (defun +org|babel-load-ipython (lang) - (and (string-prefix-p "jupyter-" (symbol-name lang)) - (require 'ob-ipython nil t))) - (add-hook '+org-babel-load-functions #'+org|babel-load-ipython) - :config - (set-popup-rules! - '(("\\*ob-ipython.*" - :slot 2 :side right :size 100 :height 0.2 - :select nil :quit nil :transient nil) - ("^\\*Python" - :slot 0 :side right :size 100 - :select nil :quit nil :ttl nil) - ("\\*Python:.*" - :slot 0 :side right :size 100 - :select nil :quit nil :transient nil))) - - ;; advices for remote kernel and org-src-edit - (advice-add 'org-babel-edit-prep:ipython :override #'+org*org-babel-edit-prep:ipython) - (advice-add 'org-babel-ipython-initiate-session :override #'+org*org-babel-ipython-initiate-session) - (advice-add 'ob-ipython--create-repl :override #'+org*ob-ipython--create-repl) - (advice-add 'org-babel-execute:ipython :override #'+org*org-babel-execute:ipython) - - ;; retina resolution image hack - (when (eq window-system 'ns) - (advice-add 'ob-ipython--write-base64-string :around #'+org*ob-ipython--write-base64-string)) - - ;; ipython has its own async keyword, disable ipython in ob-async. - (after! ob-async - (add-to-list 'ob-async-no-async-languages-alist "ipython"))) diff --git a/modules/lang/org/+capture.el b/modules/lang/org/+capture.el deleted file mode 100644 index 3c6100397..000000000 --- a/modules/lang/org/+capture.el +++ /dev/null @@ -1,93 +0,0 @@ -;;; lang/org/+capture.el -*- lexical-binding: t; -*- - -;; Sets up some reasonable defaults, as well as two `org-capture' workflows that -;; I like: -;; -;; 1. The traditional way: invoking `org-capture' directly, via SPC X, or -;; through the :cap ex command. -;; 2. Through a org-capture popup frame that is invoked from outside Emacs (the -;; ~/.emacs.d/bin/org-capture script). This can be invoked from qutebrowser, -;; vimperator, dmenu or a global keybinding. - -(defvar +org-capture-todo-file "todo.org" - "Default target for todo entries. - -Is relative to `org-directory', unless it is absolute. Is used in Doom's default -`org-capture-templates'.") - -(defvar +org-capture-changelog-file "changelog.org" - "Default target for changelog entries. - -Is relative to `org-directory' unless it is absolute. Is used in Doom's default -`org-capture-templates'.") - -(defvar +org-capture-notes-file "notes.org" - "Default target for storing notes. - -Used as a fall back file for org-capture.el, for templates that do not specify a -target file. - -Is relative to `org-directory', unless it is absolute. Is used in Doom's default -`org-capture-templates'.") - -(setq org-default-notes-file (expand-file-name +org-capture-notes-file org-directory)) - - -;; -;;; Bootstrap - -(setq org-capture-templates - '(("t" "Personal todo" entry - (file+headline +org-capture-todo-file "Inbox") - "* TODO %?\n%i\n%a" :prepend t :kill-buffer t) - ("n" "Personal notes" entry - (file+headline +org-capture-notes-file "Inbox") - "* %u %?\n%i\n%a" :prepend t :kill-buffer t) - - ;; Will use {project-root}/{todo,notes,changelog}.org, unless a - ;; {todo,notes,changelog}.org file is found in a parent directory. - ;; Uses the basename from `+org-capture-todo-file', - ;; `+org-capture-changelog-file' and `+org-capture-notes-file'. - ("p" "Templates for projects") - ("pt" "Project todo" entry ; {project-root}/todo.org - (file+headline +org-capture-project-todo-file "Inbox") - "* TODO %?\n%i\n%a" :prepend t :kill-buffer t) - ("pn" "Project notes" entry ; {project-root}/notes.org - (file+headline +org-capture-project-notes-file "Inbox") - "* TODO %?\n%i\n%a" :prepend t :kill-buffer t) - ("pc" "Project changelog" entry ; {project-root}/changelog.org - (file+headline +org-capture-project-notes-file "Unreleased") - "* TODO %?\n%i\n%a" :prepend t :kill-buffer t))) - -(add-hook 'org-capture-after-finalize-hook #'+org-capture|cleanup-frame) - -(defun +org*capture-expand-variable-file (file) - "If a variable is used for a file path in `org-capture-template', it is used -as is, and expanded relative to `default-directory'. This changes it to be -relative to `org-directory', unless it is an absolute path." - (if (and (symbolp file) (boundp file)) - (expand-file-name (symbol-value file) org-directory) - file)) -(advice-add #'org-capture-expand-file :filter-args #'+org*capture-expand-variable-file) - -(defun +org*prevent-save-prompts-when-refiling (&rest _) - "Fix #462: when refiling from org-capture, Emacs prompts to kill the -underlying, modified buffer. This fixes that." - (when (bound-and-true-p org-capture-is-refiling) - (org-save-all-org-buffers))) -(advice-add 'org-refile :after #'+org*prevent-save-prompts-when-refiling) - -(defun +org|show-target-in-capture-header () - (setq header-line-format - (format "%s%s%s" - (propertize (abbreviate-file-name (buffer-file-name (buffer-base-buffer))) - 'face 'font-lock-string-face) - org-eldoc-breadcrumb-separator - header-line-format))) -(add-hook 'org-capture-mode-hook #'+org|show-target-in-capture-header) - -(when (featurep! :editor evil) - (add-hook 'org-capture-mode-hook #'evil-insert-state)) - -(when (featurep! :ui doom-dashboard) - (add-hook '+doom-dashboard-inhibit-functions #'+org-capture-frame-p)) diff --git a/modules/lang/org/+export.el b/modules/lang/org/+export.el deleted file mode 100644 index 1e93bfe48..000000000 --- a/modules/lang/org/+export.el +++ /dev/null @@ -1,40 +0,0 @@ -;;; lang/org/+export.el -*- lexical-binding: t; -*- - -;; I don't have any beef with org's built-in export system, but I do wish it -;; would export to a central directory (by default), rather than -;; `default-directory'. This is because all my org files are usually in one -;; place, and I want to be able to refer back to old exports if needed. - -(defvar +org-export-dir ".export/" - "Where to store exported files relative to `org-directory'. Can be an absolute -path too.") -(define-obsolete-variable-alias 'org-export-directory '+org-export-dir "2.1.0") - - -;; -(setq org-export-backends '(ascii html latex md) - org-publish-timestamp-directory (concat doom-cache-dir "org-timestamps/")) - -(when (and (executable-find "pandoc") - (require 'ox-pandoc nil t)) - (add-to-list 'org-export-backends 'pandoc nil #'eq) - (setq org-pandoc-options - '((standalone . t) - (mathjax . t) - (variable . "revealjs-url=https://cdn.jsdelivr.net/npm/reveal.js@3/")))) - -;; Export to a central location by default or if target isn't in -;; `org-directory'. -(defun +org*export-output-file-name (args) - "Return a centralized export location unless one is provided or the current -file isn't in `org-directory'." - (when (and (not (nth 2 args)) - buffer-file-name - (file-in-directory-p buffer-file-name org-directory)) - (cl-destructuring-bind (extension &optional subtreep _pubdir) args - (let ((dir (expand-file-name +org-export-dir org-directory))) - (unless (file-directory-p dir) - (make-directory dir t)) - (setq args (list extension subtreep dir))))) - args) -(advice-add #'org-export-output-file-name :filter-args #'+org*export-output-file-name) diff --git a/modules/lang/org/+habit.el b/modules/lang/org/+habit.el deleted file mode 100644 index 02963d06d..000000000 --- a/modules/lang/org/+habit.el +++ /dev/null @@ -1,28 +0,0 @@ -;;; lang/org/+habit.el -*- lexical-binding: t; -*- - -(defvar +org-habit-graph-padding 2 - "The padding added to the end of the consistency graph") - -(defvar +org-habit-min-width 30 - "Hides the consistency graph if the `org-habit-graph-column' is less than this value") - -(defvar +org-habit-graph-window-ratio 0.3 - "The ratio of the consistency graphs relative to the window width") - -(defun +org-habit|resize-graph() - "Right align and resize the consistency graphs based on `+org-habit-graph-window-ratio'" - (require 'org-habit) - (let* ((total-days (float (+ org-habit-preceding-days org-habit-following-days))) - (preceding-days-ratio (/ org-habit-preceding-days total-days)) - (graph-width (floor (* (window-width) +org-habit-graph-window-ratio))) - (preceding-days (floor (* graph-width preceding-days-ratio))) - (following-days (- graph-width preceding-days)) - (graph-column (- (window-width) (+ preceding-days following-days))) - (graph-column-adjusted (if (> graph-column +org-habit-min-width) - (- graph-column +org-habit-graph-padding) - nil))) - (setq-local org-habit-preceding-days preceding-days) - (setq-local org-habit-following-days following-days) - (setq-local org-habit-graph-column graph-column-adjusted))) - -(add-hook 'org-agenda-mode-hook #'+org-habit|resize-graph) diff --git a/modules/lang/org/+protocol.el b/modules/lang/org/+protocol.el deleted file mode 100644 index 9fd46fa50..000000000 --- a/modules/lang/org/+protocol.el +++ /dev/null @@ -1,36 +0,0 @@ -;;; lang/org/+protocol.el -*- lexical-binding: t; -*- - -;; Brings lazy-loaded support for org-protocol, so external programs (like -;; browsers) can invoke specialized behavior from Emacs. Normally you'd simply -;; require `org-protocol' and use it, but the package loads all of org for no -;; compelling reason, so... -(defun +org*server-visit-files (args) - "Advise `server-visit-flist' to invoke `org-protocol' lazily." - (cl-destructuring-bind (files proc &optional nowait) args - (catch 'greedy - (let ((flist (reverse files))) - (dolist (var flist) - (when (string-match-p ":/+" (car var)) - (require 'server) - (require 'org-protocol) - ;; `\' to `/' on windows - (let ((fname (org-protocol-check-filename-for-protocol - (expand-file-name (car var)) - (member var flist) - proc))) - (cond ((eq fname t) ; greedy? We need the t return value. - (setq files nil) - (throw 'greedy t)) - ((stringp fname) ; probably filename - (setcar var fname)) - ((setq files (delq var files))))))))) - (list files proc nowait))) -(advice-add #'server-visit-files :filter-args #'+org*server-visit-files) - -;; Disable built-in, clumsy advice -(after! org-protocol - (ad-disable-advice 'server-visit-files 'before 'org-protocol-detect-protocol-server)) - - -;; TODO org-board or better link grabbing support -;; TODO org-capture + org-protocol instead of bin/org-capture diff --git a/modules/lang/org/README.org b/modules/lang/org/README.org index ecda00c6c..e4e8264fc 100644 --- a/modules/lang/org/README.org +++ b/modules/lang/org/README.org @@ -1,59 +1,194 @@ -#+TITLE: :lang org +#+TITLE: lang/org +#+DATE: February 19, 2017 +#+SINCE: 2.0 +#+STARTUP: inlineimages -This module provides support for org-mode. - -+ A custom attachment system that keeps files in a centralized location. -+ Drag-and-drop support for images (with inline preview) and media files (drops - a file icon and a short link). -+ Executable code blocks with support for a variety of languages and tools, - including REST requests, SQL, google translate, plantuml, and matlab. -+ An org-capture workflow that works from outside Emacs (through the - =bin/org-capture= shell script). -+ Exported documents are saved to a centralized location. -+ A configuration for using org-mode for slide-show presentations, or exporting - org files to reveal.js slideshows. -+ (TODO) A static site generator based in org-mode and Emacs. - -#+begin_quote -org-mode is a beast, and Doom's most difficult module to maintain. And its most -important. This module is /highly/ opinionated and experimental; my foray into -learning org is a neverending quest. -#+end_quote - -* Table of Contents :TOC: -- [[#install][Install]] +* Table of Contents :TOC_3:noexport: +- [[#description][Description]] + - [[#module-flags][Module Flags]] + - [[#plugins][Plugins]] + - [[#hacks][Hacks]] +- [[#prerequisites][Prerequisites]] - [[#macos][MacOS]] - [[#arch-linux][Arch Linux]] + - [[#nixos][NixOS]] + - [[#windows][Windows]] +- [[#features][Features]] + - [[#invoking-the-org-capture-frame-from-outside-emacs][Invoking the org-capture frame from outside Emacs]] + - [[#built-in-custom-link-types][Built-in custom link types]] - [[#configuration][Configuration]] -- [[#usage][Usage]] -- [[#appendix][Appendix]] + - [[#changing-org-directory][Changing ~org-directory~]] -* Install -Org has no hard dependencies, but there are some things you'll need to make use of Org's more esoteric features. +* Description +This module adds org-mode support to Doom Emacs, along with a number of +adjustments, extensions and reasonable defaults to make it more performant and +intuitive out of the box: + ++ A custom, centralized attachment and export system that stores files in one + place, rather than in the same directory as the input file(s) (only applies to + attachments/exporting from files in/under =org-directory=). ++ Executable code blocks with support for a variety of languages and tools + (depending on what :lang modules are enabled). ++ Supports an external org-capture workflow through the =bin/org-capture= shell + script and ~+org-capture/open-frame~. ++ A configuration for using org-mode for slide-show presentations or exporting + org files to reveal.js slideshows. ++ Drag-and-drop support for images (with inline preview) and media files (drops + a file icon and a short link) (requires =+dragndrop= flag). ++ Integration with pandoc, ipython, reveal.js, beamer, and others (requires + flags). ++ Export-to-clipboard functionality, for copying text into formatted html, + markdown or rich text to the clipboard (see ~+org/export-to-clipboard~ and + ~+org/export-to-clipboard-as-rich-text~). + +#+begin_quote +Org is a system for writing plain text notes with syntax highlighting, code +execution, task scheduling, agenda management, and many more. The whole idea is +that you can write notes and mix them with references to things like articles, +images, and example code combined with the output of that code after it is +executed. + +https://www.mfoot.com/blog/2015/11/22/literate-emacs-configuration-with-org-mode/ +#+end_quote + +** Module Flags ++ =+dragndrop= Enables drag-and-drop support for images and files; inserts + inline previews for images and an icon+link for other media types. ++ =+gnuplot= Installs gnuplot & gnuplot-mode, which enables rendering images + from gnuplot src blocks or plotting tables with ~org-plot/gnuplot~ (bound to + =SPC m b p=, by default). ++ =+ipython= Enables ipython+babel integration. ++ =+pandoc= Enables pandoc integration into the Org exporter. ++ =+present= Enables integration with reveal.js, beamer and org-tree-slide, so + Emacs can be used for presentations. + +** Plugins ++ [[https://orgmode.org/][org-plus-contrib]] ++ [[https://github.com/sabof/org-bullets][org-bullets]] ++ [[https://github.com/TobiasZawada/org-yt][org-yt]] ++ [[https://github.com/snosov1/toc-org][toc-org]] ++ [[https://github.com/jkitchin/ox-clip][ox-clip]] ++ [[https://github.com/hniksic/emacs-htmlize][htmlize]] ++ [[https://github.com/astahlman/ob-async][ob-async]] ++ =:lang crystal= + + [[https://github.com/brantou/ob-crystal][ob-crystal]] ++ =:lang go= + + [[https://github.com/pope/ob-go][ob-go]] ++ =:lang nim= + + [[https://github.com/Lompik/ob-nim][ob-nim]] ++ =:lang racket= + + [[https://github.com/DEADB17/ob-racket][ob-racket]] ++ =:lang rest= + + [[https://github.com/alf/ob-restclient.el][ob-restclient]] ++ =:lang rust= + + [[https://github.com/micanzhang/ob-rust][ob-rust]] ++ =:editor evil= + + [[https://github.com/Somelauw/evil-org-mode][evil-org]] ++ =:tools pdf= + + [[https://github.com/markus1189/org-pdfview/tree/09ef4bf8ff8319c1ac78046c7e6b89f6a0beb82c][org-pdfview]] ++ =+dragndrop= + + [[https://github.com/abo-abo/org-download][org-download]] ++ =+ipython= + + [[https://github.com/gregsexton/ob-ipython][ob-ipython]] ++ =+pandoc= + + [[https://github.com/kawabata/ox-pandoc][ox-pandoc]] ++ =+present= + + [[https://github.com/anler/centered-window-mode][centered-window]] + + [[https://github.com/takaxp/org-tree-slide][org-tree-slide]] + + [[https://github.com/yjwen/org-reveal][ox-reveal]] + +** Hacks ++ The window is recentered when following links. ++ The breadcrumbs displayed in eldoc when hovering over an org headline has been + reworked to strip out link syntax and normalize font-size disparities. ++ If =:ui workspaces= is enabled, persp-mode won't register org agenda buffers that + are temporarily opened in the background. ++ Temporary org agenda files aren't added to recentf. ++ =file:= links are highlighted with the ~error~ face if they are broken. ++ TAB was changed to toggle only the visibility state of the current subtree, + rather than cycle through it recursively. This can be reversed with: + + #+BEGIN_SRC emacs-lisp + (after! org + (remove-hook 'org-tab-first-hook #'+org|cycle-only-current-subtree t)) + #+END_SRC ++ (Evil users) Nearby tables are formatted when exiting insert or replace mode + (see ~+org|enable-auto-reformat-tables~). ++ Statistics cookies are updated when saving the buffer of exiting insert mode + (see ~+org|enable-auto-update-cookies~). ++ Org-protocol has been lazy loaded (see ~+org|init-protocol-lazy-loader~); + loaded when the server recieves a request for an org-protocol:// url. ++ Babel and babel plugins are now lazy loaded (see + ~+org|init-babel-lazy-loader~); loaded when a src block is executed. No need + to use ~org-babel-do-load-languages~ in your config, just install your babel + packages to extend language support (and ensure its ~org-babel-execute:*~ + function is autoloaded). ++ If a variable is used as a file path in ~org-capture-template~, it will be + resolved relative to ~org-directory~, instead of ~default-directory~ (see + ~+org*capture-expand-variable-file~). + +* Prerequisites +Org has a few soft dependencies that you will need to make use of Org's more +esoteric features: + For inline LaTeX previews, you need ~latex~ and ~dvipng~. -+ To run babel code blocks, you need whatever dependencies those languages - need. It is recommended you enable the associated module in =lang/= and ensure - its dependencies are met. -+ The =+crm= module uses a sqlite database to manage your contacts, invoices, - and projects; this needs sqlite installed. ++ For rendering GNUPlot images (with =+gnuplot= flag) you need the ~gnuplot~ + program installed. ++ To run babel code blocks, you need whatever dependencies those languages need. + It is recommended you enable the associated =:lang= module and ensure its + dependencies are met, e.g. install the =ruby= executable for ruby support. ** MacOS #+BEGIN_SRC sh brew cask install mactex -brew install sqlite +brew install gnuplot #+END_SRC ** Arch Linux #+BEGIN_SRC sh -sudo pacman --needed --noconfirm -S texlive-core texlive-bin texlive-science sqlite +pacman -S texlive-core texlive-bin texlive-science +pacman -S gnuplot #+END_SRC +** TODO NixOS + +** TODO Windows + +* Features +** Invoking the org-capture frame from outside Emacs +The simplest way to use the org-capture frame is through the ~bin/org-capture~ +script. I'd recommend binding a shortcut key to it. If Emacs isn't running, it +will spawn a temporary daemon for you. + +Alternatively, you can call ~+org-capture/open-frame~ directly, e.g. + +#+BEGIN_SRC sh +emacsclient --eval '(+org-capture/open-frame INTIAL-INPUT KEY)' +#+END_SRC + +** Built-in custom link types +This module defines a number of custom link types in ~+org|init-custom-links~. +They are (with examples): + ++ ~doom-docs:news/2.1.0~ (=~/.emacs.d/docs/%s=) ++ ~doom-modules:editor/evil/README.org~ (=~/.emacs.d/modules/%s=) ++ ~doom-repo:issues~ (=https://github.com/hlissner/doom-emacs/%s=) ++ ~doom:core/core.el~ (=~/.emacs.d/%s=) ++ ~duckduckgo:search terms~ ++ ~gimages:search terms~ (Google Images) ++ ~github:hlissner/doom-emacs~ ++ ~gmap:Toronto, Ontario~ (Google Maps) ++ ~google:search terms~ ++ ~org:todo.org~ (={org-directory}/%s=) ++ ~wolfram:sin(x^3)~ ++ ~youtube:P196hEuA_Xc~ (link only) ++ ~yt:P196hEuA_Xc~ (like =youtube=, but includes an inline preview of the video) + * Configuration -(Coming soon) +** Changing ~org-directory~ +To modify ~org-directory~ it must be set /before/ =org= has loaded: -* Usage -(Coming soon) - -* Appendix -(Coming soon) +#+BEGIN_SRC emacs-lisp +;; ~/.doom.d/config.el +(setq org-directory "~/new/org/location/") +#+END_SRC diff --git a/modules/lang/org/autoload/org-attach.el b/modules/lang/org/autoload/org-attach.el index c99715ef7..288811d29 100644 --- a/modules/lang/org/autoload/org-attach.el +++ b/modules/lang/org/autoload/org-attach.el @@ -1,5 +1,4 @@ ;;; lang/org/autoload/org-attach.el -*- lexical-binding: t; -*- -;;;###if (featurep! +attach) (defun +org-attach--icon (path) (char-to-string diff --git a/modules/lang/org/autoload/org-capture.el b/modules/lang/org/autoload/org-capture.el index f1b83b65a..0fbd4516a 100644 --- a/modules/lang/org/autoload/org-capture.el +++ b/modules/lang/org/autoload/org-capture.el @@ -1,5 +1,4 @@ ;;; lang/org/autoload/org-capture.el -*- lexical-binding: t; -*- -;;;###if (featurep! +capture) (defvar org-capture-initial) @@ -29,12 +28,12 @@ (frame-parameter nil 'transient))) ;;;###autoload -(defun +org-capture/open-frame (&optional string key) +(defun +org-capture/open-frame (&optional initial-input key) "Opens the org-capture window in a floating frame that cleans itself up once you're done. This can be called from an external shell script." (interactive) - (when (and string (string-empty-p string)) - (setq string nil)) + (when (and initial-input (string-empty-p initial-input)) + (setq initial-input nil)) (when (and key (string-empty-p key)) (setq key nil)) (let* ((frame-title-format "") @@ -47,7 +46,7 @@ you're done. This can be called from an external shell script." (cl-letf (((symbol-function #'pop-to-buffer) (symbol-function #'switch-to-buffer))) (switch-to-buffer (doom-fallback-buffer)) - (let ((org-capture-initial string) + (let ((org-capture-initial initial-input) org-capture-entry) (when (and key (not (string-empty-p key))) (setq org-capture-entry (org-capture-select-template key))) diff --git a/modules/lang/org/autoload/org-export.el b/modules/lang/org/autoload/org-export.el index 73ff7ea6f..9bd2d1b7b 100644 --- a/modules/lang/org/autoload/org-export.el +++ b/modules/lang/org/autoload/org-export.el @@ -1,5 +1,4 @@ ;;; lang/org/autoload/org-export.el -*- lexical-binding: t; -*- -;;;###if (featurep! +export) (defun +org--yank-html-buffer (buffer) (with-current-buffer buffer diff --git a/modules/lang/org/autoload/org.el b/modules/lang/org/autoload/org.el index 1002b57d2..4668f25f5 100644 --- a/modules/lang/org/autoload/org.el +++ b/modules/lang/org/autoload/org.el @@ -149,15 +149,13 @@ If on a: (_ (+org/refresh-inline-images))))) -;;;###autoload -(defun +org/insert-item (direction) +(defun +org-insert-item (direction) "Inserts a new heading, table cell or item, depending on the context. DIRECTION can be 'above or 'below. I use this instead of `org-insert-item' or `org-insert-heading' which are too opinionated and perform this simple task incorrectly (e.g. whitespace in the wrong places)." - (interactive) (let* ((context (save-excursion (when (bolp) @@ -237,6 +235,18 @@ wrong places)." (when (bound-and-true-p evil-local-mode) (evil-insert 1)))) +;;;###autoload +(defun +org/insert-item-below (count) + (interactive "p") + (dotimes (_ count) + (+org-insert-item 'below))) + +;;;###autoload +(defun +org/insert-item-above (count) + (interactive "p") + (dotimes (_ count) + (+org-insert-item 'above))) + ;;;###autoload (defun +org/dedent () "TODO" diff --git a/modules/lang/org/config.el b/modules/lang/org/config.el index b44efc722..ed0c6329f 100644 --- a/modules/lang/org/config.el +++ b/modules/lang/org/config.el @@ -1,82 +1,9 @@ ;;; lang/org/config.el -*- lexical-binding: t; -*- -;; Changed org defaults (should be set before org loads) -(defvar org-directory "~/org/") -(defvar org-modules - '(org-w3m - ;; org-bbdb - org-bibtex - org-docview - ;; org-gnus - org-info - ;; org-irc - ;; org-mhe - ;; org-rmail - )) - - -;; -;;; Packages - -;; `toc-org' -(setq toc-org-hrefify-default "gh") -(defun +org*unfold-toc (&rest _) - (save-excursion - (when (re-search-forward toc-org-toc-org-regexp (point-max) t) - (+org/open-fold)))) -(advice-add #'toc-org-insert-toc :before #'+org*unfold-toc) - -(def-package! evil-org - :when (featurep! :editor evil +everywhere) - :hook (org-mode . evil-org-mode) - :init - (defvar evil-org-key-theme '(navigation insert textobjects)) - (defvar evil-org-special-o/O '(table-row)) - (add-hook 'org-load-hook #'+org|setup-evil-keybinds) - (add-hook 'evil-org-mode-hook #'evil-normalize-keymaps) - :config - (setq evil-org-retain-visual-state-on-shift t) - ;; change `evil-org-key-theme' instead - (advice-add #'evil-org-set-key-theme :override #'ignore) - (def-package! evil-org-agenda - :after org-agenda - :config (evil-org-agenda-set-keys))) - -(def-package! org-pdfview - :when (featurep! :tools pdf) - :commands (org-pdfview-open) - :init - (after! org - (delete '("\\.pdf\\'" . default) org-file-apps) - ;; org links to pdf files are opened in pdf-view-mode - (add-to-list 'org-file-apps '("\\.pdf\\'" . (lambda (_file link) (org-pdfview-open link)))) - ;; support for links to specific pages - (add-to-list 'org-file-apps '("\\.pdf::\\([[:digit:]]+\\)\\'" . (lambda (_file link) (org-pdfview-open link)))))) - -(def-package! org-crypt ; built-in - :commands org-encrypt-entries - :hook (org-reveal-start . org-decrypt-entry) - :init - (add-hook! 'org-mode-hook - (add-hook 'before-save-hook 'org-encrypt-entries nil t)) - :config - (setq org-tags-exclude-from-inheritance '("crypt") - org-crypt-key user-mail-address)) - -(def-package! org-clock ; built-in - :commands org-clock-save - :hook (org-mode . org-clock-load) - :init - (setq org-clock-persist 'history - org-clock-persist-file (concat doom-etc-dir "org-clock-save.el")) - :config - (add-hook 'kill-emacs-hook #'org-clock-save)) - - ;; ;;; `org-load' hooks -(defun +org|setup-agenda () +(defun +org|init-agenda () (unless org-agenda-files (setq org-agenda-files (list org-directory))) (setq-default @@ -91,55 +18,7 @@ org-agenda-start-day "-3d")) -(defun +org|setup-custom-links () - "Set up custom org links." - (setq org-link-abbrev-alist - '(("github" . "https://github.com/%s") - ("youtube" . "https://youtube.com/watch?v=%s") - ("google" . "https://google.com/search?q=") - ("gimages" . "https://google.com/images?q=%s") - ("gmap" . "https://maps.google.com/maps?q=%s") - ("duckduckgo" . "https://duckduckgo.com/?q=%s") - ("wolfram" . "https://wolframalpha.com/input/?i=%s") - ("doom-repo" . "https://github.com/hlissner/doom-emacs/%s"))) - - (defun +org--relpath (path root) - (if (and buffer-file-name (file-in-directory-p buffer-file-name root)) - (file-relative-name path) - path)) - - ;; highlight broken links - (org-link-set-parameters - "file" - :face (lambda (path) - (if (or (file-remote-p path) - (file-exists-p path)) - 'org-link - 'error))) - - (defun +org-def-link (key dir) - (org-link-set-parameters - key - :complete (lambda () (+org--relpath (+org-link-read-file key dir) dir)) - :follow (lambda (link) (find-file (expand-file-name link dir))) - :face (lambda (link) - (if (file-exists-p (expand-file-name link dir)) - 'org-link - 'error)))) - - (+org-def-link "org" org-directory) - (+org-def-link "doom" doom-emacs-dir) - (+org-def-link "doom-docs" doom-docs-dir) - (+org-def-link "doom-modules" doom-modules-dir) - - (def-package! org-yt - :config - (org-link-set-parameters "http" :image-data-fun #'+org-image-link) - (org-link-set-parameters "https" :image-data-fun #'+org-image-link) - (org-link-set-parameters "img" :image-data-fun #'+org-inline-data-image))) - - -(defun +org|setup-ui () +(defun +org|init-appearance () "Configures the UI for `org-mode'." (setq-default org-adapt-indentation nil @@ -174,14 +53,15 @@ org-startup-with-inline-images nil org-tags-column 0 org-todo-keywords - '((sequence "TODO(t)" "|" "DONE(d)") - (sequence "[ ](T)" "[-](p)" "[?](m)" "|" "[X](D)") - (sequence "NEXT(n)" "WAITING(w)" "LATER(l)" "|" "CANCELLED(c)")) + '((sequence "TODO(t)" "PROJ(p)" "|" "DONE(d)") + (sequence "[ ](T)" "[-](P)" "[?](M)" "|" "[X](D)") + (sequence "NEXT(n)" "WAIT(w)" "HOLD(h)" "|" "ABRT(c)")) org-todo-keyword-faces '(("[-]" :inherit (font-lock-constant-face bold)) ("[?]" :inherit (warning bold)) - ("WAITING" :inherit bold) - ("LATER" :inherit (warning bold))) + ("PROJ" :inherit (bold default)) + ("HOLD" :inherit (warning bold)) + ("ABRT" :inherit (error bold))) org-use-sub-superscripts '{} ;; Scale up LaTeX previews a bit (default is too small) @@ -203,10 +83,393 @@ (face-attribute (or (cadr (assq 'default face-remapping-alist)) 'default) :background nil t)))) - (add-hook 'doom-load-theme-hook #'+org|update-latex-preview-background-color)) + (add-hook 'doom-load-theme-hook #'+org|update-latex-preview-background-color) + + (set-pretty-symbols! 'org-mode + :name "#+NAME:" + :src_block "#+BEGIN_SRC" + :src_block_end "#+END_SRC")) -(defun +org|setup-keybinds () +(defun +org|init-babel () + (setq org-src-fontify-natively t ; make code pretty + org-src-preserve-indentation t ; use native major-mode indentation + org-src-tab-acts-natively t + org-src-window-setup 'current-window + org-confirm-babel-evaluate nil) ; you don't need my permission + + ;; I prefer C-c C-c over C-c ' (more consistent) + (define-key org-src-mode-map (kbd "C-c C-c") #'org-edit-src-exit) + + ;; Use major-mode native TAB indentation in SRC blocks + (advice-add #'org-return-indent :after #'+org*fix-newline-and-indent-in-src-blocks) + + ;; `org-babel-get-header' was removed from org in 9.0. Quite a few babel + ;; plugins use it, so until those plugins update, this polyfill will do: + (defun org-babel-get-header (params key &optional others) + (cl-loop with fn = (if others #'not #'identity) + for p in params + if (funcall fn (eq (car p) key)) + collect p)) + + (when (featurep! +ipython) + (load! "contrib/ipython"))) + + +(defun +org|init-babel-lazy-loader () + "Load babel libraries lazily when babel blocks are executed." + (defvar +org-babel-mode-alist + '((cpp . C) + (C++ . C) + (D . C) + (sh . shell) + (bash . shell) + (matlab . octave)) + "An alist mapping languages to babel libraries. This is necessary for babel +libraries (ob-*.el) that don't match the name of the language. + +For example, with (fish . shell) will cause #+BEGIN_SRC fish to load ob-shell.el +when executed.") + + (defvar +org-babel-load-functions () + "A list of functions executed to load the current executing src block. They +take one argument (the language specified in the src block, as a string). Stops +at the first function to return non-nil.") + + (defun +org*babel-lazy-load-library (info) + "Load babel libraries lazily when babel blocks are executed." + (let* ((lang (nth 0 info)) + (lang (if (symbolp lang) lang (intern lang))) + (lang (or (cdr (assq lang +org-babel-mode-alist)) + lang))) + (when (and (not (cdr (assq lang org-babel-load-languages))) + (or (run-hook-with-args-until-success '+org-babel-load-functions lang) + (require (intern (format "ob-%s" lang)) nil t))) + (when (assq :async (nth 2 info)) + ;; ob-async has its own agenda for lazy loading packages (in the + ;; child process), so we only need to make sure it's loaded. + (require 'ob-async nil t)) + (add-to-list 'org-babel-load-languages (cons lang t))) + t)) + (advice-add #'org-babel-confirm-evaluate :after-while #'+org*babel-lazy-load-library)) + + +(defun +org|init-capture-defaults () + "Sets up some reasonable defaults, as well as two `org-capture' workflows that +I like: + +1. The traditional way: invoking `org-capture' directly, via SPC X, or through + the :cap ex command. +2. Through a org-capture popup frame that is invoked from outside Emacs (the + ~/.emacs.d/bin/org-capture script). This can be invoked from qutebrowser, + vimperator, dmenu or a global keybinding." + + (defvar +org-capture-todo-file "todo.org" + "Default target for todo entries. + +Is relative to `org-directory', unless it is absolute. Is used in Doom's default +`org-capture-templates'.") + + (defvar +org-capture-changelog-file "changelog.org" + "Default target for changelog entries. + +Is relative to `org-directory' unless it is absolute. Is used in Doom's default +`org-capture-templates'.") + + (defvar +org-capture-notes-file "notes.org" + "Default target for storing notes. + +Used as a fall back file for org-capture.el, for templates that do not specify a +target file. + +Is relative to `org-directory', unless it is absolute. Is used in Doom's default +`org-capture-templates'.") + + (setq org-default-notes-file + (expand-file-name +org-capture-notes-file org-directory) + org-capture-templates + '(("t" "Personal todo" entry + (file+headline +org-capture-todo-file "Inbox") + "* TODO %?\n%i\n%a" :prepend t :kill-buffer t) + ("n" "Personal notes" entry + (file+headline +org-capture-notes-file "Inbox") + "* %u %?\n%i\n%a" :prepend t :kill-buffer t) + + ;; Will use {project-root}/{todo,notes,changelog}.org, unless a + ;; {todo,notes,changelog}.org file is found in a parent directory. + ;; Uses the basename from `+org-capture-todo-file', + ;; `+org-capture-changelog-file' and `+org-capture-notes-file'. + ("p" "Templates for projects") + ("pt" "Project todo" entry ; {project-root}/todo.org + (file+headline +org-capture-project-todo-file "Inbox") + "* TODO %?\n%i\n%a" :prepend t :kill-buffer t) + ("pn" "Project notes" entry ; {project-root}/notes.org + (file+headline +org-capture-project-notes-file "Inbox") + "* TODO %?\n%i\n%a" :prepend t :kill-buffer t) + ("pc" "Project changelog" entry ; {project-root}/changelog.org + (file+headline +org-capture-project-notes-file "Unreleased") + "* TODO %?\n%i\n%a" :prepend t :kill-buffer t))) + + (defun +org*capture-expand-variable-file (file) + "If a variable is used for a file path in `org-capture-template', it is used +as is, and expanded relative to `default-directory'. This changes it to be +relative to `org-directory', unless it is an absolute path." + (if (and (symbolp file) (boundp file)) + (expand-file-name (symbol-value file) org-directory) + file)) + (advice-add #'org-capture-expand-file :filter-args #'+org*capture-expand-variable-file) + + (defun +org*prevent-save-prompts-when-refiling (&rest _) + "Fix #462: when refiling from org-capture, Emacs prompts to kill the +underlying, modified buffer. This fixes that." + (when (bound-and-true-p org-capture-is-refiling) + (org-save-all-org-buffers))) + (advice-add 'org-refile :after #'+org*prevent-save-prompts-when-refiling) + + (defun +org|show-target-in-capture-header () + (setq header-line-format + (format "%s%s%s" + (propertize (abbreviate-file-name (buffer-file-name (buffer-base-buffer))) + 'face 'font-lock-string-face) + org-eldoc-breadcrumb-separator + header-line-format))) + (add-hook 'org-capture-mode-hook #'+org|show-target-in-capture-header) + + (when (featurep! :editor evil) + (add-hook 'org-capture-mode-hook #'evil-insert-state))) + + +(defun +org|init-capture-frame () + (add-hook 'org-capture-after-finalize-hook #'+org-capture|cleanup-frame) + + (when (featurep! :ui doom-dashboard) + (add-hook '+doom-dashboard-inhibit-functions #'+org-capture-frame-p))) + + +(defun +org|init-centralized-attachments () + "I believe Org's native attachment system is over-complicated and litters +files with metadata I don't want. So I wrote my own, which: + ++ Places attachments in a centralized location (`org-attach-directory' in + `org-directory'). ++ Adds attach:* link abbreviation for quick links to these files from anywhere. ++ Use `+org-attach/sync' to index all attachments in `org-directory' that use + the attach:* abbreviation and delete orphaned ones that are no longer + referenced. ++ This compliments the +dragndrop flag which provides drag'n'drop support for + images (with preview) and media files. + +Some commands of interest: ++ `org-download-screenshot' ++ `+org-attach/file' ++ `+org-attach/url' ++ `+org-attach/sync'" + (setq org-attach-directory (expand-file-name org-attach-directory org-directory)) + + ;; A shorter link to attachments + (add-to-list 'org-link-abbrev-alist (cons "attach" (abbreviate-file-name org-attach-directory))) + + (org-link-set-parameters + "attach" + :follow (lambda (link) (find-file (expand-file-name link org-attach-directory))) + :complete (lambda (&optional _arg) + (+org--relpath (+org-link-read-file "attach" org-attach-directory) + org-attach-directory)) + :face (lambda (link) + (if (file-exists-p (expand-file-name link org-attach-directory)) + 'org-link + 'error))) + + (after! projectile + (add-to-list 'projectile-globally-ignored-directories org-attach-directory)) + + (after! recentf + (add-to-list 'recentf-exclude + (lambda (file) (file-in-directory-p file org-attach-directory))))) + + +(defun +org|init-centralized-exports () + "TODO" + (defvar +org-enable-centralized-exports t + "If non-nil, files exported from files in `org-directory' will be stored in +`+org-export-directory', rather than the same directory has the input file(s).") + + (defvar +org-export-directory ".export/" + "Where to store exported files relative to `org-directory'. Can be an absolute +path too.") + + ;; I don't have any beef with org's built-in export system, but I do wish it + ;; would export to a central directory (by default), rather than + ;; `default-directory'. This is because all my org files are usually in one + ;; place, and I want to be able to refer back to old exports if needed. + (setq +org-export-directory (expand-file-name +org-export-directory org-directory)) + + (defun +org*export-output-file-name (args) + "Return a centralized export location unless one is provided or the current +file isn't in `org-directory'." + (when (and +org-enable-centralized-exports + (not (nth 2 args)) + buffer-file-name + (file-in-directory-p buffer-file-name org-directory)) + (cl-destructuring-bind (extension &optional subtreep _pubdir) args + (let ((dir (expand-file-name +org-export-directory org-directory))) + (unless (file-directory-p dir) + (make-directory dir t)) + (setq args (list extension subtreep dir))))) + args) + (advice-add #'org-export-output-file-name :filter-args #'+org*export-output-file-name)) + + +(defun +org|init-custom-links () + (defun +org--relpath (path root) + (if (and buffer-file-name (file-in-directory-p buffer-file-name root)) + (file-relative-name path) + path)) + + (defun +org-def-link (key dir) + (org-link-set-parameters + key + :complete (lambda () (+org--relpath (+org-link-read-file key dir) dir)) + :follow (lambda (link) (find-file (expand-file-name link dir))) + :face (lambda (link) + (if (file-exists-p (expand-file-name link dir)) + 'org-link + 'error)))) + + + ;; Highlight broken file links + (org-link-set-parameters + "file" + :face (lambda (path) + (if (or (file-remote-p path) + (file-exists-p path)) + 'org-link + 'error))) + + + ;; Add custom link types + (setq org-link-abbrev-alist + '(("github" . "https://github.com/%s") + ("youtube" . "https://youtube.com/watch?v=%s") + ("google" . "https://google.com/search?q=") + ("gimages" . "https://google.com/images?q=%s") + ("gmap" . "https://maps.google.com/maps?q=%s") + ("duckduckgo" . "https://duckduckgo.com/?q=%s") + ("wolfram" . "https://wolframalpha.com/input/?i=%s") + ("doom-repo" . "https://github.com/hlissner/doom-emacs/%s"))) + + (+org-def-link "org" org-directory) + (+org-def-link "doom" doom-emacs-dir) + (+org-def-link "doom-docs" doom-docs-dir) + (+org-def-link "doom-modules" doom-modules-dir) + + ;; Allow inline image previews of http(s)? urls or data uris + (org-link-set-parameters "http" :image-data-fun #'+org-image-link) + (org-link-set-parameters "https" :image-data-fun #'+org-image-link) + (org-link-set-parameters "img" :image-data-fun #'+org-inline-data-image) + + ;; Add support for youtube links + previews + (def-package! org-yt)) + + +(defun +org|init-export () + (when (featurep! :lang markdown) + (add-to-list 'org-export-backends 'md)) + + (when (featurep! :lang latex) + (add-to-list 'org-export-backends 'latex)) + + (def-package! ox-pandoc + :when (and (featurep! +pandoc) + (executable-find "pandoc")) + :after ox + :init + (add-to-list 'org-export-backends 'pandoc) + :config + (setq org-pandoc-options + '((standalone . t) + (mathjax . t) + (variable . "revealjs-url=https://cdn.jsdelivr.net/npm/reveal.js@3/"))))) + + +(defun +org|init-habit () + "TODO" + (defvar +org-habit-graph-padding 2 + "The padding added to the end of the consistency graph") + + (defvar +org-habit-min-width 30 + "Hides the consistency graph if the `org-habit-graph-column' is less than this value") + + (defvar +org-habit-graph-window-ratio 0.3 + "The ratio of the consistency graphs relative to the window width") + + (defun +org-habit|resize-graph() + "Right align and resize the consistency graphs based on +`+org-habit-graph-window-ratio'" + (require 'org-habit) + (let* ((total-days (float (+ org-habit-preceding-days org-habit-following-days))) + (preceding-days-ratio (/ org-habit-preceding-days total-days)) + (graph-width (floor (* (window-width) +org-habit-graph-window-ratio))) + (preceding-days (floor (* graph-width preceding-days-ratio))) + (following-days (- graph-width preceding-days)) + (graph-column (- (window-width) (+ preceding-days following-days))) + (graph-column-adjusted (if (> graph-column +org-habit-min-width) + (- graph-column +org-habit-graph-padding) + nil))) + (setq-local org-habit-preceding-days preceding-days) + (setq-local org-habit-following-days following-days) + (setq-local org-habit-graph-column graph-column-adjusted))) + (add-hook 'org-agenda-mode-hook #'+org-habit|resize-graph)) + + +(defun +org|init-hacks () + "Getting org to behave." + ;; Don't open separate windows + (setf (alist-get 'file org-link-frame-setup) #'find-file) + ;; Open directory links in Emacs + (add-to-list 'org-file-apps '(directory . emacs)) + + (defun +org|delayed-recenter () + "`recenter', but after a tiny delay. Necessary to prevent certain race +conditions where a window's buffer hasn't changed at the time this hook is run." + (run-at-time 0.1 nil #'recenter)) + (add-hook 'org-follow-link-hook #'+org|delayed-recenter) + + (defun +org*strip-properties-from-outline (orig-fn path &optional width prefix separator) + "Remove link syntax and fix variable height text (e.g. org headings) in the +eldoc string." + (let ((result (funcall orig-fn path width prefix separator)) + (separator (or separator "/"))) + (string-join + (cl-loop for part + in (split-string (substring-no-properties result) separator) + for n from 0 + for face = (nth (% n org-n-level-faces) org-level-faces) + collect + (org-add-props (replace-regexp-in-string org-any-link-re "\\4" part) + nil 'face `(:foreground ,(face-foreground face nil t) :weight bold))) + separator))) + (advice-add #'org-format-outline-path :around #'+org*strip-properties-from-outline) + + (defun +org|exclude-agenda-buffers-from-workspace () + "Prevent from temporarily-opened agenda buffers from being associated with +the current workspace." + (when (and org-agenda-new-buffers (bound-and-true-p persp-mode)) + (let (persp-autokill-buffer-on-remove) + (persp-remove-buffer org-agenda-new-buffers + (get-current-persp) + nil)))) + (add-hook 'org-agenda-finalize-hook #'+org|exclude-agenda-buffers-from-workspace) + + (defun +org*exclude-agenda-buffers-from-recentf (orig-fn file) + "Prevent temporarily opened agenda buffers from polluting recentf." + (let ((recentf-exclude (list (lambda (_file) t)))) + (funcall orig-fn file))) + (advice-add #'org-get-agenda-file-buffer + :around #'+org*exclude-agenda-buffers-from-recentf)) + + +(defun +org|init-keybinds () "Sets up org-mode and evil keybindings. Tries to fix the idiosyncrasies between the two." (add-hook 'doom-escape-hook #'+org|remove-occur-highlights) @@ -224,11 +487,8 @@ between the two." (map! :map org-mode-map ;; textmate-esque newline insertion - [C-return] (λ! (+org/insert-item 'below)) - [C-S-return] (λ! (+org/insert-item 'above)) - (:when IS-MAC - [s-return] (λ! (+org/insert-item 'below)) - [s-S-return] (λ! (+org/insert-item 'above))) + [C-return] #'+org/insert-item-below + [C-S-return] #'+org/insert-item-above "C-c C-S-l" #'+org/remove-link "C-c C-i" #'org-toggle-inline-images [remap doom/backward-to-bol-or-indent] #'org-beginning-of-line @@ -292,6 +552,8 @@ between the two." "a" #'org-table-align "e" #'org-table-edit-field "h" #'org-table-field-info + (:when (featurep! +gnuplot) + "p" #'org-plot/gnuplot) (:prefix ("i" . "insert") "-" #'org-table-insert-hline "h" #'+org/table-insert-column-left @@ -309,7 +571,13 @@ between the two." "e" #'org-table-edit-formulas "=" #'org-table-eval-formulas))) - (map! :map org-agenda-mode-map + ;; Fixes #1483: this messy hack fixes `org-agenda' or `evil-org-agenda' + ;; overriding SPC, breaking the localleader. TODO Improve me! + (define-minor-mode org-agenda-localleader-mode "TODO" + :keymap (make-sparse-keymap)) + (add-hook 'org-agenda-mode-hook #'org-agenda-localleader-mode) + + (map! :map org-agenda-localleader-mode-map :localleader "d" #'org-agenda-deadline "q" #'org-agenda-set-tags @@ -317,195 +585,167 @@ between the two." "s" #'org-agenda-schedule "t" #'org-agenda-todo)) -(defun +org|setup-evil-keybinds (&rest args) - (unless args ; lookout for recursive requires - (require 'evil-org)) - ;; Only fold the current tree, rather than recursively - (add-hook 'org-tab-first-hook #'+org|cycle-only-current-subtree t) +(defun +org|init-keybinds-for-evil (&rest args) + "TODO" + (when (featurep! :editor evil +everywhere) + (def-package! evil-org + :hook (org-mode . evil-org-mode) + :init + (defvar evil-org-key-theme '(navigation insert textobjects)) + (defvar evil-org-retain-visual-state-on-shift t) + (defvar evil-org-special-o/O '(table-row)) + (add-hook 'evil-org-mode-hook #'evil-normalize-keymaps) + :config + ;; change `evil-org-key-theme' instead + (advice-add #'evil-org-set-key-theme :override #'ignore)) - ;; Fix o/O creating new list items in the middle of nested plain lists. Only - ;; has an effect when `evil-org-special-o/O' has `item' in it (not the - ;; default). - (advice-add #'evil-org-open-below :around #'+org*evil-org-open-below) + (def-package! evil-org-agenda + :after org-agenda + :config (evil-org-agenda-set-keys)) - ;; Undo keybinds in `evil-collection-outline' - (map! :map outline-mode-map - :n "^" nil - :n [backtab] nil - :n "M-j" nil - :n "M-k" nil - :n "C-j" nil - :n "C-k" nil - :n "]" nil - :n "[" nil + ;; Only fold the current tree, rather than recursively + (add-hook 'org-tab-first-hook #'+org|cycle-only-current-subtree t) - :map evil-org-mode-map - :ni [C-return] (λ! (+org/insert-item 'below)) - :ni [C-S-return] (λ! (+org/insert-item 'above)) - (:when IS-MAC - :ni [s-return] (λ! (+org/insert-item 'below)) - :ni [s-S-return] (λ! (+org/insert-item 'above))) - ;; navigate table cells (from insert-mode) - :i "C-l" (general-predicate-dispatch 'org-end-of-line - (org-at-table-p) 'org-table-next-field) - :i "C-h" (general-predicate-dispatch 'org-beginning-of-line - (org-at-table-p) 'org-table-previous-field) - :i "C-k" (general-predicate-dispatch 'org-up-element - (org-at-table-p) '+org/table-previous-row) - :i "C-j" (general-predicate-dispatch 'org-down-element - (org-at-table-p) 'org-table-next-row) - ;; expanding tables (prepend/append columns/rows) - :ni "C-S-l" (general-predicate-dispatch 'org-shiftmetaright - (org-at-table-p) 'org-table-insert-column) - :ni "C-S-h" (general-predicate-dispatch 'org-shiftmetaleft - (org-at-table-p) '+org/table-insert-column-left) - :ni "C-S-k" (general-predicate-dispatch 'org-shiftmetaup - (org-at-table-p) 'org-table-insert-row) - :ni "C-S-j" (general-predicate-dispatch 'org-shiftmetadown - (org-at-table-p) '+org/table-insert-row-below) - ;; shifting table rows/columns - :ni "C-M-S-l" (general-predicate-dispatch 'org-metaright - (org-at-table-p) 'org-table-move-column-right) - :ni "C-M-S-h" (general-predicate-dispatch 'org-metaleft - (org-at-table-p) 'org-table-move-column-left) - :ni "C-M-S-k" (general-predicate-dispatch 'org-metaup - (org-at-table-p) 'org-table-move-row-up) - :ni "C-M-S-j" (general-predicate-dispatch 'org-metadown - (org-at-table-p) 'org-table-move-row-down) - ;; more intuitive RET keybinds - :i [return] #'org-return-indent - :i "RET" #'org-return-indent - :n [return] #'+org/dwim-at-point - :n "RET" #'+org/dwim-at-point - ;; more vim-esque org motion keys (not covered by evil-org-mode) - :m "]]" (λ! (org-forward-heading-same-level nil) (org-beginning-of-line)) - :m "[[" (λ! (org-backward-heading-same-level nil) (org-beginning-of-line)) - :m "]h" #'org-next-visible-heading - :m "[h" #'org-previous-visible-heading - :m "]l" #'org-next-link - :m "[l" #'org-previous-link - :m "]c" #'org-babel-next-src-block - :m "[c" #'org-babel-previous-src-block - :m "^" #'evil-org-beginning-of-line - :m "0" (λ! (let (visual-line-mode) (org-beginning-of-line))) - :n "gQ" #'org-fill-paragraph - ;; sensible vim-esque folding keybinds - :n "za" #'+org/toggle-fold - :n "zA" #'org-shifttab - :n "zc" #'+org/close-fold - :n "zC" #'outline-hide-subtree - :n "zm" #'+org/hide-next-fold-level - :n "zo" #'+org/open-fold - :n "zO" #'outline-show-subtree - :n "zr" #'+org/show-next-fold-level - :n "zR" #'outline-show-all - :n "zi" #'org-toggle-inline-images + ;; Fix o/O creating new list items in the middle of nested plain lists. Only + ;; has an effect when `evil-org-special-o/O' has `item' in it (not the + ;; default). + (advice-add #'evil-org-open-below :around #'+org*evil-org-open-below) - :map org-read-date-minibuffer-local-map - "C-h" (λ! (org-eval-in-calendar '(calendar-backward-day 1))) - "C-l" (λ! (org-eval-in-calendar '(calendar-forward-day 1))) - "C-k" (λ! (org-eval-in-calendar '(calendar-backward-week 1))) - "C-j" (λ! (org-eval-in-calendar '(calendar-forward-week 1))) - "C-S-h" (λ! (org-eval-in-calendar '(calendar-backward-month 1))) - "C-S-l" (λ! (org-eval-in-calendar '(calendar-forward-month 1))) - "C-S-k" (λ! (org-eval-in-calendar '(calendar-backward-year 1))) - "C-S-j" (λ! (org-eval-in-calendar '(calendar-forward-year 1))))) + (map! :map outline-mode-map + ;; Undo keybinds from `evil-collection-outline' + :n "^" nil + :n [backtab] nil + :n "M-j" nil + :n "M-k" nil + :n "C-j" nil + :n "C-k" nil + :n "]" nil + :n "[" nil + + :map evil-org-mode-map + :ni [C-return] #'+org/insert-item-below + :ni [C-S-return] #'+org/insert-item-above + ;; navigate table cells (from insert-mode) + :i "C-l" (general-predicate-dispatch 'org-end-of-line + (org-at-table-p) 'org-table-next-field) + :i "C-h" (general-predicate-dispatch 'org-beginning-of-line + (org-at-table-p) 'org-table-previous-field) + :i "C-k" (general-predicate-dispatch 'org-up-element + (org-at-table-p) '+org/table-previous-row) + :i "C-j" (general-predicate-dispatch 'org-down-element + (org-at-table-p) 'org-table-next-row) + ;; expanding tables (prepend/append columns/rows) + :ni "C-S-l" (general-predicate-dispatch 'org-shiftmetaright + (org-at-table-p) 'org-table-insert-column) + :ni "C-S-h" (general-predicate-dispatch 'org-shiftmetaleft + (org-at-table-p) '+org/table-insert-column-left) + :ni "C-S-k" (general-predicate-dispatch 'org-shiftmetaup + (org-at-table-p) 'org-table-insert-row) + :ni "C-S-j" (general-predicate-dispatch 'org-shiftmetadown + (org-at-table-p) '+org/table-insert-row-below) + ;; shifting table rows/columns + :ni "C-M-S-l" (general-predicate-dispatch 'org-metaright + (org-at-table-p) 'org-table-move-column-right) + :ni "C-M-S-h" (general-predicate-dispatch 'org-metaleft + (org-at-table-p) 'org-table-move-column-left) + :ni "C-M-S-k" (general-predicate-dispatch 'org-metaup + (org-at-table-p) 'org-table-move-row-up) + :ni "C-M-S-j" (general-predicate-dispatch 'org-metadown + (org-at-table-p) 'org-table-move-row-down) + ;; more intuitive RET keybinds + :i [return] #'org-return-indent + :i "RET" #'org-return-indent + :n [return] #'+org/dwim-at-point + :n "RET" #'+org/dwim-at-point + ;; more vim-esque org motion keys (not covered by evil-org-mode) + :m "]]" (λ! (org-forward-heading-same-level nil) (org-beginning-of-line)) + :m "[[" (λ! (org-backward-heading-same-level nil) (org-beginning-of-line)) + :m "]h" #'org-next-visible-heading + :m "[h" #'org-previous-visible-heading + :m "]l" #'org-next-link + :m "[l" #'org-previous-link + :m "]c" #'org-babel-next-src-block + :m "[c" #'org-babel-previous-src-block + :m "^" #'evil-org-beginning-of-line + :m "0" (λ! (let (visual-line-mode) (org-beginning-of-line))) + :n "gQ" #'org-fill-paragraph + ;; sensible vim-esque folding keybinds + :n "za" #'+org/toggle-fold + :n "zA" #'org-shifttab + :n "zc" #'+org/close-fold + :n "zC" #'outline-hide-subtree + :n "zm" #'+org/hide-next-fold-level + :n "zn" #'org-narrow-to-subtree + :n "zN" #'org-tree-to-indirect-buffer + :n "zo" #'+org/open-fold + :n "zO" #'outline-show-subtree + :n "zr" #'+org/show-next-fold-level + :n "zR" #'outline-show-all + :n "zi" #'org-toggle-inline-images + + :map org-read-date-minibuffer-local-map + "C-h" (λ! (org-eval-in-calendar '(calendar-backward-day 1))) + "C-l" (λ! (org-eval-in-calendar '(calendar-forward-day 1))) + "C-k" (λ! (org-eval-in-calendar '(calendar-backward-week 1))) + "C-j" (λ! (org-eval-in-calendar '(calendar-forward-week 1))) + "C-S-h" (λ! (org-eval-in-calendar '(calendar-backward-month 1))) + "C-S-l" (λ! (org-eval-in-calendar '(calendar-forward-month 1))) + "C-S-k" (λ! (org-eval-in-calendar '(calendar-backward-year 1))) + "C-S-j" (λ! (org-eval-in-calendar '(calendar-forward-year 1)))))) -(defun +org|setup-hacks () - "Getting org to behave." - ;; Don't open separate windows - (setf (alist-get 'file org-link-frame-setup) #'find-file) - ;; Open directory links in Emacs - (add-to-list 'org-file-apps '(directory . emacs)) - - (defun +org|delayed-recenter () - "`recenter', but after a tiny delay. Necessary to prevent certain race -conditions where a window's buffer hasn't changed at the time this hook is run." - (run-at-time 0.1 nil #'recenter)) - (add-hook 'org-follow-link-hook #'+org|delayed-recenter) - - ;; Fix variable height text (e.g. org headings) in the eldoc string - (defun +org*strip-properties-from-outline (orig-fn path &optional width prefix separator) - (let ((result (funcall orig-fn path width prefix separator)) - (separator (or separator "/"))) - (string-join - (cl-loop for part - in (split-string (substring-no-properties result) separator) - for n from 0 - for face = (nth (% n org-n-level-faces) org-level-faces) - collect - (org-add-props (replace-regexp-in-string org-any-link-re "\\4" part) - nil 'face `(:foreground ,(face-foreground face nil t) :weight bold))) - separator))) - (advice-add #'org-format-outline-path :around #'+org*strip-properties-from-outline) - - ;; Prevent from temporarily-opened agenda buffers from being associated with - ;; the current workspace, or being added to recentf. They haven't been opened - ;; interactively, so shouldn't be treated as if they were. - (defun +org|exclude-agenda-buffers-from-workspace () - (when (and org-agenda-new-buffers (bound-and-true-p persp-mode)) - (let (persp-autokill-buffer-on-remove) - (persp-remove-buffer org-agenda-new-buffers - (get-current-persp) - nil)))) - (add-hook 'org-agenda-finalize-hook #'+org|exclude-agenda-buffers-from-workspace) - - (defun +org*exclude-agenda-buffers-from-recentf (orig-fn file) - (let ((recentf-exclude (list (lambda (_file) t)))) - (funcall orig-fn file))) - (advice-add #'org-get-agenda-file-buffer - :around #'+org*exclude-agenda-buffers-from-recentf)) - - -;; -;;; Bootstrap - -(def-package! org - :defer-incrementally - (calendar find-func format-spec org-macs org-compat org-faces org-entities - org-list org-pcomplete org-src org-footnote org-macro ob org org-agenda - org-capture) - :init - (add-hook! 'org-mode-hook - #'(org-bullets-mode ; "prettier" bullets - org-indent-mode ; margin-based indentation - toc-org-enable ; auto-table of contents - auto-fill-mode ; line wrapping - ;; `show-paren-mode' causes flickering with indentation margins made by - ;; `org-indent-mode', so we turn off show-paren-mode altogether - doom|disable-show-paren-mode - ;; Shows a lot of false positives, so... - doom|disable-show-trailing-whitespace - - +org|enable-auto-reformat-tables - +org|enable-auto-update-cookies - +org|unfold-to-2nd-level-or-point)) - - :config - (+org|setup-ui) - (+org|setup-agenda) - (+org|setup-keybinds) - (+org|setup-hacks) - (+org|setup-custom-links) - - (add-hook 'org-open-at-point-functions #'doom|set-jump) - - ;; Cross-module configuration +(defun +org|init-popup-rules () (set-popup-rules! '(("^\\*Org Links" :slot -1 :vslot -1 :size 2 :ttl 0) ("^\\*\\(?:Agenda Com\\|Calendar\\|Org \\(?:Export Dispatcher\\|Select\\)\\)" :slot -1 :vslot -1 :size #'+popup-shrink-to-fit :ttl 0) ("^\\*Org Agenda" :size 0.35 :select t :ttl nil) ("^\\*Org Src" :size 0.3 :quit nil :select t :autosave t :ttl nil) - ("^CAPTURE.*\\.org$" :size 0.2 :quit nil :select t :autosave t))) + ("^CAPTURE.*\\.org$" :size 0.2 :quit nil :select t :autosave t)))) - (set-pretty-symbols! 'org-mode - :name "#+NAME:" - :src_block "#+BEGIN_SRC" - :src_block_end "#+END_SRC") +(defun +org|init-protocol-lazy-loader () + "Brings lazy-loaded support for org-protocol, so external programs (like +browsers) can invoke specialized behavior from Emacs. Normally you'd simply +require `org-protocol' and use it, but the package loads all of org for no +compelling reason, so..." + (defun +org*server-visit-files (args) + "Advise `server-visit-flist' to invoke `org-protocol' lazily." + (cl-destructuring-bind (files proc &optional nowait) args + (catch 'greedy + (let ((flist (reverse files))) + (dolist (var flist) + (when (string-match-p ":/+" (car var)) + (require 'server) + (require 'org-protocol) + ;; `\' to `/' on windows + (let ((fname (org-protocol-check-filename-for-protocol + (expand-file-name (car var)) + (member var flist) + proc))) + (cond ((eq fname t) ; greedy? We need the t return value. + (setq files nil) + (throw 'greedy t)) + ((stringp fname) ; probably filename + (setcar var fname)) + ((setq files (delq var files))))))))) + (list files proc nowait))) + (advice-add #'server-visit-files :filter-args #'+org*server-visit-files) + + ;; Disable built-in, clumsy advice + (after! org-protocol + (ad-disable-advice 'server-visit-files 'before 'org-protocol-detect-protocol-server))) + + +(defun +org|init-protocol () + ;; TODO org-board or better link grabbing support + ;; TODO org-capture + org-protocol instead of bin/org-capture + ) + + +(defun +org|init-smartparens () + "TODO" (after! smartparens (defun +org-sp-point-in-checkbox-p (_id action _context) (and (eq action 'insert) @@ -518,18 +758,120 @@ conditions where a window's buffer hasn't changed at the time this hook is run." ;; make delimiter auto-closing a little more conservative (sp-with-modes 'org-mode - (sp-local-pair "*" "*" :unless '(:add sp-point-before-word-p +org-sp-point-at-bol-p)) - (sp-local-pair "_" "_" :unless '(:add sp-point-before-word-p)) - (sp-local-pair "/" "/" :unless '(:add sp-point-before-word-p +org-sp-point-in-checkbox-p)) + (sp-local-pair "*" "*" :unless '(:add sp-point-before-word-p sp-in-math-p +org-sp-point-at-bol-p)) + (sp-local-pair "_" "_" :unless '(:add sp-point-before-word-p sp-in-math-p)) + (sp-local-pair "/" "/" :unless '(:add sp-point-before-word-p sp-in-math-p +org-sp-point-in-checkbox-p)) (sp-local-pair "~" "~" :unless '(:add sp-point-before-word-p)) - (sp-local-pair "=" "=" :unless '(:add sp-point-before-word-p)))) + (sp-local-pair "=" "=" :unless '(:add sp-point-before-word-p sp-in-math-p))))) - ;; Sub-modules - (if (featurep! +attach) (load! "+attach")) - (if (featurep! +babel) (load! "+babel")) - (if (featurep! +capture) (load! "+capture")) - (if (featurep! +export) (load! "+export")) - (if (featurep! +habit) (load! "+habit")) - (if (featurep! +present) (load! "+present")) - (if (featurep! +protocol) (load! "+protocol"))) +;; +;;; Bootstrap + +(def-package! org + :defer-incrementally + calendar find-func format-spec org-macs org-compat org-faces org-entities + org-list org-pcomplete org-src org-footnote org-macro ob org org-agenda + org-capture + :preface + ;; Change org defaults (should be set before org loads) + (defvar org-directory "~/org/") + (defvar org-attach-directory ".attach/") + (defvar org-publish-timestamp-directory (concat doom-cache-dir "org-timestamps/")) + + (defvar org-export-backends '(html ascii odt)) + (defvar org-modules + '(;; org-w3m + ;; org-bbdb + org-bibtex + ;; org-docview + ;; org-gnus + ;; org-info + ;; org-irc + ;; org-mhe + ;; org-rmail + )) + + (add-hook! 'org-mode-hook + #'(org-bullets-mode ; "prettier" bullets + org-indent-mode ; margin-based indentation + toc-org-enable ; auto-table of contents + auto-fill-mode ; hard line wrapping + ;; `show-paren-mode' causes flickering with indentation margins made by + ;; `org-indent-mode', so we turn off show-paren-mode altogether + doom|disable-show-paren-mode + ;; Shows a lot of false positives, so... + doom|disable-show-trailing-whitespace + + +org|enable-auto-reformat-tables + +org|enable-auto-update-cookies + +org|unfold-to-2nd-level-or-point)) + + (add-hook! 'org-load-hook + #'(+org|init-appearance + +org|init-agenda + +org|init-babel + +org|init-babel-lazy-loader + +org|init-capture-defaults + +org|init-capture-frame + +org|init-centralized-attachments + +org|init-centralized-exports + +org|init-custom-links + +org|init-habit + +org|init-hacks + +org|init-keybinds + +org|init-keybinds-for-evil ; will noop without :editor evil + +org|init-popup-rules + +org|init-protocol + +org|init-protocol-lazy-loader + +org|init-smartparens)) + + ;; In case the user has eagerly loaded org from their configs + (when (featurep 'org) + (message "`org' was already loaded by the time lang/org loaded, this may cause issues") + (run-hooks 'org-load-hook)) + + :config + (add-hook 'org-open-at-point-functions #'doom|set-jump) + + ;;; Custom org modules + (if (featurep! +dragndrop) (load! "contrib/dragndrop")) + (if (featurep! +present) (load! "contrib/present")) + + ;;; Packages + (after! toc-org + (setq toc-org-hrefify-default "gh") + (defun +org*unfold-toc (&rest _) + (save-excursion + (when (re-search-forward toc-org-toc-org-regexp (point-max) t) + (+org/open-fold)))) + (advice-add #'toc-org-insert-toc :before #'+org*unfold-toc)) + + (def-package! org-pdfview + :when (featurep! :tools pdf) + :commands (org-pdfview-open) + :init + (delete '("\\.pdf\\'" . default) org-file-apps) + ;; org links to pdf files are opened in pdf-view-mode + (add-to-list 'org-file-apps '("\\.pdf\\'" . (lambda (_file link) (org-pdfview-open link)))) + ;; support for links to specific pages + (add-to-list 'org-file-apps '("\\.pdf::\\([[:digit:]]+\\)\\'" . (lambda (_file link) (org-pdfview-open link))))) + + (def-package! org-crypt ; built-in + :commands org-encrypt-entries + :hook (org-reveal-start . org-decrypt-entry) + :init + (add-hook! 'org-mode-hook + (add-hook 'before-save-hook 'org-encrypt-entries nil t)) + :config + (setq org-tags-exclude-from-inheritance '("crypt") + org-crypt-key user-mail-address)) + + (def-package! org-clock ; built-in + :commands org-clock-save + :hook (org-mode . org-clock-load) + :init + (setq org-clock-persist 'history + org-clock-persist-file (concat doom-etc-dir "org-clock-save.el")) + :config + (add-hook 'kill-emacs-hook #'org-clock-save))) diff --git a/modules/lang/org/contrib/dragndrop.el b/modules/lang/org/contrib/dragndrop.el new file mode 100644 index 000000000..a80d0156f --- /dev/null +++ b/modules/lang/org/contrib/dragndrop.el @@ -0,0 +1,39 @@ +;;; lang/org/contrib/dragndrop.el -*- lexical-binding: t; -*- + +(def-package! org-download + :commands (org-download-dnd org-download-dnd-base64) + :init + ;; Add these manually so that org-download is lazy-loaded... + (add-to-list 'dnd-protocol-alist '("^\\(https?\\|ftp\\|file\\|nfs\\):" . +org-dragndrop-download-dnd)) + (add-to-list 'dnd-protocol-alist '("^data:" . org-download-dnd-base64)) + + (advice-add #'org-download-enable :override #'ignore) + :config + (setq org-download-image-dir org-attach-directory + org-download-heading-lvl nil + org-download-timestamp "_%Y%m%d_%H%M%S" + org-download-screenshot-method + (cond (IS-MAC "screencapture -i %s") + (IS-LINUX + (cond ((executable-find "maim") "maim -s %s") + ((executable-find "scrot") "scrot -s %s"))))) + + ;; Handle non-image files a little differently. Images should be inserted + ;; as-is, as image previews. Other files, like pdfs or zips, should be linked + ;; to, with an icon indicating the type of file. + (advice-add #'org-download-insert-link :override #'+org-dragndrop*insert-link) + + (defun +org-dragndrop*download-subdir () + (when (file-in-directory-p buffer-file-name org-directory) + (file-relative-name buffer-file-name org-directory))) + + (defun +org-dragndrop*download-fullname (path) + "Write PATH relative to current file." + (let ((dir (or (if buffer-file-name (file-name-directory buffer-file-name)) + default-directory))) + (if (file-in-directory-p dir org-directory) + (file-relative-name path dir) + path))) + (advice-add #'org-download--dir-2 :override #'ignore) + (advice-add #'org-download--fullname + :filter-return #'+org-dragndrop*download-fullname)) diff --git a/modules/lang/org/contrib/ipython.el b/modules/lang/org/contrib/ipython.el new file mode 100644 index 000000000..ec6d3d99d --- /dev/null +++ b/modules/lang/org/contrib/ipython.el @@ -0,0 +1,38 @@ +;;; lang/org/contrib/babel.el -*- lexical-binding: t; -*- + +(def-package! ob-ipython + :defer t + :init + (defvar +ob-ipython-local-runtime-dir nil) + + (setq ob-ipython-resources-dir ".ob-ipython-resrc") + + (defun +org|babel-load-ipython (lang) + (and (string-prefix-p "jupyter-" (symbol-name lang)) + (require 'ob-ipython nil t))) + (add-hook '+org-babel-load-functions #'+org|babel-load-ipython) + :config + (set-popup-rules! + '(("\\*ob-ipython.*" + :slot 2 :side right :size 100 :height 0.2 + :select nil :quit nil :transient nil) + ("^\\*Python" + :slot 0 :side right :size 100 + :select nil :quit nil :ttl nil) + ("\\*Python:.*" + :slot 0 :side right :size 100 + :select nil :quit nil :transient nil))) + + ;; advices for remote kernel and org-src-edit + (advice-add 'ob-ipython--create-repl :override #'+org*ob-ipython--create-repl) + (advice-add 'org-babel-edit-prep:ipython :override #'+org*org-babel-edit-prep:ipython) + (advice-add 'org-babel-execute:ipython :override #'+org*org-babel-execute:ipython) + (advice-add 'org-babel-ipython-initiate-session :override #'+org*org-babel-ipython-initiate-session) + + ;; retina resolution image hack + (when (eq window-system 'ns) + (advice-add 'ob-ipython--write-base64-string :around #'+org*ob-ipython--write-base64-string)) + + ;; ipython has its own async keyword, disable ipython in ob-async. + (after! ob-async + (add-to-list 'ob-async-no-async-languages-alist "ipython"))) diff --git a/modules/lang/org/+present.el b/modules/lang/org/contrib/present.el similarity index 89% rename from modules/lang/org/+present.el rename to modules/lang/org/contrib/present.el index bc6938a4f..80a8e5b27 100644 --- a/modules/lang/org/+present.el +++ b/modules/lang/org/contrib/present.el @@ -1,11 +1,14 @@ -;;; lang/org/+present.el -*- lexical-binding: t; -*- +;;; lang/org/contrib/present.el -*- lexical-binding: t; -*- (defvar +org-present-text-scale 7 "The `text-scale-amount' for `org-tree-slide-mode'.") +(after! ox + (add-to-list 'org-export-backends 'beamer)) + ;; -;; Packages +;;; Packages (def-package! ox-reveal :after ox diff --git a/modules/lang/org/packages.el b/modules/lang/org/packages.el index ea34a43b2..2697d0896 100644 --- a/modules/lang/org/packages.el +++ b/modules/lang/org/packages.el @@ -1,60 +1,54 @@ ;; -*- no-byte-compile: t; -*- ;;; lang/org/packages.el -;; Installs a cutting-edge version of org-mode -(package! org-plus-contrib) - ;; Prevent built-in Org from playing into the byte-compilation of ;; `org-plus-contrib'. -(when-let* ((orglib (locate-library "org" nil doom-site-load-path))) +(when-let (orglib (locate-library "org" nil doom-site-load-path)) (setq load-path (delete (substring (file-name-directory orglib) 0 -1) load-path))) -;; Ignore org on ELPA, if possible -(package! org :ignore t) +(package! org-plus-contrib) ; install cutting-edge version of org-mode +(package! org :ignore t) ; ignore org on ELPA, if possible (package! org-bullets :recipe (:fetcher github :repo "Kaligule/org-bullets")) -(package! org-yt :recipe (:fetcher github :repo "TobiasZawada/org-yt")) (package! toc-org) - (when (featurep! :editor evil) (package! evil-org)) - (when (featurep! :tools pdf) (package! org-pdfview)) +(package! htmlize) +(package! ox-clip) +(package! org-yt :recipe (:fetcher github :repo "TobiasZawada/org-yt")) -(when (featurep! +attach) +;;; Babel +(package! ob-async) +(when (featurep! :lang crystal) + (package! ob-crystal)) +(when (featurep! :lang go) + (package! ob-go)) +(when (featurep! :lang nim) + (package! ob-nim)) +(when (featurep! :lang racket) + (package! ob-racket :recipe (:fetcher github :repo "DEADB17/ob-racket"))) +(when (featurep! :lang rest) + (package! ob-restclient)) +(when (featurep! :lang rust) + (package! ob-rust)) + +;;; Modules +(when (featurep! +dragndrop) (package! org-download)) -(when (featurep! +babel) - (package! ob-async) - (package! ob-mongo) - (package! ob-sql-mode) - (package! ob-translate) +(when (featurep! +gnuplot) + (package! gnuplot) + (package! gnuplot-mode)) - (when (featurep! +ipython) - (package! ob-ipython)) +(when (featurep! +ipython) + (package! ob-ipython)) - (when (featurep! :lang crystal) - (package! ob-crystal)) - (when (featurep! :lang go) - (package! ob-go)) - (when (featurep! :lang nim) - (package! ob-nim)) - (when (featurep! :lang racket) - (package! ob-racket :recipe (:fetcher github :repo "DEADB17/ob-racket"))) - (when (featurep! :lang rest) - (package! ob-restclient)) - (when (featurep! :lang rust) - (package! ob-rust))) - -(when (featurep! +export) - (package! ox-clip) - (package! ox-pandoc) - (package! htmlize)) +(when (featurep! +pandoc) + (package! ox-pandoc)) (when (featurep! +present) (package! centered-window :recipe (:fetcher github :repo "anler/centered-window-mode")) (package! org-tree-slide) (package! ox-reveal)) - -;; (when (featurep! +publish))