init-eww.el 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. ;;; EWW
  2. ;; TODO: Fix `eww-forward-url' infinite forwarding.
  3. ;; TODO: Extend the history / bookmarks view to display tags, mark and search engine.
  4. ;; With colors: [mark] title url (tags)
  5. ;; Mark is red if no search engine, green otherwise.
  6. ;; Tags have their own face.
  7. ;; TODO: Make something useful with the tags. Helm function? Could chain two
  8. ;; functions: tag selection then filtered bookmark selection, then tag selection
  9. ;; again, etc.
  10. ;; Alternative: use special syntax in prompt, like find-files does. This does
  11. ;; not allow for listing tags though.
  12. (require 'helm-eww nil t)
  13. (setq eww-bookmarks-directory "~/personal/bookmarks"
  14. eww-download-directory "~/temp")
  15. ;; (add-to-list 'auto-mode-alist '("eww-bookmarks$" . emacs-lisp-mode))
  16. (defun ambrevar/eww-copy-page-title ()
  17. "Copy the URL of the current page into the kill ring."
  18. (interactive)
  19. (message "%s" (plist-get eww-data :title))
  20. (kill-new (plist-get eww-data :title)))
  21. (defun ambrevar/eww-next-url (&optional backward)
  22. "Like `eww-next-url' but if no next URL is found, go to next URL numerically.
  23. The URL index is the last number after the last '/'."
  24. (interactive)
  25. (condition-case nil
  26. (if backward
  27. (eww-previous-url)
  28. (eww-next-url))
  29. (user-error
  30. (when (eq major-mode 'eww-mode)
  31. (require 'rx)
  32. (let* ((url (plist-get eww-data :url))
  33. (re (rx (group (one-or-more digit))
  34. (zero-or-more (not (any "/")))
  35. line-end)))
  36. (if (and (string-match re url)
  37. (or (not backward)
  38. (> (string-to-number (match-string 1 url)) 0)))
  39. (eww
  40. (replace-regexp-in-string
  41. re
  42. (format (format "%%0.%dd" (length (match-string 1 url))) ; In case matched number is zero-padded.
  43. (funcall (if backward '1- '1+) (string-to-number (match-string 1 url))))
  44. url nil nil 1))
  45. (message "No index in URL.")))))))
  46. (defun ambrevar/eww-previous-url ()
  47. "Like `eww-previous-url' but if no next URL is found, go to next URL numerically.
  48. The URL index is the last number after the last '/'."
  49. (interactive)
  50. (ambrevar/eww-next-url 'backward))
  51. (defun ambrevar/eww-reload-all (&optional buffers)
  52. "Like `eww-reload' but for multiple EWW BUFFERS.
  53. If BUFFERS is not specified, then reload all buffers."
  54. (interactive)
  55. (dolist (b (or buffers (buffer-list)))
  56. (with-current-buffer b
  57. (when (derived-mode-p 'eww-mode)
  58. (eww-reload)))))
  59. (defun ambrevar/eww-switch-back ()
  60. "Switch to the *eww* buffer."
  61. (interactive)
  62. (let (buffer-info)
  63. (dolist (buffer (buffer-list))
  64. (with-current-buffer buffer
  65. (when (derived-mode-p 'eww-mode)
  66. (push buffer buffer-info))))
  67. (setq buffer-info (nreverse buffer-info))
  68. (if buffer-info
  69. (if (derived-mode-p 'eww-mode)
  70. (if (fboundp 'helm-eww)
  71. (helm-eww)
  72. (switch-to-buffer (completing-read "EWW: " (mapcar 'buffer-name buffer-info))))
  73. (switch-to-buffer (car buffer-info)))
  74. (if (fboundp 'helm-eww)
  75. (helm-eww)
  76. (call-interactively 'eww)))))
  77. (defun ambrevar/eww (url)
  78. "Fetch URL and render the page.
  79. If the input doesn't look like an URL or a domain name, the
  80. word(s) will be searched for via `eww-search-prefix'."
  81. (interactive
  82. (let* ((uris (eww-suggested-uris))
  83. (prompt (concat "Enter URL or keywords: "))) ; PATCH
  84. (list (read-string prompt (car uris) nil uris)))) ; PATCH
  85. (setq url (eww--dwim-expand-url url))
  86. (pop-to-buffer-same-window
  87. (if (eq major-mode 'eww-mode)
  88. (current-buffer)
  89. (get-buffer-create "*eww*")))
  90. (eww-setup-buffer)
  91. ;; Check whether the domain only uses "Highly Restricted" Unicode
  92. ;; IDNA characters. If not, transform to punycode to indicate that
  93. ;; there may be funny business going on.
  94. (let ((parsed (url-generic-parse-url url)))
  95. (when (url-host parsed)
  96. (unless (puny-highly-restrictive-domain-p (url-host parsed))
  97. (setf (url-host parsed) (puny-encode-domain (url-host parsed)))
  98. (setq url (url-recreate-url parsed)))))
  99. (plist-put eww-data :url url)
  100. (plist-put eww-data :title "")
  101. (eww-update-header-line-format)
  102. (let ((inhibit-read-only t))
  103. (insert (format "Loading %s..." url))
  104. (goto-char (point-min)))
  105. (url-retrieve url 'eww-render
  106. (list url nil (current-buffer))))
  107. (advice-add 'eww :override 'ambrevar/eww)
  108. (defun ambrevar/eww-open-in-new-buffer (url)
  109. "Fetch URL and render the page.
  110. If the input doesn't look like an URL or a domain name, the
  111. word(s) will be searched for via `eww-search-prefix'."
  112. (interactive
  113. (let* ((uris (eww-suggested-uris))
  114. (prompt (concat "Open URL or keywords in new buffer: ")))
  115. (list (read-string prompt (car uris) nil uris))))
  116. (setq url (eww--dwim-expand-url url))
  117. (pop-to-buffer-same-window
  118. (if (eq major-mode 'eww-mode)
  119. (clone-buffer)
  120. (generate-new-buffer "*eww*")))
  121. (unless (equal url (eww-current-url))
  122. (eww-mode)
  123. (eww (if (consp url) (car url) url))))
  124. (advice-add 'eww-open-in-new-buffer :override 'ambrevar/eww-open-in-new-buffer)
  125. (defun ambrevar/eww-name-buffer-with-title ()
  126. "Include the page title in current EWW buffer name."
  127. (interactive)
  128. (when (derived-mode-p 'eww-mode)
  129. (rename-buffer (format "*eww: %s*" (plist-get eww-data :title)) t)))
  130. (defun ambrevar/eww-update-header-line-format ()
  131. (setq header-line-format
  132. (and eww-header-line-format
  133. (let ((title (plist-get eww-data :title))
  134. (peer (plist-get eww-data :peer)))
  135. (when (zerop (length title))
  136. (setq title "[untitled]"))
  137. ;; This connection has is https.
  138. (when peer
  139. (setq title
  140. (propertize title 'face
  141. (if (plist-get peer :warnings)
  142. 'eww-invalid-certificate
  143. 'eww-valid-certificate))))
  144. (replace-regexp-in-string
  145. "%" "%%"
  146. (format-spec
  147. eww-header-line-format
  148. `((?u . ,(or (plist-get eww-data :url) ""))
  149. (?t . ,title)))))))
  150. ;; PATCH
  151. (ambrevar/eww-name-buffer-with-title))
  152. (advice-add 'eww-update-header-line-format :override 'ambrevar/eww-update-header-line-format)
  153. ;; TODO: Fix quickmarks bindings. Or maybe just display the quickmarks buffer
  154. ;; and start `eww', which follows the quickmarks when first word is the mark.
  155. ;; TODO: Merge qutebrowser quickmarks.
  156. ;; TODO: Add bookmark editing functions such as edit title, tags, quickmark,
  157. ;; search-engine. Use eww-buffers and Helm.
  158. (defun ambrevar/eww-add-bookmark ()
  159. "Bookmark the current page."
  160. (interactive)
  161. (eww-read-bookmarks)
  162. (let (tag-list)
  163. (dolist (bookmark eww-bookmarks)
  164. (when (equal
  165. ;; PATCH: Ignore protocol when sorting.
  166. ;; TODO: Include "sort-bookmarks": Warn for unique tags, warn for same URL up to paragraph. Make this customizable.
  167. (replace-regexp-in-string "^https?" "" (plist-get eww-data :url))
  168. (replace-regexp-in-string "^https?" "" (plist-get bookmark :url)))
  169. (user-error "Already bookmarked"))
  170. (setq tag-list (append tag-list (plist-get bookmark :tags))))
  171. (delete-duplicates tag-list)
  172. (let ((tags (completing-read-multiple "Tags for bookmark (comma separated): " tag-list))
  173. (title (replace-regexp-in-string "[\n\t\r]" " "
  174. (plist-get eww-data :title))))
  175. (setq title (replace-regexp-in-string "\\` +\\| +\\'" "" title))
  176. (push `(:url ,(plist-get eww-data :url)
  177. :title ,title
  178. :time ,(current-time-string)
  179. ,@(if tags (list :tags tags)))
  180. eww-bookmarks)
  181. (eww-write-bookmarks)
  182. (message "Bookmarked %s (%s)" (plist-get eww-data :url)
  183. (plist-get eww-data :title)))))
  184. (advice-add 'eww-add-bookmark :override 'ambrevar/eww-add-bookmark)
  185. (defun ambrevar/eww-write-bookmarks ()
  186. (require 'rx)
  187. ;; PATCH
  188. (setq eww-bookmarks
  189. (sort eww-bookmarks
  190. (lambda (a b) (string<
  191. ;; Ignore protocol when sorting.
  192. (replace-regexp-in-string "^[a-zA-Z]+://" "" (plist-get a :url))
  193. (replace-regexp-in-string "^[a-zA-Z]+://" "" (plist-get b :url))))))
  194. (with-temp-file (expand-file-name "eww-bookmarks" eww-bookmarks-directory)
  195. ;; PATCH: Remove newline for tags.
  196. (insert
  197. (replace-regexp-in-string
  198. (rx ":tags" ?\n (1+ space)) ":tags "
  199. (with-temp-buffer
  200. (insert ";; Auto-generated file; don't edit -*- mode: emacs-lisp; -*-\n")
  201. (pp eww-bookmarks (current-buffer))
  202. (buffer-string))))))
  203. (advice-add 'eww-write-bookmarks :override 'ambrevar/eww-write-bookmarks)
  204. (defvar ambrevar/eww-quickmark-prefix ""
  205. "Prefix to load a quickmark.")
  206. (defun ambrevar/eww-bookmark-prepare ()
  207. ;; PATCH: Don't load if already loaded. This allows for overrides (e.g. quickmarks).
  208. (unless eww-bookmarks
  209. (eww-read-bookmarks))
  210. (unless eww-bookmarks
  211. (user-error "No bookmarks are defined"))
  212. (set-buffer (get-buffer-create "*eww bookmarks*"))
  213. (eww-bookmark-mode)
  214. (let* ((width (/ (window-width) 2))
  215. (format (format "%%-%ds %%s" width))
  216. (inhibit-read-only t)
  217. start title)
  218. (erase-buffer)
  219. (setq header-line-format (concat " " (format format "Title" "URL")))
  220. (dolist (bookmark eww-bookmarks)
  221. (setq start (point)
  222. title (plist-get bookmark :title))
  223. ;; PATCH: Show quickmark.
  224. (when (plist-get bookmark :mark)
  225. (setq title (format "[%s] %s" (plist-get bookmark :mark) title)))
  226. (when (> (length title) width)
  227. (setq title (truncate-string-to-width title width)))
  228. (insert (format format title
  229. (concat (plist-get bookmark :url)
  230. (when (plist-get bookmark :tags)
  231. (concat " ("
  232. (mapconcat 'identity (plist-get bookmark :tags) ",")
  233. ")"))))
  234. "\n")
  235. (put-text-property start (1+ start) 'eww-bookmark bookmark)
  236. ;; PATCH: Bind keys
  237. (when (plist-get bookmark :mark)
  238. (define-key eww-bookmark-mode-map
  239. (kbd (concat ambrevar/eww-quickmark-prefix (plist-get bookmark :mark)))
  240. (lambda (&optional new-window)
  241. (interactive "P")
  242. (if new-window
  243. (ambrevar/eww-open-in-new-buffer (plist-get bookmark :url))
  244. (eww (plist-get bookmark :url)))))))
  245. (goto-char (point-min))))
  246. (advice-add 'eww-bookmark-prepare :override 'ambrevar/eww-bookmark-prepare)
  247. (defun ambrevar/eww-quickmarks (&optional new-window)
  248. "Display quickmarks."
  249. (interactive "P")
  250. (let ((eww-bookmarks (seq-filter (lambda (b) (plist-get b :mark)) eww-bookmarks)))
  251. (eww-list-bookmarks)))
  252. (defun ambrevar/eww-bookmarks-list-by-tags (&optional arg)
  253. "Return bookmarks matching one of the specified tags.
  254. With prefix argument or ARG, bookmarks much match all tags."
  255. (let ((tag-list (delq nil (mapcar (lambda (b) (plist-get b :tags)) eww-bookmarks))))
  256. (seq-uniq (mapcar 'append tag-list))
  257. (let ((tags (completing-read-multiple "Tags for bookmark (comma separated): " tag-list)))
  258. (seq-filter (lambda (b)
  259. (if arg
  260. (null (seq-difference tags (plist-get b :tags)))
  261. (seq-intersection tags (plist-get b :tags))))
  262. eww-bookmarks))))
  263. (defun ambrevar/eww-bookmarks-by-tags (&optional arg)
  264. "Display bookmarks matching one of the specified tags.
  265. With prefix argument or ARG, bookmarks much match all tags."
  266. (interactive "P")
  267. (let ((eww-bookmarks (ambrevar/eww-bookmarks-list-by-tags arg)))
  268. (eww-list-bookmarks)))
  269. (defun ambrevar/eww--dwim-expand-url (url)
  270. (setq url (string-trim url))
  271. (cond ((string-match-p "\\`file:/" url))
  272. ;; Don't mangle file: URLs at all.
  273. ((string-match-p "\\`ftp://" url)
  274. (user-error "FTP is not supported"))
  275. (t
  276. ;; Anything that starts with something that vaguely looks
  277. ;; like a protocol designator is interpreted as a full URL.
  278. (if (or (string-match "\\`[A-Za-z]+:" url)
  279. ;; Also try to match "naked" URLs like
  280. ;; en.wikipedia.org/wiki/Free software
  281. (string-match "\\`[A-Za-z_]+\\.[A-Za-z._]+/" url)
  282. (and (= (length (split-string url)) 1)
  283. (or (and (not (string-match-p "\\`[\"'].*[\"']\\'" url))
  284. (> (length (split-string url "[.:]")) 1))
  285. (string-match eww-local-regex url))))
  286. (progn
  287. (unless (string-match-p "\\`[a-zA-Z][-a-zA-Z0-9+.]*://" url)
  288. (setq url (concat "http://" url)))
  289. ;; Some sites do not redirect final /
  290. (when (string= (url-filename (url-generic-parse-url url)) "")
  291. (setq url (concat url "/"))))
  292. ;; PATCH: Add support for search engines and quickmarks.
  293. (string-match (rx (group (1+ (not space)))
  294. (0+ space)
  295. (? (group (0+ any))))
  296. url)
  297. (let* ((first-word (match-string 1 url))
  298. (rest-url (match-string 2 url))
  299. (marks (make-hash-table :test 'equal))
  300. (engines (make-hash-table :test 'equal)))
  301. (dolist (b eww-bookmarks)
  302. (let ((mark (plist-get b :mark))
  303. engine)
  304. (when mark
  305. (puthash mark (plist-get b :url) marks)
  306. (setq engine (plist-get b :search))
  307. (when engine
  308. (puthash mark (concat (let ((case-fold-search t))
  309. (unless (string-match "^https?://" engine)
  310. (plist-get b :url)))
  311. engine)
  312. engines)))))
  313. (cond
  314. ((and (gethash first-word engines)
  315. (not (string= rest-url "")) )
  316. (setq url (format (gethash first-word engines) ; Engines must have exactly one "%s".
  317. (mapconcat
  318. #'url-hexify-string (split-string rest-url) "+"))))
  319. ((and (gethash first-word marks)
  320. (string= rest-url ""))
  321. (setq url (gethash first-word marks)))
  322. (t (setq url (concat eww-search-prefix
  323. (mapconcat
  324. #'url-hexify-string (split-string url) "+")))))))))
  325. url)
  326. (advice-add 'eww--dwim-expand-url :override 'ambrevar/eww--dwim-expand-url)
  327. (provide 'init-eww)