Browse Source

Upload files to ''

fred 11 months ago
parent
commit
257650dde9
5 changed files with 1003 additions and 0 deletions
  1. 1003 0
      bible-mode.el
  2. BIN
      example1.png
  3. BIN
      example2.png
  4. BIN
      example3.png
  5. BIN
      example4.png

+ 1003 - 0
bible-mode.el

@@ -0,0 +1,1003 @@
+;;;; -*- mode: EMACS-LISP; lexical-binding: t -*-
+;;
+;; bible-mode.el --- A browsing interface for the SWORD Project's Diatheke CLI
+;; Time-stamp: <2024-05-14 11:32:24 fred>
+
+;; Author: Zacalot
+;; Fixes and modifications by Fred Gilham
+;; Url: https://github.com/fmgilham/bible-mode
+;; Forked from
+;; Url: https://github.com/Zacalot/bible-mode
+;; Version: 1.0.0
+;; Package-Requires: ((emacs "24.1"))
+;; Keywords: diatheke, sword, research, bible
+
+;;; Commentary:
+
+;; This package uses the `diatheke' program to browse and search 
+;; Biblical texts provided by the Sword project. 
+;; Word study is also supported.
+
+;;; Usage:
+
+;; First install `diatheke'.  On Debian/Ubuntu it's in the `diatheke'
+;; package. In other distributions it might be in the sword package.
+
+;; For Windows I found that you can simply install the Xiphos package.
+;; It includes the Sword library and its utilities including diatheke,
+;; installmgr and mkfastmod. Add the "Program Files\Xiphos\bin" path
+;; to your execution path.
+
+;; Use M-x `bible-open' to open a Bible buffer.
+;; Use C-h f `bible-mode' to see available keybindings.
+
+;; You may customize `bible-mode-module' to set a default browsing
+;; module, as well as `bible-mode-word-study-enabled' to enable word
+;; study by default.
+
+;;; Design:
+
+;; The idea here is to use the diatheke program to insert code from
+;; modules into buffers. The main bible display uses an "internal" XML
+;; format. The whole buffer gets parsed by libxml-parse-html-region to
+;; create a dom tree. This gets parsed by
+;; bible-mode--insert-domnode-recursive to render the text into
+;; reading format.
+
+;; The text is then decorated using information from the dom format as
+;; necessary along with regular expressions to identify the verse
+;; references. This is for red letters, purple highlighting of the
+;; verse numbers, bold face of the divine name in the OT and so on.
+
+;; If strongs tags and/or morphological tags are present, they are
+;; looked up in appropriate lexical and morphological modules and used
+;; to add tooltips to the text so that mousing over words will bring
+;; up a tooltip with information about the word. Clicking on a word
+;; with lexical information will display that informatio in a "term"
+;; buffer.
+
+
+;;;
+;;; bm- is used as shorthand (see Local Variables) for bible-mode-
+
+;;; Code:
+
+;;;; Requirements
+(require 'cl-lib) ; XXX FMG there are just a few constructs that use this; use elisp versions instead.
+;; (require 'bidi)
+(require 'dom)
+(require 'shr)
+
+;;;; Variables
+
+(defgroup bible-mode nil
+  "Settings for `bible-mode'."
+  :group 'tools
+  :link '(url-link "https://github.com/fmgilham/bible-mode"))
+
+(defcustom bm-module
+  "KJV"
+  "Book module for Diatheke to query."
+  :type '(choice (const :tag "None" nil)
+                 (string :tag "Module abbreviation (e.g. \"KJV\")"))
+  :local t
+  :group 'bible-mode)
+
+(defcustom bm-font
+  "Ezra SIL"
+  "Default font for bible-mode."
+  :type '(string :tag "Font family name (e.g. \"Ezra SIL\")")
+  :local t
+  :group 'bible-mode)
+
+
+(defcustom bm-greek-lexicon
+  "MLStrong"
+  "Lexicon used for displaying definitions of Greek words using Strong's codes."
+  :type '(string :tag "Lexicon module (e.g. \"StrongsRealGreek\").")
+  :local nil
+  :group 'bible-mode)
+
+(defcustom bm-short-greek-lexicon
+  "StrongsRealGreek"
+  "Lexicon used for displaying definitions of Greek words in tooltips."
+  :type '(string :tag "Lexicon module (e.g. \"StrongsRealGreek\").")
+  :local nil
+  :group 'bible-mode)
+
+(defcustom bm-hebrew-lexicon
+  "StrongsRealHebrew"  ; Nice to use BDBGlosses_Strongs but it needs to be special-cased
+  "Lexicon used for displaying definitions of Hebrew words using Strong's codes."
+  :type '(string :tag "Lexicon module (e.g. \"StrongsRealHebrew\")")
+  :local nil
+  :group 'bible-mode)
+
+(defcustom bm-short-hebrew-lexicon
+  "StrongsRealHebrew"
+  "Lexicon used for displaying definitions of Hebrew words in tooltips."
+  :type '(string :tag "Lexicon module (e.g. \"StrongsRealHebrew\")")
+  :local nil
+  :group 'bible-mode)
+
+
+(defcustom bm-word-study-enabled
+  nil
+  "Display Strong's Hebrew, Strong's Greek, and Lemma words for study."
+  :type 'boolean
+  :local t
+  :group 'bible-mode)
+
+(defcustom bm-red-letter-enabled
+  t
+  "Display words of Jesus in red when module has that information."
+  :type 'boolean
+  :local t
+  :group 'bible-mode)
+
+;;; defvars
+
+(defvar bm-modules (lazy-completion-table bm-modules bm-list-biblical-modules))
+
+;; XXX I believe these chapter counts aren't the same for all modules, e.g. JPS.
+(defvar bm-books
+  '(;; Old Testament
+    ("Genesis"		. 50)	("Exodus"		. 40)	("Leviticus"		. 27)	("Numbers"		. 36)
+    ("Deuteronomy"	. 34)	("Joshua"		. 24)	("Judges"		. 21)	("Ruth"			. 4)
+    ("I Samuel"		. 31)	("II Samuel"		. 24)	("I Kings"		. 22)	("II Kings"		. 25)
+    ("I Chronicles"	. 29)	("II Chronicles"	. 36)	("Ezra"			. 10)	("Nehemiah"		. 13)
+    ("Esther" 		. 10)	("Job"			. 42)	("Psalms"		. 150)	("Proverbs"		. 31)
+    ("Ecclesiastes"	. 12)	("Song of Solomon"	. 8)	("Isaiah"		. 66)	("Jeremiah"		. 52)
+    ("Lamentations"	. 5)	("Ezekiel"		. 48)	("Daniel"		. 12)	("Hosea"		. 14)
+    ("Joel"		. 3)	("Amos"			. 9)	("Obadiah"		. 1)	("Jonah"		. 4)
+    ("Micah"		. 7)	("Nahum"		. 3)	("Habakkuk"		. 3)	("Zephaniah"		. 3)
+    ("Haggai"		. 2)	("Zechariah"		. 14)	("Malachi"		. 4)
+    ;; New Testament
+    ("Matthew"		. 28)	("Mark"			. 16)	("Luke"			. 24)	("John"			. 21)
+    ("Acts"		. 28)	("Romans"		. 16)	("I Corinthians"	. 16)	("II Corinthians" 	. 13)
+    ("Galatians"	. 6)	("Ephesians"		. 6)	("Philippians"		. 4)	("Colossians"		. 4)
+    ("I Thessalonians"	. 5)	("II Thessalonians"	. 3)	("I Timothy"		. 6)	("II Timothy"		. 4)
+    ("Titus"		. 3)	("Philemon"		. 1)	("Hebrews"		. 13)	("James"		. 5)
+    ("I Peter"		. 5)	("II Peter"		. 3)	("I John"		. 5)	("II John"		. 1)
+    ("III John"		. 1)    ("Jude"			. 1)	("Revelation of John"	. 22))
+  "A-list of name / chapter count for Bible books.")
+
+;;;; Book / chapter
+
+(defvar-local bm-current-book (assoc "Genesis" bm-books)
+  "Current book data (name . chapter).")
+
+(defvar-local bm-current-book-name "Genesis"
+  "Current book name.")
+
+(defvar-local bm-current-chapter 1
+  "Current book chapter number.")
+
+(defvar-local bm-search-query nil 
+  "Search query associated with the buffer.")
+
+(defvar-local bm-search-mode "phrase"
+  "Search mode: either `lucene' or `phrase'.")
+
+(defvar-local bm-has-strongs nil
+  "Set if the module being displayed has strongs numbers availabile.")
+
+(defvar-local bm-has-morphology nil
+  "Set if the module being displayed has morphology availabile.")
+
+
+;; (defvar bm-current-module nil)
+
+;;;; Keymaps
+
+(defconst bm-map (make-keymap))
+;;;;; Navigation
+(define-key bm-map "n" 'bm-next-chapter)
+(define-key bm-map "p" 'bm-previous-chapter)
+(define-key bm-map (kbd "TAB") 'bm-forward-word) ; TODO: bm-forward-word
+;;;;; Direct jump
+(define-key bm-map "b" 'bm-select-book)
+(define-key bm-map "c" 'bm-select-chapter)
+;;;;; Search
+(define-key bm-map "s" 'bible-search)
+(define-key bm-map "/" 'bible-search)
+;;;; Not yet
+;;(define-key bm-map "" 'bm-set-search-range)
+;;;;; Misc
+(define-key bm-map "m" 'bm-select-module)
+(define-key bm-map "w" 'bm-toggle-word-study)
+(define-key bm-map "x" 'bm-split-display)
+;;;;; Deal with visual-line-mode
+(define-key bm-map "\C-n" 'next-logical-line)
+(define-key bm-map "\C-p" 'previous-logical-line)
+
+(defconst bible-search-mode-map (make-keymap))
+(define-key bible-search-mode-map "s" 'bible-search)
+(define-key bible-search-mode-map "w" 'bm-toggle-word-study)
+(define-key bible-search-mode-map (kbd "RET") 'bible-search-mode-follow-verse)
+
+(defconst bible-term-hebrew-mode-map (make-keymap))
+(defconst bible-term-greek-mode-map (make-keymap))
+(defconst bible-term-morph-mode-map (make-keymap))
+
+
+;;;
+;;; Menu bar items
+;;;
+;;; Right now just convenience items. More as I think of them.
+;;;
+(define-key global-map [menu-bar bible-mode]
+	    (cons "Bible Mode" (make-sparse-keymap "Bible Mode")))
+
+(defun bm-set-left-to-right ()
+  (interactive)
+  (setq-local bidi-paragraph-direction 'left-to-right))
+
+(defun bm-set-right-to-left ()
+  (interactive)
+  (setq-local bidi-paragraph-direction 'right-to-left))
+
+(define-key global-map
+	    [menu-bar bible-mode left-to-right]
+	    '("Left-to-right" . bm-set-left-to-right))
+
+(define-key global-map 
+	    [menu-bar bible-mode right-to-left]
+	    '("Right-to-left" . bm-set-right-to-left))
+
+(defvar-local bm-debugme nil)
+
+(defun bm-set-display-xml ()
+  (interactive)
+  (setq-local bm-debugme t)
+  (bm-display))
+
+(defun bm-set-display-text ()
+  (interactive)
+  (setq-local bm-debugme nil)
+  (bm-display))
+
+
+(define-key global-map 
+	    [menu-bar bible-mode display-xml]
+	    '("Display XML" . bm-set-display-xml))
+
+(define-key global-map 
+	    [menu-bar bible-mode display-text]
+	    '("Display Text" . bm-set-display-text))
+
+
+(define-key global-map
+	    [menu-bar bible-mode select-biblical-text]
+	    '("Select Text" . bm-display-available-modules))
+
+
+
+(defun bm-display-greek ()
+  (interactive)
+  (let ((item (car (split-string (get-text-property (point) 'strong)))))
+    ;; Remove "strong:G" prefix
+    (bible-term-greek (replace-regexp-in-string "strong:G" "" item))))
+	      
+(defconst bm-greek-keymap (make-sparse-keymap))
+(define-key bm-greek-keymap (kbd "RET") 'bm-display-greek)
+(define-key bm-greek-keymap [mouse-1] 'bm-display-greek)
+
+(defun bm-display-hebrew () 
+  (interactive)
+  (let ((item (car (split-string (get-text-property (point) 'strong)))))
+    ;; Remove "strong:H" prefix and any alphabetic suffixes.
+    (bible-term-hebrew (replace-regexp-in-string "[a-zA-Z]" "" item nil nil nil 8))))
+
+(defconst bm-hebrew-keymap (make-sparse-keymap))
+(define-key bm-hebrew-keymap (kbd "RET") 'bm-display-hebrew)
+(define-key bm-hebrew-keymap [mouse-1] 'bm-display-hebrew)
+
+(defconst bm-lemma-keymap (make-sparse-keymap))
+(define-key bm-lemma-keymap (kbd "RET")
+	    (lambda ()
+	      (interactive)
+	      ))
+
+(defconst bm-morph-keymap (make-sparse-keymap))
+(define-key bm-morph-keymap (kbd "RET")
+	    (lambda ()
+	      (interactive)
+;;;	      (let ((thing (thing-at-point 'word)))
+;;;		(message "thing at point: %s" thing)
+;;;		(message "morph property %s" (get-text-property 0 'field thing))
+		))
+
+;;;; Modes
+
+(define-derived-mode bible-mode special-mode "Bible"
+  "Mode for reading the Bible.
+\\{bm-map}"
+  (buffer-disable-undo)
+  (font-lock-mode t)
+  (use-local-map bm-map)
+  (setq buffer-read-only t)
+  (visual-line-mode t))
+
+
+(define-derived-mode bible-search-mode special-mode "Bible Search"
+  "Mode for performing Bible searches.
+\\{bible-search-mode-map}"
+  (buffer-disable-undo)
+  (font-lock-mode t)
+  (use-local-map bible-search-mode-map)
+  (setq buffer-read-only t)
+  (visual-line-mode t)
+)
+
+(define-derived-mode bible-term-hebrew-mode special-mode "Bible Term (Hebrew)"
+  "Mode for researching Hebrew terms in the Bible.
+\\{bible-term-hebrew-mode-map}"
+  (buffer-disable-undo)
+  (font-lock-mode t)
+  (use-local-map bible-term-hebrew-mode-map)
+  (setq buffer-read-only t)
+  (visual-line-mode t))
+
+(define-derived-mode bible-term-greek-mode special-mode "Bible Term (Greek)"
+  "Mode for researching Greek terms in the Bible.
+\\{bible-term-greek-mode-map}"
+  (buffer-disable-undo)
+  (font-lock-mode t)
+  (use-local-map bible-term-greek-mode-map)
+  (setq buffer-read-only t)
+  (visual-line-mode t))
+
+;;;; Functions
+
+;;;;; Commands
+
+;;;###autoload
+(defun bible-open (&optional book-name chapter verse)
+  "Creates and opens a `bible-mode' buffer"
+  (interactive)
+  (let ((buf (get-buffer-create (generate-new-buffer-name (concat "*bible*")))))
+    (set-buffer buf)
+    (bible-mode)
+    (bm-set-location (assoc (or book-name "Genesis") bm-books) (or chapter 1) verse)
+    (set-window-buffer (get-buffer-window (current-buffer)) buf)))
+
+;;;###autoload
+(defun bm-next-chapter ()
+  "Pages to the next chapter for the active `bible-mode' buffer."
+  (interactive)
+  (let* ((book-chapters (cdr bm-current-book))
+	 (chapter (min book-chapters (+ bm-current-chapter 1))))
+    (bm-set-location bm-current-book chapter)))
+
+;;;###autoload
+(defun bm-previous-chapter ()
+  "Pages to the previous chapter for the active `bible-mode' buffer."
+  (interactive)
+  (bm-set-location bm-current-book (max 1 (- bm-current-chapter 1))))
+
+
+(defun bm-forward-word ()
+  "Moves forward a word, taking into account the relevant text properties.
+XXX Doesn't work yet."
+  (interactive)
+  (field-end))
+
+
+
+;;;###autoload
+(defun bm-select-book ()
+  "Queries user to select a new book and chapter for the current
+`bible-mode' buffer."
+  (interactive)
+  (let* ((completion-ignore-case t)
+	 (book-data (assoc (completing-read "Book: " bm-books nil t) bm-books))
+         (chapter (string-to-number (completing-read "Chapter: " (bm-list-number-range 1 (cdr book-data)) nil t))))
+    (setq-local bm-current-book book-data)
+    (setq-local bm-current-book-name (car book-data))
+    (setq-local bm-current-chapter chapter)
+    (bm-display)))
+
+
+;;;###autoload
+(defun bm-select-chapter ()
+  "Queries user to select a new chapter for the current `bible-mode' buffer."
+  (interactive)
+  (let* ((book-chapters (cdr bm-current-book))
+	 (chapter (string-to-number (completing-read "Chapter: " (bm-list-number-range 1 book-chapters) nil t))))
+    (when chapter
+      (bm-set-location bm-current-book chapter))))
+
+;;;###autoload
+(defun bm-select-module ()
+  "Queries user to select a new reading module for the current `bible-mode' buffer."
+  (interactive)
+  (let ((module (completing-read "Module: " bm-modules)))
+    (setq-local bm-module module)
+    (bm-display)))
+
+;;;###autoload
+(defun bm-toggle-word-study()
+  "Toggles the inclusion of word study for the active `bible-mode' buffer."
+  (interactive)
+  (setq bm-word-study-enabled (not bm-word-study-enabled))
+  (if (equal major-mode 'bible-search-mode)
+      (bm-display-search bm-search-query bm-search-mode bm-module)
+    (bm-display)))
+
+;;;###autoload
+(defun bm-split-display ()
+  "Copies the active `bible-mode' buffer into a new buffer in another window."
+  (interactive)
+  (split-window-right)
+  (balance-windows)
+  (other-window 1)
+  (bible-open bm-current-book-name bm-current-chapter))
+
+;;;###autoload
+(defun bible-search (query)
+  "Prompts the user for a Bible search query: word or phrase and type of
+search: either `lucene' or `phrase'. `lucene' mode requires an index
+to be built using the `mkfastmod' program. `lucene' is the default
+search."
+  (interactive "sBible Search: ")
+  (when (> (length query) 0)
+    (let* ((searchmode (completing-read "Search Mode: " '("lucene" "phrase") nil t "lucene")))
+      (bm-open-search query searchmode))))
+
+;;;###autoload
+(defun bible-search-mode-follow-verse ()
+  "Follows the hovered verse in a `bible-search-mode' buffer,
+creating a new `bible-mode' buffer positioned at the specified verse."
+  (interactive)
+  (let* ((text (thing-at-point 'line t))
+         book
+         chapter
+         verse)
+    (string-match ".+ [0-9]?[0-9]?[0-9]?:[0-9]?[0-9]?[0-9]?:" text)
+    (setq text (match-string 0 text))
+    (string-match " [0-9]?[0-9]?[0-9]?:" text)
+    (setq chapter (replace-regexp-in-string "[^0-9]" "" (match-string 0 text)))
+    (string-match ":[0-9]?[0-9]?[0-9]?" text)
+    (setq verse (replace-regexp-in-string "[^0-9]" "" (match-string 0 text)))
+    (setq book (replace-regexp-in-string "[ ][0-9]?[0-9]?[0-9]?:[0-9]?[0-9]?[0-9]?:$" "" text))
+    (bible-open book (string-to-number chapter) (string-to-number verse))))
+
+;;;###autoload
+(defun bible-term-hebrew (term)
+  "Queries user for a Strong's Hebrew Lexicon term."
+  (interactive "sTerm: ")
+  (bm-open-term-hebrew term))
+
+;;;###autoload
+(defun bible-term-greek (term)
+  "Queries user for a Strong's Greek Lexicon term."
+  (interactive "sTerm: ")
+  (bm-open-term-greek term))
+
+;; (defun bible-term-morph (term morph-type)
+;;   "Queries user for a Strong's Greek Lexicon term."
+;;   (interactive "sTerm: ")
+;; ;;;  (message "bible-term-morph: %s:%s" term morph-type)
+;; ;;;  (bm-open-term-greek term)
+;;   )
+
+;;;###autoload
+(defun bible-insert ()
+  "Queries user to select a verse for insertion into the current buffer."
+  (interactive)
+  (let* ((completion-ignore-case t)
+	 (book-data (assoc (completing-read "Book: " bm-books nil t) bm-books))
+         (chapter (when book-data (completing-read "Chapter: " (bm-list-number-range 1 (cdr book-data)) nil t)))
+         (verse (when chapter (read-from-minibuffer "Verse: "))))
+    (when verse
+      (insert (string-trim
+	       (replace-regexp-in-string 
+		(regexp-opt `(,(concat "(" bm-module ")")))
+		"" 
+		(bm-exec-diatheke (concat (car book-data) " " chapter ":" verse) nil "plain")))))))
+
+;;;;; Support
+
+;;;
+;;; XXX I've magled this in an ad-hoc manner. It needs to be
+;;; re-written so it is clearer (and correct, for that matter).
+(defun bm-exec-diatheke (query &optional filter format searchtype module)
+  "Executes `diatheke' with specified query options, returning the output."
+  (let ((module (or module bm-module)))
+    (with-temp-buffer
+      (let ((args (list "diatheke" nil (current-buffer) t "-b" module)))
+	(if filter
+	    (setq filter (concat filter " avmws"))
+	    (setq filter "avmws"))
+	(when filter (setq args (append args (list "-o" filter))))
+	(when searchtype
+	  (setq args (append args (list "-s" (pcase searchtype ("lucene" "lucene") ("phrase" "phrase"))))))
+	(setq args (append args (list "-f" (pcase format ("plain" "plain") (_ "internal")) "-k" query)))
+	(message "%s" args)
+	(apply 'call-process args))
+      (buffer-string))))
+
+(defvar-local bm-chapter-title nil
+  "Document text at start of chapter, mostly in Psalms, 
+like `Of David' or the like.")
+
+
+;;;
+;;; Greek and Hebrew lexicon and morphology tooltip rendering.
+;;;
+
+;;; Hash tables for STRONGS definitions.
+(defvar greek-hash (make-hash-table :test 'equal))
+(defvar hebrew-hash (make-hash-table :test 'equal))
+
+;;; Hash tables for morphologies. Three at present.
+(defvar robinson-hash (make-hash-table :test 'equal))
+(defvar packard-hash (make-hash-table :test 'equal))
+(defvar oshm-hash (make-hash-table :test 'equal))
+
+;;; Use HTMLHREF format with diatheke, post-process to render html.
+(defun bm-morph-query (query module)
+  "Executes `diatheke' to do morph query, renders HTML, returns string.
+Does some tweaking specific to morphology."
+  (with-temp-buffer
+    (let ((args (list "diatheke" nil (current-buffer) t "-b" module "-o" "m" "-f" "HTMLHREF" "-k" query)))
+      (apply 'call-process args)
+      (shr-render-region (point-min) (point-max))
+      (replace-regexp-in-string
+       "\n:" ""                   ; This makes the Packard morphology display look better.
+       (replace-regexp-in-string 
+	"Part of Speech" ""       ; This helps the Robinson display look better.
+	(substring (buffer-string) (+ (length query) 1)) ; This tries to get rid of unnecessary query identifier.
+	)))))
+
+
+;;; Use "plain" format with diatheke.
+(defun bm-lex-query (query module)
+  "Executes `diatheke' for query, plain format, returns string."
+  ;; Get rid of query ID at front of string: ?????:
+  (bm-exec-diatheke query nil "plain" nil module))
+
+(defun bm-lookup-strongs-greek (window object pos)
+  "Look up Greek lexical data for object at point. If not found in hash table,
+get it from sword database, stash in hash table, and return data.
+Note: compiler warns about unused argument `window'."
+  (let* ((query (get-text-property pos 'strong object))
+	 (match (string-match "[0-9]+" query))           ; Compiler warns about match.
+	 (lookup-key (match-string 0 query)))
+    (and lookup-key
+	 (or (gethash lookup-key greek-hash)
+	     (puthash lookup-key (bm-lex-query lookup-key bm-short-greek-lexicon) greek-hash)))))
+
+
+(defun bm-hebrew-lex-query (query module)
+  "Executes `diatheke' to do hebrew query, renders HTML, returns string."
+  (with-temp-buffer
+    (let ((args (list "diatheke" nil (current-buffer) t "-b" module "-o" "m" "-f" "HTMLHREF" "-k" query)))
+      (apply 'call-process args)
+      (shr-render-region (point-min) (point-max)))))
+
+(defun bm-lookup-strongs-hebrew (window object pos)
+  "Look up Hebrew lexical data for object at point. If not found in hash table,
+get it from sword database, stash in hash table, and return data.
+Note: compiler warns about unused `window' argument."
+  (let* ((query (get-text-property pos 'strong object))
+	 (match (string-match "[0-9]+" query))         ; Compiler warns about match.
+	 (lookup-key (match-string 0 query)))
+    (and lookup-key
+	 (or (gethash lookup-key hebrew-hash)
+	     ;; Use PLAIN format for lookup. XXX directionality problems.
+	     (puthash lookup-key (bm-lex-query lookup-key bm-short-hebrew-lexicon) hebrew-hash)))))
+
+(defun bm-morph-database-lookup (query database hash)
+  (or (gethash query hash)
+      (puthash query (bm-morph-query query database) hash)))
+
+(defun bm-show-lex-morph (window object pos)
+  (let* ((lex-morph-text "")
+	 (lex (get-text-property pos 'strong object))
+	 (lex-text
+	  (cond ((string-match "strong:G" lex)
+	   	 (bm-lookup-strongs-greek window object pos))
+		((string-match "strong:H" lex)
+		 (bm-lookup-strongs-hebrew window object pos)))))
+    (let* ((morph (get-text-property pos 'morph object))
+	   (morph-text
+	    (cond ((null morph) "")
+		  ((string-match "robinson:" morph)
+	   	   (bm-morph-database-lookup (replace-regexp-in-string "robinson:" "" morph) "Robinson" robinson-hash))
+		  ((string-match "packard:" morph)
+		   (bm-morph-database-lookup (replace-regexp-in-string "packard:" "" morph) "Packard" packard-hash))
+		  ((string-match "oshm:" morph)
+	   	   (bm-morph-database-lookup (replace-regexp-in-string "oshm:" "" morph) "OSHM" oshm-hash)))))
+      (when lex-text
+	(setq lex-morph-text lex-text))
+      (when morph-text
+	(setq lex-morph-text (concat lex-morph-text "\n" morph-text)))
+      ;; This prevents weird substitutions in the tooltip.
+      (propertize lex-morph-text 'help-echo-inhibit-substitution t)
+      lex-morph-text)))
+
+
+(defun bm-process-word (item iproperties)
+  "Word study. Add tooltips for definitions and morphologyl. 
+Insert lemmas in buffer. Must be done after item is inserted in buffer."
+
+  (let ((word (dom-text item))
+	(morph (dom-attr item 'morph))
+	(savlm (dom-attr item 'savlm))
+	(divinename (dom-by-tag item 'divinename)))
+
+    (insert word)
+
+    (let ((refstart (- (point) (length word)))
+	  (refend  (point)))
+
+      ;; Red letter
+      (when (plist-get iproperties 'jesus)
+	(add-face-text-property refstart refend '(:foreground "red")))
+
+      ;; Special case this. XXX Some modules do this differently.
+      (when divinename
+	(insert "LORD")
+	(let* ((refstart (- (point) (length "LORD")))
+	       (refend (point))
+	       (strongs (dom-attr item 'savlm)))
+	  (string-match "strong:H.*" strongs)
+	  (let ((strongs-ref (match-string 0 strongs)))
+	    (add-face-text-property refstart refend 'bold)
+      	    (put-text-property refstart refend 'keymap bm-hebrew-keymap)
+	    (put-text-property refstart refend 'help-echo 'bm-show-lex-morph)
+	    (put-text-property refstart refend 'strong strongs-ref))))
+
+      ;; lexical definitions
+      (when savlm
+	(let ((matched nil))
+	  (cond ((string-match "strong:G.*" savlm)        ; Greek
+		 (setq matched (match-string 0 savlm))
+		 (put-text-property refstart refend 'keymap bm-greek-keymap))
+		((string-match "strong:H.*" savlm)        ; Hebrew
+		 (setq matched (match-string 0 savlm))
+		 (put-text-property refstart refend 'keymap bm-hebrew-keymap)))
+	  ;; Add help-echo, strongs reference for tooltips if match.
+	  (when matched
+	    (setq-local bm-has-strongs t)
+	    (put-text-property refstart refend 'help-echo 'bm-show-lex-morph)
+	    (put-text-property refstart refend 'strong matched))))
+
+      ;; morphology
+      (when morph
+	(let ((matched nil))
+	  (cond ((string-match "robinson:.*" morph)    ; Robinson Greek morphology
+		 (setq matched (match-string 0 morph)))
+		((string-match "packard:.*" morph)     ; Packard Greek morphology --- LXX seems to use this
+		 (setq matched (match-string 0 morph)))
+		((string-match "oshm:.*" morph)        ; OSHM Hebrew morphology
+		 (setq matched (match-string 0 morph)))
+		(t nil
+		   ;;(message "Unknown morphology %s" morph)
+		   ))
+	  (when matched
+	    (setq-local bm-has-morphology t)
+	    (put-text-property refstart refend 'morph matched)
+	    (put-text-property refstart refend 'help-echo 'bm-show-lex-morph))))
+
+      ;; Insert lemma into buffer. Lemma tag will be part of savlm item.
+      (when (and bm-word-study-enabled savlm (string-match "lemma.*:.*" savlm))
+	(dolist (word (split-string (match-string 0 savlm) " "))
+	  (setq word (replace-regexp-in-string "[.:a-zA-Z0-9]+" "" word))
+	  (insert " " word)
+	  (let ((refstart (- (point) 1 (length word)))
+		(refend (point)))
+	    (add-face-text-property refstart refend '(:foreground "blue"))
+	    (put-text-property refstart refend 'keymap bm-lemma-keymap)))))))
+
+
+(defun bm-insert-domnode-recursive (node &optional iproperties notitle)
+  "Recursively parses a domnode from `libxml-parse-html-region's usage on text
+produced by `bm-exec-diatheke'. Outputs text to active buffer 
+with properties.
+In processing subnodes, each case will prepend a space if it needs it."
+
+  (if (and bm-red-letter-enabled (equal (dom-attr node 'who) "Jesus"))
+      ;; For red-letter display.
+      (setq iproperties (plist-put iproperties 'jesus t))
+    (setq iproperties nil))
+
+;;  (when (equal (dom-tag node) 'title)
+;;    ;; Space one line down so there's room for the title at the beginning.
+;;    (insert "\n"))
+
+  (dolist (subnode (dom-children node))
+    (cond ((null subnode) nil)
+	  ((stringp subnode)
+	   ;; Insert the subnode. Highlight the verse references.
+	   (insert subnode)
+	   ;; XXX this is still not quite right
+	   (let ((verse-start (string-match ".+?:[0-9]?[0-9]?[0-9]?:" subnode)))
+	     (when verse-start
+	       (let* ((verse-match (string-trim (match-string 0 subnode)))
+		      (verse-start-text (string-trim-left (substring subnode verse-start (length subnode))))
+		      (subnode (concat (substring subnode 0 verse-start) verse-start-text))
+		      (start (- (point) 1 (length (string-trim-right verse-start-text)))))
+		 (add-face-text-property start (+ start (length (string-trim-right verse-match))) '(:foreground "purple"))))))
+	  ((eq (dom-tag subnode) 'title)
+	   (if notitle nil
+	     (progn
+	       (setq bm-chapter-title subnode))))
+	  ((eq (dom-tag subnode) 'body) (bm-insert-domnode-recursive subnode iproperties notitle))
+	  ((eq (dom-tag subnode) 'seg) ; NASB Module uses this to indicate OT quotations (and others?).
+	   (bm-insert-domnode-recursive subnode iproperties notitle))
+	  ((eq (dom-tag subnode) 'q) (bm-insert-domnode-recursive subnode iproperties notitle))
+	  ((eq (dom-tag subnode) 'p) (bm-insert-domnode-recursive subnode iproperties notitle))
+	  ((eq (dom-tag subnode) 'w) (insert " ") (bm-process-word subnode iproperties))
+	  ((eq (dom-tag subnode) 'milestone) (insert "\n"))
+	  ((eq (dom-tag subnode) 'transchange) ; Word inserted by translation, not in original, give visual indication.
+	   (let ((word (dom-text subnode)))
+	     (insert " " word)
+	     (add-face-text-property (- (point) (length word)) (point) '(:foreground "gray50")))))))
+
+
+(defvar bm-debugme nil)
+(setf bm-debugme nil)
+
+(defun bm-display (&optional verse)
+  "Renders text for `bible-mode'"
+
+  ;; Clear buffer and insert the result of calling bm-exec-diatheke.
+  (setq buffer-read-only nil)
+  (erase-buffer)
+  (setq bm-chapter-title nil
+	bm-has-strongs nil
+	bm-has-morphology nil)
+  (insert (bm-exec-diatheke (concat bm-current-book-name ":" (number-to-string bm-current-chapter))))
+
+  ;; Parse the xml in the buffer into a DOM tree.
+  (let ((html-dom-tree (libxml-parse-html-region (point-min) (point-max))))
+    ;; Render the DOM tree into the buffer.
+    (if (not bm-debugme)
+	(progn
+	  (erase-buffer)
+	  ;; Looking for the "body" tag in the DOM node.
+	  (bm-insert-domnode-recursive (dom-by-tag html-dom-tree 'body) nil nil)
+	  (goto-char (point-min)))
+;;;      (shr-render-region (point-min) (point-max))
+      ))
+
+  ;; Remove the module name from the buffer.
+  (while (search-forward (concat "(" bm-module ")") nil t)
+    (replace-match ""))
+
+  ;; Set the mode line of the biffer.
+  (setq mode-name (concat "Bible (" 
+			  bm-module
+			  (when bm-has-strongs " Lex")
+			  (when bm-has-morphology " Morph")
+			  ")"))
+
+  ;; Deal with chapter titles (i.e. in Psalms)
+  ;; N.B. This won't change a title inside a chapter, and so it
+  ;; doesn't work with Psalm 119 where the acrostic letters get
+  ;; printed as "titles".
+  (when bm-chapter-title  ; This gets set in bm-insert-domnode-recursive.
+    (goto-char (point-min))
+    (let ((title-text (dom-texts bm-chapter-title))
+	  (refstart (point-min))
+	  refend)
+      ;; Insert and make bold the title.
+      (when (string-or-null-p title-text)
+	  (insert title-text "\n")
+	  (setq refend (point))
+	  (put-text-property refstart refend 'face 'bold))))
+
+  (setq buffer-read-only t)
+  (goto-char (point-min))
+
+  ;; If optional verse specification go to that verse.
+  (when verse
+    (goto-char (string-match (regexp-opt `(,(concat ":" (number-to-string verse) ":"))) (buffer-string)))
+    (beginning-of-line)))
+
+
+(defun bm-list-biblical-modules ()
+  "Returns a list of accessible Biblical Text modules."
+  (let ((text (bm-exec-diatheke "modulelist" nil nil nil "system"))
+	modules)
+    (catch 'done
+      (dolist (line (split-string text "\n"))
+	(when (equal line "Commentaries:")
+          (throw 'done nil))
+	(when (not (equal "Biblical Texts:" line))
+          (push (split-string line " : ") modules))))
+    modules))
+
+(defun bm-pick-module ()
+  (interactive)
+  (message "Picking module at %s" (point))
+  (let ((item (get-text-property (point) 'module)))
+    (setq-default bm-module item)
+    (bible-open)))
+    
+
+(defconst bm-module-map (make-keymap))
+(define-key bm-module-map [mouse-1] 'bm-pick-module)
+
+
+(defun bm-display-available-modules ()
+  (interactive)
+  (let ((buf (get-buffer-create "Modules"))
+	(mods (bm-list-biblical-modules)))
+    (set-buffer buf)
+    (setq buffer-read-only nil)
+    (erase-buffer)
+    (dolist (mod mods)
+      (insert 
+       (propertize (car mod) 
+		   'face 'bold 
+		   'module (car mod)
+		   'help-echo (concat "Select " (car mod))
+		   'keymap bm-module-map)
+       "\t\t"
+       (format "%s\n" (cadr mod))))
+    (setq buffer-read-only t)
+    (pop-to-buffer buf)))
+
+
+
+;;;;; Bible Searching
+
+(defun bm-open-search (query searchmode)
+  "Opens a search buffer of QUERY using SEARCHMODE."
+  (let ((buf (get-buffer-create (concat "*bible-search-" (downcase bm-module) "-" query "*"))))
+    (set-buffer buf)
+    (bible-search-mode)
+    (bm-display-search query searchmode bm-module)
+    (pop-to-buffer buf nil t)))
+
+(defun bm-display-search (query searchmode mod)
+  "Renders results of search QUERY from SEARHCMODE"
+  (setq buffer-read-only nil)
+  (erase-buffer)
+  
+  (let* ((result (string-trim (replace-regexp-in-string 
+			       "Entries .+?--" "" 
+			       (bm-exec-diatheke query nil "plain" searchmode mod))))
+	 (match 0)
+	 (matchstr "")
+	 (verses "")
+	 fullverses)
+    (if (equal result (concat "none (" bm-module ")"))
+	(insert "No results found." (when (equal searchmode "lucene") " Verify index has been build with mkfastmod."))
+      (progn
+	(while match
+	  (setq match (string-match ".+?:[0-9]?[0-9]?" result (+ match (length matchstr)))
+		matchstr (match-string 0 result))
+	  (when match
+            (setq verses (concat verses (replace-regexp-in-string ".+; " "" matchstr) ";"))))
+
+	(setq match 0)
+	(setq fullverses (bm-exec-diatheke verses))
+
+	(insert fullverses)
+	(sort-lines nil (point-min) (point-max))
+	    
+	(let* ((html-dom-tree (libxml-parse-html-region (point-min) (point-max))))
+	  (erase-buffer)
+	  (bm-insert-domnode-recursive (dom-by-tag html-dom-tree 'body) nil nil)
+	  (goto-char (point-min))
+	  (while (search-forward (concat "(" bm-module ")") nil t)
+	    (replace-match "")))))
+
+    (setq mode-name (concat "Bible Search (" bm-module ")"))
+    (setq buffer-read-only t)
+    (setq-local bm-search-query query)
+    (setq-local bm-search-mode searchmode)
+    (goto-char (point-min))))
+
+;;;;; Terms
+
+
+;;(defun bm-display-morphology (morph)
+;; ;; xxx Do something here?
+;;  )
+
+(defun bm-display-term (termtype)
+  (cl-do* ((text (buffer-string))
+           (match (string-match "[0-9]+" text) (string-match "[0-9]+" text (match-end 0))))
+      ((not match))
+    (let* ((matchstr (match-string 0 text))
+	   (matchstrlen (length matchstr))
+           (refstart (+ match 1))
+           (refend (+ match 1 matchstrlen)))
+      ;; This enables clicking on the Strong's numbers inside the term display.
+      (add-face-text-property refstart refend `(:foreground "blue"))
+      (cond ((eq termtype 'hebrew)
+	     (put-text-property refstart refend 'strong (concat "strong:H" matchstr))
+	     (put-text-property refstart refend 'keymap bm-hebrew-keymap))
+	    ((eq termtype 'greek)
+	     (put-text-property refstart refend 'strong (concat "strong:G" matchstr))
+	     (put-text-property refstart refend 'keymap bm-greek-keymap)))))
+  (goto-char (point-min))
+  (while (search-forward (concat "(" bm-module ")") nil t)
+    (replace-match ""))
+  (while (search-forward "()" nil t)
+    (replace-match ""))
+  (goto-char (point-min))
+  (setq buffer-read-only t))
+
+(defun bm-open-term-hebrew (term)
+  "Opens a buffer of the Strong's Hebrew TERM's definition"
+  (let ((buf (get-buffer-create (concat "*bible-term-hebrew-" term "*"))))
+    (set-buffer buf)
+    (bible-term-hebrew-mode)
+    (bm-display-term-hebrew term)
+    (pop-to-buffer buf nil t)
+    (fit-window-to-buffer)))
+
+(defun bm-open-term-greek (term)
+  "Opens a buffer of the Strong's Greek TERM's definition"
+  (let ((buf (get-buffer-create (concat "*bible-term-greek-" term "*"))))
+    (set-buffer buf)
+    (bible-term-greek-mode)
+    (bm-display-term-greek term)
+    (pop-to-buffer buf nil t)
+    (fit-window-to-buffer)))
+
+;;;
+;;; Note: Hebrew display of terms is backwards; set bidi direction to
+;;; 'left-to-right.
+(defun bm-display-term-hebrew (term)
+  "Render the definition of the Strong's Hebrew TERM. Use
+bidi-paragraph-direction so the English text will render
+left-to-right. XXX Why doesn't this work for the tooltips?"
+  (setq buffer-read-only nil)
+  (erase-buffer)
+  (insert (replace-regexp-in-string
+	   (regexp-opt `(,bm-hebrew-lexicon))
+	   ""
+	   (bm-exec-diatheke term nil "plain" nil bm-hebrew-lexicon)
+	   nil nil nil 7
+	   ))
+  (bm-display-term 'hebrew)
+  (setq bidi-paragraph-direction 'left-to-right))
+
+
+(defun bm-display-term-greek (term)
+  "Render the definition of the Strong's Greek TERM."
+  (setq buffer-read-only nil)
+  (erase-buffer)
+  (insert (replace-regexp-in-string
+	   (regexp-opt `(,bm-greek-lexicon))
+	   "" 
+	   (bm-exec-diatheke term nil "plain" nil bm-greek-lexicon)
+	   nil nil nil 7
+	   ))
+  ;;  (insert "\n")
+  (bm-display-term 'greek))
+
+
+(defun bm-set-location (book chapter &optional verse)
+  "Sets the global chapter of the active `bible-mode' buffer."
+  (setq-local bm-current-book book)
+  (setq-local bm-current-book-name (car book))
+  (setq-local bm-current-chapter chapter)
+  (bm-display verse))
+
+;;;;; Utilities
+
+(defun bm-list-number-range (min max &optional prefix)
+  "Returns a list containing entries for each integer between min and max.
+Used in tandem with `completing-read' for chapter selection."
+  (let ((range-list nil))
+    (dotimes (num (1+ max))
+      (when (>= num min)
+	(push (cons (concat prefix (number-to-string num)) num) range-list)))
+    (nreverse range-list)))
+
+
+;;; Provides
+
+(provide 'bible-mode)
+
+;; Local Variables:
+;; read-symbol-shorthands: (("bm-" . "bible-mode-"))
+;; End:

BIN
example1.png


BIN
example2.png


BIN
example3.png


BIN
example4.png