diff options
Diffstat (limited to 'lib/doc')
-rw-r--r-- | lib/doc/jao-doc-session.el | 59 | ||||
-rw-r--r-- | lib/doc/jao-doc-view.el | 219 | ||||
-rw-r--r-- | lib/doc/jao-org-links.el | 68 | ||||
-rw-r--r-- | lib/doc/jao-org-notes.el | 171 | ||||
-rw-r--r-- | lib/doc/jao-pdf.el | 100 | ||||
-rw-r--r-- | lib/doc/jao-recoll.el | 116 |
6 files changed, 351 insertions, 382 deletions
diff --git a/lib/doc/jao-doc-session.el b/lib/doc/jao-doc-session.el new file mode 100644 index 0000000..877a8cb --- /dev/null +++ b/lib/doc/jao-doc-session.el @@ -0,0 +1,59 @@ +;;; jao-doc-session.el --- persistent document sessions -*- lexical-binding: t; -*- + +;; Copyright (C) 2022, 2024 jao + +;; Author: jao <mail@jao.io> +;; Keywords: docs + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Code: + +(persist-defvar jao-doc-session nil "Documents session") + +(defvar-local jao-doc-session--is-doc nil) + +(defun jao-doc-session-is-doc (&optional buffer) + "Check whether the given or current buffer belong to the doc session." + (buffer-local-value 'jao-doc-session--is-doc (or buffer (current-buffer)))) + +(defun jao-doc-session (&optional file) jao-doc-session) + +(defun jao-doc-session-save (&optional skip-current force) + "Traverse all current buffers and update the value of `jao-doc-session'." + (interactive) + (let ((docs '()) + (cb (and skip-current (current-buffer)))) + (dolist (b (buffer-list)) + (when-let (fs (and (not (eq cb b)) (jao-doc-session-is-doc b))) + (dolist (f fs) (add-to-list 'docs f)))) + (when (or force (> (length docs) 0)) + (setq jao-doc-session docs)))) + +(defun jao-doc-session-mark (&optional path) + "Mark the current buffer's file, or PATH, as persistent across sessions." + (unless (listp jao-doc-session--is-doc) + (setq jao-doc-session--is-doc (ensure-list jao-doc-session--is-doc))) + (cl-pushnew (or path (buffer-file-name)) jao-doc-session--is-doc) + (jao-doc-session-save)) + +(defun jao-doc-session--maybe-save () + (when (jao-doc-session-is-doc) (jao-doc-session-save t))) + +(defvar jao-doc-session-inhibit-save nil) + +(add-hook 'kill-buffer-hook #'jao-doc-session--maybe-save) + +(provide 'jao-doc-session) +;;; jao-doc-session.el ends here diff --git a/lib/doc/jao-doc-view.el b/lib/doc/jao-doc-view.el index ea55565..fe26c1d 100644 --- a/lib/doc/jao-doc-view.el +++ b/lib/doc/jao-doc-view.el @@ -1,4 +1,4 @@ -;; jao-doc-view.el -- Remembering visited documents -*- lexical-binding: t; -*- +;;; jao-doc-view.el -- extensions for doc-view -*- lexical-binding: t; -*- ;; Copyright (c) 2013, 2015, 2017, 2018, 2019, 2021, 2022 Jose Antonio Ortega Ruiz @@ -18,162 +18,32 @@ ;; Author: Jose Antonio Ortega Ruiz <jao@gnu.org> ;; Start date: Fri Feb 15, 2013 01:21 -;;; Comentary: - -;; Some utilities to keep track of visited documents and their structure. - -;;; Code: - - -;;; Session - (require 'doc-view) +(require 'jao-pdf) -(defvar jao-doc-view-session-file "~/.emacs.d/cache/doc-view-session.eld") -(defvar-local jao-doc-view--is-doc nil) +;;; Utilities -(defun jao-doc-view-session-mark (path) (setq-local jao-doc-view--is-doc path)) -(defun jao-doc-view--is-doc () - (or jao-doc-view--is-doc - (when (derived-mode-p 'doc-view-mode 'pdf-view-mode 'nov-mode) - (buffer-file-name)))) - -(defun jao-doc-view--read-file (file) - (let ((buff (find-file-noselect file))) - (ignore-errors - (with-current-buffer buff - (goto-char (point-min))) - (read buff)))) - -(defun jao-doc-view--save-to-file (file value) - (with-current-buffer (find-file-noselect file) - (erase-buffer) - (insert (format "%S" value)) - (save-buffer))) - -(defun jao-doc-view-session (&optional file) - (let ((file (or file jao-doc-view-session-file))) - (jao-doc-view--read-file file))) - -(defun jao-doc-view-save-session (&optional skip-current) - (interactive) - (let ((docs '()) - (cb (when skip-current (current-buffer)))) - (dolist (b (buffer-list)) - (with-current-buffer b - (when-let (fn (and (not (eq cb b)) (jao-doc-view--is-doc))) - (add-to-list 'docs fn)))) - (when (> (length docs) 0) - (jao-doc-view--save-to-file jao-doc-view-session-file docs)))) - -(defun jao-doc-view--save-session-1 () - (when (jao-doc-view--is-doc) (jao-doc-view-save-session t))) - -(defvar jao-doc-view-inhibit-session-save nil) - -(defun jao-doc-view--save-session () - (let ((inhibit-message t) - (message-log-max nil)) - (when (not jao-doc-view-inhibit-session-save) - (jao-doc-view-save-session)) - t)) - -(add-hook 'kill-emacs-query-functions #'jao-doc-view--save-session) -(add-hook 'kill-buffer-hook #'jao-doc-view--save-session-1) -(add-hook 'doc-view-mode-hook #'jao-doc-view--save-session) -(add-hook 'pdf-view-mode-hook #'jao-doc-view--save-session) -(add-hook 'nov-mode-hook #'jao-doc-view--save-session) - - -;;; PDF info - -(defvar-local jao--pdf-outline nil) - -(defmacro jao-doc-view--pdf-call (a b &rest args) +(defmacro jao-doc-view--funcall (a b &rest args) `(cond ((derived-mode-p 'pdf-view-mode) (,a ,@args)) ((derived-mode-p 'doc-view-mode) (,b ,@args)))) -(defun jao-doc-view-is-pdf (file) (string-match-p ".*\\.pdf$" file)) - -(defun jao-doc-view-title->file (title) - (concat (mapconcat 'downcase (split-string title nil t) "-") ".pdf")) - (defun jao-doc-view-current-page () - (jao-doc-view--pdf-call pdf-view-current-page doc-view-current-page)) + (jao-doc-view--funcall pdf-view-current-page doc-view-current-page)) (defun jao-doc-view-goto-page (page &optional height) (when page - (jao-doc-view--pdf-call pdf-view-goto-page doc-view-goto-page page)) + (jao-doc-view--funcall pdf-view-goto-page doc-view-goto-page page)) (when (and height (derived-mode-p 'pdf-view-mode)) (image-set-window-vscroll (round (/ (* height (cdr (pdf-view-image-size))) (frame-char-height)))))) -(defun jao-doc-view-pdf-outline (&optional file-name) - (if (derived-mode-p 'pdf-view-mode) - (pdf-info-outline) - (let* ((outline nil) - (fn (or file-name (buffer-file-name) jao-doc-view--imenu-file)) - (fn (shell-quote-argument (expand-file-name fn)))) - (with-temp-buffer - (insert (shell-command-to-string (format "mutool show %s outline" fn))) - (goto-char (point-min)) - (while (re-search-forward ".+\\(\t+\\)\"\\(.+\\)\"\t#\\([0-9]+\\)," nil t) - (push `((level . ,(length (match-string 1))) - (title . ,(match-string 2)) - (page . ,(string-to-number (match-string 3)))) - outline))) - (nreverse outline)))) - -(defun jao-doc-view-section-title (&optional page file-name) - (when (not jao--pdf-outline) - (setq-local jao--pdf-outline (jao-doc-view-pdf-outline file-name))) - (let ((page (or page (jao-doc-view-current-page))) - (outline jao--pdf-outline) - (cur-page 0) - (cur-title (jao-doc-view-title (or file-name buffer-file-name "title")))) - (while (and (car outline) (< cur-page page)) - (setq cur-page (cdr (assoc 'page (car outline)))) - (when (<= cur-page page) - (setq cur-title (cdr (assoc 'title (car outline))))) - (setq outline (cdr outline))) - (replace-regexp-in-string "[[:blank:]]+" " " cur-title))) - -(defun jao-doc-view-title (&optional fname) - (if (or fname (not (derived-mode-p 'doc-view-mode 'pdf-view-mode))) - (let ((base (file-name-base (or fname (buffer-file-name))))) - (capitalize (replace-regexp-in-string "-" " " base))) - (or (jao-doc-view-section-title) - (when buffer-file-name (jao-doc-view-title buffer-file-name))))) - - ;;; imenu -(defvar-local jao-doc-view--imenu-file nil) -(defvar-local jao-doc-view--goer 'jao-doc-view-goto-page) - -(defun jao-doc-view--enable-imenu (&optional file-name goto-page) - (setq-local imenu-create-index-function #'jao-doc-view--imenu-create-index - jao-doc-view--imenu-file (or file-name jao-doc-view--imenu-file) - jao-doc-view--goer (or goto-page 'jao-doc-view-goto-page)) - (imenu-add-to-menubar "PDF outline")) - -(defun jao-doc-view--imenu-create-index () - (let (index) - (dolist (item (or jao--pdf-outline - (setq jao--pdf-outline - (jao-doc-view-pdf-outline jao-doc-view--imenu-file)))) - (let-alist item - (let* ((lvl (make-string (max 0 (1- .level)) ?.)) - (title (format "%s%s (%s)" lvl .title .page))) - (push `(,title 0 jao-doc-view--go ,item) index)))) - (nreverse index))) - -(defun jao-doc-view--go (&rest args) - (when-let (item (car (last args))) - (let-alist item (funcall jao-doc-view--goer .page)))) - -(add-hook 'doc-view-mode-hook #'jao-doc-view--enable-imenu) - - +(defun jao-doc-view-enable-imenu (file-name goto-page) + (let ((ifun (lambda () (doc-view-imenu-index file-name goto-page))) + (doc-view-imenu-enabled t)) + (doc-view-imenu-setup) + (setq-local imenu-create-index-function ifun))) + ;;; Page trailing (defvar-local jao-doc-view--trail-back ()) (defvar-local jao-doc-view--trail-fwd ()) @@ -199,38 +69,55 @@ (advice-add 'doc-view-goto-page :before #'jao-doc-view--trail-push) - +;;; Extract text +(defun jao-doc-view-page-text (&optional re-render no-select) + (interactive "P") + (let* ((pno (doc-view-current-page)) + (in buffer-file-name) + (cdir (or (doc-view--current-cache-dir) "/tmp")) + (out (format "%s/p%s.txt" cdir pno))) + (when (and (file-exists-p out) re-render) + (delete-file out)) + (unless (file-exists-p out) + (shell-command-to-string (format "mutool convert -o %s %s %s" out in pno))) + (if no-select + out + (find-file out) + (view-mode)))) + +(define-key doc-view-mode-map "t" #'jao-doc-view-page-text) + ;;; Find URLs -(defun jao-doc-view--page-urls (all) - (if doc-view--current-converter-processes - (message "DocView: please wait till conversion finished.") - (let ((txt (expand-file-name "doc.txt" (doc-view--current-cache-dir))) - (page (doc-view-current-page)) - (pd-rx (rx (+ (literal page-delimiter)))) - (urls)) - (if (file-readable-p txt) - (with-current-buffer (find-file-noselect txt) - (goto-char (point-min)) - (unless all (re-search-forward pd-rx nil t (1- page))) - (let ((end (save-excursion - (if (and (not all) (re-search-forward pd-rx nil t)) - (point) - (point-max))))) - (while (re-search-forward "https?://" end t) - (push (thing-at-point-url-at-point) urls)) - urls)) - (doc-view-doc->txt txt (lambda () (jao-doc-view--page-urls all))) - 'wait)))) +(defun jao-doc-view--full-txt () + (expand-file-name "doc.txt" (doc-view--current-cache-dir))) + +(defun jao-doc-view--collect-urls (file) + (with-current-buffer (find-file-noselect file) + (goto-char (point-min)) + (let ((urls nil)) + (while (re-search-forward "https?://" nil t) + (push (thing-at-point-url-at-point) urls)) + urls))) + +(defun jao-doc-view--page-urls (&optional all) + (let ((txt (jao-doc-view--full-txt))) + (cond ((and all (not (file-exists-p txt))) + (message "Full text not extracted yet: doing so!") + (doc-view-doc->txt txt (lambda () (message "Text extracted"))) + 'wait) + (all (jao-doc-view--collect-urls txt)) + (t (jao-doc-view--collect-urls (jao-doc-view-page-text nil t)))))) (defun jao-doc-view-visit-url (all) "Visit URL displayed in this page." - (interactive "P" doc-view-mode) + (interactive "P") (let ((urls (jao-doc-view--page-urls all))) (cond ((eq 'wait urls) (message "Extracting text, please wait and retry.")) - ((zerop (length urls)) (message "No URLs in this page")) + ((zerop (length urls)) + (message "No URLs in this %s" (if all "document" "page"))) (t (when-let (url (completing-read "URL: " urls nil nil (when (null (cdr urls)) (car urls)))) (browse-url url)))))) - +;;; . (provide 'jao-doc-view) diff --git a/lib/doc/jao-org-links.el b/lib/doc/jao-org-links.el index 9102927..88c0561 100644 --- a/lib/doc/jao-org-links.el +++ b/lib/doc/jao-org-links.el @@ -1,21 +1,20 @@ ;; -*- lexical-binding: t; -*- -(require 'pdf-tools nil t) - (require 'jao-org-notes) (require 'jao-doc-view) - -(declare pdf-info-outline "pdf-info") +(require 'jao-doc-session) +(require 'jao-pdf) (defvar jao-org--sink-dir "./") -(defvar jao-org-open-pdf-fun #'jao-org--pdf-tools-open) +(defvar jao-org-open-pdf-fun #'jao-org--default-open) -(defun jao-org--pdf-tools-open (path page &optional height) +(defun jao-org--default-open (path page &optional height) (org-open-file path 1) (jao-doc-view-goto-page page height)) (defun jao-org--pdf-open (path page &optional height) - (funcall (or jao-org-open-pdf-fun #'jao-org--pdf-tools-open) path page height)) + (when (file-exists-p path) (jao-doc-session-mark path)) + (funcall (or jao-org-open-pdf-fun #'jao-org--default-open) path page height)) (defun jao-org-links--open-pdf (link) "Open LINK in pdf-view-mode." @@ -39,7 +38,7 @@ (read-file-name "Import file: " jao-org--sink-dir link link)))) (rename-file real-file dest-path))) - (if (jao-doc-view-is-pdf dest-path) + (if (jao-pdf-is-pdf-file dest-path) (jao-org-links--open-pdf full-link) (browse-url (format "file://%s" (expand-file-name dest-path)))))) @@ -47,7 +46,7 @@ (let ((default-directory jao-org--sink-dir)) (let ((f (replace-regexp-in-string "^file:" "doc:" (org-file-complete-link arg)))) - (if (jao-doc-view-is-pdf f) + (if (jao-pdf-is-pdf-file f) (let ((page (read-from-minibuffer "Page: " ""))) (if (> (string-to-number page) 0) (concat f "::" (read-from-minibuffer "Page: " "")) @@ -63,7 +62,7 @@ (when (derived-mode-p 'pdf-view-mode 'doc-view-mode) (jao-org-links-store-pdf-link buffer-file-name (jao-doc-view-current-page) - (jao-doc-view-section-title))))) + (jao-pdf-section-title))))) ;;;###autoload (defun jao-org-links-store-pdf-link (path page title) @@ -75,29 +74,28 @@ ;;;###autoload (defun jao-org-insert-doc (title) (interactive "sDocument title: ") - (insert (format "[[doc:%s][%s]]" (jao-doc-view-title->file title) title))) + (insert (format "[[doc:%s][%s]]" (jao-pdf-title-to-file-name title) title))) ;;;###autoload -(defun jao-org-org-to-pdf-file () - (expand-file-name (concat "doc/" (file-name-base buffer-file-name) ".pdf") - (file-name-directory jao-org-notes-dir))) - -;;;###autoload -(defun jao-org-pdf-to-org-file (&optional file-name) - (let* ((file-name (or file-name buffer-file-name)) - (bn (file-name-base file-name)) - (rx (format "%s\\.org$" (regexp-quote bn)))) - (save-some-buffers nil - (lambda () - (string-prefix-p jao-org-notes-dir buffer-file-name))) - (or (car (directory-files-recursively jao-org-notes-dir rx)) - (let* ((dirs (jao-org-notes-cats)) - (dir (completing-read "Notes subdir: " dirs nil t))) - (expand-file-name (concat dir "/" bn ".org") jao-org-notes-dir))))) +(defun jao-org-open-from-zathura (title &optional no-ask) + (when-let* ((info (jao-pdf-zathura-file-info title)) + (pdf-file (car info)) + (page (cadr info)) + (file (jao-org-notes-find-for-pdf pdf-file))) + (jao-afio-goto-docs) + (let ((exists (file-exists-p file))) + (find-file file) + (unless exists (jao-org-insert-doc-skeleton)) + (let ((lnk (jao-pdf--zathura-link info))) + (jao-doc-session-mark) + (if (or (not exists) (and (not no-ask) (y-or-n-p "Insert link?"))) + (insert lnk "\n") + (kill-new lnk) + (message "Link to %s (%s) killed" file page)))))) ;;;###autoload (defun jao-org-insert-doc-skeleton (&optional title) - (insert "#+title: " (or title (jao-doc-view-title (buffer-file-name))) + (insert "#+title: " (or title (jao-pdf-title (buffer-file-name))) "\n#+author:\n#+filetags: ") (jao-org-notes-insert-tags) (insert "\n#+startup: latexpreview\n\n")) @@ -105,10 +103,10 @@ ;;;###autoload (defun jao-org-pdf-goto-org (arg) (interactive "P") - (when (jao-doc-view-is-pdf buffer-file-name) - (let* ((file (jao-org-pdf-to-org-file)) + (when (jao-pdf-is-pdf-file buffer-file-name) + (let* ((file (jao-org-notes-find-for-pdf)) (new (not (file-exists-p file))) - (title (jao-doc-view-title))) + (title (jao-pdf-title))) (when (or arg new) (org-store-link nil t)) (find-file-other-window file) (when new @@ -119,12 +117,15 @@ (defun jao-org-pdf-goto-org* () (interactive) (jao-org-pdf-goto-org t)) ;;;###autoload -(defun jao-org-org-goto-pdf () +(defun jao-org-goto-pdf () (interactive) (if-let (f (jao-org-org-to-pdf-file)) - (find-file-other-window f) + (jao-org--pdf-open f nil) (user-error "No PDF file associated with this buffer"))) +(with-eval-after-load "org" + (define-key org-mode-map (kbd "C-c o") #'jao-org-goto-pdf)) + ;;;###autoload (defun jao-org-links-setup (sink-dir) (interactive) @@ -133,7 +134,6 @@ :complete #'jao-org-links--complete-doc :store #'jao-org-links--store-pdf-link) (org-link-set-parameters "docview" :store #'ignore) - (org-link-set-parameters "message" :follow #'jao-org-links-open-mail) (setq jao-org--sink-dir (file-name-as-directory sink-dir))) (provide 'jao-org-links) diff --git a/lib/doc/jao-org-notes.el b/lib/doc/jao-org-notes.el index 738c938..bd82543 100644 --- a/lib/doc/jao-org-notes.el +++ b/lib/doc/jao-org-notes.el @@ -1,6 +1,6 @@ ;;; jao-org-notes.el --- A simple system for org note taking -*- lexical-binding: t; -*- -;; Copyright (C) 2020, 2021, 2022 jao +;; Copyright (C) 2020, 2021, 2022, 2024 jao ;; Author: jao <mail@jao.io> ;; Keywords: tools @@ -26,36 +26,64 @@ ;;; Code: (require 'org) (require 'consult) +(require 'jao-shell) (defvar jao-org-notes-dir (expand-file-name "notes" org-directory)) -(defun jao-org-notes--rg (str) +(defun jao-org-notes-list () + (directory-files-recursively jao-org-notes-dir "\\.org$")) + +(defun jao-org-notes--rg-cmd (rgx &rest args) `("rg" "--null" "--line-buffered" "--color=never" "--max-columns=250" - "--no-heading" "--line-number" "--smart-case" "." "-e" - ,(format "^(#.(title|filetags): .*)%s" str))) + "--type=org" "--line-number" "--no-heading" "--smart-case" + ,@args ,default-directory "-e" ,rgx)) + +(defun jao-org-notes--rg-title-or-tags (str) + (let* ((m (string-match "^\\([^/]+\\)/\\(.*\\)" str)) + (d (or (and m (match-string 1 str)) "")) + (str (if m (match-string 2 str) str)) + (default-directory + (if (file-directory-p d) (expand-file-name d) default-directory)) + (ts (mapconcat #'identity (split-string str "[:,]+" t) ":|")) + (rgx (format "^#.(title: .*%s|(tags:.*(%s:)))" str ts))) + (jao-org-notes--rg-cmd rgx "-m" "2"))) (defun jao-org-notes--clean-match (m) - (cons (format "%s %s" - (replace-regexp-in-string "^\\./" "" (car m)) - (replace-regexp-in-string "[0-9]+:#\\+\\(file\\)?\\(title\\|tags\\):" - " (\\2)" (cadr m))) - (expand-file-name (car m) default-directory))) + (list (format "%s %s" + (replace-regexp-in-string default-directory "" (car m) nil t) + (replace-regexp-in-string "[0-9]+:#\\+\\(title\\|tags\\):" + "" (cadr m))) + (expand-file-name (car m) default-directory) + (string-to-number (cadr m)))) (defun jao-org-notes--matches (lines) (mapcar (lambda (l) (jao-org-notes--clean-match (split-string l "\0" t))) lines)) +(defun jao-org-notes--grep-rx (rx &rest rg-args) + (let ((default-directory jao-org-notes-dir)) + (jao-org-notes--matches + (apply #'jao-shell-cmd-lines (apply #'jao-org-notes--rg-cmd rx rg-args))))) + (defvar jao-org-notes--grep-history nil) -(defun jao-org--grep (prompt &optional cat no-req) +(defun jao-org-notes--consult-group (m transform) + (or (and transform m) + (and (string-match-p "^[^:]+ + :" m) "tags") + "titles")) + +(defun jao-org-notes--consult-rg (prompt &optional cat no-req cmd) (let ((default-directory (expand-file-name (or cat "") jao-org-notes-dir))) (consult--read - (consult--async-command #'jao-org-notes--rg + (consult--async-command #'jao-org-notes--rg-title-or-tags (consult--async-transform jao-org-notes--matches)) :prompt prompt :initial (consult--async-split-initial "") :add-history (concat (consult--async-split-initial (thing-at-point 'symbol))) :require-match (not no-req) :category 'jao-org-notes-lookup + :group 'jao-org-notes--consult-group + :lookup (lambda (cand cands &rest _) + (or (cadr (assoc cand cands)) (substring cand 1))) :history '(:input jao-org-notes--grep-history)))) (defun jao-org-notes-cats () @@ -64,15 +92,15 @@ (defun jao-org-notes--cat () (let* ((cat (completing-read "Top level category: " (jao-org-notes-cats)))) (cond ((file-exists-p (expand-file-name cat jao-org-notes-dir)) cat) - ((yes-or-no-p "New category, create?") cat) - (t (jao-roam--cat))))) + ((yes-or-no-p "New category, create?") cat)))) (defun jao-org-notes--insert-title () (let* ((cat (jao-org-notes--cat)) - (title (file-name-base (jao-org--grep "Title: " cat t))) + (title (file-name-base (jao-org-notes--consult-rg "Title: " cat t))) (title (replace-regexp-in-string "^#" "" title))) (when (not (string-empty-p title)) (let* ((base (replace-regexp-in-string " +" "-" (downcase title))) + (base (replace-regexp-in-string "[^-[:alnum:][:digit:]]" "" base)) (fname (expand-file-name (concat cat "/" base ".org") jao-org-notes-dir)) (exists? (file-exists-p fname))) @@ -81,85 +109,88 @@ (insert "#+title: " title "\n") t))))) -(defvar jao-org-notes--tags nil) -(defvar jao-org-notes-tags-cache-file "~/.emacs.d/cache/tags.eld") - -(defun jao-org-notes--save-tags () - (with-current-buffer (find-file-noselect jao-org-notes-tags-cache-file) - (delete-region (point-min) (point-max)) - (print jao-org-notes--tags (current-buffer)) - (let ((message-log-max nil) - (inhibit-message t)) - (save-buffer)))) +(defun jao-org-notes--find-tag (tag) + (jao-org-notes--grep-rx (format "^#.tags:.*:%s:" tag) "-m" "1")) -(defun jao-org-notes--read-tags-cache () - (let ((b (find-file-noselect jao-org-notes-tags-cache-file))) - (with-current-buffer b (goto-char (point-min))) - (setq jao-org-notes--tags (read b)))) +(defvar jao-org-notes--tags nil) +(defvar jao-org-notes--tag-history nil) (defun jao-org-notes--read-tags () - (unless jao-org-notes--tags (jao-org-notes--read-tags-cache)) - (let* ((tags (completing-read-multiple "Tags: " jao-org-notes--tags)) - (new-tags (seq-difference tags jao-org-notes--tags))) - (when new-tags - (setq jao-org--notes-tags - (sort (append new-tags jao-org-notes--tags) #'string<)) - (jao-org-notes--save-tags)) + (let* ((tags (completing-read-multiple "Tags: " jao-org-notes--tags nil nil nil + 'jao-org-notes--tag-history))) + (setq jao-org-notes--tags (seq-union jao-org-notes--tags tags #'string=)) tags)) -(defun jao-org-notes--insert-tags () - (insert "#+filetags: " (mapconcat #'identity (jao-org-notes--read-tags) " ") "\n")) - -(defun jao-org-notes--insert-date () - (insert "#+date: ") - (org-insert-time-stamp (current-time)) - (insert "\n")) - (defun jao-org-notes--template (k) - `(,k "Note" plain (file jao-org-notes-open-or-create) - "\n- %a\n %i" - :jump-to-captured t)) + `(,k "Note" plain (file jao-org-notes-create) + "%(if %:url \"#+link: %:url\" \"\")\n\n- %a\n %i")) + +(defun jao-org-notes-all-tags () + (let ((tags nil)) + (dolist (m (jao-org-notes--find-tag ".*")) + (setq tags (seq-union tags (cdr (split-string (car m) ":" t))))) + (sort tags #'string<))) + +(defun jao-org-notes-find-for-pdf (&optional file-name) + "Given a PDF file name, find its org notes counterpart." + (let* ((file-name (or file-name buffer-file-name)) + (bn (file-name-base file-name)) + (rx (format "%s\\.org$" (regexp-quote bn))) + (pred (lambda () (string-prefix-p jao-org-notes-dir buffer-file-name)))) + (save-some-buffers nil pred) + (or (car (directory-files-recursively jao-org-notes-dir rx)) + (let* ((d (completing-read "Notes subdir: " (jao-org-notes-cats) nil t)) + (d (file-name-as-directory d))) + (expand-file-name (concat d bn ".org") jao-org-notes-dir))))) -;;;###autoload (defun jao-org-notes-open () "Search for a note file, matching tags and titles with completion." (interactive) - (when-let (f (jao-org--grep "Search notes: ")) + (when-let (f (jao-org-notes--consult-rg "Search notes: ")) (find-file f))) -;;;###autoload -(defun jao-org-notes-open-or-create () - "Open or create a new note file, matching tags and titles with completion." +(defun jao-org-notes-consult-tags () + "Search for a note file, matching all tags with completion." + (interactive) + (let* ((tags (jao-org-notes--read-tags)) + (init (concat "^..tags: " (mapconcat #'identity tags " ")))) + (consult-ripgrep jao-org-notes-dir init))) + +(defun jao-org-notes-consult-ripgrep (&optional initial cat) + (interactive) + (consult-ripgrep (expand-file-name (or cat "") jao-org-notes-dir) initial)) + +(defun jao-org-notes-create () + "Create a new note file, matching tags and titles with completion." (interactive) (when (jao-org-notes--insert-title) - (jao-org-notes--insert-date) - (jao-org-notes--insert-tags)) + (org-insert-time-stamp (current-time) t t "#+date: " "\n") + (insert "#+tags: :" + (mapconcat #'identity (jao-org-notes--read-tags) ":") + ":\n")) (save-buffer) (buffer-file-name)) -;;;###autoload -(defun jao-org-notes-grep (&optional initial) - "Perform a grep search on all org notes body, via consult-ripgrep." - (interactive) - (consult-ripgrep jao-org-notes-dir initial)) - -;;;###autoload (defun jao-org-notes-backlinks () "Show a list of note files linking to the current one." (interactive) - (jao-org-notes-search (concat "\\[\\[file:\\(.*/\\)?" (buffer-name)))) + (if-let* ((res (jao-org-notes--grep-rx + (concat "\\[file:.*" (regexp-quote (buffer-name)) "\\]\\["))) + (file (completing-read "File: " res nil t nil)) + (entry (assoc file res))) + (progn (find-file (cadr entry)) + (when-let (line (caddr entry)) (goto-line line))) + (message "Nobody links here!"))) -;;;###autoload (defun jao-org-notes-insert-tags () "Insert a list of tags at point, with completing read." (interactive) - (insert (mapconcat 'identity (jao-org-notes--read-tags) " "))) + (insert ":" (mapconcat 'identity (jao-org-notes--read-tags) ":") ":")) -;;;###autoload (defun jao-org-notes-insert-link () "Select a note file (with completion) and insert a link to it." (interactive) - (when-let (f (jao-org--grep "Notes file: ")) + (when-let (f (jao-org-notes--consult-rg "Notes file: ")) (let ((rel-path (file-relative-name f default-directory)) (title (with-current-buffer (find-file-noselect f) (save-excursion @@ -168,11 +199,19 @@ (match-string 1)))))) (insert (format "[[file:%s][%s]]" rel-path title))))) +(defun jao-org-notes-stats () + (interactive) + (message "%d notes, %d tags in %s" + (length (jao-org-notes-list)) + (length jao-org--notes-tags) + jao-org-notes-dir)) + ;;;###autoload (defun jao-org-notes-setup (mnemonic) "Set up the notes system, providing a mnemonic character for its org template." (setq org-capture-templates - (add-to-list 'org-capture-templates (jao-org-notes--template mnemonic))) + (add-to-list 'org-capture-templates (jao-org-notes--template mnemonic)) + jao-org-notes--tags (jao-org-notes-all-tags)) (when (fboundp 'org-capture-upgrade-templates) (org-capture-upgrade-templates org-capture-templates))) diff --git a/lib/doc/jao-pdf.el b/lib/doc/jao-pdf.el new file mode 100644 index 0000000..1ee74bc --- /dev/null +++ b/lib/doc/jao-pdf.el @@ -0,0 +1,100 @@ +;;; jao-pdf.el --- utilities for pdf files -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 jao + +;; Author: jao <mail@jao.io> +;; Keywords: docs + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Some niceties for PDFs: +;; +;; - Using mutools, we can extract the outline of PDFs, and tell back the +;; section title of a given page. +;; - Interoperability with zathura. + +(require 'jao-doc-session) + +;;; PDF info + +(declare-function 'pdf-info-outline "pdf-info") + +(defvar-local jao-pdf--outline nil) + +(defun jao-pdf-is-pdf-file (file) + "Simply checks the FILE extension." + (string-match-p ".*\\.pdf$" file)) + +(defun jao-pdf-title-to-file-name (title) + "Convert a title, possibly with embedded spaces, to a PDF filename." + (concat (mapconcat 'downcase (split-string title nil t) "-") ".pdf")) + +(defun jao-pdf-title (&optional fname) + (if (or fname (not (derived-mode-p 'doc-view-mode 'pdf-view-mode))) + (let ((base (file-name-base (or fname (buffer-file-name))))) + (capitalize (replace-regexp-in-string "-" " " base))) + (or (jao-pdf-section-title) + (when buffer-file-name (jao-pdf-title buffer-file-name))))) + +(defvar-local jao-pdf--outline nil) + +(defun jao-pdf-section-title (&optional page file-name) + (when (not jao-pdf--outline) + (setq-local jao-pdf--outline (doc-view--pdf-outline file-name))) + (let ((page (or page + (and (derived-mode-p 'doc-view-mode) (doc-view-current-page)) + (and (derived-mode-p 'pdf-view) (pdf-view-current-page)))) + (outline jao-pdf--outline) + (cur-page 0) + (cur-title (jao-pdf-title (or file-name buffer-file-name "title")))) + (while (and (car outline) page (< cur-page page)) + (setq cur-page (cdr (assoc 'page (car outline)))) + (when (<= cur-page page) + (setq cur-title (cdr (assoc 'title (car outline))))) + (setq outline (cdr outline))) + (replace-regexp-in-string "[[:blank:]]+" " " cur-title))) + +;;; zathura interop +(defun jao-pdf-zathura-open-cmd (file page &optional suffix) + (let ((page (if page (format "-P %s" page) ""))) + (format "zathura %s %s %s" file page (or suffix "")))) + +(defun jao-pdf-zathura-title-rx (file) + (concat (file-name-nondirectory file) " \\[.+\\]")) + +;; e.g. "~/org/doc/write-yourself-a-scheme-in-48-hours.pdf [96 (96/138)]" +(defun jao-pdf-zathura-file-info (title) + (when (string-match "\\(.+\\) \\[\\(.+\\) (\\([0-9]+\\)/\\([0-9]+\\))\\]" + title) + (list (expand-file-name (match-string 1 title)) + (string-to-number (match-string 3 title)) + (string-to-number (match-string 4 title)) + (match-string 2 title)))) + +(defun jao-pdf--zathura-link (info) + (when-let* ((file (car info)) + (page (cadr info)) + (no (or (car (last info)) page)) + (fn (file-name-nondirectory file)) + (lnk (format "doc:%s::%s" fn page)) + (desc (format "%s (p. %s)" (jao-pdf-section-title page file) no))) + (org-make-link-string lnk desc))) + +(defun jao-pdf-zathura-org-link (title) + (jao-pdf--zathura-link (jao-pdf-zathura-file-info title))) + +(provide 'jao-pdf) +;;; jao-pdf.el ends here diff --git a/lib/doc/jao-recoll.el b/lib/doc/jao-recoll.el deleted file mode 100644 index f43451f..0000000 --- a/lib/doc/jao-recoll.el +++ /dev/null @@ -1,116 +0,0 @@ -;; jao-recoll.el -- Displaying recoll queries - -;; Copyright (c) 2017, 2020, 2021, 2022 Jose Antonio Ortega Ruiz - -;; This file is free software; you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation; either version 3 of the License, or -;; (at your option) any later version. - -;; This file is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see <http://www.gnu.org/licenses/>. - -;; Author: Jose Antonio Ortega Ruiz <jao@gnu.org> -;; Start date: Wed Nov 01, 2017 18:14 - - -;;; Comentary: - -;; A simple interactive command to perform recoll queries and display -;; its results using org-mode. - -;;; Code: - - -(require 'org) - -(define-derived-mode recoll-mode org-mode "Recoll" - "Simple mode for showing recoll query results" - (read-only-mode 1)) - -(defvar jao-recoll--file-regexp - "\\(\\w+/.+\\)\t+\\[\\([^]]+\\)\\]\t+\\[\\([^]]+\\)\\].+") - -(defvar jao-recoll-flags "-A -p 5 -n 100") - -(defvar jao-recoll-single-buffer t) -(defvar-local jao-recoll--last-query nil) -(defvar-local jao-recoll--last-full-query nil) - -(defun jao-recoll-show-query () - (interactive) - (message (concat jao-recoll--last-query "\n" - jao-recoll--last-full-query))) - -(defun jao-recoll-requery () - (interactive) - (jao-recoll jao-recoll--last-query)) - -(defun jao-recoll--buffer (q) - (get-buffer-create (if jao-recoll-single-buffer - "*Recoll*" - (format "*Recoll: '%s'*" q)))) - -;;;###autoload -(defun jao-recoll (&optional prefix-query) - "Performs a query using recoll and shows the results in a buffer -using org mode." - (interactive) - (let* ((query (read-string "Recoll query: " prefix-query)) - (cmd (format "recoll %s -t %s" - jao-recoll-flags (shell-quote-argument query))) - (inhibit-read-only t) - (lnk nil)) - (with-current-buffer (jao-recoll--buffer query) - (recoll-mode) - (delete-region (point-min) (point-max)) - (shell-command cmd t) - (setq jao-recoll--last-query query) - (goto-char (point-min)) - (when (looking-at-p "Recoll query:") - (setq jao-recoll--last-full-query - (string-trim (thing-at-point 'line))) - (let ((kill-whole-line nil)) (kill-line)) - (insert query) - (forward-line 2)) - (open-line 1) - (while (search-forward-regexp jao-recoll--file-regexp nil t) - (setq lnk - (cond ((string= (match-string 1) "application/pdf") - (concat "doc:" - (file-name-nondirectory (match-string 2)))) - ((string= (match-string 1) "message/rfc822") - (concat "message:" (substring (match-string 2) 7))) - (t (match-string 2)))) - (replace-match (format "* [[%s][\\3]] (\\1)" lnk)) - (forward-line) - (when (looking-at-p "SNIPPETS") - (let ((kill-whole-line t)) - (kill-line) - (while (and (not (eobp)) (not (looking-at-p "/SNIPPETS"))) - (if (looking-at "^\\([1-9][0-9]*\\) : ") - (replace-match (format " - [[%s::\\1][\\1]] : " lnk)) - (insert " - ")) - (forward-line 1)) - (unless (eobp) (kill-line))))) - (pop-to-buffer (current-buffer)) - (goto-char (point-min)) - (org-next-visible-heading 1) - (org-overview) - (jao-recoll-show-query)))) - -(define-key recoll-mode-map [?n] 'org-next-link) -(define-key recoll-mode-map [?p] 'org-previous-link) -(define-key recoll-mode-map [?q] 'bury-buffer) -(define-key recoll-mode-map [?r] 'jao-recoll-requery) -(define-key recoll-mode-map [?g] 'jao-recoll-requery) -(define-key recoll-mode-map [?w] 'jao-recoll-show-query) - - -(provide 'jao-recoll) -;;; jao-recoll.el ends here |