bible-mode.el 51 KB

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