Moving to Doom Emacs!

This commit is contained in:
Derek Taylor
2019-12-16 20:21:19 -06:00
parent d9f2f456f1
commit d4b4c33550
683 changed files with 51877 additions and 100 deletions

View File

@@ -0,0 +1,210 @@
#+TITLE: lang/org
#+DATE: February 19, 2017
#+SINCE: 2.0
#+STARTUP: inlineimages
* 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]]
- [[#changing-org-directory][Changing ~org-directory~]]
* 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.
+ =+pomodoro= Enables a pomodoro timer for clocking time on tasks.
+ =+present= Enables integration with reveal.js, beamer and org-tree-slide, so
Emacs can be used for presentations.
+ =+hugo= Enables integration with [[https://gohugo.io][hugo]] to export from Emacs well-formed
([[https://github.com/russross/blackfriday][blackfriday]]) markdown.
** 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]]
+ =+gnuplot=
+ [[https://github.com/mkmcc/gnuplot-mode][gnuplot]]
+ [[https://github.com/bruceravel/gnuplot-mode][gnuplot-mode]]
+ =+ipython=
+ [[https://github.com/gregsexton/ob-ipython][ob-ipython]]
+ =+pandoc=
+ [[https://github.com/kawabata/ox-pandoc][ox-pandoc]]
+ =+pomodoro=
+ [[https://github.com/marcinkoziej/org-pomodoro][org-pomodoro]]
+ =+present=
+ [[https://github.com/anler/centered-window-mode][centered-window]]
+ [[https://github.com/takaxp/org-tree-slide][org-tree-slide]]
+ [[https://gitlab.com/oer/org-re-reveal][org-re-reveal]]
+ =+hugo=
+ [[https://github.com/kaushalmodi/ox-hugo][ox-hugo]]
** 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! evil-org
(remove-hook 'org-tab-first-hook #'+org-cycle-only-current-subtree-h))
#+END_SRC
+ (Evil users) Nearby tables are formatted when exiting insert or replace mode
(see ~+org-enable-auto-reformat-tables-h~).
+ Statistics cookies are updated when saving the buffer of exiting insert mode
(see ~+org-enable-auto-update-cookies-h~).
+ Org-protocol has been lazy loaded (see ~+org-init-protocol-lazy-loader-h~);
loaded when the server receives a request for an org-protocol:// url.
+ Babel and babel plugins are now lazy loaded (see
~+org-init-babel-lazy-loader-h~); 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-a~).
* 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~.
+ 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 gnuplot
#+END_SRC
** Arch Linux
#+BEGIN_SRC sh
pacman -S texlive-core texlive-bin texlive-science
pacman -S gnuplot
#+END_SRC
** NixOS
#+BEGIN_SRC nix
environment.systemPackages = with pkgs; [
# any less than medium isn't guaranteed to work
texlive.combined.scheme-medium
];
#+END_SRC
** 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-h~.
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
** Changing ~org-directory~
To modify ~org-directory~ it must be set /before/ =org= has loaded:
#+BEGIN_SRC emacs-lisp
;; ~/.doom.d/config.el
(setq org-directory "~/new/org/location/")
#+END_SRC

View File

@@ -0,0 +1,12 @@
;;; lang/org/autoload/contrib-dragndrop.el -*- lexical-binding: t; -*-
;;;###if (featurep! +dragndrop)
;;;###autoload
(defun +org-dragndrop-download-dnd-fn (uri action)
"Handle file links and base64 data uris."
(if (eq major-mode 'org-mode)
(+org-attach/uri uri)
(let ((dnd-protocol-alist
(rassq-delete-all '+org-attach-download-dnd
(copy-alist dnd-protocol-alist))))
(dnd-handle-one-url nil action uri))))

View File

@@ -0,0 +1,152 @@
;;; lang/org/autoload/contrib-ipython.el -*- lexical-binding: t; -*-
;;;###if (featurep! +ipython)
;;;###autoload
(defun +org-ob-ipython-initiate-session-a (&optional session params)
"Create a session named SESSION according to PARAMS."
(if (string= session "none")
(error
"ob-ipython currently only supports evaluation using a session.
Make sure your src block has a :session param.")
(when (not (string-suffix-p ".json" session t))
(ob-ipython--create-kernel
(ob-ipython--normalize-session
session)
(cdr (assoc :kernel params))))
(ob-ipython--create-repl
(ob-ipython--normalize-session
session)
params)))
(defun +org--ob-ipython-generate-local-path-from-remote (session host params)
"Given a remote SESSION with PARAMS and corresponding HOST, copy remote config to local, start a jupyter console to generate a new one."
(let* ((runtime-dir
(cdr
(doom-call-process "ssh " host "jupyter" "--runtime-dir")))
(runtime-file (concat runtime-dir "/" "kernel-" session ".json"))
(tramp-path (concat "/ssh:" host ":" runtime-file))
(tramp-copy (concat (or +ob-ipython-local-runtime-dir
(cdr (doom-call-process "jupyter" "--runtime-dir")))
"/remote-" host "-kernel-" session ".json"))
(local-path
(concat
"Python:ob-ipython-"
(file-name-sans-extension (file-name-nondirectory tramp-copy))
"-ssh.json")))
;; scp remote file to local
(copy-file tramp-path tramp-copy t)
;; connect to remote use new config
(let* ((python-shell-interpreter-interactive-arg " console --simple-prompt")
(python-shell-prompt-detect-enabled nil)
(python-shell-completion-native-enable nil)
(buf (python-shell-make-comint
(concat ob-ipython-command
" console --simple-prompt --existing "
tramp-copy " --ssh " host)
(concat "" local-path)
t))
(proc (get-buffer-process buf))
(dir (cdr (assoc :pydir params))))
(sleep-for 3)
(when dir
(with-current-buffer buf
(setq-local default-directory dir)))
(format "*%s*" proc))))
;;;###autoload
(defun +org-ob-ipython-create-repl-a (name &optional params)
"Create repl based on NAME and PARAMS.
If PARAMS specifies remote kernel, copy the kernel config from remote server and
create a repl connecting to remote session."
(let ((cmd (string-join (ob-ipython--kernel-repl-cmd name) " ")))
(cond ((string= "default" name)
(run-python cmd nil nil)
(format "*%s*" python-shell-buffer-name))
((string-match "^remote-.*ssh.json" name)
(when (not (ignore-errors
(process-live-p
(get-process
(format
"Python:ob-ipython-%s"
name)))))
(let* ((remote (s-split "-" name))
(remote-host (nth 1 remote))
(remote-session (nth 3 remote)))
(+org--ob-ipython-generate-local-path-from-remote
remote-session
remote-host
params))))
((let* ((process-name (format "Python:ob-ipython-%s" name))
(python-shell-prompt-detect-enabled nil)
(python-shell-completion-native-enable nil)
(buf (python-shell-make-comint cmd process-name t))
(dir (cdr (assoc :pydir params))))
(if dir
(with-current-buffer buf
(setq-local default-directory dir)))
(sleep-for 1)
(format "*%s*" process-name))))))
;;;###autoload
(defun +org-babel-execute:ipython-a (body params)
"Execute a BODY of IPython code with PARAMS in org-babel.
This function is called by `org-babel-execute-src-block'."
(message default-directory)
(org-babel-ipython-initiate-session (cdr (assoc :session params))
params))
;;
;; * org-src-edit
;;;###autoload
(defun +org-babel-edit-prep:ipython-a (info)
(let* ((params (nth 2 info))
(session (cdr (assoc :session params))))
(org-babel-ipython-initiate-session session params))
;; Support for python.el's "send-code" commands within edit buffers.
(setq-local python-shell-buffer-name
(format "Python:ob-ipython-%s"
(ob-ipython--normalize-session
(cdr (assoc :session (nth 2 info))))))
(setq-local default-directory
(format "%s"
(ob-ipython--normalize-session
(cdr (assoc :pydir (nth 2 info))))))
(ob-ipython-mode 1)
;; hack on company mode to use company-capf rather than company-anaconda
(when (featurep! :completion company)
(setq-local company-backends
'(company-capf
company-dabbrev
company-files
company-yasnippet))
(setq-local company-idle-delay nil))
(when (featurep 'lpy)
(setq lispy-python-proc
(format "Python:ob-ipython-%s"
(ob-ipython--normalize-session
(cdr (assoc :session (nth 2 info)))))
lispy--python-middleware-loaded-p nil)
(lispy--python-middleware-load)))
;;
;; * retina
(defun +org--ob-ipython-mac-2x-image-file-name (filename &optional scale)
"Return the name of high-resolution image file for FILENAME.
The optional arg SCALE is scale factor, and defaults to 2."
(let ((pos (or (string-match "\\.[^./]*\\'" filename) (length filename))))
(format "%s@%dx%s"
(substring filename 0 pos)
(or scale 2)
(substring filename pos))))
;;;###autoload
(defun +org-ob-ipython-write-base64-string-a (oldfunc &rest args)
(let ((file (car args))
(b64-string (cdr args)))
(let ((file2x (+org--ob-ipython-mac-2x-image-file-name file)))
(apply oldfunc file2x b64-string)
(shell-command (concat "convert " file2x " -resize 50% " file)))))

View File

@@ -0,0 +1,97 @@
;;; lang/org/autoload/contrib-present.el -*- lexical-binding: t; -*-
;;;###if (featurep! +present)
;;
;;; Helpers
(defun +org-present--cleanup-org-tree-slides-mode ()
(unless (cl-loop for buf in (doom-buffers-in-mode 'org-mode)
if (buffer-local-value 'org-tree-slide-mode buf)
return t)
(org-tree-slide-mode -1)
(remove-hook 'kill-buffer-hook #'+org-present--cleanup-org-tree-slides-mode)))
(defun +org-present--make-invisible (beg end)
(let ((overlay (make-overlay beg end)))
(push overlay +org-present--overlays)
(overlay-put overlay 'invisible '+org-present)))
;;
;;; Hooks
;;;###autoload
(defun +org-present-add-overlays-h ()
"TODO"
(add-to-invisibility-spec '(+org-present))
(save-excursion
;; hide org-mode options starting with #+
(goto-char (point-min))
(while (re-search-forward "^[[:space:]]*\\(#\\+\\)\\(\\(?:BEGIN\\|END\\|ATTR\\)[^[:space:]]+\\).*" nil t)
(+org-present--make-invisible
(match-beginning 1)
(match-end 0)))
;; hide stars in headings
(goto-char (point-min))
(while (re-search-forward "^\\(\\*+\\s-\\)" nil t)
(+org-present--make-invisible (match-beginning 1) (match-end 1)))))
;;;###autoload
(defun +org-present-remove-overlays-h ()
"TODO"
(mapc #'delete-overlay +org-present--overlays)
(remove-from-invisibility-spec '(+org-present)))
;;;###autoload
(defun +org-present-detect-slide-h ()
"TODO"
(outline-show-all)
(if (member "title" (org-get-tags))
(text-scale-set 10)
(text-scale-set +org-present-text-scale)))
(defvar cwm-use-vertical-padding)
(defvar cwm-frame-internal-border)
(defvar cwm-left-fringe-ratio)
(defvar cwm-centered-window-width)
;;;###autoload
(defun +org-present-init-org-tree-window-h ()
"TODO"
"Set up the org window for presentation."
(doom/window-maximize-buffer)
(let ((arg (if org-tree-slide-mode +1 -1)))
(when (fboundp 'centered-window-mode)
(let ((cwm-use-vertical-padding t)
(cwm-frame-internal-border 110)
(cwm-left-fringe-ratio -10)
(cwm-centered-window-width 240))
(centered-window-mode arg)))
(window-divider-mode (* arg -1))
(hide-mode-line-mode arg)
(+org-pretty-mode arg)
(cond (org-tree-slide-mode
(org-indent-mode -1)
(text-scale-set +org-present-text-scale)
(ignore-errors (org-latex-preview '(4)))
(set-face-attribute 'org-level-2 nil :height 1.4))
(t
(org-indent-mode +1)
(text-scale-set 0)
(org-clear-latex-preview)
(set-face-attribute 'org-level-2 nil :height 1.0)
(+org-present-remove-overlays-h)
(org-remove-inline-images)))))
;;
;;; Commands
(defvar +org-present--overlays nil)
;;;###autoload
(defun +org-present/start ()
"TODO"
(interactive)
(unless (derived-mode-p 'org-mode)
(error "Not in an org buffer"))
(call-interactively #'org-tree-slide-mode)
(add-hook 'kill-buffer-hook #'+org-present--cleanup-org-tree-slides-mode))

View File

@@ -0,0 +1,101 @@
;;; lang/org/autoload/org-attach.el -*- lexical-binding: t; -*-
;;
(defvar +org-attachments nil
"A list of all indexed attachments in `org-directory'.")
(defvar +org-attachments-files nil
"A list of all attachments in `org-attach-id-dir'.")
(defun +org-list-attachments (&optional beg end)
"Return a list of all attachment file names in the current buffer between BEG
and END (defaults to `point-min' and `point-max')."
(let ((case-fold-search t)
attachments)
(or end (setq end (point-max)))
(org-save-outline-visibility nil
(org-with-wide-buffer
(goto-char (or beg (point-min)))
(while (search-forward "[[attach:" end t)
(let* ((context (save-match-data (org-element-context)))
(link (expand-file-name (org-link-unescape (org-element-property :path context))
org-attach-id-dir)))
(when (and (equal "file" (org-element-property :type context))
(file-in-directory-p link org-attach-id-dir))
(push (file-name-nondirectory link) attachments))))))
(cl-delete-duplicates attachments :test #'string=)))
;;;###autoload
(defun +org-attach-icon-for (path)
(char-to-string
(pcase (downcase (file-name-extension path))
((or "jpg" "jpeg" "png" "gif") ?)
("pdf" ?)
((or "ppt" "pptx") ?)
((or "xls" "xlsx") ?)
((or "doc" "docx") ?)
((or "ogg" "mp3" "wav" "aiff" "flac") ?)
((or "mp4" "mov" "avi") ?)
((or "zip" "gz" "tar" "7z" "rar") ?)
(_ ?))))
;;;###autoload
(defun +org-attach/sync (arg)
"Reindex all attachments in `org-directory' and delete orphaned attachments in
`org-attach-id-dir'. If ARG (universal arg), conduct a dry run."
(declare (interactive-only t))
(interactive "P")
(message "Reloading")
(setq +org-attachments-files (directory-files org-attach-id-dir nil "^[^.]" t))
(with-temp-buffer
(delay-mode-hooks (org-mode))
(dolist (org-file (directory-files-recursively org-directory "\\.org$"))
(insert-file-contents-literally org-file))
(setq +org-attachments (+org-list-attachments)))
;; clean up
(let ((deleted 0))
(dolist (file (cl-set-difference +org-attachments-files +org-attachments
:test #'string=))
(message "Deleting orphaned attachment: %s" file)
(cl-incf deleted)
(unless arg
(delete-file (expand-file-name file org-attach-id-dir))))
(message "Buffer's attachments synced (%d deleted)" deleted)))
;;;###autoload
(defun +org-attach/find-file ()
"Open a file from `org-attach-id-dir'."
(interactive)
(doom-project-browse org-attach-id-dir))
;;;###autoload
(defun +org-attach/file (path)
"Copies the file at PATH to `+org-attach-dir' and places an org link to it at
the cursor."
(interactive "fAttach file: ")
(+org-attach/uri path))
;;;###autoload
(defun +org-attach/uri (uri)
"Downloads the file at URL and place an org link to it at the cursor."
(interactive "sUri/file: ")
(unless (eq major-mode 'org-mode)
(user-error "Not in an org buffer"))
(require 'org-download)
(let ((raw-uri (url-unhex-string uri)))
(condition-case ex
(cond ((string-match-p "^data:image/png;base64," uri)
(org-download-dnd-base64 uri nil))
((image-type-from-file-name raw-uri)
(org-download-image raw-uri))
(t
(let ((new-path (expand-file-name (org-download--fullname raw-uri))))
;; Download the file
(if (string-match-p (concat "^" (regexp-opt '("http" "https" "nfs" "ftp" "file")) ":/") uri)
(url-copy-file raw-uri new-path)
(copy-file uri new-path))
;; insert the link
(org-download-insert-link raw-uri new-path))))
(error
(user-error "Failed to attach file: %s" (error-message-string ex))))))

View File

@@ -0,0 +1,19 @@
;;; lang/org/autoload/org-avy.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +org-headline-avy ()
"TODO"
(save-excursion
(when-let* ((org-reverse-note-order t)
(pos (avy-with avy-goto-line (avy-jump (rx bol (1+ "*") (1+ blank))))))
(when (integerp (car pos))
;; If avy is aborted with "C-g", it returns `t', so we know it was NOT
;; aborted when it returns an int. If it doesn't return an int, we
;; return nil.
(copy-marker (car pos))))))
;;;###autoload
(defun +org/goto-visible ()
"TODO"
(interactive)
(goto-char (+org-headline-avy)))

View File

@@ -0,0 +1,163 @@
;;; lang/org/autoload/org-capture.el -*- lexical-binding: t; -*-
(defvar org-capture-initial)
;;
;;; External frame
;;;###autoload
(defvar +org-capture-frame-parameters
`((name . "org-capture")
(width . 70)
(height . 25)
(transient . t)
,(if IS-LINUX '(display . ":0")))
"TODO")
;;;###autoload
(defun +org-capture-cleanup-frame-h ()
"Closes the org-capture frame once done adding an entry."
(when (+org-capture-frame-p)
(delete-frame nil t)))
;;;###autoload
(defun +org-capture-frame-p (&rest _)
"Return t if the current frame is an org-capture frame opened by
`+org-capture/open-frame'."
(and (equal "org-capture" (frame-parameter nil 'name))
(frame-parameter nil 'transient)))
;;;###autoload
(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 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 "")
(frame (if (+org-capture-frame-p)
(selected-frame)
(make-frame +org-capture-frame-parameters))))
(select-frame-set-input-focus frame) ; fix MacOS not focusing new frames
(with-selected-frame frame
(require 'org-capture)
(condition-case ex
(cl-letf (((symbol-function #'pop-to-buffer)
(symbol-function #'switch-to-buffer)))
(switch-to-buffer (doom-fallback-buffer))
(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)))
(if (or org-capture-entry
(not (fboundp 'counsel-org-capture)))
(org-capture)
(unwind-protect
(counsel-org-capture)
(if-let (buf (cl-find-if (doom-partial #'buffer-local-value 'org-capture-mode)
(buffer-list)))
(with-current-buffer buf
(add-hook 'kill-buffer-hook #'+org-capture-cleanup-frame-h nil t))
(delete-frame frame))))))
('error
(message "org-capture: %s" (error-message-string ex))
(delete-frame frame))))))
;;;###autoload
(defun +org-capture-available-keys ()
"TODO"
(string-join (mapcar #'car org-capture-templates) ""))
;;
;;; Capture targets
;;;###autoload
(defun +org-capture-todo-file ()
"Expand `+org-capture-todo-file' from `org-directory'.
If it is an absolute path return `+org-capture-todo-file' verbatim."
(expand-file-name +org-capture-todo-file org-directory))
;;;###autoload
(defun +org-capture-notes-file ()
"Expand `+org-capture-notes-file' from `org-directory'.
If it is an absolute path return `+org-capture-todo-file' verbatim."
(expand-file-name +org-capture-notes-file org-directory))
(defun +org--capture-local-root (path)
(let ((filename (file-name-nondirectory path)))
(expand-file-name
filename
(or (locate-dominating-file (file-truename default-directory)
filename)
(doom-project-root)
(user-error "Couldn't detect a project")))))
;;;###autoload
(defun +org-capture-project-todo-file ()
"Find the nearest `+org-capture-todo-file' in a parent directory, otherwise,
opens a blank one at the project root. Throws an error if not in a project."
(+org--capture-local-root +org-capture-todo-file))
;;;###autoload
(defun +org-capture-project-notes-file ()
"Find the nearest `+org-capture-notes-file' in a parent directory, otherwise,
opens a blank one at the project root. Throws an error if not in a project."
(+org--capture-local-root +org-capture-notes-file))
;;;###autoload
(defun +org-capture-project-changelog-file ()
"Find the nearest `+org-capture-changelog-file' in a parent directory,
otherwise, opens a blank one at the project root. Throws an error if not in a
project."
(+org--capture-local-root +org-capture-changelog-file))
(defun +org--capture-ensure-heading (headings &optional initial-level)
(if (not headings)
(widen)
(let ((initial-level (or initial-level 1)))
(if (and (re-search-forward (format org-complex-heading-regexp-format
(regexp-quote (car headings)))
nil t)
(= (org-current-level) initial-level))
(progn
(beginning-of-line)
(org-narrow-to-subtree))
(goto-char (point-max))
(unless (and (bolp) (eolp)) (insert "\n"))
(insert (make-string initial-level ?*)
" " (car headings) "\n")
(beginning-of-line 0))
(+org--capture-ensure-heading (cdr headings) (1+ initial-level)))))
(defun +org--capture-central-file (file project)
(let ((file (expand-file-name file org-directory)))
(set-buffer (org-capture-target-buffer file))
(org-capture-put-target-region-and-position)
(widen)
(goto-char (point-min))
;; Find or create the project headling
(+org--capture-ensure-heading
(append (org-capture-get :parents)
(list project (org-capture-get :heading))))))
;;;###autoload
(defun +org-capture-central-project-todo-file ()
"TODO"
(+org--capture-central-file
+org-capture-todo-file (projectile-project-name)))
;;;###autoload
(defun +org-capture-central-project-notes-file ()
"TODO"
(+org--capture-central-file
+org-capture-notes-file (projectile-project-name)))
;;;###autoload
(defun +org-capture-central-project-changelog-file ()
"TODO"
(+org--capture-central-file
+org-capture-changelog-file (projectile-project-name)))

View File

@@ -0,0 +1,47 @@
;;; lang/org/autoload/org-export.el -*- lexical-binding: t; -*-
(defun +org--yank-html-buffer (buffer)
(with-current-buffer buffer
(require 'ox-clip)
(cond ((or IS-WINDOWS IS-MAC)
(shell-command-on-region
(point-min)
(point-max)
(cond (IS-WINDOWS ox-clip-w32-cmd)
(IS-MAC ox-clip-osx-cmd))))
(IS-LINUX
(let ((html (buffer-string)))
(with-temp-file (make-temp-file "ox-clip-md" nil ".html")
(insert html))
(apply #'start-process "ox-clip" "*ox-clip*"
(split-string ox-clip-linux-cmd " ")))))))
;;
;;; Commands
;;;###autoload
(defun +org/export-to-clipboard (backend)
"Exports the current buffer/selection to the clipboard.
Prompts for what BACKEND to use. See `org-export-backends' for options."
(interactive
(list (intern (completing-read "Export to: " org-export-backends))))
(let ((buffer (org-export-to-buffer backend "*Formatted Copy*" nil nil t t)))
(unwind-protect
(with-current-buffer buffer
(kill-new (buffer-string)))
(kill-buffer (current-buffer)))))
;;;###autoload
(defun +org/export-to-clipboard-as-rich-text (beg end)
"Export the current buffer to HTML then copies it to clipboard as rich text.
Supports org-mode, markdown-mode, and gfm-mode buffers. In any other mode,
htmlize is used (takes what you see in Emacs and converts it to html, text
properties and font-locking et all)."
(interactive "r")
(pcase major-mode
((or `markdown-mode `gfm-mode)
(+org--yank-html-buffer (markdown)))
(_ (ox-clip-formatted-copy beg end))))

View File

@@ -0,0 +1,49 @@
;;; lang/org/autoload/org-link.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +org-link-read-file (key dir)
(let ((file (read-file-name (format "%s: " (capitalize key)) dir)))
(format "%s:%s"
key
(file-relative-name file dir))))
;;;###autoload
(defun +org-link-read-directory (key dir)
(let ((file (read-directory-name (format "%s: " (capitalize key)) dir)))
(format "%s:%s"
key
(file-relative-name file dir))))
;;;###autoload
(defun +org-inline-data-image (_protocol link _description)
"Interpret LINK as base64-encoded image data."
(base64-decode-string link))
;;;###autoload
(defun +org-image-link (protocol link _description)
"Interpret LINK as base64-encoded image data."
(when (image-type-from-file-name link)
(if-let* ((buf (url-retrieve-synchronously (concat protocol ":" link))))
(with-current-buffer buf
(goto-char (point-min))
(re-search-forward "\r?\n\r?\n" nil t)
(buffer-substring-no-properties (point) (point-max)))
(message "Download of image \"%s\" failed" link)
nil)))
;;
;;; Commands
;;;###autoload
(defun +org/remove-link ()
"Unlink the text at point."
(interactive)
(unless (org-in-regexp org-link-bracket-re 1)
(user-error "No link at point"))
(save-excursion
(let ((label (if (match-end 2)
(match-string-no-properties 2)
(org-link-unescape (match-string-no-properties 1)))))
(delete-region (match-beginning 0) (match-end 0))
(insert label))))

View File

@@ -0,0 +1,83 @@
;;; lang/org/autoload/org-refile.el -*- lexical-binding: t; -*-
;; REVIEW These are all proof-of-concept. Refactor me!
;;;###autoload
(defun +org/refile-to-current-file (arg)
"TODO"
(interactive "P")
(let ((org-refile-targets `((nil :maxlevel . 10)))
(org-refile-use-outline-path nil)
(org-refile-keep arg)
current-prefix-arg)
(call-interactively #'org-refile)))
;;;###autoload
(defun +org/refile-to-other-window (arg)
"TODO"
(interactive "P")
(let ((org-refile-keep arg)
org-refile-targets
current-prefix-arg)
(dolist (win (delq (selected-window) (window-list)))
(with-selected-window win
(and (eq major-mode 'org-mode)
buffer-file-name
(cl-pushnew (cons buffer-file-name (cons :maxlevel 10))
org-refile-targets))))
(call-interactively #'org-refile)))
;;;###autoload
(defun +org/refile-to-other-buffer (arg)
"TODO"
(interactive "P")
(let ((org-refile-keep arg)
org-refile-targets
current-prefix-arg)
(dolist (buf (delq (current-buffer) (doom-buffers-in-mode 'org-mode)))
(with-current-buffer buf
(and buffer-file-name
(cl-pushnew (cons buffer-file-name (cons :maxlevel 10))
org-refile-targets))))
(call-interactively #'org-refile)))
;;;###autoload
(defun +org/refile-to-running-clock (arg)
"TODO"
(interactive "P")
(unless (bound-and-true-p org-clock-current-task)
(user-error "No active clock to refile to"))
(let ((org-refile-keep arg))
(org-refile 2)))
;;;###autoload
(defun +org/refile-to-last-location (arg)
"TODO"
(interactive "P")
(or (assoc (plist-get org-bookmark-names-plist :last-refile)
bookmark-alist)
(user-error "No saved location to refile to"))
(let ((org-refile-keep arg)
(completing-read-function
(lambda (_p _coll _pred _rm _ii _h default &rest _)
default)))
(org-refile)))
(defvar org-after-refile-insert-hook)
;; Inspired by org-teleport and alphapapa/alpha-org
;;;###autoload
(defun +org/refile-to-visible ()
"Refile current heading as first child of visible heading selected with Avy."
(interactive)
(when-let (marker (+org-headline-avy))
(let* ((buffer (marker-buffer marker))
(filename
(buffer-file-name (or (buffer-base-buffer buffer)
buffer)))
(heading
(org-with-point-at marker
(org-get-heading 'no-tags 'no-todo)))
;; Won't work with target buffers whose filename is nil
(rfloc (list heading filename nil marker))
(org-after-refile-insert-hook (cons #'org-reveal org-after-refile-insert-hook)))
(org-refile nil nil rfloc))))

View File

@@ -0,0 +1,97 @@
;;; lang/org/autoload/org-tables.el -*- lexical-binding: t; -*-
;;
;;; Row/Column traversal
;;;###autoload
(defun +org/table-previous-row ()
"Go to the previous row (same column) in the current table. Before doing so,
re-align the table if necessary. (Necessary because org-mode has a
`org-table-next-row', but not `org-table-previous-row')"
(interactive)
(org-table-maybe-eval-formula)
(org-table-maybe-recalculate-line)
(if (and org-table-automatic-realign
org-table-may-need-update)
(org-table-align))
(let ((col (org-table-current-column)))
(beginning-of-line 0)
(when (or (not (org-at-table-p)) (org-at-table-hline-p))
(beginning-of-line))
(org-table-goto-column col)
(skip-chars-backward "^|\n\r")
(when (org-looking-at-p " ")
(forward-char))))
;;
;;; Row/Column insertion
;;;###autoload
(defun +org/table-insert-column-left ()
"Insert a new column left of the current column."
(interactive)
(org-table-insert-column)
(org-table-move-column-left))
;;;###autoload
(defun +org/table-insert-row-below ()
"Insert a new row below the current row."
(interactive)
(org-table-insert-row 'below))
;;
;;; Hooks
;;;###autoload
(defun +org-realign-table-maybe-h ()
"Auto-align table under cursor and re-calculate formulas."
(when (and (org-at-table-p) org-table-may-need-update)
(let ((pt (point))
(inhibit-message t))
(org-table-recalculate)
(if org-table-may-need-update (org-table-align))
(goto-char pt))))
;;;###autoload
(defun +org-enable-auto-reformat-tables-h ()
"Realign tables & update formulas when exiting insert mode (`evil-mode').
Meant for `org-mode-hook'."
(when (featurep 'evil)
(add-hook 'evil-insert-state-exit-hook #'+org-realign-table-maybe-h nil t)
(add-hook 'evil-replace-state-exit-hook #'+org-realign-table-maybe-h nil t)
(advice-add #'evil-replace :after #'+org-realign-table-maybe-a)))
;;;###autoload
(defun +org-delete-backward-char-and-realign-table-maybe-h ()
"Ensure deleting characters with backspace doesn't deform the table cell."
(when (eq major-mode 'org-mode)
(org-check-before-invisible-edit 'delete-backward)
(save-match-data
(when (and (org-at-table-p)
(not (org-region-active-p))
(string-match-p "|" (buffer-substring (point-at-bol) (point)))
(looking-at-p ".*?|"))
(let ((pos (point))
(noalign (looking-at-p "[^|\n\r]* |"))
(c org-table-may-need-update))
(delete-char -1)
(unless overwrite-mode
(skip-chars-forward "^|")
(insert " ")
(goto-char (1- pos)))
;; noalign: if there were two spaces at the end, this field
;; does not determine the width of the column.
(when noalign (setq org-table-may-need-update c)))
t))))
;;
;;; Advice
;;;###autoload
(defun +org-realign-table-maybe-a (&rest _)
"Auto-align table under cursor and re-calculate formulas."
(when (eq major-mode 'org-mode)
(+org-realign-table-maybe-h)))

View File

@@ -0,0 +1,440 @@
;;; lang/org/autoload/org.el -*- lexical-binding: t; -*-
;;
;;; Helpers
(defun +org--refresh-inline-images-in-subtree ()
"Refresh image previews in the current heading/tree."
(if (> (length org-inline-image-overlays) 0)
(org-remove-inline-images)
(org-display-inline-images
t t
(if (org-before-first-heading-p)
(line-beginning-position)
(save-excursion (org-back-to-heading) (point)))
(if (org-before-first-heading-p)
(line-end-position)
(save-excursion (org-end-of-subtree) (point))))))
(defun +org--insert-item (direction)
(let* ((context
(save-excursion
(when (bolp)
(back-to-indentation)
(forward-char))
(org-element-lineage
(org-element-context)
'(table table-row headline inlinetask item plain-list)
t)))
(type (org-element-type context)))
(cond ((memq type '(item plain-list))
(let ((marker (org-element-property :bullet context))
(pad (save-excursion
(org-beginning-of-item)
(back-to-indentation)
(- (point) (line-beginning-position)))))
(save-match-data
(pcase direction
(`below
(org-end-of-item)
(backward-char)
(end-of-line)
(if (and marker (string-match "\\([0-9]+\\)\\([).] *\\)" marker))
(let ((l (line-number-at-pos)))
(org-insert-item)
(when (= l (line-number-at-pos))
(org-next-item)
(org-end-of-line)))
(insert "\n" (make-string pad 32) (or marker ""))))
(`above
(org-beginning-of-item)
(if (and marker (string-match-p "[0-9]+[).]" marker))
(org-insert-item)
(insert (make-string pad 32) (or marker ""))
(save-excursion (insert "\n")))))))
(when (org-element-property :checkbox context)
(insert "[ ] ")))
((memq type '(table table-row))
(pcase direction
('below (save-excursion (org-table-insert-row t))
(org-table-next-row))
('above (save-excursion (org-shiftmetadown))
(+org/table-previous-row))))
((memq type '(headline inlinetask))
(let ((level (if (eq (org-element-type context) 'headline)
(org-element-property :level context)
1)))
(pcase direction
(`below
(let (org-insert-heading-respect-content)
(goto-char (line-end-position))
(org-end-of-subtree)
(insert "\n" (make-string level ?*) " ")))
(`above
(org-back-to-heading)
(insert (make-string level ?*) " ")
(save-excursion (insert "\n"))))
(when-let* ((todo-keyword (org-element-property :todo-keyword context))
(todo-type (org-element-property :todo-type context)))
(org-todo (cond ((eq todo-type 'done)
(car (+org-get-todo-keywords-for todo-keyword)))
(todo-keyword)
('todo))))))
((user-error "Not a valid list, heading or table")))
(when (org-invisible-p)
(org-show-hidden-entry))
(when (bound-and-true-p evil-local-mode)
(evil-insert 1))))
(defun +org--get-property (name &optional bound)
(save-excursion
(let ((re (format "^#\\+%s:[ \t]*\\([^\n]+\\)" (upcase name))))
(goto-char (point-min))
(when (re-search-forward re bound t)
(buffer-substring-no-properties (match-beginning 1) (match-end 1))))))
;;;###autoload
(defun +org-get-global-property (name &optional file bound)
"Get a document property named NAME (string) from an org FILE (defaults to
current file). Only scans first 2048 bytes of the document."
(unless bound
(setq bound 256))
(if file
(with-temp-buffer
(insert-file-contents-literally file nil 0 bound)
(+org--get-property name))
(+org--get-property name bound)))
;;;###autoload
(defun +org-get-todo-keywords-for (&optional keyword)
"Returns the list of todo keywords that KEYWORD belongs to."
(when keyword
(cl-loop for (type . keyword-spec)
in (cl-remove-if-not #'listp org-todo-keywords)
for keywords =
(mapcar (lambda (x) (if (string-match "^\\([^(]+\\)(" x)
(match-string 1 x)
x))
keyword-spec)
if (eq type 'sequence)
if (member keyword keywords)
return keywords)))
;;
;;; Modes
;;;###autoload
(define-minor-mode +org-pretty-mode
"Hides emphasis markers and toggles pretty entities."
:init-value nil
:lighter " *"
:group 'evil-org
(setq org-hide-emphasis-markers +org-pretty-mode)
(org-toggle-pretty-entities)
(with-silent-modifications
;; In case the above un-align tables
(org-table-map-tables 'org-table-align t)))
;;
;;; Commands
;;;###autoload
(defun +org/dwim-at-point ()
"Do-what-I-mean at point.
If on a:
- checkbox list item or todo heading: toggle it.
- clock: update its time.
- headline: toggle latex fragments and inline images underneath.
- footnote reference: jump to the footnote's definition
- footnote definition: jump to the first reference of this footnote
- table-row or a TBLFM: recalculate the table's formulas
- table-cell: clear it and go into insert mode. If this is a formula cell,
recaluclate it instead.
- babel-call: execute the source block
- statistics-cookie: update it.
- latex fragment: toggle it.
- link: follow it
- otherwise, refresh all inline images in current tree."
(interactive)
(let* ((context (org-element-context))
(type (org-element-type context)))
;; skip over unimportant contexts
(while (and context (memq type '(verbatim code bold italic underline strike-through subscript superscript)))
(setq context (org-element-property :parent context)
type (org-element-type context)))
(pcase type
(`headline
(cond ((and (fboundp 'toc-org-insert-toc)
(member "TOC" (org-get-tags)))
(toc-org-insert-toc)
(message "Updating table of contents"))
((string= "ARCHIVE" (car-safe (org-get-tags)))
(org-force-cycle-archived))
((or (org-element-property :todo-type context)
(org-element-property :scheduled context))
(org-todo
(if (eq (org-element-property :todo-type context) 'done)
(or (car (+org-get-todo-keywords-for (org-element-property :todo-keyword context)))
'todo)
'done)))
(t
(+org--refresh-inline-images-in-subtree)
(org-clear-latex-preview)
(org-latex-preview '(4)))))
(`clock (org-clock-update-time-maybe))
(`footnote-reference
(org-footnote-goto-definition (org-element-property :label context)))
(`footnote-definition
(org-footnote-goto-previous-reference (org-element-property :label context)))
((or `planning `timestamp)
(org-follow-timestamp-link))
((or `table `table-row)
(if (org-at-TBLFM-p)
(org-table-calc-current-TBLFM)
(ignore-errors
(save-excursion
(goto-char (org-element-property :contents-begin context))
(org-call-with-arg 'org-table-recalculate (or arg t))))))
(`table-cell
(org-table-blank-field)
(org-table-recalculate)
(when (and (string-empty-p (string-trim (org-table-get-field)))
(bound-and-true-p evil-local-mode))
(evil-change-state 'insert)))
(`babel-call
(org-babel-lob-execute-maybe))
(`statistics-cookie
(save-excursion (org-update-statistics-cookies nil)))
((or `src-block `inline-src-block)
(org-babel-execute-src-block))
((or `latex-fragment `latex-environment)
(org-latex-preview))
(`link
(let* ((lineage (org-element-lineage context '(link) t))
(path (org-element-property :path lineage)))
(if (or (equal (org-element-property :type lineage) "img")
(and path (image-type-from-file-name path)))
(+org--refresh-inline-images-in-subtree)
(org-open-at-point))))
((guard (org-element-property :checkbox (org-element-lineage context '(item) t)))
(let ((match (and (org-at-item-checkbox-p) (match-string 1))))
(org-toggle-checkbox (if (equal match "[ ]") '(16)))))
(_ (+org--refresh-inline-images-in-subtree)))))
;; 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).
;;;###autoload
(defun +org/insert-item-below (count)
"Inserts a new heading, table cell or item below the current one."
(interactive "p")
(dotimes (_ count) (+org--insert-item 'below)))
;;;###autoload
(defun +org/insert-item-above (count)
"Inserts a new heading, table cell or item above the current one."
(interactive "p")
(dotimes (_ count) (+org--insert-item 'above)))
;;;###autoload
(defun +org/dedent ()
"TODO"
(interactive)
(cond ((org-at-item-p)
(org-list-indent-item-generic
-1 nil
(save-excursion
(when (org-region-active-p)
(goto-char (region-beginning)))
(org-list-struct))))
((org-at-heading-p)
(ignore-errors (org-promote)))
((call-interactively #'self-insert-command))))
;;;###autoload
(defun +org/toggle-clock (arg)
"Toggles clock on the last clocked item.
Clock out if an active clock is running. Clock in otherwise.
If in an org file, clock in on the item at point. Otherwise clock into the last
task you clocked into.
See `org-clock-out', `org-clock-in' and `org-clock-in-last' for details on how
the prefix ARG changes this command's behavior."
(interactive "P")
(if (org-clocking-p)
(if arg
(org-clock-cancel)
(org-clock-out))
(org-clock-in-last arg)))
;;; Folds
;;;###autoload
(defalias #'+org/toggle-fold #'+org-cycle-only-current-subtree-h)
;;;###autoload
(defun +org/open-fold ()
"Open the current fold (not but its children)."
(interactive)
(+org/toggle-fold t))
;;;###autoload
(defalias #'+org/close-fold #'outline-hide-subtree)
(defun +org--get-foldlevel ()
(let ((max 1))
(save-restriction
(narrow-to-region (window-start) (window-end))
(save-excursion
(goto-char (point-min))
(while (not (eobp))
(org-next-visible-heading 1)
(when (outline-invisible-p (line-end-position))
(let ((level (org-outline-level)))
(when (> level max)
(setq max level))))))
max)))
;;;###autoload
(defun +org/show-next-fold-level ()
"Decrease the fold-level of the visible area of the buffer. This unfolds
another level of headings on each invocation."
(interactive)
(let* ((current-level (+org--get-foldlevel))
(new-level (1+ current-level)))
(outline-hide-sublevels new-level)
(message "Folded to level %s" new-level)))
;;;###autoload
(defun +org/hide-next-fold-level ()
"Increase the global fold-level of the visible area of the buffer. This folds
another level of headings on each invocation."
(interactive)
(let* ((current-level (+org--get-foldlevel))
(new-level (max 1 (1- current-level))))
(outline-hide-sublevels new-level)
(message "Folded to level %s" new-level)))
;;
;;; Hooks
;;;###autoload
(defun +org-indent-maybe-h ()
"Indent the current item (header or item), if possible.
Made for `org-tab-first-hook' in evil-mode."
(interactive)
(cond ((not (and (bound-and-true-p evil-local-mode)
(evil-insert-state-p)))
nil)
((org-at-item-p)
(if (eq this-command 'org-shifttab)
(org-outdent-item-tree)
(org-indent-item-tree))
t)
((org-at-heading-p)
(ignore-errors
(if (eq this-command 'org-shifttab)
(org-promote)
(org-demote)))
t)
((org-in-src-block-p t)
(org-babel-do-in-edit-buffer
(call-interactively #'indent-for-tab-command))
t)))
;;;###autoload
(defun +org-update-cookies-h ()
"Update counts in headlines (aka \"cookies\")."
(when (and buffer-file-name (file-exists-p buffer-file-name))
(let (org-hierarchical-todo-statistics)
(org-update-parent-todo-statistics))))
;;;###autoload
(defun +org-yas-expand-maybe-h ()
"Tries to expand a yasnippet snippet, if one is available. Made for
`org-tab-first-hook'."
(when (bound-and-true-p yas-minor-mode)
(cond ((and (or (not (bound-and-true-p evil-local-mode))
(evil-insert-state-p))
(yas--templates-for-key-at-point))
(call-interactively #'yas-expand)
t)
((use-region-p)
;; Triggering mode-specific indentation is expensive in src blocks
;; (if `org-src-tab-acts-natively' is non-nil), and can cause errors,
;; so we avoid smart indentation in this case.
(let ((yas-indent-line 'fixed))
(call-interactively #'yas-insert-snippet))
t))))
;;;###autoload
(defun +org-cycle-only-current-subtree-h (&optional arg)
"Toggle the local fold at the point (as opposed to cycling through all levels
with `org-cycle')."
(interactive "P")
(unless (eq this-command 'org-shifttab)
(save-excursion
(org-beginning-of-line)
(let (invisible-p)
(when (and (org-at-heading-p)
(or org-cycle-open-archived-trees
(not (member org-archive-tag (org-get-tags))))
(or (not arg)
(setq invisible-p (outline-invisible-p (line-end-position)))))
(unless invisible-p
(setq org-cycle-subtree-status 'subtree))
(org-cycle-internal-local)
t)))))
;;;###autoload
(defun +org-unfold-to-2nd-level-or-point-h ()
"My version of the 'overview' #+STARTUP option: expand first-level headings.
Expands the first level, but no further. If point was left somewhere deeper,
unfold to point on startup."
(unless org-agenda-inhibit-startup
(when (eq org-startup-folded t)
(outline-hide-sublevels +org-initial-fold-level))
(when (outline-invisible-p)
(ignore-errors
(save-excursion
(outline-previous-visible-heading 1)
(org-show-subtree))))))
;;;###autoload
(defun +org-remove-occur-highlights-h ()
"Remove org occur highlights on ESC in normal mode."
(when org-occur-highlights
(org-remove-occur-highlights)
t))
;;;###autoload
(defun +org-enable-auto-update-cookies-h ()
"Update statistics cookies when saving or exiting insert mode (`evil-mode')."
(when (bound-and-true-p evil-local-mode)
(add-hook 'evil-insert-state-exit-hook #'+org-update-cookies-h nil t))
(add-hook 'before-save-hook #'+org-update-cookies-h nil t))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
;;; lang/org/contrib/dragndrop.el -*- lexical-binding: t; -*-
;;;###if (featurep! +dragndrop)
(use-package! org-download
:commands org-download-dnd org-download-dnd-base64
:init
;; HACK We add these manually so that org-download is truly lazy-loaded
(pushnew! dnd-protocol-alist
'("^\\(?:https?\\|ftp\\|file\\|nfs\\):" . +org-dragndrop-download-dnd-fn)
'("^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.
(defadvice! +org--dragndrop-insert-link-a (_link filename)
"Produces and inserts a link to FILENAME into the document.
If FILENAME is an image, produce an attach:%s path, otherwise use file:%s (with
an file icon produced by `+org-attach-icon-for')."
:override #'org-download-insert-link
(if (looking-back "^[ \t]+" (line-beginning-position))
(delete-region (match-beginning 0) (match-end 0))
(newline))
(cond ((image-type-from-file-name filename)
(insert
(concat (if (= org-download-image-html-width 0) ""
(format "#+attr_html: :width %dpx\n" org-download-image-html-width))
(if (= org-download-image-latex-width 0) ""
(format "#+attr_latex: :width %dcm\n" org-download-image-latex-width))
(cond ((file-in-directory-p filename org-attach-directory)
(format "[[attach:%s]]" (file-relative-name filename org-attach-directory)))
((file-in-directory-p filename org-directory)
(format org-download-link-format (file-relative-name filename org-directory)))
((format org-download-link-format filename)))))
(org-display-inline-images))
((insert
(format "%s [[./%s][%s]] "
(+org-attach-icon-for filename)
(file-relative-name filename (file-name-directory buffer-file-name))
(file-name-nondirectory (directory-file-name filename)))))))
(advice-add #'org-download--dir-2 :override #'ignore)
(defadvice! +org--dragndrop-download-fullname-a (path)
"Write PATH relative to current file."
:filter-return #'org-download--fullname
(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))))

View File

@@ -0,0 +1,41 @@
;;; lang/org/contrib/babel.el -*- lexical-binding: t; -*-
;;;###if (featurep! +ipython)
(use-package! ob-ipython
:defer t
:init
(defvar +ob-ipython-local-runtime-dir nil)
(setq ob-ipython-resources-dir ".ob-ipython-resrc")
(add-hook! '+org-babel-load-functions
(defun +org-babel-load-ipython-h (lang)
(and (string-prefix-p "jupyter-" (symbol-name lang))
(require 'ob-ipython nil t))))
(after! org-src
(add-to-list 'org-src-lang-modes '("ipython" . python)))
(after! ox-latex
(add-to-list 'org-latex-minted-langs '(ipython "python")))
:config
(set-popup-rules!
'(("\\*ob-ipython.*"
:slot 2 :side right :size 100 :height 0.2
:select nil :quit nil :ttl nil)
("^ \\*Python"
:slot 0 :side right :size 100
:select nil :quit nil :ttl nil)))
;; advices for remote kernel and org-src-edit
(advice-add #'ob-ipython--create-repl :override #'+org-ob-ipython-create-repl-a)
(advice-add #'org-babel-edit-prep:ipython :override #'+org-babel-edit-prep:ipython-a)
(advice-add #'org-babel-execute:ipython :before #'+org-babel-execute:ipython-a)
(advice-add #'org-babel-ipython-initiate-session :override #'+org-ob-ipython-initiate-session-a)
;; retina resolution image hack
(when IS-MAC
(advice-add #'ob-ipython--write-base64-string :around #'+org-ob-ipython-write-base64-string-a))
;; ipython has its own async keyword, disable ipython in ob-async.
(after! ob-async
(add-to-list 'ob-async-no-async-languages-alist "ipython")))

View File

@@ -0,0 +1,16 @@
;;; lang/org/contrib/journal.el -*- lexical-binding: t; -*-
;;;###if (featurep! +journal)
(after! org-journal
(setq org-journal-dir (expand-file-name "journal/" org-directory)
org-journal-file-pattern
(expand-file-name "\\(?1:[0-9]\\{4\\}\\)\\(?2:[0-9][0-9]\\)\\(?3:[0-9][0-9]\\)\\'"
org-journal-dir))
(map! :localleader
(:map org-journal-search-mode-map
"n" #'org-journal-search-next
"p" #'org-journal-search-prev)
(:map org-journal-mode-map
"n" #'org-journal-open-next-entry
"p" #'org-journal-open-previous-entry)))

View File

@@ -0,0 +1,15 @@
;;; lang/org/contrib/pomodoro.el -*- lexical-binding: t; -*-
;;;###if (featurep! +pomodoro)
(after! org-pomodoro
;; prefer PulseAudio to ALSA in $current_year
(setq org-pomodoro-audio-player (or (executable-find "paplay")
org-pomodoro-audio-player))
;; configure pomodoro alerts to use growl or libnotify
(alert-add-rule :category "org-pomodoro"
:style (cond (alert-growl-command
'growl)
(alert-libnotify-command
'libnotify)
(alert-default-style))))

View File

@@ -0,0 +1,54 @@
;;; lang/org/contrib/present.el -*- lexical-binding: t; -*-
;;;###if (featurep! +present)
(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
(use-package! org-re-reveal
:after ox
:init
(setq org-re-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js@3/"))
(use-package! org-tree-slide
:commands org-tree-slide-mode
:config
(org-tree-slide-simple-profile)
(setq org-tree-slide-skip-outline-level 2
org-tree-slide-activate-message " "
org-tree-slide-deactivate-message " "
org-tree-slide-modeline-display nil)
(map! :map org-tree-slide-mode-map
:n [right] #'org-tree-slide-move-next-tree
:n [left] #'org-tree-slide-move-previous-tree)
(add-hook! 'org-tree-slide-mode-after-narrow-hook
#'+org-present-detect-slide-h
#'+org-present-add-overlays-h
#'org-display-inline-images)
(add-hook 'org-tree-slide-mode-hook #'+org-present-init-org-tree-window-h)
(defadvice! +org-present--narrow-to-subtree-a (orig-fn &rest args)
"Narrow to the target subtree when you start the presentation."
:around #'org-tree-slide--display-tree-with-narrow
(cl-letf (((symbol-function #'org-narrow-to-subtree)
(lambda () (save-excursion
(save-match-data
(org-with-limited-levels
(narrow-to-region
(progn (org-back-to-heading t)
(forward-line 1)
(point))
(progn (org-end-of-subtree t t)
(when (and (org-at-heading-p) (not (eobp))) (backward-char 1))
(point)))))))))
(apply orig-fn args))))

View File

@@ -0,0 +1,6 @@
;; -*- lexical-binding: t; no-byte-compile: t; -*-
;;; lang/org/doctor.el
(when (featurep! +gnuplot)
(unless (executable-find "gnuplot")
(warn! "Couldn't find gnuplot. org-plot/gnuplot will not work")))

View File

@@ -0,0 +1,81 @@
;; -*- no-byte-compile: t; -*-
;;; lang/org/packages.el
;; Prevent built-in Org from playing into the byte-compilation of
;; `org-plus-contrib'.
(when-let (orglib (locate-library "org" nil doom--initial-load-path))
(setq load-path (delete (substring (file-name-directory orglib) 0 -1)
load-path)))
;; HACK A necessary hack because org requires a compilation step after being
;; cloned, and during that compilation a org-version.el is generated with
;; these two functions, which return the output of a 'git describe ...'
;; call in the repo's root. Of course, this command won't work in a sparse
;; clone, and more than that, initiating these compilation step is a
;; hassle, so...
(setq straight-fix-org nil)
(add-hook! 'straight-use-package-pre-build-functions
(defun +org-fix-package-h (package &rest _)
(when (member package '("org" "org-plus-contrib"))
(with-temp-file (expand-file-name "org-version.el" (straight--repos-dir "org"))
(insert "(fset 'org-release (lambda () \"9.3\"))\n"
"(fset 'org-git-version #'ignore)\n"
"(provide 'org-version)\n")))))
(package! org-plus-contrib) ; install cutting-edge version of org-mode
(package! avy)
(package! htmlize)
(package! org-bullets :recipe (:host github :repo "Kaligule/org-bullets"))
(package! org-fancy-priorities)
(package! org-yt :recipe (:host github :repo "TobiasZawada/org-yt"))
(package! ox-clip)
(package! toc-org)
(package! org-cliplink)
(when (featurep! :editor evil +everywhere)
(package! evil-org :recipe (:host github :repo "hlissner/evil-org-mode")))
(when (featurep! :tools pdf)
(package! org-pdfview))
(when (featurep! :tools magit)
(package! orgit))
(when (featurep! +dragndrop)
(package! org-download))
(when (featurep! +gnuplot)
(package! gnuplot)
(package! gnuplot-mode))
(when (featurep! +ipython)
(package! ob-ipython))
(when (featurep! +pomodoro)
(package! org-pomodoro))
(when (featurep! +present)
(package! centered-window
:recipe (:host github :repo "anler/centered-window-mode"))
(package! org-tree-slide)
(package! org-re-reveal))
(when (featurep! +journal)
(package! org-journal))
;;; 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 (:host github :repo "DEADB17/ob-racket")))
(when (featurep! :lang rest)
(package! ob-restclient))
(when (featurep! :lang rust)
(package! ob-rust))
;;; Export
(when (featurep! +pandoc)
(package! ox-pandoc))
(when (featurep! +hugo)
(package! ox-hugo
:recipe (:host github :repo "kaushalmodi/ox-hugo" :nonrecursive t)))
(when (featurep! :lang rst)
(package! ox-rst))

View File

@@ -0,0 +1,133 @@
;; -*- no-byte-compile: t; -*-
;;; lang/org/test/test-autoload-org.el
(describe "lang/org"
;; `+org/insert-item'
(describe "insert-item"
(before-all
(require 'org)
(load! "../autoload/org.el"))
(after-all
(unload-feature 'org t))
(before-each
(set-buffer (get-buffer-create "org"))
(erase-buffer)
(delay-mode-hooks (org-mode)))
(after-each
(kill-buffer (get-buffer "org")))
(describe "headlines"
(it "opens new headline below"
(insert!! "* {0}Header")
(+org/insert-item-below 1)
(expect (eobp))
(expect (buffer-substring-no-properties (point-min) (point-max))
:to-equal "* Header\n* "))
(it "opens new headline above"
(insert!! "* {0}Header")
(+org/insert-item-above 1)
(expect (eolp))
(expect (buffer-substring-no-properties (point-min) (point-max))
:to-equal "* \n* Header"))
(it "appends headlines, skipping subtrees"
(insert!! "** {0}First\n"
"*** sub 1\n"
"*** sub 2\n"
"**** subsub 1\n"
"** Header")
(+org/insert-item-below 1)
(expect (eolp))
(expect (line-number-at-pos) :to-be 5)
(expect (buffer-substring-no-properties (point-min) (point-max))
:to-equal
(string-join '("** First"
"*** sub 1"
"*** sub 2"
"**** subsub 1"
"** "
"** Header")
"\n")))
(it "prepends headlines, skipping subtrees"
(insert!! "** First\n"
"*** sub 1\n"
"*** sub 2\n"
"**** {0}subsub 1\n"
"** Header")
(+org/insert-item-above 1)
(expect (eolp))
(expect (line-number-at-pos) :to-be 4)
(expect (buffer-substring-no-properties (point-min) (point-max))
:to-equal
(string-join '("** First"
"*** sub 1"
"*** sub 2"
"**** "
"**** subsub 1"
"** Header")
"\n"))))
(describe "plain lists"
(it "appends items"
(insert!! "+ {0}List item")
(+org/insert-item-below 1)
(expect (buffer-substring-no-properties (point-min) (point-max))
:to-equal "+ List item\n+ "))
(it "prepends items"
(insert!! "+ {0}List item")
(+org/insert-item-above 1)
(expect (buffer-substring-no-properties (point-min) (point-max))
:to-equal "+ \n+ List item"))
(it "appends items, but skips over child items"
(insert!! "+ {0}List item\n"
" + Sub item\n"
"+ List item")
(+org/insert-item-below 1)
(expect (buffer-substring-no-properties (point-min) (point-max))
:to-equal
(string-join '("+ List item"
" + Sub item"
"+ "
"+ List item")
"\n")))
(it "prepends items, but skips over child items"
(insert!! "+ List item\n"
" + Sub item\n"
"+ {0}List item")
(+org/insert-item-above 1)
(expect (buffer-substring-no-properties (point-min) (point-max))
:to-equal
(string-join '("+ List item"
" + Sub item"
"+ "
"+ List item")
"\n"))))
(describe "numbered lists"
(it "appends items and updates numbers"
(insert!! "1. {0}List item\n"
"2. Sub item\n"
"3. List item")
(+org/insert-item-below 1)
(expect (buffer-substring-no-properties (point-min) (point-max))
:to-equal
(string-join '("1. List item"
"2. "
"3. Sub item"
"4. List item")
"\n")))
(it "prepends items and updates numbers"
(insert!! "1. List item\n"
"2. Sub item\n"
"3. {0}List item")
(+org/insert-item-above 1)
(expect (buffer-substring-no-properties (point-min) (point-max))
:to-equal
(string-join '("1. List item"
"2. Sub item"
"3. "
"4. List item")
"\n"))))))