init.lisp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. (in-package :nyxt-user) ; While implicit, this allows SLY to know which package we are in.
  2. (defun eval-in-emacs (&rest s-exps)
  3. "Evaluate S-exps with `emacsclient'."
  4. (let ((s-exps-string (cl-ppcre:regex-replace-all
  5. ;; Discard the package prefix.
  6. "next-user::?"
  7. (write-to-string
  8. `(progn ,@s-exps) :case :downcase)
  9. "")))
  10. (log:debug "Sending to Emacs: ~s" s-exps-string)
  11. (ignore-errors (uiop:run-program
  12. (list "emacsclient" "--eval" s-exps-string)))))
  13. (defvar *my-keymap* (make-keymap "my-map")
  14. "Keymap for `my-mode'.")
  15. (define-command org-capture (&optional (buffer (current-buffer)))
  16. "Org-capture current page."
  17. (eval-in-emacs
  18. `(org-link-set-parameters
  19. "next"
  20. :store (lambda ()
  21. (org-store-link-props
  22. :type "next"
  23. :link ,(url buffer)
  24. :description ,(title buffer))))
  25. `(org-capture)))
  26. (define-key *my-keymap* "C-M-o" 'org-capture)
  27. (define-command youtube-dl-current-page (&optional (buffer (current-buffer)))
  28. "Download a video in the currently open buffer."
  29. (eval-in-emacs
  30. (if (search "youtu" (url buffer))
  31. `(progn (youtube-dl ,(url buffer)) (youtube-dl-list))
  32. `(ambrevar/youtube-dl-url ,(url buffer)))))
  33. (define-key *my-keymap* "C-M-c d" 'youtube-dl-current-page)
  34. (define-command play-video-in-current-page (&optional (buffer (current-buffer)))
  35. "Play video in the currently open buffer."
  36. (uiop:run-program (list "mpv" (url buffer))))
  37. (define-key *my-keymap* "C-M-c v" 'play-video-in-current-page)
  38. (define-mode my-mode ()
  39. "Dummy mode for the custom key bindings in `*my-keymap*'."
  40. ((keymap-scheme (keymap:make-scheme
  41. scheme:emacs *my-keymap*
  42. scheme:vi-normal *my-keymap*))))
  43. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  44. (defvar +youtube-dl-command+ "youtube-dl"
  45. "Path to the 'youtube-dl' program.")
  46. ;; (defun auto-yt-dl-handler (url)
  47. ;; "Download a Youtube URL asynchronously to /tmp/videos/.
  48. ;; Videos are downloaded with `+youtube-dl-command+'."
  49. ;; (let ((uri (quri:uri url)))
  50. ;; (when (and uri
  51. ;; (member-string (quri:uri-domain uri) '("youtube.com" "youtu.be"))
  52. ;; (string= (quri:uri-path uri) "/watch"))
  53. ;; (log:info "Youtube: downloading ~a" url)
  54. ;; (uiop:launch-program (list +youtube-dl-command+ url "-o" "/tmp/videos/%(title)s.%(ext)s"))))
  55. ;; url)
  56. (defparameter old-reddit-handler
  57. (url-dispatching-handler
  58. 'old-reddit-dispatcher
  59. (match-host "www.reddit.com")
  60. (lambda (url)
  61. (quri:copy-uri url :host "old.reddit.com"))))
  62. (defparameter magnet-handler
  63. (url-dispatching-handler
  64. 'transmission-magnet-links
  65. (match-scheme "magnet")
  66. (lambda (url)
  67. (uiop:launch-program
  68. (list "transmission-remote" "--add"
  69. (object-string url)))
  70. (echo "Magnet link opened in Transmission.")
  71. nil)))
  72. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  73. (defvar *my-blocked-hosts*
  74. (nyxt/blocker-mode:make-hostlist
  75. :hosts '("platform.twitter.com"
  76. "syndication.twitter.com"
  77. "m.media-amazon.com")))
  78. (define-configuration nyxt/blocker-mode:blocker-mode
  79. ((nyxt/blocker-mode:hostlists (append (list *my-blocked-hosts*) %slot-default))))
  80. (defun format-c->lisp (s)
  81. "Incomplete substitution of C format string to Lisp format string.
  82. Recognized formats:
  83. - %%
  84. - %s"
  85. (str:join "%" (mapcar (lambda (s) (str:replace-all "%s" "~a" s))
  86. (str:split "%%" s))))
  87. (defun read-emacs-engines (stream)
  88. "Return a list of (NAME URL SHORTCUT)."
  89. (loop for object = (read stream nil :eof)
  90. until (eq object :eof)
  91. when (eq (car object) 'defengine)
  92. collect (make-instance 'search-engine
  93. :shortcut (getf (nthcdr 3 object) :keybinding)
  94. :search-url (format-c->lisp (nth 2 object)))))
  95. (defun personal-file (path)
  96. (str:concat (uiop:getenv "PERSONAL") "/" path))
  97. (defvar my-search-engines
  98. (loop for file in `("~/.emacs.d/lisp/init-engine.el"
  99. ,(personal-file "/bookmarks/engines.el"))
  100. append (nyxt::with-maybe-gpg-file (s file)
  101. (read-emacs-engines s))))
  102. (define-configuration (buffer web-buffer)
  103. ((default-modes (append '(my-mode vi-normal-mode) %slot-default))))
  104. (define-configuration buffer ; Multiple configurations work!
  105. ((search-engines (append my-search-engines %slot-default))
  106. (bookmarks-path (make-instance 'bookmarks-data-path
  107. :basename (personal-file "bookmarks/bookmarks.lisp.gpg")))
  108. (auto-mode-rules-path
  109. (make-instance 'auto-mode-rules-data-path :basename (personal-file "bookmarks/auto-mode-rules.lisp.gpg")))))
  110. (define-configuration web-buffer
  111. ((default-modes (append
  112. '(auto-mode
  113. blocker-mode
  114. force-https-mode
  115. noimage-mode
  116. noscript-mode
  117. proxy-mode
  118. reduce-tracking-mode)
  119. %slot-default))))
  120. (defvar *my-request-resource-handlers*
  121. (list
  122. magnet-handler
  123. old-reddit-handler))
  124. ;; (load-after-system :invidious-handler
  125. ;; (nyxt-init-file "invidious.lisp"))
  126. (defmethod deserialize-eww-bookmarks (stream)
  127. "This version of deserialize-bookmarks is compatible with Ambrevar's EWW
  128. format."
  129. (handler-case
  130. (let ((*standard-input* stream))
  131. (let ((entries (read stream)))
  132. (mapcar (lambda (entry)
  133. (when (getf entry :date)
  134. (setf (getf entry :date)
  135. (local-time:parse-timestring (getf entry :date))))
  136. (when (getf entry :time)
  137. (let ((timestamp (asctime->timestamp (getf entry :time))))
  138. (when timestamp
  139. (setf (getf entry :date) timestamp)))
  140. (remf entry :time))
  141. (when (getf entry :search)
  142. (setf (getf entry :search-url) (getf entry :search))
  143. (remf entry :search))
  144. (when (getf entry :mark)
  145. (setf (getf entry :shortcut) (getf entry :mark))
  146. (remf entry :mark))
  147. (apply #'make-instance 'nyxt:bookmark-entry
  148. entry))
  149. entries)))
  150. (error (c)
  151. (log:error "During bookmark deserialization: ~a" c)
  152. nil)))
  153. (defun restore-eww-bookmarks ()
  154. "Restore the bookmarks from EWW."
  155. (handler-case
  156. (let ((data (with-data-file (file (make-instance 'data-path
  157. :basename (personal-file "bookmarks/eww-bookmarks.gpg"))
  158. :direction :input
  159. :if-does-not-exist nil)
  160. (when file
  161. (deserialize-eww-bookmarks file)))))
  162. (when data
  163. (echo "Loading ~a bookmarks from ~s."
  164. (length data)
  165. (expand-path (bookmarks-path *browser*)))
  166. (setf (slot-value *browser* 'nyxt::bookmarks-data) data)))
  167. (error (c)
  168. (echo-warning "Failed to load bookmarks from ~s: ~a" (expand-path (bookmarks-path *browser*)) c))))
  169. (define-configuration browser
  170. ((session-restore-prompt :always-restore)))
  171. (setf nyxt/vcs:*vcs-projects-roots* '("~/projects"
  172. "~/common-lisp"
  173. "~/.local/share/emacs/site-lisp"))
  174. (defun my-status-style (&key (mode-background-color "rgb(120,120,120)"))
  175. (cl-css:css
  176. `((body
  177. :background "rgb(160, 160, 160)"
  178. :font-size "14px"
  179. :color "rgb(32, 32, 32)"
  180. :padding 0
  181. :margin 0
  182. :line-height "20px")
  183. (".arrow"
  184. :width "10px"
  185. :height "20px")
  186. (".arrow-right"
  187. :clip-path "polygon(0 100%, 100% 50%, 0 0)")
  188. (".arrow-left"
  189. :clip-path "polygon(0 50%, 100% 100%, 100% 0)")
  190. ("#container"
  191. :display "grid"
  192. ;; Columns: controls, arrow, url, arrow, modes
  193. :grid-template-columns "115px 10px auto 10px auto"
  194. :overflow-y "hidden")
  195. ("#controls"
  196. :background-color "rgb(80,80,80)"
  197. :padding-left "5px"
  198. :overflow "hidden"
  199. :white-space "nowrap")
  200. ("#url"
  201. :background-color "rgb(160,160,160)"
  202. :min-width "100px"
  203. :text-overflow "ellipsis"
  204. :overflow-x "hidden"
  205. :white-space "nowrap"
  206. :padding-left "15px"
  207. :padding-right "10px"
  208. :margin-left "-10px")
  209. ("#modes"
  210. :background-color ,mode-background-color
  211. :color "rgb(230, 230, 230)"
  212. :text-align "right"
  213. :padding-right "5px"
  214. ;; Uncomment the following to trim the mode list.
  215. ;; :text-overflow "ellipsis"
  216. ;; :overflow-x "hidden"
  217. :white-space "nowrap")
  218. (.button
  219. :color "rgb(230, 230, 230)"
  220. :text-decoration "none"
  221. :padding-left "2px"
  222. :padding-right "2px"
  223. :margin-left "2px"
  224. :margin-right "2px")
  225. (|.button:hover|
  226. :color "black"))))
  227. (defun my-format-status (window)
  228. (let ((buffer (current-buffer window)))
  229. (if (or (internal-buffer-p buffer)
  230. (find-submode buffer 'proxy-mode))
  231. (setf (style (status-buffer window))
  232. (my-status-style))
  233. (setf (style (status-buffer window))
  234. (my-status-style :mode-background-color "rgb(255,0,0)")))
  235. (markup:markup
  236. (:div :id "container"
  237. (:div :id "controls"
  238. (markup:raw (format-status-buttons)))
  239. (:div :class "arrow arrow-right"
  240. :style "background-color:rgb(80,80,80)" "")
  241. (:div :id "url"
  242. (markup:raw
  243. (format-status-load-status buffer)
  244. (format-status-url buffer)))
  245. (:div :class "arrow arrow-left"
  246. :style "background-color:rgb(220,120,120);background-color:rgb(120,120,120)" "")
  247. (:div :id "modes"
  248. (format-status-modes buffer))))))
  249. (define-configuration window
  250. ((status-formatter #'my-format-status)))
  251. (load-after-system :slynk (nyxt-init-file "slynk.lisp"))
  252. (defvar +dev-data-profile+ (make-instance 'data-profile :name "dev")
  253. "Development profile.")
  254. (defmethod nyxt:expand-data-path ((profile (eql +dev-data-profile+)) (path data-path))
  255. "Persist data to /tmp/nyxt/."
  256. (expand-default-path (make-instance (class-name (class-of path))
  257. :basename (basename path)
  258. :dirname "/tmp/nyxt/")))
  259. ;; After init:
  260. (load (nyxt-init-file "config.lisp"))