bible.el 56 KB

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