emacs/lisp/mir-orgstuff.el
2024-10-20 09:16:54 -08:00

294 lines
10 KiB
EmacsLisp

;;; mir-orgstuff.el --- Hacking on top of Org mode.
;;; Commentary:
;; Some of the things I want to do with org aren't built-in or intended. This
;; file is the cum total of my intent to implement these things.
;;; Code:
(require 'dash)
(require 'f)
(require 'ox-publish)
(require 'hyperbole)
(defvar org-directory (expand-file-name "org/" "~"))
(defvar org-agenda-files '("~/org/"))
(defvar org-vw-staging-dir "/unicron_shared/webbed-site/staging/")
(defvar org-vw-export-dir "/unicron_shared/webbed-site/public/")
(defvar org-wiki-regex "\\`[^.].*\\.org\\'")
(defvar org-vw--link-re (rx "[[" (* any) (+ alnum)
".org][" (* any) "]]"))
;; need to make a hyperbole
(add-to-list 'hkey-alist '((and
(eq major-mode 'org-mode)
(not (smart-org)))
. ((org-vw-make-newlink) . nil)))
;; ------------------- links and exporting -------------------------------------
(defun org-vw-link-at-point ()
(interactive)
(save-match-data
(string-match org-vw--link-re (thing-at-point 'line))
(with-current-buffer (
;; it was at this point that she gave up from scratch
))
(buffer-substring-no-properties match-beginning match-end)))
(defun org-vw--get-link-basename (link)
(if (string-match-p org-vw--link-re link)
(--> link
(split-string it "]\\[")
(car it)
(string-trim it "\\[\\[" "]]")
(string-trim-right it "/index\.org$"))
(error "malformed link, what are you doing")))
;; (org-vw--get-link-basename "[[lasdfjl/index.org][asdlkfj]]")
(defun fix-links-temporarily-in-current-buffer ()
"Run every link thru `org-vw-preexp-link'."
(interactive)
(save-excursion
(goto-char (point-min))
(while (save-excursion
(re-search-forward
org-vw--link-re nil 'no-error))
(goto-char (match-beginning 0))
(org-vw-preexp-link))))
(string-match-p org-vw--link-re
"[[maybe-not.org][maybe not]]")
(defun htmlize-current-buffer-to-file (file)
"Run `htmlize-buffer' and save the result to FILE."
(interactive "sWrite to file: ")
(with-current-buffer (htmlize-buffer)
(write-file file)
(kill-buffer (current-buffer))))
(defun mir/export-buffer-to-html-fixed-links (file)
"Synthesis of `fix-links-temporarily-in-current-buffer' and `htmlize-current-buffer-to-file'. FILE is to be written."
(interactive "sExport to file: ")
(ignore-errors
(evil-with-single-undo
(fix-links-temporarily-in-current-buffer)
(htmlize-current-buffer-to-file file))
(evil-undo-pop)) ;todo later: can we wrap in something less evil?
)
(defun org-vw-get-index-name (source basedir)
"Given SOURCE, return the org link pointing to the corresponding html."
(let* ((basename (file-name-directory source)))
(if (string= (expand-file-name "index" basedir) basename)
"./"
(progn
;; (ignore-errors (mkdir (format "%s/%s/" basedir basename) t))
(format "./%s/" basename)) )))
;; (file-name-directory "test/index.tft")
;; big folder level export
(defun all-orgs-mds-in-dir (dir)
"Return a list of every markdown and org file in DIR."
(let ((default-directory dir))
(--remove
(or
(f-directory-p it)
(not (or
(string= (f-ext it) "md")
(string= (f-ext it) "org")
))
)
(f-entries dir nil t))))
;; (all-orgs-mds-in-dir "/unicron_shared/webbed-site/public/")
;; loop over this
(defun do-file-in-sitedir (file sitedir)
"too tired to write words. only code sry"
(with-current-buffer (or (get-file-buffer file) (find-file file))
(mir/export-buffer-to-html-fixed-links
(org-vw-get-index-name file sitedir))))
(defun org-vw-export ()
"htmlize recursively all org files in SITEDIR."
(interactive "sDirectory (leave blank for current): ")
(let* ((orgs-mds (all-orgs-mds-in-dir org-vw-staging-dir)))
(message "------- org and markdown files: %s" orgs-mds)
(--map
(progn
(message "operating on %s" it)
(do-file-in-sitedir it (org-vw-get-index-name it sitedir)))
orgs-mds)
))
;; --------------------
;; --------------------- below be dragons --------------------------------------
(defun org-vw-preexp-link ()
"Re-format the next instance of name.org to name/ ."
(let* ((link-regex org-vw--link-re)
(endpoint (re-search-forward link-regex))
(startpoint (re-search-backward link-regex))
(old-link-text (buffer-substring-no-properties startpoint endpoint))
(old-link-components
(--map (string-trim it (rx (+ "[")) (rx (+ "]")))
(split-string old-link-text "]\\[" t)))
(new-link-components
(list (org-vw-get-index-name
(car old-link-components)
org-vw-export-dir
;; default-directory ; figure because we're necessarily in the buffer right?
)
(cadr old-link-components)))
(new-link-text (format "[[%s][%s]]"
(car new-link-components)
(cadr new-link-components)))
(length-diff (- (length old-link-text) (length new-link-text))))
(delete-region startpoint endpoint)
(insert new-link-text)
)
)
;; ----------- that was a big dragon. carry on ---------------------------------
(defun org-vw-named-index-org (word)
"given word, generate the absolute filename for that org-vw entry."
(expand-file-name
(format "%s/index.org" (downcase word)) org-vw-staging-dir))
(defun org-vw-make-newlink ()
"Make the word at point the link to an org file, like in VimWiki."
(interactive)
;; todo: can we use `save-excusion' ?
(let* ((oldpoint (point))
(current-word (thing-at-point 'word 'no-properties))
(cw-file-name (org-vw-named-index-org current-word)))
(backward-word)
(kill-word 1)
(org-insert-link nil cw-file-name current-word)
(goto-char oldpoint)))
;; (org-insert-link nil "~/" "a file or sth")
;; (defun org-vw-show-markup () ;;broken, just use org-appear or sth
;; "When on a line containing hidden characters, show them."
;; (interactive)
;; (let ((point (point))
;; (bol (point-at-bol))
;; (eol (point-at-eol)))
;; (remove-text-properties bol eol
;; '(invisible nil))
;; t))
(defun org-vw-back ()
"Go back to the previous org file and bury this buffer."
(interactive)
(if (buffer-modified-p)
(if (y-or-n-p "Do you want to save this file?")
(save-buffer)))
(bury-buffer)
(other-window 1))
(defvar org-vw-pre-hook nil
"Hook to run before org-vw-mode has been loaded.")
(define-minor-mode org-vw-mode
"Org VimWiki mode."
:lighter " VW"
:keymap (make-sparse-keymap)
(run-hooks org-vw-pre-hook)
;; TODO: This is a hack, because hyperbole is scary. Ideal implementation
;; would add a proper case rather than this weird fallback.
;; (require 'hyperbole)
;; (make-local-variable action-key-default-function)
;; (setq-local action-key-default-function #'org-vw-make-newlink)
;; General:
(general-define-key
:keymaps 'local
:states 'normal
"DEL" 'org-vw-back)
(dolist (key mir/org-dwim-char-chars)
(general-define-key
:keymaps 'local
key 'mir/org-dwim-char))
)
;; (add-to-list 'auto-mode-alist `(,org-wiki-regex . org-vw-mode))
(add-hook 'org-mode-hook #'org-vw-mode)
;; ------------------ ABANDON ALL SANITY, YE WHO ENTER HERE --------------------
(defvar mir/org-dwim-char-chars '("_" "/" "*" "+" "~" "="))
(defun mir/org-dwim-char (&optional char)
"If a region is active (visual mode), surround selection with CHAR.
If in a word, surround it with CHAR (like `evil-surround') or, if word is
already surrounded with CHAR, un-surround it. If on the first character of a
word, run the default function that Evil binds the key CHAR to.
If the next character is the same as CHAR, move cursor past it \(like
`electric-pair').
If the line is a block delimiter or heading, or when in a protected block \(see
variable `org-protecting-blocks') or properties drawer, just insert the
character CHAR.
Otherwise, insert two of CHAR and put point between them like `electric-pair'."
(interactive) ; TODO: can we make CHAR an arg to `interactive'?
(let* ((char (or char (string-to-char (this-command-keys))))
(word (thing-at-point 'word 'no-properties))
;;; test conditions here to be eval'd, separating flow from data
;; at beginning of line
(beginning-of-line-test '(bolp))
;; at beginning of word
(beginning-of-word-test `(or ,beginning-of-line-test
(eq (char-before (point)) ?\ )))
;; word is surrounded by `char'
(surrounded-by-char-test
'(and (eq (char-before (beginning-of-thing 'word)) char)
(eq (char-after (end-of-thing 'word)) char)))
;; char before and char after are the same
(double-char-test (eq char (char-after)))
;; Weird Org exceptions because org
(org-exceptions-test
'(or (and (bolp)
(string-match-p (rx bol (or "#" "*"))
(thing-at-point 'line 'no-properties)))
(org-in-block-p org-protecting-blocks)
(org-at-property-p))))
(cond
((eq evil-state 'visual)
(let* ((beg (region-beginning))
(end (region-end)))
(evil-surround-region beg end evil-visual-selection char nil)))
((eq evil-state 'normal)
(if (eval beginning-of-word-test)
;; run original function bound to key
(let ((charstr (make-string 1 char)))
(call-interactively (alist-get charstr org-vw-old-binds-alist
nil nil #'string=)))
(save-excursion
(if (eval surrounded-by-char-test)
(progn
(search-backward (char-to-string char)) (delete-char 1)
(search-forward (char-to-string char)) (delete-char -1))
(evil-with-single-undo
(beginning-of-thing 'word)
(insert char)
(end-of-thing 'word)
(insert char))))))
((eval double-char-test) (right-char))
((eval org-exceptions-test) (insert char))
((and (eval beginning-of-line-test) (eq char ?*))
(insert char))
(t (progn
(insert (make-string 2 char))
(left-char))))))
(defun org-vw-snapshot-bindings ()
"Make an alist of the functions formerly ran by keys to be bound to `mir/org-dwim-char'.
Store it in the variable `org-vw-old-binds-alist'."
(unless org-vw-old-binds-alist
(defvar-local org-vw-old-binds-alist
(mapcar (lambda (key)
(cons key (key-binding key)))
mir/org-dwim-char-chars))))
(add-hook 'org-vw-pre-hook #'org-vw-snapshot-bindings nil 'local)
;; (defun org-vw-backspace)
;; (setq debug-on-error t)
;; --------- LET THY BRAIN NO LONGER TREMBLE, FOR I AM BECOME COMPLETE ---------
(provide 'mir-orgstuff)
;;; mir-orgstuff.el ends here