Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

525 lines
18 KiB

4 years ago
  1. ;;; pdf-tools.el --- Support library for PDF documents. -*- lexical-binding:t -*-
  2. ;; Copyright (C) 2013, 2014 Andreas Politz
  3. ;; Author: Andreas Politz <politza@fh-trier.de>
  4. ;; Keywords: files, multimedia
  5. ;; Package: pdf-tools
  6. ;; Version: 1.0
  7. ;; Package-Requires: ((emacs "24.3") (tablist "1.0") (let-alist "1.0.4"))
  8. ;; This program is free software; you can redistribute it and/or modify
  9. ;; it under the terms of the GNU General Public License as published by
  10. ;; the Free Software Foundation, either version 3 of the License, or
  11. ;; (at your option) any later version.
  12. ;; This program is distributed in the hope that it will be useful,
  13. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;; GNU General Public License for more details.
  16. ;; You should have received a copy of the GNU General Public License
  17. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. ;;; Commentary:
  19. ;;
  20. ;; PDF Tools is, among other things, a replacement of DocView for PDF
  21. ;; files. The key difference is, that pages are not prerendered by
  22. ;; e.g. ghostscript and stored in the file-system, but rather created
  23. ;; on-demand and stored in memory.
  24. ;;
  25. ;; Note: This package requires external libraries and works currently
  26. ;; only on GNU/Linux systems.
  27. ;;
  28. ;; Note: If you ever update it, you need to restart Emacs afterwards.
  29. ;;
  30. ;; To activate the package put
  31. ;;
  32. ;; (pdf-tools-install)
  33. ;;
  34. ;; somewhere in your .emacs.el .
  35. ;;
  36. ;; M-x pdf-tools-help RET
  37. ;;
  38. ;; gives some help on using the package and
  39. ;;
  40. ;; M-x pdf-tools-customize RET
  41. ;;
  42. ;; offers some customization options.
  43. ;; Features:
  44. ;;
  45. ;; * View
  46. ;; View PDF documents in a buffer with DocView-like bindings.
  47. ;;
  48. ;; * Isearch
  49. ;; Interactively search PDF documents like any other buffer. (Though
  50. ;; there is currently no regexp support.)
  51. ;;
  52. ;; * Follow links
  53. ;; Click on highlighted links, moving to some part of a different
  54. ;; page, some external file, a website or any other URI. Links may
  55. ;; also be followed by keyboard commands.
  56. ;;
  57. ;; * Annotations
  58. ;; Display and list text and markup annotations (like underline),
  59. ;; edit their contents and attributes (e.g. color), move them around,
  60. ;; delete them or create new ones and then save the modifications
  61. ;; back to the PDF file.
  62. ;;
  63. ;; * Attachments
  64. ;; Save files attached to the PDF-file or list them in a dired buffer.
  65. ;;
  66. ;; * Outline
  67. ;; Use imenu or a special buffer to examine and navigate the PDF's
  68. ;; outline.
  69. ;;
  70. ;; * SyncTeX
  71. ;; Jump from a position on a page directly to the TeX source and
  72. ;; vice-versa.
  73. ;;
  74. ;; * Misc
  75. ;; + Display PDF's metadata.
  76. ;; + Mark a region and kill the text from the PDF.
  77. ;; + Search for occurrences of a string.
  78. ;; + Keep track of visited pages via a history.
  79. ;;; Code:
  80. (require 'pdf-view)
  81. (require 'pdf-util)
  82. (require 'pdf-info)
  83. (require 'cus-edit)
  84. (require 'compile)
  85. (require 'cl-lib)
  86. (require 'package)
  87. ;; * ================================================================== *
  88. ;; * Customizables
  89. ;; * ================================================================== *
  90. (defgroup pdf-tools nil
  91. "Support library for PDF documents."
  92. :group 'data)
  93. (defgroup pdf-tools-faces nil
  94. "Faces determining the colors used in the pdf-tools package.
  95. In order to customize dark and light colors use
  96. `pdf-tools-customize-faces', or set `custom-face-default-form' to
  97. 'all."
  98. :group 'pdf-tools)
  99. (defconst pdf-tools-modes
  100. '(pdf-history-minor-mode
  101. pdf-isearch-minor-mode
  102. pdf-links-minor-mode
  103. pdf-misc-minor-mode
  104. pdf-outline-minor-mode
  105. pdf-misc-size-indication-minor-mode
  106. pdf-misc-menu-bar-minor-mode
  107. pdf-annot-minor-mode
  108. pdf-sync-minor-mode
  109. pdf-misc-context-menu-minor-mode
  110. pdf-cache-prefetch-minor-mode
  111. pdf-view-auto-slice-minor-mode
  112. pdf-occur-global-minor-mode
  113. pdf-virtual-global-minor-mode))
  114. (defcustom pdf-tools-enabled-modes
  115. '(pdf-history-minor-mode
  116. pdf-isearch-minor-mode
  117. pdf-links-minor-mode
  118. pdf-misc-minor-mode
  119. pdf-outline-minor-mode
  120. pdf-misc-size-indication-minor-mode
  121. pdf-misc-menu-bar-minor-mode
  122. pdf-annot-minor-mode
  123. pdf-sync-minor-mode
  124. pdf-misc-context-menu-minor-mode
  125. pdf-cache-prefetch-minor-mode
  126. pdf-occur-global-minor-mode
  127. ;; pdf-virtual-global-minor-mode
  128. )
  129. "A list of automatically enabled minor-modes.
  130. PDF Tools is build as a series of minor-modes. This variable and
  131. the function `pdf-tools-install' merely serve as a convenient
  132. wrapper in order to load these modes in current and newly created
  133. PDF buffers."
  134. :group 'pdf-tools
  135. :type `(set ,@(mapcar (lambda (mode)
  136. `(function-item ,mode))
  137. pdf-tools-modes)))
  138. (defcustom pdf-tools-enabled-hook nil
  139. "A hook ran after PDF Tools is enabled in a buffer."
  140. :group 'pdf-tools
  141. :type 'hook)
  142. (defconst pdf-tools-auto-mode-alist-entry
  143. '("\\.[pP][dD][fF]\\'" . pdf-view-mode)
  144. "The entry to use for `auto-mode-alist'.")
  145. (defconst pdf-tools-magic-mode-alist-entry
  146. '("%PDF" . pdf-view-mode)
  147. "The entry to use for `magic-mode-alist'.")
  148. (defun pdf-tools-customize ()
  149. "Customize Pdf Tools."
  150. (interactive)
  151. (customize-group 'pdf-tools))
  152. (defun pdf-tools-customize-faces ()
  153. "Customize PDF Tool's faces."
  154. (interactive)
  155. (let ((buffer (format "*Customize Group: %s*"
  156. (custom-unlispify-tag-name 'pdf-tools-faces))))
  157. (when (buffer-live-p (get-buffer buffer))
  158. (with-current-buffer (get-buffer buffer)
  159. (rename-uniquely)))
  160. (customize-group 'pdf-tools-faces)
  161. (with-current-buffer buffer
  162. (set (make-local-variable 'custom-face-default-form) 'all))))
  163. ;; * ================================================================== *
  164. ;; * Installation
  165. ;; * ================================================================== *
  166. ;;;###autoload
  167. (defcustom pdf-tools-handle-upgrades t
  168. "Whether PDF Tools should handle upgrading itself."
  169. :group 'pdf-tools
  170. :type 'boolean)
  171. (make-obsolete-variable 'pdf-tools-handle-upgrades
  172. "Not used anymore" "0.90")
  173. (defconst pdf-tools-directory
  174. (or (and load-file-name
  175. (file-name-directory load-file-name))
  176. default-directory)
  177. "The directory from where this library was first loaded.")
  178. (defvar pdf-tools-msys2-directory nil)
  179. (defcustom pdf-tools-installer-os nil
  180. "Specifies which installer to use.
  181. If nil the installer is chosen automatically. This variable is
  182. useful if you have multiple installers present on your
  183. system (e.g. nix on arch linux)"
  184. :group 'pdf-tools
  185. :type 'string)
  186. (defun pdf-tools-identify-build-directory (directory)
  187. "Return non-nil, if DIRECTORY appears to contain the epdfinfo source.
  188. Returns the expanded directory-name of DIRECTORY or nil."
  189. (setq directory (file-name-as-directory
  190. (expand-file-name directory)))
  191. (and (file-exists-p (expand-file-name "autobuild" directory))
  192. (file-exists-p (expand-file-name "epdfinfo.c" directory))
  193. directory))
  194. (defun pdf-tools-locate-build-directory ()
  195. "Attempt to locate a source directory.
  196. Returns a appropriate directory or nil. See also
  197. `pdf-tools-identify-build-directory'."
  198. (cl-some #'pdf-tools-identify-build-directory
  199. (list default-directory
  200. (expand-file-name "build/server" pdf-tools-directory)
  201. (expand-file-name "server")
  202. (expand-file-name "../server" pdf-tools-directory))))
  203. (defun pdf-tools-msys2-directory (&optional noninteractive-p)
  204. "Locate the Msys2 installation directory.
  205. Ask the user if necessary and NONINTERACTIVE-P is nil.
  206. Returns always nil, unless `system-type' equals windows-nt."
  207. (cl-labels ((if-msys2-directory (directory)
  208. (and (stringp directory)
  209. (file-directory-p directory)
  210. (file-exists-p
  211. (expand-file-name "usr/bin/bash.exe" directory))
  212. directory)))
  213. (when (eq system-type 'windows-nt)
  214. (setq pdf-tools-msys2-directory
  215. (or pdf-tools-msys2-directory
  216. (cl-some #'if-msys2-directory
  217. (cl-mapcan
  218. (lambda (drive)
  219. (list (format "%c:/msys64" drive)
  220. (format "%c:/msys32" drive)))
  221. (number-sequence ?c ?z)))
  222. (unless (or noninteractive-p
  223. (not (y-or-n-p "Do you have Msys2 installed ? ")))
  224. (if-msys2-directory
  225. (read-directory-name
  226. "Please enter Msys2 installation directory: " nil nil t))))))))
  227. (defun pdf-tools-msys2-mingw-bin ()
  228. "Return the location of /mingw*/bin."
  229. (when (pdf-tools-msys2-directory)
  230. (let ((arch (intern (car (split-string system-configuration "-" t)))))
  231. (expand-file-name
  232. (format "./mingw%s/bin" (if (eq arch 'x86_64) "64" "32"))
  233. (pdf-tools-msys2-directory)))))
  234. (defun pdf-tools-find-bourne-shell ()
  235. "Locate a usable sh."
  236. (or (and (eq system-type 'windows-nt)
  237. (let* ((directory (pdf-tools-msys2-directory)))
  238. (when directory
  239. (expand-file-name "usr/bin/bash.exe" directory))))
  240. (executable-find "sh")))
  241. (defun pdf-tools-build-server (target-directory
  242. &optional
  243. skip-dependencies-p
  244. force-dependencies-p
  245. callback
  246. build-directory)
  247. "Build the epdfinfo program in the background.
  248. Install into TARGET-DIRECTORY, which should be a directory.
  249. If CALLBACK is non-nil, it should be a function. It is called
  250. with the compiled executable as the single argument or nil, if
  251. the build failed.
  252. Expect sources to be in BUILD-DIRECTORY. If nil, search for it
  253. using `pdf-tools-locate-build-directory'.
  254. See `pdf-tools-install' for the SKIP-DEPENDENCIES-P and
  255. FORCE-DEPENDENCIES-P arguments.
  256. Returns the buffer of the compilation process."
  257. (unless callback (setq callback #'ignore))
  258. (unless build-directory
  259. (setq build-directory (pdf-tools-locate-build-directory)))
  260. (cl-check-type target-directory file-directory)
  261. (setq target-directory (file-name-as-directory
  262. (expand-file-name target-directory)))
  263. (cl-check-type build-directory (and (not null) file-directory))
  264. (when (and skip-dependencies-p force-dependencies-p)
  265. (error "Can't simultaneously skip and force dependencies"))
  266. (let* ((compilation-auto-jump-to-first-error nil)
  267. (compilation-scroll-output t)
  268. (shell-file-name (pdf-tools-find-bourne-shell))
  269. (shell-command-switch "-c")
  270. (process-environment process-environment)
  271. (default-directory build-directory)
  272. (autobuild (shell-quote-argument
  273. (expand-file-name "autobuild" build-directory)))
  274. (msys2-p (equal "bash.exe" (file-name-nondirectory shell-file-name))))
  275. (unless shell-file-name
  276. (error "No suitable shell found"))
  277. (when msys2-p
  278. (push "BASH_ENV=/etc/profile" process-environment))
  279. (let ((executable
  280. (expand-file-name
  281. (concat "epdfinfo" (and (eq system-type 'windows-nt) ".exe"))
  282. target-directory))
  283. (compilation-buffer
  284. (compilation-start
  285. (format "%s -i %s%s%s"
  286. autobuild
  287. (shell-quote-argument target-directory)
  288. (cond
  289. (skip-dependencies-p " -D")
  290. (force-dependencies-p " -d")
  291. (t ""))
  292. (if pdf-tools-installer-os (concat " --os " pdf-tools-installer-os) ""))
  293. t)))
  294. ;; In most cases user-input is required, so select the window.
  295. (if (get-buffer-window compilation-buffer)
  296. (select-window (get-buffer-window compilation-buffer))
  297. (pop-to-buffer compilation-buffer))
  298. (with-current-buffer compilation-buffer
  299. (setq-local compilation-error-regexp-alist nil)
  300. (add-hook 'compilation-finish-functions
  301. (lambda (_buffer status)
  302. (funcall callback
  303. (and (equal status "finished\n")
  304. executable)))
  305. nil t)
  306. (current-buffer)))))
  307. ;; * ================================================================== *
  308. ;; * Initialization
  309. ;; * ================================================================== *
  310. ;;;###autoload
  311. (defun pdf-tools-install (&optional no-query-p skip-dependencies-p
  312. no-error-p force-dependencies-p)
  313. "Install PDF-Tools in all current and future PDF buffers.
  314. If the `pdf-info-epdfinfo-program' is not running or does not
  315. appear to be working, attempt to rebuild it. If this build
  316. succeeded, continue with the activation of the package.
  317. Otherwise fail silently, i.e. no error is signaled.
  318. Build the program (if necessary) without asking first, if
  319. NO-QUERY-P is non-nil.
  320. Don't attempt to install system packages, if SKIP-DEPENDENCIES-P
  321. is non-nil.
  322. Do not signal an error in case the build failed, if NO-ERROR-P is
  323. non-nil.
  324. Attempt to install system packages (even if it is deemed
  325. unnecessary), if FORCE-DEPENDENCIES-P is non-nil.
  326. Note that SKIP-DEPENDENCIES-P and FORCE-DEPENDENCIES-P are
  327. mutually exclusive.
  328. Note further, that you can influence the installation directory
  329. by setting `pdf-info-epdfinfo-program' to an appropriate
  330. value (e.g. ~/bin/epdfinfo) before calling this function.
  331. See `pdf-view-mode' and `pdf-tools-enabled-modes'."
  332. (interactive)
  333. (if (or (pdf-info-running-p)
  334. (ignore-errors (pdf-info-check-epdfinfo) t))
  335. (pdf-tools-install-noverify)
  336. (let ((target-directory
  337. (or (and (stringp pdf-info-epdfinfo-program)
  338. (file-name-directory
  339. pdf-info-epdfinfo-program))
  340. pdf-tools-directory)))
  341. (if (or no-query-p
  342. (y-or-n-p "Need to (re)build the epdfinfo program, do it now ?"))
  343. (pdf-tools-build-server
  344. target-directory
  345. skip-dependencies-p
  346. force-dependencies-p
  347. (lambda (executable)
  348. (let ((msg (format
  349. "Building the PDF Tools server %s"
  350. (if executable "succeeded" "failed"))))
  351. (if (not executable)
  352. (funcall (if no-error-p #'message #'error) "%s" msg)
  353. (message "%s" msg)
  354. (setq pdf-info-epdfinfo-program executable)
  355. (let ((pdf-info-restart-process-p t))
  356. (pdf-tools-install-noverify))))))
  357. (message "PDF Tools not activated")))))
  358. (defun pdf-tools-install-noverify ()
  359. "Like `pdf-tools-install', but skip checking `pdf-info-epdfinfo-program'."
  360. (add-to-list 'auto-mode-alist pdf-tools-auto-mode-alist-entry)
  361. (add-to-list 'magic-mode-alist pdf-tools-magic-mode-alist-entry)
  362. ;; FIXME: Generalize this sometime.
  363. (when (memq 'pdf-occur-global-minor-mode
  364. pdf-tools-enabled-modes)
  365. (pdf-occur-global-minor-mode 1))
  366. (when (memq 'pdf-virtual-global-minor-mode
  367. pdf-tools-enabled-modes)
  368. (pdf-virtual-global-minor-mode 1))
  369. (add-hook 'pdf-view-mode-hook 'pdf-tools-enable-minor-modes)
  370. (dolist (buf (buffer-list))
  371. (with-current-buffer buf
  372. (when (and (not (derived-mode-p 'pdf-view-mode))
  373. (pdf-tools-pdf-buffer-p)
  374. (buffer-file-name))
  375. (pdf-view-mode)))))
  376. (defun pdf-tools-uninstall ()
  377. "Uninstall PDF-Tools in all current and future PDF buffers."
  378. (interactive)
  379. (pdf-info-quit)
  380. (setq-default auto-mode-alist
  381. (remove pdf-tools-auto-mode-alist-entry auto-mode-alist))
  382. (setq-default magic-mode-alist
  383. (remove pdf-tools-magic-mode-alist-entry magic-mode-alist))
  384. (pdf-occur-global-minor-mode -1)
  385. (pdf-virtual-global-minor-mode -1)
  386. (remove-hook 'pdf-view-mode-hook 'pdf-tools-enable-minor-modes)
  387. (dolist (buf (buffer-list))
  388. (with-current-buffer buf
  389. (when (pdf-util-pdf-buffer-p buf)
  390. (pdf-tools-disable-minor-modes pdf-tools-modes)
  391. (normal-mode)))))
  392. (defun pdf-tools-pdf-buffer-p (&optional buffer)
  393. "Return non-nil if BUFFER contains a PDF document."
  394. (save-current-buffer
  395. (when buffer (set-buffer buffer))
  396. (save-excursion
  397. (save-restriction
  398. (widen)
  399. (goto-char 1)
  400. (looking-at "%PDF")))))
  401. (defun pdf-tools-assert-pdf-buffer (&optional buffer)
  402. (unless (pdf-tools-pdf-buffer-p buffer)
  403. (error "Buffer does not contain a PDF document")))
  404. (defun pdf-tools-set-modes-enabled (enable &optional modes)
  405. (dolist (m (or modes pdf-tools-enabled-modes))
  406. (let ((enabled-p (and (boundp m)
  407. (symbol-value m))))
  408. (unless (or (and enabled-p enable)
  409. (and (not enabled-p) (not enable)))
  410. (funcall m (if enable 1 -1))))))
  411. ;;;###autoload
  412. (defun pdf-tools-enable-minor-modes (&optional modes)
  413. "Enable MODES in the current buffer.
  414. MODES defaults to `pdf-tools-enabled-modes'."
  415. (interactive)
  416. (pdf-util-assert-pdf-buffer)
  417. (pdf-tools-set-modes-enabled t modes)
  418. (run-hooks 'pdf-tools-enabled-hook))
  419. (defun pdf-tools-disable-minor-modes (&optional modes)
  420. "Disable MODES in the current buffer.
  421. MODES defaults to `pdf-tools-enabled-modes'."
  422. (interactive)
  423. (pdf-tools-set-modes-enabled nil modes))
  424. (declare-function pdf-occur-global-minor-mode "pdf-occur.el")
  425. (declare-function pdf-virtual-global-minor-mode "pdf-virtual.el")
  426. ;;;###autoload
  427. (defun pdf-tools-help ()
  428. (interactive)
  429. (help-setup-xref (list #'pdf-tools-help)
  430. (called-interactively-p 'interactive))
  431. (with-help-window (help-buffer)
  432. (princ "PDF Tools Help\n\n")
  433. (princ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n")
  434. (dolist (m (cons 'pdf-view-mode
  435. (sort (copy-sequence pdf-tools-modes) 'string<)))
  436. (princ (format "`%s' is " m))
  437. (describe-function-1 m)
  438. (terpri) (terpri)
  439. (princ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"))))
  440. ;; * ================================================================== *
  441. ;; * Debugging
  442. ;; * ================================================================== *
  443. (defvar pdf-tools-debug nil
  444. "Non-nil, if debugging PDF Tools.")
  445. (defun pdf-tools-toggle-debug ()
  446. (interactive)
  447. (setq pdf-tools-debug (not pdf-tools-debug))
  448. (when (called-interactively-p 'any)
  449. (message "Toggled debugging %s" (if pdf-tools-debug "on" "off"))))
  450. (provide 'pdf-tools)
  451. ;;; pdf-tools.el ends here