diff options
Diffstat (limited to 'emacs/.emacs.d/lisp/my/my-nov.el')
-rw-r--r-- | emacs/.emacs.d/lisp/my/my-nov.el | 172 |
1 files changed, 163 insertions, 9 deletions
diff --git a/emacs/.emacs.d/lisp/my/my-nov.el b/emacs/.emacs.d/lisp/my/my-nov.el index 863d09a..21df675 100644 --- a/emacs/.emacs.d/lisp/my/my-nov.el +++ b/emacs/.emacs.d/lisp/my/my-nov.el @@ -28,22 +28,59 @@ (require 'nov) +(defvar my-nov-mode-line-format "%p%% %t: %c") +(defvar-local my-nov-title nil) +(defvar-local my-nov-chapter-title nil) +(defvar-local my-nov-position-percent nil) + ;; override nov-render-title ;; this is because header line does not work with follow mode (defun my-nov-render-title (dom) "Custom <title> rendering function for DOM. Sets `header-line-format' to a combination of the EPUB title and chapter title." - (let ((title (cdr (assq 'title nov-metadata))) - (chapter-title (car (esxml-node-children dom)))) - (when (not chapter-title) - (setq chapter-title "No title")) - ;; this shouldn't happen for properly authored EPUBs - (when (not title) - (setq title "No title")) + (setq my-nov-title (cdr (assq 'title nov-metadata)) + my-nov-chapter-title (car (esxml-node-children dom)))) + +(defun my-nov-update-mode-line () + (setq my-nov-position-percent + (/ (* 100 (my-nov-word-position)) my-nov-total-word-count)) + (let ((title (or my-nov-title (propertize "No title" 'face 'italic))) + (chapter-title (or my-nov-chapter-title + (propertize "No title" 'face 'italic)))) (setq mode-line-buffer-identification - (concat title ": " chapter-title)) - )) + (format-spec + my-nov-mode-line-format + `((?c . ,chapter-title) + (?t . ,title) + (?p . ,my-nov-position-percent)))))) + +(defun my-nov-render-span (dom) + (unless (equal (dom-attr dom 'epub:type) "pagebreak") + (shr-generic dom))) + +;;; TODO: perhaps no indentation? +(defun my-nov-render-ol (dom) + (shr-ensure-paragraph) + (let* ((attrs (dom-attributes dom)) + (start-attr (alist-get 'start attrs)) + ;; Start at 1 if there is no start attribute + ;; or if start can't be parsed as an integer. + (start-index (condition-case _ + (cl-parse-integer start-attr) + (t nil))) + (shr-list-mode (or start-index 'ul)) + (shr-internal-bullet `(" " . ,(shr-string-pixel-width " ")))) + (shr-generic dom)) + (shr-ensure-paragraph)) + +(defun my-nov-find-file-with-ipath (file-name ipath) + "Find epub file and goto IPATH. + +Useful for recoll." + (find-file file-name) + (unless (derived-mode-p 'nov-mode) (nov-mode)) + (nov-goto-document (nov-find-document (lambda (p) (eq ipath (car p)))))) (defun my-nov-scroll-up (arg) "Scroll with `scroll-up' or visit next chapter if at bottom." @@ -52,5 +89,122 @@ chapter title." (nov-next-document) (follow-scroll-up arg))) +(defun my-nov-copy-buffer-file-with-staging () + (interactive) + (unless (derived-mode-p 'nov-mode) (error "Not in nov mode")) + (pcase-let* ((name + (completing-read (format "Copy %s to: " nov-file-name) + my-copy-file-targets + nil t)) + (`(,dest ,staging) (alist-get name my-copy-file-targets + nil nil #'equal))) + (my-copy-file-with-staging + nov-file-name dest staging))) + +(defun my-nov-set-margins () + ;; Does not work as well as setq left- and right-margin-width + ;; (set-window-margins nil 3 2) + (setq left-margin-width 3) + (setq right-margin-width 2) + ;; Does not work as well as setq left- and right-fringe-width + ;; (set-window-fringes nil 0 0) + (setq left-fringe-width 0) + (setq right-fringe-width 0) + (visual-line-mode) + ) + +(defvar-local my-nov-document-word-counts nil + "Word count of each nov document.") + +(defvar-local my-nov-total-word-count nil + "Total word count of the epub.") + +(defun my-nov-count-words () + (interactive) + (unless my-nov-document-word-counts + (message "Counting words...") + (setq my-nov-document-word-counts + (apply + 'vector + (seq-map + (lambda (doc) + (with-temp-buffer + (pcase-let ((`(,name . ,file) doc)) + (insert-file-contents file) + (nov-render-html) + (cons name (count-words (point-min) (point-max)))))) + nov-documents))) + (setq my-nov-total-word-count + (seq-reduce + (lambda (sum pair) + (+ sum (cdr pair))) + my-nov-document-word-counts + 0)) + (message "Counting words...done"))) + +(defun my-nov-stats () + (interactive) + (message "%d words; %d standard pages" + my-nov-total-word-count + (ceiling (/ my-nov-total-word-count 300.0)))) + +;;; TODO: also show current percentage in the total book in the mode +;;; line +(defun my-nov-goto-nth-word (n) + "Go to the nth word of the current epub." + (my-nov-count-words) + (setq nov-documents-index -1) + (let ((found + (seq-find + (lambda (pair) + (setq n (- n (cdr pair))) + (setq nov-documents-index (1+ nov-documents-index)) + (<= n 0)) + my-nov-document-word-counts))) + (nov-render-document) + (if (> n 0) + (end-of-buffer) + (forward-word (+ n (cdr found))))) + ) + +(defun my-nov-word-position () + "Where are we in terms of word position? + +Return n, such that nth word of the epub is at point." + (my-nov-count-words) + (let ((result 0)) + (dotimes (i nov-documents-index) + (setq result (+ result (cdr (aref my-nov-document-word-counts i))))) + (setq result (+ result (count-words (point-min) (point)))))) + +(defun my-nov-skim-forward () + "Forward by 3-10% of the book." + (interactive) + (let ((pc (+ 3 (random 8)))) + (my-nov-goto-nth-word + (+ (my-nov-word-position) + (/ (* my-nov-total-word-count pc) 100))) + (message "Skimmed forward by %d%% of the book" pc))) + +(defun my-nov-skim-backward () + "Backward by 3-10% of the book." + (interactive) + (let ((pc (+ 3 (random 8)))) + (my-nov-goto-nth-word + (max + 0 + (- (my-nov-word-position) + (/ (* my-nov-total-word-count pc) 100)))) + (message "Skimmed backward by %d%% of the book" pc))) + +(defun my-nov-goto-random-position () + "Goto a random position in the epub." + (interactive) + (my-nov-count-words) + (let ((n (random my-nov-total-word-count))) + (my-nov-goto-nth-word n) + (message "Went to the %dth word (%d%% of the book)." + n (/ (* n 100) my-nov-total-word-count)))) + (provide 'my-nov) ;;; my-nov.el ends here |