bible-mode.el 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428
  1. ;;;; -*- mode: EMACS-LISP; lexical-binding: t -*-
  2. ;;
  3. ;; bible-mode.el --- A browsing interface for the SWORD Project's Diatheke CLI
  4. ;; Time-stamp: <2024-06-19 16:02:32 fred>
  5. ;; Author: Zacalot
  6. ;; Fixes and modifications by Fred Gilham
  7. ;; Url: https://gitbot.homedns.org/fred/bible-mode
  8. ;; Forked from
  9. ;; Url: https://github.com/Zacalot/bible-mode
  10. ;; Version: 1.0.0
  11. ;; Package-Requires: ((emacs "24.1"))
  12. ;; Keywords: diatheke, sword, research, bible
  13. ;;; Commentary:
  14. ;; This package uses the `diatheke' program to browse and search
  15. ;; Biblical texts provided by the Sword project.
  16. ;; Word study is also supported.
  17. ;;; Usage:
  18. ;; First install `diatheke'. On Debian/Ubuntu it's in the `diatheke'
  19. ;; package. In other distributions it might be in the sword package.
  20. ;; For Windows I found that you can simply install the Xiphos package.
  21. ;; It includes the Sword library and its utilities including diatheke,
  22. ;; installmgr and mkfastmod. Add the "Program Files\Xiphos\bin" path
  23. ;; to your execution path.
  24. ;; Use M-x `bible-open' to open a Bible buffer.
  25. ;; Use C-h f `bible-mode' to see available keybindings.
  26. ;; You may customize `bible-mode-module' to set a default browsing
  27. ;; module, as well as `bible-mode-word-study-enabled' to enable word
  28. ;; study by default.
  29. ;;; Design:
  30. ;; The idea here is to use the diatheke program to insert code from
  31. ;; modules into buffers. The main bible display uses an "internal" XML
  32. ;; format. The whole buffer gets parsed by libxml-parse-html-region to
  33. ;; create a dom tree. This gets parsed by
  34. ;; bible-mode--insert-domnode-recursive to render the text into
  35. ;; reading format.
  36. ;; The text is then decorated using information from the dom format as
  37. ;; necessary along with regular expressions to identify the verse
  38. ;; references. This is for red letters, purple highlighting of the
  39. ;; verse numbers, bold face of the divine name in the OT and so on.
  40. ;; If strongs tags and/or morphological tags are present, they are
  41. ;; looked up in appropriate lexical and morphological modules and used
  42. ;; to add tooltips to the text so that mousing over words will bring
  43. ;; up a tooltip with information about the word. Clicking on a word
  44. ;; with lexical information will display that informatio in a "term"
  45. ;; buffer.
  46. ;;;
  47. ;;; bm- is used as shorthand (see Local Variables) for bible-mode-
  48. ;;; Code:
  49. ;;;; Requirements
  50. ;;; XXX FMG there are just a few constructs that use this; use elisp versions instead.
  51. ;; cl-do* cl-fresh-line
  52. (require 'cl-lib)
  53. ;; (require 'bidi)
  54. (require 'dom)
  55. (require 'shr)
  56. ;; Turn off tool bar mode because we want the pixels....
  57. (tool-bar-mode -1)
  58. ;;;; Variables
  59. (defgroup bible-mode nil
  60. "Settings for `bible-mode'."
  61. :group 'tools
  62. :link '(url-link "https://gitbot.homedns.org/fred/bible-mode"))
  63. (defcustom bm-module
  64. "KJV"
  65. "Book module for Diatheke to query."
  66. :type '(choice (const :tag "None" nil)
  67. (string :tag "Module abbreviation (e.g. \"KJV\")"))
  68. :local t
  69. :group 'bible-mode)
  70. ;;;
  71. ;;; XXX Not implememted yet
  72. (defcustom bm-font
  73. "Ezra SIL"
  74. "Default font for bible-mode."
  75. :type '(string :tag "Font family name (e.g. \"Ezra SIL\")")
  76. :local t
  77. :group 'bible-mode)
  78. (defcustom bm-greek-lexicon
  79. "MLStrong"
  80. "Lexicon used for displaying definitions of Greek words using Strong's codes."
  81. :type '(string :tag "Lexicon module (e.g. \"StrongsRealGreek\").")
  82. :local nil
  83. :group 'bible-mode)
  84. (defcustom bm-use-index-for-lexicon nil
  85. "Some lexicons are accessed by lemmas rather than Strong's numbers. Use
  86. an index to look up lemmas from Strong's numbers so these lexicons can
  87. be used. Examples of this type of lexicon are AbbottSmith and
  88. LiddellScott. XXX LiddellScott currently doesn't work."
  89. :type 'boolean
  90. :local nil
  91. :group 'bible-mode)
  92. (defcustom bm-lexicon-index "AbbottSmithStrongs"
  93. "A module that consists of an index mapping Strong's numbers to Greek
  94. lemmas. The code is written to use the entries in AbbottSmithStrongs
  95. which are of the form
  96. <strong's number>: @LINK <greek lemma>"
  97. :type '(string :tag "Lexicon index.")
  98. :local nil
  99. :group 'bible-mode)
  100. (defcustom bm-greek-lexicon-short
  101. "StrongsRealGreek"
  102. "Lexicon used for displaying definitions of Greek words in tooltips."
  103. :type '(string :tag "Lexicon module (e.g. \"StrongsRealGreek\").")
  104. :local nil
  105. :group 'bible-mode)
  106. (defcustom bm-hebrew-lexicon
  107. "StrongsRealHebrew"
  108. "Lexicon used for displaying definitions of Hebrew words using Strong's codes."
  109. :type '(string :tag "Lexicon module (e.g. \"StrongsRealHebrew\")")
  110. :local nil
  111. :group 'bible-mode)
  112. (defcustom bm-hebrew-lexicon-short
  113. "BDBGlosses_Strongs" ; This seems to work
  114. "Lexicon used for displaying definitions of Hebrew words in tooltips."
  115. :type '(string :tag "Lexicon module (e.g. \"StrongsRealHebrew\")")
  116. :local nil
  117. :group 'bible-mode)
  118. (defcustom bm-word-study-enabled
  119. nil
  120. "Display Strong's Hebrew, Strong's Greek, and Lemma words for study."
  121. :type 'boolean
  122. :local t
  123. :group 'bible-mode)
  124. (defcustom bm-red-letter-enabled
  125. t
  126. "Display words of Jesus in red when module has that information."
  127. :type 'boolean
  128. :local t
  129. :group 'bible-mode)
  130. ;;; defvars
  131. ;;(defvar bm-verse-regexp "([\d ]*[a-zA-Z]+( \d*:\d*)?)(( - )| )?(((\d* )?[a-zA-Z]+ )?\d*([:-]+\d*)?)")
  132. ;; (defvar bm-verse-regexp "/(\d*)\s*([a-z]+)\s*(\d+)(?::(\d+))?(\s*-\s*(\d+)(?:\s*([a-z]+)\s*(\d+))?(?::(\d+))?)?/i")
  133. (defvar bm-verse-regexp "\\(I?I \\)*[a-zA-Z]* [0-9]*[:.][0-9]*")
  134. (defvar bm-modules (lazy-completion-table bm-modules bm--list-biblical-modules))
  135. ;; XXX I believe these chapter counts aren't the same for all modules, e.g. JPS.
  136. (defvar bm-books
  137. '(;; Old Testament
  138. ("Genesis" . 50) ("Exodus" . 40) ("Leviticus" . 27) ("Numbers" . 36)
  139. ("Deuteronomy" . 34) ("Joshua" . 24) ("Judges" . 21) ("Ruth" . 4)
  140. ("I Samuel" . 31) ("II Samuel" . 24) ("I Kings" . 22) ("II Kings" . 25)
  141. ("I Chronicles" . 29) ("II Chronicles" . 36) ("Ezra" . 10) ("Nehemiah" . 13)
  142. ("Esther" . 10) ("Job" . 42) ("Psalms" . 150) ("Proverbs" . 31)
  143. ("Ecclesiastes" . 12) ("Song of Solomon" . 8) ("Isaiah" . 66) ("Jeremiah" . 52)
  144. ("Lamentations" . 5) ("Ezekiel" . 48) ("Daniel" . 12) ("Hosea" . 14)
  145. ("Joel" . 3) ("Amos" . 9) ("Obadiah" . 1) ("Jonah" . 4)
  146. ("Micah" . 7) ("Nahum" . 3) ("Habakkuk" . 3) ("Zephaniah" . 3)
  147. ("Haggai" . 2) ("Zechariah" . 14) ("Malachi" . 4)
  148. ;; New Testament
  149. ("Matthew" . 28) ("Mark" . 16) ("Luke" . 24) ("John" . 21)
  150. ("Acts" . 28) ("Romans" . 16) ("I Corinthians" . 16) ("II Corinthians" . 13)
  151. ("Galatians" . 6) ("Ephesians" . 6) ("Philippians" . 4) ("Colossians" . 4)
  152. ("I Thessalonians" . 5) ("II Thessalonians" . 3) ("I Timothy" . 6) ("II Timothy" . 4)
  153. ("Titus" . 3) ("Philemon" . 1) ("Hebrews" . 13) ("James" . 5)
  154. ("I Peter" . 5) ("II Peter" . 3) ("I John" . 5) ("II John" . 1)
  155. ("III John" . 1) ("Jude" . 1) ("Revelation of John" . 22))
  156. "A-list of name / chapter count for Bible books.")
  157. (defvar bm-book-name-abbreviations-alist
  158. '(;; Old Testament
  159. ("Ge" . "Genesis") ("Ex" . "Exodus") ("Le" . "Leviticus") ("Nu" . "Numbers")
  160. ("De" . "Deuteronomy") ("Js" . "Joshua") ("Jg" . "Judges") ("Ru" . "Ruth")
  161. ("I Sa" . "I Samuel") ("II Sa" . "II Samuel") ("I Ki" . "I Kings") ("II Ki" . "II Kings")
  162. ("I Ch" . "I Chronicles") ("II Ch" . "II Chronicles") ("Ezr" . "Ezra") ("Ne" . "Nehemiah")
  163. ("Es" . "Esther") ("Jb" . "Job") ("Ps" . "Psalms") ("Pr" . "Proverbs")
  164. ("Ec" . "Ecclesiastes") ("So" . "Song of Solomon") ("Is" . "Isaiah") ("Je" . "Jeremiah")
  165. ("La" . "Lamentations") ("Ez" . "Ezekiel") ("Da" . "Daniel") ("Ho" . "Hosea")
  166. ("Joe" . "Joel") ("Am" . "Amos") ("Ob" . "Obadiah") ("Jon" . "Jonah")
  167. ("Mi" . "Micah") ("Na" . "Nahum") ("Ha" . "Habakkuk") ("Zep" . "Zephaniah")
  168. ("Hag" . "Haggai") ("Ze" . "Zechariah") ("Mal" . "Malachi")
  169. ;; New Testament
  170. ("Mt" . "Matthew") ("Mk" . "Mark") ("Lk" . "Luke") ("Jo" . "John")
  171. ("Ac" . "Acts") ("Ro" . "Romans") ("I Co" . "I Corinthians") ("II Co" . "II Corinthians")
  172. ("Ga" . "Galatians") ("Eph" . "Ephesians") ("Phl" . "Philippians") ("Col" . "Colossians")
  173. ("I Th" . "I Thessalonians") ("II Th" . "II Thessalonians") ("I Ti" . "I Timothy") ("II Ti" . "II Timothy")
  174. ("Tit" . "Titus") ("Phm" . "Philemon") ("He" . "Hebrews") ("Ja" . "James")
  175. ("I Pe" . "I Peter") ("II Pe" . "II Peter") ("I Jo" . "I John") ("II Jo" . "II John")
  176. ("III Jo" . "III John") ("Ju" . "Jude") ("Re" . "Revelation of John"))
  177. "A-list of abbreviations for Bible books.")
  178. ;;;; Book / chapter
  179. (defvar-local bm-current-book (assoc "Genesis" bm-books)
  180. "Current book data (name . chapter).")
  181. (defvar-local bm-current-book-name "Genesis"
  182. "Current book name.")
  183. (defvar-local bm-current-chapter 1
  184. "Current book chapter number.")
  185. (defvar-local bm-search-query nil
  186. "Search query associated with the buffer.")
  187. (defvar-local bm-search-mode "phrase"
  188. "Search mode: either `lucene' or `phrase'.")
  189. (defvar-local bm-has-strongs nil
  190. "Set if the module being displayed has strongs numbers availabile.")
  191. (defvar-local bm-has-morphology nil
  192. "Set if the module being displayed has morphology availabile.")
  193. ;; (defvar bm-current-module nil)
  194. ;;;; Keymaps
  195. (defconst bm-map (make-sparse-keymap)
  196. "Keymap for bible-mode.")
  197. (define-key bm-map [menu-bar bible-mode]
  198. (cons "Bible Mode" (make-sparse-keymap "Bible Mode")))
  199. (define-key bm-map
  200. [menu-bar bible-mode toggle-debug]
  201. '("Toggle debug-on-error" . toggle-debug-on-error))
  202. (defun bm-toggle-display-xml ()
  203. "Toggle XML display."
  204. (interactive)
  205. (setq-local bm-debugme (not bm-debugme))
  206. (bm--display))
  207. (define-key bm-map "d" 'bm-toggle-display-xml)
  208. (define-key bm-map
  209. [menu-bar bible-mode display-xml]
  210. '("Toggle XML Display" . bm-toggle-display-xml))
  211. (define-key bm-map
  212. [menu-bar bible-mode sep]
  213. '(menu-item '"--"))
  214. ;;;;; Navigation
  215. (define-key bm-map "n" 'bm-next-chapter)
  216. (define-key bm-map
  217. [menu-bar bible-mode next-chapter]
  218. '("Next Chapter" . bm-next-chapter))
  219. (define-key bm-map "p" 'bm-previous-chapter)
  220. (define-key bm-map
  221. [menu-bar bible-mode previous-chapter]
  222. '("Previous Chapter" . bm-previous-chapter))
  223. (define-key bm-map (kbd "TAB") 'bm-forward-word) ; TODO: bm-forward-word
  224. ;;;;; Direct jump
  225. (define-key bm-map "b" 'bm-select-book)
  226. (define-key bm-map
  227. [menu-bar bible-mode select-book]
  228. '("Select Book" . bm-select-book))
  229. (define-key bm-map "c" 'bm-select-chapter)
  230. (define-key bm-map
  231. [menu-bar bible-mode select-chapter]
  232. '("Select Chapter" . bm-select-chapter))
  233. ;;;;; Search
  234. (define-key bm-map "/" 'bible-search)
  235. (define-key bm-map "s" 'bible-search)
  236. (define-key bm-map
  237. [menu-bar bible-mode search]
  238. '("Bible Search" . bible-search))
  239. ;;;; Not yet
  240. ;;(define-key bm-map "" 'bm-set-search-range)
  241. ;;;;; Misc
  242. (define-key bm-map "m" 'bm-select-module)
  243. (define-key bm-map "w" 'bm-toggle-word-study)
  244. (define-key bm-map "x" 'bm-split-display)
  245. (define-key bm-map
  246. [menu-bar bible-mode split-display]
  247. '("Split Display" . bm-split-display))
  248. (define-key bm-map
  249. [menu-bar bible-mode sep]
  250. '(menu-item '"--"))
  251. ;;;;; Deal with visual-line-mode
  252. (define-key bm-map "\C-n" 'next-logical-line)
  253. (define-key bm-map "\C-p" 'previous-logical-line)
  254. (defconst bible-search-mode-map (make-keymap))
  255. (define-key bible-search-mode-map "s" 'bible-search)
  256. (define-key bible-search-mode-map "w" 'bm-toggle-word-study)
  257. (define-key bible-search-mode-map (kbd "RET") 'bible-search-mode-follow-verse)
  258. (define-key bible-search-mode-map [mouse-1] 'bible-search-mode-follow-xref)
  259. (defconst bible-term-hebrew-mode-map (make-sparse-keymap))
  260. (defconst bible-term-greek-mode-map (make-sparse-keymap))
  261. ;; (defconst bible-term-morph-mode-map (make-keymap))
  262. (defun bible-term-mode-follow-xref ()
  263. (message "Bible term mode follow xref"))
  264. (define-key bible-term-greek-mode-map [mouse-1] 'bible-search-mode-follow-xref)
  265. ;;;
  266. ;;; Menu bar items
  267. ;;;
  268. (defvar-local bm-text-direction 'left-to-right)
  269. (defun bm-toggle-text-direction ()
  270. (interactive)
  271. (if (eq bm-text-direction 'left-to-right)
  272. (setq-local bm-text-direction 'right-to-left)
  273. (setq-local bm-text-direction 'left-to-right))
  274. (setq-local bidi-paragraph-direction bm-text-direction))
  275. (defvar-local bm-debugme nil
  276. "Make text show up as XML when set.")
  277. (defvar use-tooltips t)
  278. (defun bm-toggle-tooltips ()
  279. "Toggle use of tooltips to display lexical/morphological items."
  280. (interactive)
  281. (setq use-tooltips (not use-tooltips))
  282. (tooltip-mode 'toggle)
  283. (setq tooltip-resize-echo-area use-tooltips))
  284. (define-key bm-map
  285. [menu-bar bible-mode sepp]
  286. '(menu-item '"--"))
  287. (define-key bm-map
  288. [menu-bar bible-mode toggle-text-direction]
  289. '("Toggle text direction (for Hebrew display)" . bm-toggle-text-direction))
  290. (define-key bm-map
  291. [menu-bar bible-mode toggle-tooltip-display]
  292. '("Toggle Tooltip Display" . bm-toggle-tooltips))
  293. (define-key bm-map
  294. [menu-bar bible-mode sepp]
  295. '(menu-item '"--"))
  296. (define-key bm-map
  297. [menu-bar bible-mode select-biblical-text]
  298. '("Select Module" . bm-display-available-modules))
  299. (defun bm-display-greek ()
  300. "This command is run by clicking on text, not directly by the user."
  301. (interactive)
  302. (let ((item (car (split-string (get-text-property (point) 'strong)))))
  303. ;; Remove "strong:G" prefix
  304. (bible-term-greek (replace-regexp-in-string "strong:G" "" item))))
  305. (defconst bm-greek-keymap (make-sparse-keymap))
  306. (define-key bm-greek-keymap (kbd "RET") 'bm-display-greek)
  307. (define-key bm-greek-keymap [mouse-1] 'bm-display-greek)
  308. (defun bm-display-hebrew ()
  309. "This command is run by clicking on text, not directly by the user."
  310. (interactive)
  311. (let ((item (car (split-string (get-text-property (point) 'strong)))))
  312. ;; Remove "strong:H" prefix and any alphabetic suffixes.
  313. (bible-term-hebrew (replace-regexp-in-string "strong:H" "" item))))
  314. (defconst bm-hebrew-keymap (make-sparse-keymap))
  315. (define-key bm-hebrew-keymap (kbd "RET") 'bm-display-hebrew)
  316. (define-key bm-hebrew-keymap [mouse-1] 'bm-display-hebrew)
  317. (defconst bm-lemma-keymap (make-sparse-keymap))
  318. (define-key bm-lemma-keymap (kbd "RET")
  319. (lambda ()
  320. (interactive)
  321. ))
  322. (defconst bm-morph-keymap (make-sparse-keymap))
  323. (define-key bm-morph-keymap (kbd "RET")
  324. (lambda ()
  325. (interactive)
  326. ;;; (let ((thing (thing-at-point 'word)))
  327. ;;; (message "thing at point: %s" thing)
  328. ;;; (message "morph property %s" (get-text-property 0 'field thing))
  329. ))
  330. ;;;; Modes
  331. (define-derived-mode bible-mode special-mode "Bible"
  332. "Mode for reading the Bible.
  333. \\{bm-map}"
  334. (buffer-disable-undo)
  335. (font-lock-mode t)
  336. (use-local-map bm-map)
  337. (setq buffer-read-only t)
  338. (visual-line-mode t))
  339. (define-derived-mode bible-search-mode special-mode "Bible Search"
  340. "Mode for performing Bible searches.
  341. \\{bible-search-mode-map}"
  342. (buffer-disable-undo)
  343. (font-lock-mode t)
  344. (use-local-map bible-search-mode-map)
  345. (setq buffer-read-only t)
  346. (visual-line-mode t)
  347. )
  348. (define-derived-mode bible-term-hebrew-mode special-mode "Bible Term (Hebrew)"
  349. "Mode for researching Hebrew terms in the Bible.
  350. \\{bible-term-hebrew-mode-map}"
  351. (buffer-disable-undo)
  352. (font-lock-mode t)
  353. (use-local-map bible-term-hebrew-mode-map)
  354. (setq buffer-read-only t)
  355. (visual-line-mode t))
  356. (define-derived-mode bible-term-greek-mode special-mode "Bible Term (Greek)"
  357. "Mode for researching Greek terms in the Bible.
  358. \\{bible-term-greek-mode-map}"
  359. (buffer-disable-undo)
  360. (font-lock-mode t)
  361. (use-local-map bible-term-greek-mode-map)
  362. (setq buffer-read-only t)
  363. (visual-line-mode t))
  364. (define-derived-mode module-select-mode special-mode "Select Text Module"
  365. (buffer-disable-undo)
  366. (font-lock-mode t)
  367. (setq buffer-read-only t))
  368. ;;;; Functions
  369. ;;;;; Commands
  370. ;;;###autoload
  371. (defun bible-open (&optional book-name chapter verse)
  372. "Creates and opens a `bible-mode' buffer"
  373. (interactive)
  374. (let ((buf (get-buffer-create (generate-new-buffer-name (concat "*bible*")))))
  375. (set-buffer buf)
  376. (bible-mode)
  377. (bm--set-location (or (assoc (or book-name "Genesis") bm-books) (list book-name)) (or chapter 1) verse)
  378. (set-window-buffer (get-buffer-window (current-buffer)) buf)))
  379. ;;;###autoload
  380. (defun bm-next-chapter ()
  381. "Pages to the next chapter for the active `bible-mode' buffer."
  382. (interactive)
  383. (let* ((book-chapters (cdr bm-current-book))
  384. (chapter (min book-chapters (+ bm-current-chapter 1))))
  385. (bm--set-location bm-current-book chapter)))
  386. ;;;###autoload
  387. (defun bm-previous-chapter ()
  388. "Pages to the previous chapter for the active `bible-mode' buffer."
  389. (interactive)
  390. (bm--set-location bm-current-book (max 1 (- bm-current-chapter 1))))
  391. (defun bm-forward-word ()
  392. "Moves forward a word, taking into account the relevant text properties.
  393. XXX Doesn't work yet."
  394. (interactive)
  395. (field-end))
  396. ;;;###autoload
  397. (defun bm-select-book ()
  398. "Queries user to select a new book and chapter for the current
  399. `bible-mode' buffer."
  400. (interactive)
  401. (let* ((completion-ignore-case t)
  402. (book-data (assoc (completing-read "Book: " bm-books nil t) bm-books))
  403. (chapter (string-to-number (completing-read "Chapter: " (bm--list-number-range 1 (cdr book-data)) nil t))))
  404. (setq-local bm-current-book book-data)
  405. (setq-local bm-current-book-name (car book-data))
  406. (setq-local bm-current-chapter chapter)
  407. (bm--display)))
  408. ;;;###autoload
  409. (defun bm-select-chapter ()
  410. "Queries user to select a new chapter for the current `bible-mode' buffer."
  411. (interactive)
  412. (let* ((book-chapters (cdr bm-current-book))
  413. (chapter (string-to-number (completing-read "Chapter: " (bm--list-number-range 1 book-chapters) nil t))))
  414. (when chapter
  415. (bm--set-location bm-current-book chapter))))
  416. ;;;###autoload
  417. (defun bm-select-module ()
  418. "Queries user to select a new reading module for the current `bible-mode' buffer."
  419. (interactive)
  420. (let ((module (completing-read "Module: " bm-modules)))
  421. (setq-local bm-module module)
  422. (bm--display)))
  423. ;;;###autoload
  424. (defun bm-toggle-word-study()
  425. "Toggles the inclusion of word study for the active `bible-mode' buffer."
  426. (interactive)
  427. (setq bm-word-study-enabled (not bm-word-study-enabled))
  428. (if (equal major-mode 'bible-search-mode)
  429. (bm--display-search bm-search-query bm-search-mode bm-module)
  430. (bm--display)))
  431. ;;;###autoload
  432. (defun bm-split-display ()
  433. "Copies the active `bible-mode' buffer into a new buffer in another window."
  434. (interactive)
  435. (split-window-right)
  436. (balance-windows)
  437. (other-window 1)
  438. (bible-open bm-current-book-name bm-current-chapter))
  439. ;;;###autoload
  440. (defun bible-search (query)
  441. "Prompts the user for a Bible search query: word or phrase and type of
  442. search: either `lucene' or `phrase'. `lucene' mode requires an index
  443. to be built using the `mkfastmod' program. `lucene' is the default
  444. search."
  445. (interactive "sBible Search: ")
  446. (when (> (length query) 0)
  447. (let* ((searchmode (completing-read "Search Mode: " '("lucene" "phrase") nil t "lucene")))
  448. (bm--open-search query searchmode bm-module))))
  449. ;;;###autoload
  450. (defun bible-search-mode-follow-verse ()
  451. "Follows the hovered verse in a `bible-search-mode' buffer,
  452. creating a new `bible-mode' buffer positioned at the specified verse."
  453. (interactive)
  454. (let* ((text (thing-at-point 'line t))
  455. book
  456. chapter
  457. verse)
  458. (string-match ".+ [0-9]?[0-9]?[0-9]?:[0-9]?[0-9]?[0-9]?:" text)
  459. (setq text (match-string 0 text))
  460. (string-match " [0-9]?[0-9]?[0-9]?:" text)
  461. (setq chapter (replace-regexp-in-string "[^0-9]" "" (match-string 0 text)))
  462. (string-match ":[0-9]?[0-9]?[0-9]?" text)
  463. (setq verse (replace-regexp-in-string "[^0-9]" "" (match-string 0 text)))
  464. (setq book (replace-regexp-in-string "[ ][0-9]?[0-9]?[0-9]?:[0-9]?[0-9]?[0-9]?:$" "" text))
  465. (bible-open (string-trim book) (string-to-number chapter) (string-to-number verse))))
  466. (defun bible-search-mode-follow-xref ()
  467. "Follows the hovered verse in a `bible-search-mode' buffer,
  468. creating a new `bible-mode' buffer positioned at the specified verse.
  469. N.B. We use the default module to avoid opening cans of worms regarding
  470. OT/NT etc."
  471. (interactive)
  472. (let* ((xref (get-text-property (point) 'xref))
  473. (verse-ref (string-split xref))
  474. book-abbrev
  475. book
  476. chapter-verse
  477. chapter
  478. verse)
  479. (message "Following xref %s" xref)
  480. (if (= (length verse-ref) 3) ; II Cor 3:17 or the like
  481. (progn
  482. (setq book-abbrev (concat (car verse-ref) " " (cadr verse-ref)))
  483. (setq chapter-verse (split-string (caddr verse-ref) ":")))
  484. (progn ; Mat 5 or the like
  485. (setq book-abbrev (car verse-ref))
  486. (setq chapter-verse (split-string (cadr verse-ref) ":"))))
  487. ;; (setq book (cdr (assoc book-abbrev bm-book-name-abbreviations-alist)))
  488. (setq book (car verse-ref))
  489. (message "Xref is %s %s" book chapter-verse )
  490. (setq chapter (car chapter-verse)
  491. verse (cadr chapter-verse))
  492. (bible-open (string-trim book) (string-to-number chapter) (string-to-number verse))))
  493. ;;;###autoload
  494. (defun bible-term-hebrew (term)
  495. "Queries user for a Strong's Hebrew Lexicon term."
  496. (interactive "sTerm: ")
  497. (bm--open-term-hebrew term))
  498. ;;;###autoload
  499. (defun bible-term-greek (term)
  500. "Queries user for a Strong's Greek Lexicon term."
  501. (interactive "sTerm: ")
  502. (bm--open-term-greek term))
  503. ;;;###autoload
  504. (defun bible-insert ()
  505. "Queries user to select a verse for insertion into the current buffer."
  506. (interactive)
  507. (let* ((completion-ignore-case t)
  508. (book-data (assoc (completing-read "Book: " bm-books nil t) bm-books))
  509. (chapter (when book-data (completing-read "Chapter: " (bm--list-number-range 1 (cdr book-data)) nil t)))
  510. (verse (when chapter (read-from-minibuffer "Verse: "))))
  511. (when verse
  512. (insert (string-trim
  513. (replace-regexp-in-string
  514. (regexp-opt `(,(concat "(" bm-module ")")))
  515. ""
  516. (bm--exec-diatheke (concat (car book-data) " " chapter ":" verse) nil "plain")))))))
  517. ;;;;; Support
  518. ;;;
  519. ;;; XXX I've magled this in an ad-hoc manner. It needs to be
  520. ;;; re-written so it is clearer (and correct, for that matter).
  521. (defun bm--exec-diatheke (query &optional filter format searchtype module)
  522. "Executes `diatheke' with specified query options, returning the output."
  523. (let ((module (or module bm-module)))
  524. (with-temp-buffer
  525. (let ((args (list "diatheke" nil (current-buffer) t "-b" module)))
  526. (if filter
  527. (setq filter (concat filter " avmws"))
  528. (setq filter "avmws"))
  529. (when filter (setq args (append args (list "-o" filter))))
  530. (when searchtype
  531. (setq args (append args (list "-s" (pcase searchtype ("lucene" "lucene") ("phrase" "phrase"))))))
  532. (setq args (append args (list "-f" (pcase format ("plain" "plain") (_ "internal")) "-k" query)))
  533. (message "%s" args)
  534. (apply 'call-process args))
  535. (buffer-string))))
  536. (defvar-local bm-chapter-title nil
  537. "Document text at start of chapter, mostly in Psalms,
  538. like `Of David' or the like.")
  539. ;;;
  540. ;;; Greek and Hebrew lexicon and morphology tooltip rendering.
  541. ;;;
  542. ;;; Hash tables for Lexical definitions.
  543. (defvar greek-hash (make-hash-table :test 'equal))
  544. (defvar greek-hash-short (make-hash-table :test 'equal))
  545. (defvar hebrew-hash (make-hash-table :test 'equal))
  546. (defvar hebrew-hash-short (make-hash-table :test 'equal))
  547. ;; Do lookups using index to lexicon with lookups by lemma.
  548. (defvar lemma-index-hash (make-hash-table :test 'equal))
  549. (defvar lemma-lex-hash (make-hash-table :test 'equal))
  550. ;;; Hash tables for Morphologies. Three at present.
  551. (defvar robinson-hash (make-hash-table :test 'equal))
  552. (defvar packard-hash (make-hash-table :test 'equal))
  553. (defvar oshm-hash (make-hash-table :test 'equal))
  554. ;;; Use HTMLHREF format with diatheke, post-process to render html.
  555. (defun bm--morph-query (query module)
  556. "Executes `diatheke' to do morph query, renders HTML, returns string.
  557. Does some tweaking specific to morphology."
  558. (with-temp-buffer
  559. (let ((args (list "diatheke" nil (current-buffer) t "-b" module "-o" "m" "-f" "HTMLHREF" "-k" query)))
  560. (apply 'call-process args)
  561. (shr-render-region (point-min) (point-max))
  562. (format-replace-strings
  563. '(("\n:" . "") ; This makes the Packard morphology display look better.
  564. ("Part of Speech" . "")) ; This helps the Robinson display look better.
  565. nil (point-min) (point-max))
  566. (substring (buffer-string) (+ (length query) 1))) ; This tries to get rid of unnecessary query identifier.
  567. ))
  568. ;;; Use "plain" format with diatheke.
  569. (defun bm--lex-query (query module)
  570. "Executes `diatheke' for query, plain format, returns string."
  571. ;; Get rid of query ID at front of string: ?????:
  572. (bm--exec-diatheke query nil "plain" nil module))
  573. (defun bm--lookup-lemma-index (key)
  574. "Given a strong's number, return the Greek lemma from lemma index."
  575. (or (gethash key lemma-index-hash)
  576. (puthash key
  577. (string-trim
  578. (replace-regexp-in-string
  579. (concat "(" bm-lexicon-index ")") ""
  580. (bm--lex-query key bm-lexicon-index)))
  581. lemma-index-hash)))
  582. ;;;; (defun bm--lookup-def-by-greek-lemma (lemma)
  583. ;;;; "Executes `diatheke' to do query by lemma, sets text properties to allow
  584. ;;;; verse cross references. Returns string. Note that this looks up by lemmas,
  585. ;;;; not Strong's numbers. The lemmas are retrieved from a Strong's number-to-lemma
  586. ;;;; index (or possibly otherwise)."
  587. ;;;; (with-temp-buffer
  588. ;;;; (let ((args (list "diatheke" nil (current-buffer) t "-b" bm-greek-lexicon "-o" "m" "-f" "plain" "-k" lemma)))
  589. ;;;; (apply 'call-process args)
  590. ;;;; ;; Clean up outlining in the term buffer. Just fix the first
  591. ;;;; ;; level of outline. This is specific to the AbbottSmith module;
  592. ;;;; ;; it may help for other versions.
  593. ;;;; (format-replace-strings
  594. ;;;; '((" I." . "\n I.")
  595. ;;;; (" 1." . "\n 1.")
  596. ;;;; (" (a)" . "\n (a)")
  597. ;;;; (" (α)" . "\n (α)")
  598. ;;;; (" (i)" . "\n (i)")
  599. ;;;; (" (1)" . "\n (1)")
  600. ;;;; (". ." . ".")
  601. ;;;; (" . " . ". ")))
  602. ;;;; (goto-char (point-min))
  603. ;;;; (while (search-forward "\n" nil t)
  604. ;;;; (delete-blank-lines))
  605. ;;;; (goto-char (point-min))
  606. ;;;; ;; Highlight verse references to allow lookup from lexicon
  607. ;;;; ;; entry. XXX This is incomplete and does not handle all the
  608. ;;;; ;; types of cross-reference.
  609. ;;;; (while (search-forward-regexp bm-verse-regexp nil t)
  610. ;;;; (put-text-property (match-beginning 0) (match-end 0) 'xref (match-string 0))
  611. ;;;; (put-text-property (match-beginning 0) (match-end 0) 'keymap bible-search-mode-map)
  612. ;;;; (add-face-text-property (match-beginning 0) (match-end 0) '(:foreground "blue"))
  613. ;;;; ))
  614. ;;;; (buffer-string)))
  615. ;;;; (defun bm--process-href ()
  616. ;;;; (cl-do* ((text (buffer-string))
  617. ;;;; (href-match (string-match "<a href=\"passagestudy.*?</a>" text) (string-match "<a href=\"passagestudy.*?</a>" text new-start))
  618. ;;;; (href-string (match-string 0 text) (match-string 0 text))
  619. ;;;; (new-start (match-end 0) (match-end 0)))
  620. ;;;; ((not href-match))
  621. ;;;; (let* ((value-match (string-match "value=.*?&" href-string))
  622. ;;;; (value-string (match-string 0 href-string))
  623. ;;;; (verse-ref-string (substring value-string 6 (1- (length value-string)))))
  624. ;;;; (set-text-properties (match-beginning 0) (match-end 0) nil)
  625. ;;;; (replace-regexp "<a href=\"passagestudy.*?</a>" verse-ref-string t href-match new-start)
  626. ;;;; (message "Replaced %s with %s" href-string verse-ref-string)
  627. ;;;; (put-text-property (match-beginning 0) (length verse-ref-string) 'xref (match-string 0))
  628. ;;;; (put-text-property (match-beginning 0) (length verse-ref-string) 'keymap bible-search-mode-map)
  629. ;;;; (add-face-text-property (match-beginning 0) (length verse-ref-string) '(:foreground "blue")))))
  630. ;;; (while (re-search-forward REGEXP nil t)
  631. ;;; (replace-match TO-STRING nil nil))
  632. (defun bm--process-href ()
  633. (goto-char (point-min))
  634. (cl-do* ((href-match (re-search-forward "<a href=\"passagestudy.*?</a>" nil t) (re-search-forward "<a href=\"passagestudy.*?</a>" nil t))
  635. (match-text (match-string 0) (match-string 0)))
  636. ((not href-match))
  637. (replace-match "" nil nil)
  638. (let* ((value-match (string-match "value=.*?&" match-text))
  639. (value-string (match-string 0 match-text))
  640. (verse-ref-string (substring value-string 6 (1- (length value-string))))
  641. (verse-ref-length (length verse-ref-string))
  642. (prefix-1 (seq-position verse-ref-string ?1))
  643. (prefix-2 (seq-position verse-ref-string ?2)))
  644. (aset verse-ref-string (cl-search "." verse-ref-string) 32) ; Substitute first period with space
  645. (aset verse-ref-string (cl-search "." verse-ref-string) 58) ; Substitute second period with colon
  646. ;; Handle leading 1 or 2.
  647. (when (and prefix-1 (= prefix-1 0))
  648. (setq verse-ref-string (concat "I" (substring verse-ref-string 1))))
  649. (when (and prefix-2 (= prefix-2 0))
  650. (setq verse-ref-string (concat "II" (substring verse-ref-string 1))))
  651. (message "verse-ref-string: %s; length %s; point %s" verse-ref-string (length verse-ref-string) (point))
  652. (set-text-properties 0 verse-ref-length nil verse-ref-string)
  653. (put-text-property 0 verse-ref-length 'xref verse-ref-string verse-ref-string)
  654. (put-text-property 0 verse-ref-length 'keymap bible-search-mode-map verse-ref-string)
  655. (add-face-text-property 0 (length verse-ref-string) '(:foreground "blue") nil verse-ref-string)
  656. (insert verse-ref-string))))
  657. ;;;; (string-match "[0-9]+" text) (string-match "[0-9]+" text (match-end 0))))
  658. ;;;; ((not match))
  659. ;;;; ;; This enables clicking on the Strong's numbers inside the term display.
  660. ;;;; (let* ((matchstr (match-string 0 text))
  661. ;;;; (matchstrlen (length matchstr))
  662. ;;;; (refstart (+ match 1))
  663. ;;;; (refend (+ match 1 matchstrlen)))
  664. ;;;; (cond ((eq termtype 'hebrew)
  665. ;;;; (put-text-property refstart refend 'strong (concat "strong:H" matchstr))
  666. ;;;; (put-text-property refstart refend 'keymap bm-hebrew-keymap)
  667. ;;;; (add-face-text-property refstart refend `(:foreground "blue")))
  668. ;;;; ((eq termtype 'greek)
  669. ;;;; (put-text-property refstart refend 'strong (concat "strong:G" matchstr))
  670. ;;;; (put-text-property refstart refend 'keymap bm-greek-keymap)
  671. ;;;; (add-face-text-property refstart refend `(:foreground "blue"))))))
  672. (defun bm--lookup-def-by-greek-lemma (lemma)
  673. "Executes `diatheke' to do query by lemma, sets text properties to allow
  674. verse cross references. Returns string. Note that this looks up by lemmas,
  675. not Strong's numbers. The lemmas are retrieved from a Strong's number-to-lemma
  676. index (or possibly otherwise)."
  677. (with-temp-buffer
  678. (let ((args (list "diatheke" nil (current-buffer) t "-b" bm-greek-lexicon "-o" "m" "-f" "HTMLHREF" "-k" lemma)))
  679. (apply 'call-process args)
  680. ;;;; ;; Clean up outlining in the term buffer. Just fix the first
  681. ;;;; ;; level of outline. This is specific to the AbbottSmith module;
  682. ;;;; ;; it may help for other versions.
  683. ;;;; (format-replace-strings
  684. ;;;; '((" I." . "\n I.")
  685. ;;;; (" 1." . "\n 1.")
  686. ;;;; (" (a)" . "\n (a)")
  687. ;;;; (" (α)" . "\n (α)")
  688. ;;;; (" (i)" . "\n (i)")
  689. ;;;; (" (1)" . "\n (1)")
  690. ;;;; (". ." . ".")
  691. ;;;; (" . " . ". ")))
  692. ;;;; (goto-char (point-min))
  693. ;;;; (while (search-forward "\n" nil t)
  694. ;;;; (delete-blank-lines))
  695. ;;;; (goto-char (point-min))
  696. ;;;; ;; Highlight verse references to allow lookup from lexicon
  697. ;;;; ;; entry. XXX This is incomplete and does not handle all the
  698. ;;;; ;; types of cross-reference.
  699. ;;;; (while (search-forward-regexp bm-verse-regexp nil t)
  700. ;;;; (put-text-property (match-beginning 0) (match-end 0) 'xref (match-string 0))
  701. ;;;; (put-text-property (match-beginning 0) (match-end 0) 'keymap bible-search-mode-map)
  702. ;;;; (add-face-text-property (match-beginning 0) (match-end 0) '(:foreground "blue"))
  703. ;;;; ))
  704. (bm--process-href)
  705. (shr-render-region (point-min) (point-max)))
  706. (buffer-string)))
  707. (defun bm--lookup-lex-greek-indexed (key)
  708. (let ((lemma-entry (bm--lookup-lemma-index key))) ; Get lemma from Strong's number
  709. (when lemma-entry
  710. (let ((lemma (caddr (split-string lemma-entry " "))))
  711. (bm--lookup-def-by-greek-lemma lemma)))))
  712. (defun bm--lookup-lex-greek (key)
  713. "Lookup lexical definition using Strong's number. Check hash table first.
  714. Then, if a lexicon is accessed by lemmas, do lookup using index method;
  715. otherwise just use the Strong's number method."
  716. (or (gethash key greek-hash)
  717. (puthash key
  718. (if bm-use-index-for-lexicon
  719. (bm--lookup-lex-greek-indexed key)
  720. (bm--lex-query key bible-mode-greek-lexicon))
  721. greek-hash)))
  722. (defun bm--lookup-strongs-greek (window object pos)
  723. "Look up Greek lexical string from Greek lexicon for object
  724. at point. If not found in hash table, get it from sword database.
  725. stash in hash table, and return string.
  726. Note: compiler warns about unused `window' argument."
  727. (let* ((query (get-text-property pos 'strong object)) ; Get Strong's number from text property
  728. (match (string-match "[0-9]+" query)) ; Compiler warns about unused match variable.
  729. (lookup-key (match-string 0 query)))
  730. (bm--lookup-lex-greek lookup-key)))
  731. (defun bm--lookup-strongs-greek-short (window object pos)
  732. "Look up Greek lexical string from short Greek lexicon for object
  733. at point. If not found in hash table, get it from sword database,
  734. stash in hash table, and return string.
  735. Note: compiler warns about unused `window' argument."
  736. (let* ((query (get-text-property pos 'strong object)) ; Get Strong's number from text property
  737. (match (string-match "[0-9]+" query)) ; Compiler warns about unused match variable.
  738. (lookup-key (match-string 0 query)))
  739. ;; Easier to debug longer version.
  740. ;;; (when lookup-key
  741. ;;; (or (gethash lookup-key greek-hash-short)
  742. ;;; (puthash lookup-key (bm--lex-query lookup-key bm-greek-lexicon-short) greek-hash-short)))
  743. (when lookup-key
  744. (let ((data (gethash lookup-key greek-hash-short)))
  745. (if data data
  746. (let ((raw-text (bm--lex-query lookup-key bm-greek-lexicon-short)))
  747. (puthash lookup-key raw-text greek-hash-short)))))))
  748. (defun bm--lookup-strongs-hebrew (window object pos)
  749. "Look up Hebrew lexical string for object at point.
  750. If not found in hash table, get it from sword database,
  751. stash in hash table, and return string.
  752. Note: compiler warns about unused `window' argument."
  753. (let* ((query (get-text-property pos 'strong object))
  754. (match (string-match "[0-9]+" query)) ; Compiler warns about match.
  755. (match-string (match-string 0 query)))
  756. (when match-string
  757. (let ((lookup-key (concat "H" (match-string 0 query))))
  758. (or (gethash lookup-key hebrew-hash)
  759. ;; Use PLAIN format for lookup. XXX directionality problems.
  760. (let ((raw-text (bm--lex-query lookup-key bm-hebrew-lexicon)))
  761. ;; XXX massage this text to handle outline formatting a bit better.
  762. (puthash lookup-key raw-text hebrew-hash)))))))
  763. (defun bm--lookup-strongs-hebrew-short (window object pos)
  764. "Look up Hebrew lexical string from short Hebrew lexicon for object
  765. at point. If not found in hash table, get it from sword database,
  766. stash in hash table, and return string.
  767. Note: compiler warns about unused `window' argument."
  768. (let* ((query (get-text-property pos 'strong object))
  769. (match (string-match "[0-9]+" query)) ; Compiler warns about match.
  770. (match-string (match-string 0 query)))
  771. (when match-string
  772. (let ((lookup-key (concat "H" (match-string 0 query))))
  773. (or (gethash lookup-key hebrew-hash-short)
  774. ;; Use PLAIN format for lookup. XXX directionality problems.
  775. (let ((raw-text (bm--lex-query lookup-key bm-hebrew-lexicon-short)))
  776. ;; XXX massage this text to handle outline formatting a bit better.
  777. (puthash lookup-key raw-text hebrew-hash-short)))))))
  778. (defun bm--morph-database-lookup (query database hash)
  779. (or (gethash query hash)
  780. (puthash query (bm--morph-query query database) hash)))
  781. (defvar bm-outline-strings
  782. '((" I." . "\n I.")
  783. (" 1." . "\n 1.")
  784. (" 2." . "\n 2.")
  785. (" 3." . "\n 3.")
  786. (" a." . "\n a.")
  787. (" b." . "\n b.")
  788. (" c." . "\n c.")
  789. (". ." . ".")
  790. (" . " . ". ")))
  791. (defun bm-cleanup-tooltip-text (lex-text)
  792. (dolist (outline-string bm-outline-strings)
  793. (setq lex-text (string-replace (car outline-string) (cdr outline-string) lex-text)))
  794. lex-text)
  795. ;;;
  796. ;;; Get string for tooltip display
  797. ;;;
  798. (defun bm--show-lex-morph (window object pos)
  799. "Get text for tooltip display. Includes both lex and morph
  800. definitions if text module has both tags, otherwise just get
  801. lex definition."
  802. (let* ((lex-morph-text "")
  803. (lex (get-text-property pos 'strong object))
  804. (lex-module nil)
  805. (lex-text
  806. (cond ((string-match "strong:G" lex)
  807. (bm--lookup-strongs-greek-short window object pos))
  808. ((string-match "strong:H" lex)
  809. (bm--lookup-strongs-hebrew-short window object pos)))))
  810. (setq lex-text (string-replace (concat "(" lex-module ")") "" lex-text))
  811. (let* ((morph (get-text-property pos 'morph object))
  812. (morph-module nil)
  813. (morph-text
  814. (cond ((null morph) nil)
  815. ((string-match "robinson:" morph)
  816. (setq morph-module "Robinson")
  817. (bm--morph-database-lookup (replace-regexp-in-string "robinson:" "" morph) morph-module robinson-hash))
  818. ((string-match "packard:" morph)
  819. (setq morph-module "Packard")
  820. (bm--morph-database-lookup (replace-regexp-in-string "packard:" "" morph) morph-module packard-hash))
  821. ((string-match "oshm:" morph)
  822. (setq morph-module "OSHM")
  823. (bm--morph-database-lookup (replace-regexp-in-string "oshm:" "" morph) morph-module oshm-hash)))))
  824. (when lex-text
  825. (setq lex-morph-text (string-trim (bm-cleanup-tooltip-text (string-fill lex-text 75)))))
  826. (when morph-text
  827. (setq lex-morph-text
  828. (concat lex-morph-text "\n\n"
  829. (string-trim (string-replace (concat "(" morph-module ")") "" morph-text)))))
  830. ;; This prevents bogus command substitutions in the tooltip by
  831. ;; removing backslashes. XXX I couldn't figure out a better way
  832. ;; to bypass command substitution in the tooltips.
  833. (setq lex-morph-text (replace-regexp-in-string "\\\\" "" lex-morph-text)))))
  834. (defun bm-handle-divine-name (item)
  835. (insert "LORD")
  836. (let* ((refstart (- (point) (length "LORD")))
  837. (refend (point))
  838. (strongs (dom-attr item 'savlm)))
  839. (add-face-text-property refstart refend 'bold)
  840. (put-text-property refstart refend 'keymap bm-hebrew-keymap)
  841. (when (and strongs (string-match "strong:H.*" strongs))
  842. (put-text-property refstart refend 'help-echo 'bm--show-lex-morph)
  843. (put-text-property refstart refend 'strong (match-string 0 strongs)))))
  844. (defun bm--process-word (item iproperties)
  845. "Word study. Add tooltips for definitions and morphologyl.
  846. Insert lemmas in buffer. Must be done after item is inserted in buffer."
  847. (let ((word (dom-text item))
  848. (morph (dom-attr item 'morph))
  849. (savlm (dom-attr item 'savlm))
  850. (divinename (dom-by-tag item 'divinename)))
  851. (insert word)
  852. (let ((refstart (- (point) (length word)))
  853. (refend (point)))
  854. ;; Red letter (Yuck, some modules need this below)
  855. (when (plist-get iproperties 'jesus)
  856. (add-face-text-property refstart refend '(:foreground "red")))
  857. ;; Special case this. XXX Some modules do this differently.
  858. (when divinename (bm-handle-divine-name item))
  859. ;; lexical definitions
  860. (when savlm
  861. (let ((matched nil))
  862. (cond ((string-match "strong:G.*" savlm) ; Greek
  863. (setq matched (match-string 0 savlm))
  864. (put-text-property refstart refend 'keymap bm-greek-keymap))
  865. ((string-match "strong:H.*" savlm) ; Hebrew
  866. (setq matched (match-string 0 savlm))
  867. (put-text-property refstart refend 'keymap bm-hebrew-keymap)))
  868. ;; Add help-echo, strongs reference for tooltips if match.
  869. (when matched
  870. (setq-local bm-has-strongs t)
  871. (put-text-property refstart refend 'help-echo 'bm--show-lex-morph)
  872. (put-text-property refstart refend 'strong matched))))
  873. ;; morphology
  874. (when morph
  875. (let ((matched nil))
  876. (cond ((string-match "robinson:.*" morph) ; Robinson Greek morphology
  877. (setq matched (match-string 0 morph)))
  878. ((string-match "packard:.*" morph) ; Packard Greek morphology --- LXX seems to use this
  879. (setq matched (match-string 0 morph)))
  880. ((string-match "oshm:.*" morph) ; OSHM Hebrew morphology
  881. (setq matched (match-string 0 morph)))
  882. (t nil
  883. ;;(message "Unknown morphology %s" morph)
  884. ))
  885. (when matched
  886. (setq-local bm-has-morphology t)
  887. (put-text-property refstart refend 'morph matched)
  888. (put-text-property refstart refend 'help-echo 'bm--show-lex-morph))))
  889. ;; Insert lemma into buffer. Lemma tag will be part of savlm item.
  890. (when (and bm-word-study-enabled savlm (string-match "lemma.*:.*" savlm))
  891. (dolist (word (split-string (match-string 0 savlm) " "))
  892. (setq word (replace-regexp-in-string "[.:a-zA-Z0-9]+" "" word))
  893. (insert " " word)
  894. (let ((refstart (- (point) 1 (length word)))
  895. (refend (point)))
  896. (add-face-text-property refstart refend '(:foreground "blue"))
  897. (put-text-property refstart refend 'keymap bm-lemma-keymap)))))))
  898. (defun bm-new-line ()
  899. "Ensure beginning of line. Try to avoid redundant blank lines."
  900. (unless (= (current-column) 0)
  901. (insert "\n")))
  902. (defun bm--insert-domnode-recursive (node &optional iproperties notitle)
  903. "Recursively parses a domnode from `libxml-parse-html-region's usage on text
  904. produced by `bm--exec-diatheke'. Outputs text to active buffer
  905. with properties.
  906. In processing subnodes, each case will prepend a space if it needs it."
  907. (if (and bm-red-letter-enabled (equal (dom-attr node 'who) "Jesus"))
  908. ;; For red-letter display.
  909. (setq iproperties (plist-put iproperties 'jesus t))
  910. (setq iproperties nil))
  911. (dolist (subnode (dom-children node))
  912. (cond ((null subnode) nil)
  913. ((stringp subnode)
  914. ;; This still goes wrong, but I blame it on the module. ESV2011 in particular.
  915. (let ((verse-start (string-match bm-verse-regexp subnode)))
  916. (if verse-start
  917. (progn
  918. (when (= verse-start 0)
  919. (bm-new-line))
  920. ;; Insert the subnode. Highlight the verse references.
  921. (insert subnode)
  922. (let* ((verse-match (string-trim (match-string 0 subnode)))
  923. (verse-start-text (string-trim-left (substring subnode verse-start (length subnode))))
  924. (start (- (point) 1 (length (string-trim-right verse-start-text)))))
  925. (add-face-text-property start (+ start (length (string-trim-right verse-match))) '(:foreground "purple"))))
  926. (insert subnode)))
  927. ;; Red letter (some modules just have to be different....)
  928. (when (plist-get iproperties 'jesus)
  929. (add-face-text-property (- (point) (length subnode)) (point) '(:foreground "red"))))
  930. ((eq (dom-tag subnode) 'title)
  931. (when (not notitle) (setq bm-chapter-title subnode)))
  932. ((eq (dom-tag subnode) 'body) (bm--insert-domnode-recursive subnode iproperties notitle))
  933. ((eq (dom-tag subnode) 'seg) ; NASB Module uses this to indicate OT quotations (and others?).
  934. (bm--insert-domnode-recursive subnode iproperties notitle))
  935. ((eq (dom-tag subnode) 'divinename) (bm-handle-divine-name subnode))
  936. ;; This tag is used for red letter.
  937. ((eq (dom-tag subnode) 'q) (bm--insert-domnode-recursive subnode iproperties notitle))
  938. ((eq (dom-tag subnode) 'p) (bm--insert-domnode-recursive subnode iproperties notitle))
  939. ((eq (dom-tag subnode) 'w) (insert " ") (bm--process-word subnode iproperties))
  940. ((and (eq (dom-tag subnode) 'milestone) (equal (cdr (assoc 'type (dom-attributes subnode))) "line"))
  941. (bm-new-line))
  942. ((or (eq (dom-tag subnode) 'transchange)
  943. (eq (dom-tag subnode) 'hi))
  944. ;; Word inserted by translation, not in original, give visual indication.
  945. (let ((word (dom-text subnode)))
  946. (insert " " word)
  947. (add-face-text-property (- (point) (length word)) (point) '(:foreground "gray50")))))))
  948. (defvar bm-debugme nil)
  949. (defun bm--display (&optional verse)
  950. "Renders text for `bible-mode'"
  951. ;; Clear buffer and insert the result of calling bm--exec-diatheke.
  952. (setq buffer-read-only nil)
  953. (erase-buffer)
  954. (setq bm-chapter-title nil
  955. bm-has-strongs nil
  956. bm-has-morphology nil)
  957. (insert (bm--exec-diatheke (concat bm-current-book-name ":" (number-to-string bm-current-chapter))))
  958. ;; Parse the xml in the buffer into a DOM tree.
  959. (let ((html-dom-tree (libxml-parse-html-region (point-min) (point-max))))
  960. ;; Render the DOM tree into the buffer.
  961. (if (not bm-debugme)
  962. (progn
  963. (erase-buffer)
  964. ;; Looking for the "body" tag in the DOM node.
  965. (bm--insert-domnode-recursive (dom-by-tag html-dom-tree 'body) nil nil)
  966. (goto-char (point-min)))
  967. ;;; (shr-render-region (point-min) (point-max))
  968. ))
  969. ;; Remove the module name from the buffer.
  970. (while (search-forward (concat "(" bm-module ")") nil t)
  971. (replace-match ""))
  972. ;; Set the mode line of the biffer.
  973. (setq mode-name (concat "Bible ("
  974. bm-module
  975. (when bm-has-strongs " Lex")
  976. (when bm-has-morphology " Morph")
  977. ")"))
  978. ;; Deal with chapter titles (i.e. in Psalms)
  979. ;; N.B. This won't change a title inside a chapter, and so it
  980. ;; doesn't work with Psalm 119 where the acrostic letters get
  981. ;; printed as "titles".
  982. (when bm-chapter-title ; This gets set in bm-insert-domnode-recursive.
  983. (goto-char (point-min))
  984. (let ((title-text (dom-texts bm-chapter-title))
  985. (refstart (point-min))
  986. refend)
  987. ;; Insert and make bold the title.
  988. (when (string-or-null-p title-text)
  989. (insert title-text "\n")
  990. (setq refend (point))
  991. (put-text-property refstart refend 'face 'bold))))
  992. (setq buffer-read-only t)
  993. (goto-char (point-min))
  994. ;; If optional verse specification go to that verse.
  995. (when verse
  996. (goto-char (string-match (regexp-opt `(,(concat ":" (number-to-string verse) ":"))) (buffer-string)))
  997. (beginning-of-line)))
  998. (defun bm--list-biblical-modules ()
  999. "Returns a list of accessible Biblical Text modules."
  1000. (let ((text (bm--exec-diatheke "modulelist" nil nil nil "system"))
  1001. modules)
  1002. (catch 'done
  1003. (dolist (line (split-string text "[\n\r]+"))
  1004. (when (equal line "Commentaries:")
  1005. (throw 'done nil))
  1006. (when (not (equal "Biblical Texts:" line))
  1007. (push (split-string line " : ") modules))))
  1008. modules))
  1009. (defun bm-pick-module ()
  1010. (interactive)
  1011. (let ((item (get-text-property (point) 'module)))
  1012. (setq-default bm-module item)
  1013. (bible-open)))
  1014. (defconst bm-module-map (make-keymap))
  1015. (define-key bm-module-map [mouse-1] 'bm-pick-module)
  1016. (defun bm-display-available-modules ()
  1017. (interactive)
  1018. (let ((buf (get-buffer-create "Modules"))
  1019. (mods (bm--list-biblical-modules)))
  1020. (set-buffer buf)
  1021. (module-select-mode)
  1022. (setq buffer-read-only nil)
  1023. (erase-buffer)
  1024. (setq-local tab-stop-list '(25))
  1025. (dolist (mod mods)
  1026. (let ((name (string-trim (car mod)))
  1027. (description (string-trim-left (cadr mod))))
  1028. (insert
  1029. (propertize (string-trim name)
  1030. 'face 'bold
  1031. 'module name
  1032. 'help-echo (concat "Select " name)
  1033. 'keymap bm-module-map))
  1034. (move-to-tab-stop)
  1035. (insert (format "%s\n" description))))
  1036. (setq buffer-read-only t)
  1037. (goto-char (point-min))
  1038. (pop-to-buffer buf nil t)))
  1039. ;;;;; Bible Searching
  1040. (defun bm--open-search (query searchmode module)
  1041. "Opens a search buffer of QUERY using SEARCHMODE."
  1042. (let ((buf (get-buffer-create (concat "*bible-search-" (downcase module) "-" query "*"))))
  1043. (set-buffer buf)
  1044. (bible-search-mode)
  1045. (bm--display-search query searchmode module)
  1046. (pop-to-buffer buf nil t)))
  1047. (defun bm--display-search (query searchmode mod)
  1048. "Renders results of search QUERY from SEARHCMODE"
  1049. (setq buffer-read-only nil)
  1050. (erase-buffer)
  1051. (let* ((result (string-trim (replace-regexp-in-string
  1052. "Entries .+?--" ""
  1053. (bm--exec-diatheke query nil "plain" searchmode mod))))
  1054. (match 0)
  1055. (matchstr "")
  1056. (verses nil)
  1057. (query-verses "")
  1058. fullverses)
  1059. (if (equal result (concat "none (" mod ")"))
  1060. (insert "No results found." (when (equal searchmode "lucene") " Verify index has been build with mkfastmod."))
  1061. (progn
  1062. (while match
  1063. (setq match (string-match ".+?:[0-9]?[0-9]?" result (+ match (length matchstr)))
  1064. matchstr (match-string 0 result))
  1065. (when match
  1066. (push
  1067. ;; Massage match to make it more sortable, get rid of
  1068. ;; some characters.
  1069. (replace-regexp-in-string
  1070. "I " "1"
  1071. (replace-regexp-in-string
  1072. "II " "2"
  1073. (replace-regexp-in-string ".+; " "" matchstr)))
  1074. verses)))
  1075. (setq match 0)
  1076. (setq verses (sort verses))
  1077. (dolist (verse verses)
  1078. (if query-verses
  1079. (setq query-verses (concat query-verses ";" verse))
  1080. (setq query-verses verse)))
  1081. (setq fullverses (bm--exec-diatheke query-verses nil nil nil mod))
  1082. (insert fullverses)
  1083. (let* ((html-dom-tree (libxml-parse-html-region (point-min) (point-max))))
  1084. (erase-buffer)
  1085. (bm--insert-domnode-recursive (dom-by-tag html-dom-tree 'body) nil nil)
  1086. (goto-char (point-min))
  1087. (while (search-forward (concat "(" mod ")") nil t)
  1088. (replace-match "")))))
  1089. (setq mode-name (concat "Bible Search (" mod ")"))
  1090. (setq buffer-read-only t)
  1091. (setq-local bm-search-query query)
  1092. (setq-local bm-search-mode searchmode)
  1093. (goto-char (point-min))))
  1094. ;;;;; Terms
  1095. ;;(defun bm-display-morphology (morph)
  1096. ;; ;; xxx Do something here?
  1097. ;; )
  1098. (defun bm--display-term (termtype)
  1099. (message "bible-mode--display-term %s" termtype)
  1100. (setq buffer-read-only nil)
  1101. (cl-do* ((text (buffer-string))
  1102. (match (string-match "[0-9]+" text) (string-match "[0-9]+" text (match-end 0))))
  1103. ((not match))
  1104. ;; This enables clicking on the Strong's numbers inside the term display.
  1105. (let* ((matchstr (match-string 0 text))
  1106. (matchstrlen (length matchstr))
  1107. (refstart (+ match 1))
  1108. (refend (+ match 1 matchstrlen)))
  1109. (cond ((eq termtype 'hebrew)
  1110. (put-text-property refstart refend 'strong (concat "strong:H" matchstr))
  1111. (put-text-property refstart refend 'keymap bm-hebrew-keymap)
  1112. (add-face-text-property refstart refend `(:foreground "blue")))
  1113. ((eq termtype 'greek))
  1114. (put-text-property refstart refend 'strong (concat "strong:G" matchstr))
  1115. (put-text-property refstart refend 'keymap bm-greek-keymap)
  1116. (add-face-text-property refstart refend `(:foreground "blue")))))
  1117. (goto-char (point-min))
  1118. (while (search-forward-regexp bm-verse-regexp nil t)
  1119. (put-text-property (match-beginning 0) (match-end 0) 'xref (match-string 0))
  1120. (put-text-property (match-beginning 0) (match-end 0) 'keymap bible-search-mode-map)
  1121. (put-text-property (match-beginning 0) (match-end 0) 'help-echo (concat "Go to " (match-string 0)))
  1122. (add-face-text-property (match-beginning 0) (match-end 0) '(:foreground "blue"))
  1123. (message "Found xref %s" (match-string 0))
  1124. )
  1125. (goto-char (point-min))
  1126. ;; (while (search-forward (concat "(" bm-module ")") nil t)
  1127. ;; (replace-match ""))
  1128. (while (search-forward "()" nil t)
  1129. (replace-match ""))
  1130. (goto-char (point-min))
  1131. (setq buffer-read-only t))
  1132. (defun bm--open-term-hebrew (term)
  1133. "Opens a buffer of the Strong's Hebrew TERM's definition"
  1134. (let ((buf (get-buffer-create (concat "*bible-term-hebrew-" term "*"))))
  1135. (set-buffer buf)
  1136. (bible-term-hebrew-mode)
  1137. (bm--display-term-hebrew term)
  1138. (pop-to-buffer buf nil t)
  1139. (fit-window-to-buffer)))
  1140. (defun bm--open-term-greek (term)
  1141. "Opens a buffer of the Strong's Greek TERM's definition"
  1142. (let ((buf (get-buffer-create (concat "*bible-term-greek-" term "*"))))
  1143. (set-buffer buf)
  1144. (bible-term-greek-mode)
  1145. (bm--display-term-greek term)
  1146. (pop-to-buffer buf nil t)
  1147. (fit-window-to-buffer)))
  1148. ;;;
  1149. ;;; Note: Hebrew display of terms is backwards; set bidi direction to
  1150. ;;; 'left-to-right.
  1151. (defun bm--display-term-hebrew (term)
  1152. "Render the definition of the Strong's Hebrew TERM. Use
  1153. bidi-paragraph-direction so the English text will render
  1154. left-to-right. XXX Why doesn't this work for the tooltips?"
  1155. (setq buffer-read-only nil)
  1156. (erase-buffer)
  1157. (insert (replace-regexp-in-string
  1158. (regexp-opt `(,bm-hebrew-lexicon))
  1159. ""
  1160. (bm--exec-diatheke term nil "plain" nil bm-hebrew-lexicon)
  1161. nil nil nil 7
  1162. ))
  1163. (bm--display-term 'hebrew)
  1164. (setq bidi-paragraph-direction 'left-to-right))
  1165. (defun bm--display-term-greek (term)
  1166. "Render the definition of the Strong's Greek TERM."
  1167. (setq buffer-read-only nil)
  1168. (erase-buffer)
  1169. (insert (replace-regexp-in-string
  1170. (regexp-opt `(,bm-greek-lexicon))
  1171. ""
  1172. ;; (bm--exec-diatheke term nil "plain" nil bm-greek-lexicon)
  1173. (bm--lookup-lex-greek term)))
  1174. (bm--display-term 'greek))
  1175. (defun bm--set-location (book chapter &optional verse)
  1176. "Sets the global chapter of the active `bible-mode' buffer."
  1177. (setq-local bm-current-book book)
  1178. (setq-local bm-current-book-name (car book))
  1179. (setq-local bm-current-chapter chapter)
  1180. (bm--display verse))
  1181. ;;;;; Utilities
  1182. (defun bm--list-number-range (min max &optional prefix)
  1183. "Returns a list containing entries for each integer between min and max.
  1184. Used in tandem with `completing-read' for chapter selection."
  1185. (let ((range-list nil))
  1186. (dotimes (num (1+ max))
  1187. (when (>= num min)
  1188. (push (cons (concat prefix (number-to-string num)) num) range-list)))
  1189. (nreverse range-list)))
  1190. ;;; Provides
  1191. (provide 'bible-mode)
  1192. ;; Local Variables:
  1193. ;; read-symbol-shorthands: (("bm-" . "bible-mode-"))
  1194. ;; End: