Moving to Doom Emacs!

This commit is contained in:
Derek Taylor
2019-12-16 20:21:19 -06:00
parent d9f2f456f1
commit d4b4c33550
683 changed files with 51877 additions and 100 deletions

View File

@@ -0,0 +1,141 @@
#+TITLE: app/irc
#+DATE: June 11, 2017
#+SINCE: v2.0.3
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#dependencies][Dependencies]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#an-irc-client-in-emacs][An IRC Client in Emacs]]
- [[#configuration][Configuration]]
- [[#pass-the-unix-password-manager][Pass: the unix password manager]]
- [[#emacs-auth-source-api][Emacs' auth-source API]]
- [[#troubleshooting][Troubleshooting]]
* Description
This module turns adds an IRC client to Emacs with OS notifications.
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/jorgenschaefer/circe][circe]]
+ [[https://github.com/eqyiel/circe-notifications][circe-notifications]]
* Dependencies
This module requires =gnutls-cli= or =openssl= for secure connections.
* Prerequisites
This module has no direct prerequisites.
* Features
** An IRC Client in Emacs
To connect to IRC you can invoke the ~=irc~ function using =M-x= or your own
custom keybinding.
| command | description |
|---------+-------------------------------------------|
| ~=irc~ | Connect to IRC and all configured servers |
When in a circe buffer these keybindings will be available.
| command | key | description |
|-----------------------------+-----------+----------------------------------------------|
| ~+irc/tracking-next-buffer~ | =SPC m a= | Switch to the next active buffer |
| ~circe-command-JOIN~ | =SPC m j= | Join a channel |
| ~+irc/send-message~ | =SPC m m= | Send a private message |
| ~circe-command-NAMES~ | =SPC m n= | List the names of the current channel |
| ~circe-command-PART~ | =SPC m p= | Part the current channel |
| ~+irc/quit~ | =SPC m Q= | Kill the current circe session and workgroup |
| ~circe-reconnect~ | =SPC m R= | Reconnect the current server |
* Configuration
Use ~set-irc-server!~ to configure IRC servers. Its second argument (a plist)
takes the same arguments as ~circe-network-options~.
#+BEGIN_SRC emacs-lisp :tangle no
(set-irc-server! "chat.freenode.net"
`(:tls t
:nick "doom"
:sasl-username "myusername"
:sasl-password "mypassword"
:channels ("#emacs")))
#+END_SRC
*It is a obviously a bad idea to store auth-details in plaintext,* so here are
some ways to avoid that:
** Pass: the unix password manager
[[https://www.passwordstore.org/][Pass]] is my tool of choice. I use it to manage my passwords. If you activate the
[[../../../modules/tools/pass/README.org][:tools pass]] module you get an elisp API through which to access your
password store.
~set-irc-server!~ accepts a plist can use functions instead of strings.
~+pass-get-user~ and ~+pass-get-secret~ can help here:
#+BEGIN_SRC emacs-lisp :tangle no
(set-irc-server! "chat.freenode.net"
`(:tls t
:nick "doom"
:sasl-username ,(+pass-get-user "irc/freenode.net")
:sasl-password ,(+pass-get-secret "irc/freenode.net")
:channels ("#emacs")))
#+END_SRC
But wait, there's more! This stores your password in a public variable which
could be accessed or appear in backtraces. Not good! So we go a step further:
#+BEGIN_SRC emacs-lisp :tangle no
(set-irc-server! "chat.freenode.net"
`(:tls t
:port 6697
:nick "doom"
:sasl-username ,(+pass-get-user "irc/freenode.net")
:sasl-password (lambda (&rest _) (+pass-get-secret "irc/freenode.net"))
:channels ("#emacs")))
#+END_SRC
And you're good to go!
Note that =+pass-get-user= tries to find your username by looking for the fields
listed in =+pass-user-fields= (by default =login=, =user==, =username== and
=email=)=). An example configuration looks like
#+BEGIN_SRC txt :tangle no
mysecretpassword
username: myusername
#+END_SRC
** Emacs' auth-source API
~auth-source~ is built into Emacs. As suggested [[https://github.com/jorgenschaefer/circe/wiki/Configuration#safer-password-management][in the circe wiki]], you can store
(and retrieve) encrypted passwords with it.
#+BEGIN_SRC emacs-lisp :tangle no
(setq auth-sources '("~/.authinfo.gpg"))
(defun my-fetch-password (&rest params)
(require 'auth-source)
(let ((match (car (apply #'auth-source-search params))))
(if match
(let ((secret (plist-get match :secret)))
(if (functionp secret)
(funcall secret)
secret))
(error "Password not found for %S" params))))
(defun my-nickserv-password (server)
(my-fetch-password :user "forcer" :host "irc.freenode.net"))
(set-irc-server! "chat.freenode.net"
'(:tls t
:port 6697
:nick "doom"
:sasl-password my-nickserver-password
:channels ("#emacs")))
#+END_SRC
* TODO Troubleshooting

View File

@@ -0,0 +1,123 @@
;;; app/irc/autoload/email.el -*- lexical-binding: t; -*-
(defvar +irc--workspace-name "*IRC*")
(defun +irc-setup-wconf (&optional inhibit-workspace)
(unless inhibit-workspace
(+workspace-switch +irc--workspace-name t))
(let ((buffers (doom-buffers-in-mode 'circe-mode nil t)))
(if buffers
(ignore (switch-to-buffer (car buffers)))
(require 'circe)
(delete-other-windows)
(switch-to-buffer (doom-fallback-buffer))
t)))
;;;###autoload
(defun =irc (&optional inhibit-workspace)
"Connect to IRC and auto-connect to all registered networks.
If INHIBIT-WORKSPACE (the universal argument) is non-nil, don't spawn a new
workspace for it."
(interactive "P")
(cond ((and (featurep! :ui workspaces)
(+workspace-exists-p +irc--workspace-name))
(+workspace-switch +irc--workspace-name))
((not (+irc-setup-wconf inhibit-workspace))
(user-error "Couldn't start up a workspace for IRC")))
(if (doom-buffers-in-mode 'circe-mode (buffer-list) t)
(message "Circe buffers are already open")
(if circe-network-options
(cl-loop for network in circe-network-options
collect (circe (car network)))
(call-interactively #'circe))))
;;;###autoload
(defun +irc/connect (&optional inhibit-workspace)
"Connect to a specific registered server.
If INHIBIT-WORKSPACE (the universal argument) is non-nil, don't spawn a new
workspace for it."
(interactive "P")
(and (+irc-setup-wconf inhibit-workspace)
(call-interactively #'circe)))
;;;###autoload
(defun +irc/send-message (who what)
"Send WHO a message containing WHAT."
(interactive "sWho: \nsWhat: ")
(circe-command-MSG who what))
;;;###autoload
(defun +irc/quit ()
"Kill current circe session and workgroup."
(interactive)
(if (y-or-n-p "Really kill IRC session?")
(let (circe-channel-killed-confirmation
circe-server-killed-confirmation)
(when +irc--defer-timer
(cancel-timer +irc--defer-timer))
(disable-circe-notifications)
(mapc #'kill-buffer (doom-buffers-in-mode 'circe-mode (buffer-list) t))
(when (equal (+workspace-current-name) +irc--workspace-name)
(+workspace/delete +irc--workspace-name)))
(message "Aborted")))
;;;###autoload
(defun +irc/ivy-jump-to-channel (&optional this-server)
"Jump to an open channel or server buffer with ivy. If THIS-SERVER (universal
argument) is non-nil only show channels in current server."
(interactive "P")
(if (not (circe-server-buffers))
(message "No circe buffers available")
(when (and this-server (not circe-server-buffer))
(setq this-server nil))
(ivy-read (format "Jump to%s: " (if this-server (format " (%s)" (buffer-name circe-server-buffer)) ""))
(cl-loop with servers = (if this-server (list circe-server-buffer) (circe-server-buffers))
with current-buffer = (current-buffer)
for server in servers
collect (buffer-name server)
nconc
(with-current-buffer server
(cl-loop for buf in (circe-server-chat-buffers)
unless (eq buf current-buffer)
collect (format " %s" (buffer-name buf)))))
:action #'+irc--ivy-switch-to-buffer-action
:preselect (buffer-name (current-buffer))
:keymap ivy-switch-buffer-map
:caller '+irc/ivy-jump-to-channel)))
(defun +irc--ivy-switch-to-buffer-action (buffer)
(when (stringp buffer)
(ivy--switch-buffer-action (string-trim-left buffer))))
;;;###autoload
(defun +irc/tracking-next-buffer ()
"Dissables switching to an unread buffer unless in the irc workspace."
(interactive)
(when (derived-mode-p 'circe-mode)
(tracking-next-buffer)))
;;
;;; Hooks/fns
;;;###autoload
(defun +circe-buffer-p (buf)
"Return non-nil if BUF is a `circe-mode' buffer."
(with-current-buffer buf
(and (derived-mode-p 'circe-mode)
(eq (safe-persp-name (get-current-persp))
+irc--workspace-name))))
;;;###autoload
(defun +irc--add-circe-buffer-to-persp-h ()
(when (bound-and-true-p persp-mode)
(let ((persp (get-current-persp))
(buf (current-buffer)))
;; Add a new circe buffer to irc workspace when we're in another workspace
(unless (eq (safe-persp-name persp) +irc--workspace-name)
;; Add new circe buffers to the persp containing circe buffers
(persp-add-buffer buf (persp-get-by-name +irc--workspace-name))
;; Remove new buffer from accidental workspace
(persp-remove-buffer buf persp)))))

View File

@@ -0,0 +1,9 @@
;;; app/irc/autoload/settings.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-irc-server! (server letvars)
"Registers an irc SERVER for circe.
See `circe-network-options' for details."
(after! circe
(push (cons server letvars) circe-network-options)))

View File

@@ -0,0 +1,235 @@
;;; app/irc/config.el -*- lexical-binding: t; -*-
(defvar +irc-left-padding 13
"By how much spaces the left hand side of the line should be padded.
Below a value of 12 this may result in uneven alignment between the various
types of messages.")
(defvar +irc-truncate-nick-char ?…
"Character to displayed when nick > `+irc-left-padding' in length.")
(defvar +irc-scroll-to-bottom-on-commands
'(self-insert-command yank hilit-yank)
"If these commands are called pre prompt the buffer will scroll to `point-max'.")
(defvar +irc-disconnect-hook nil
"Runs each hook when circe noticies the connection has been disconnected.
Useful for scenarios where an instant reconnect will not be successful.")
(defvar +irc-bot-list '("fsbot" "rudybot")
"Nicks listed have `circe-fool-face' applied and will not be tracked.")
(defvar +irc-time-stamp-format "%H:%M"
"The format of time stamps.
See `format-time-string' for a full description of available
formatting directives. ")
(defvar +irc-notifications-watch-strings nil
"A list of strings which can trigger a notification. You don't need to put
your nick here.
See `circe-notifications-watch-strings'.")
(defvar +irc-defer-notifications nil
"How long to defer enabling notifications, in seconds (e.g. 5min = 300).
Useful for ZNC users who want to avoid the deluge of notifications during buffer
playback.")
(defvar +irc--defer-timer nil)
(defsubst +irc--pad (left right)
(format (format "%%%ds | %%s" +irc-left-padding)
(concat "*** " left) right))
;;
;; Packages
(use-package! circe
:commands circe circe-server-buffers
:init (setq circe-network-defaults nil)
:config
(setq circe-default-quit-message nil
circe-default-part-message nil
circe-use-cycle-completion t
circe-reduce-lurker-spam t
circe-format-say (format "{nick:+%ss} │ {body}" +irc-left-padding)
circe-format-self-say circe-format-say
circe-format-action (format "{nick:+%ss} * {body}" +irc-left-padding)
circe-format-self-action circe-format-action
circe-format-server-notice
(let ((left "-Server-")) (concat (make-string (- +irc-left-padding (length left)) ? )
(concat left " _ {body}")))
circe-format-notice (format "{nick:%ss} _ {body}" +irc-left-padding)
circe-format-server-topic
(+irc--pad "Topic" "{userhost}: {topic-diff}")
circe-format-server-join-in-channel
(+irc--pad "Join" "{nick} ({userinfo}) joined {channel}")
circe-format-server-join
(+irc--pad "Join" "{nick} ({userinfo})")
circe-format-server-part
(+irc--pad "Part" "{nick} ({userhost}) left {channel}: {reason}")
circe-format-server-quit
(+irc--pad "Quit" "{nick} ({userhost}) left IRC: {reason}]")
circe-format-server-quit-channel
(+irc--pad "Quit" "{nick} ({userhost}) left {channel}: {reason}]")
circe-format-server-rejoin
(+irc--pad "Re-join" "{nick} ({userhost}), left {departuredelta} ago")
circe-format-server-netmerge
(+irc--pad "Netmerge" "{split}, split {ago} ago (Use /WL to see who's still missing)")
circe-format-server-nick-change
(+irc--pad "Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
circe-format-server-nick-change-self
(+irc--pad "Nick" "You are now known as {new-nick} ({old-nick})")
circe-format-server-nick-change-self
(+irc--pad "Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
circe-format-server-mode-change
(+irc--pad "Mode" "{change} on {target} by {setter} ({userhost})")
circe-format-server-lurker-activity
(+irc--pad "Lurk" "{nick} joined {joindelta} ago"))
(add-hook 'doom-real-buffer-functions #'+circe-buffer-p)
(add-hook 'circe-channel-mode-hook #'turn-on-visual-line-mode)
(add-hook 'circe-mode-hook #'+irc--add-circe-buffer-to-persp-h)
(defadvice! +irc--circe-run-disconnect-hook-a (&rest _)
"Runs `+irc-disconnect-hook' after circe disconnects."
:after #'circe--irc-conn-disconnected
(run-hooks '+irc-disconnect-hook))
(add-hook! 'lui-pre-output-hook
(defun +irc-circe-truncate-nicks-h ()
"Truncate long nicknames in chat output non-destructively."
(when-let (beg (text-property-any (point-min) (point-max) 'lui-format-argument 'nick))
(goto-char beg)
(let ((end (next-single-property-change beg 'lui-format-argument))
(nick (plist-get (plist-get (text-properties-at beg) 'lui-keywords)
:nick)))
(when (> (length nick) +irc-left-padding)
(compose-region (+ beg +irc-left-padding -1) end
+irc-truncate-nick-char))))))
(add-hook! 'circe-message-option-functions
(defun +irc-circe-message-option-bot-h (nick &rest ignored)
"Fontify known bots and mark them to not be tracked."
(when (member nick +irc-bot-list)
'((text-properties . (face circe-fool-face lui-do-not-track t))))))
;; Let `+irc/quit' and `circe' handle buffer cleanup
(define-key circe-mode-map [remap kill-buffer] #'bury-buffer)
;; Fail gracefully if not in a circe buffer
(global-set-key [remap tracking-next-buffer] #'+irc/tracking-next-buffer)
(map! :localleader
(:map circe-mode-map
"a" #'tracking-next-buffer
"j" #'circe-command-JOIN
"m" #'+irc/send-message
"p" #'circe-command-PART
"Q" #'+irc/quit
"R" #'circe-reconnect
(:when (featurep! :completion ivy)
"c" #'+irc/ivy-jump-to-channel))
(:map circe-channel-mode-map
"n" #'circe-command-NAMES)))
(use-package! circe-color-nicks
:hook (circe-channel-mode . enable-circe-color-nicks)
:config
(setq circe-color-nicks-min-constrast-ratio 4.5
circe-color-nicks-everywhere t))
(use-package! circe-new-day-notifier
:after circe
:config
(enable-circe-new-day-notifier)
(setq circe-new-day-notifier-format-message
(+irc--pad "Day" "Date changed [{day}]")))
(use-package! circe-notifications
:commands enable-circe-notifications
:init
(if +irc-defer-notifications
(add-hook! 'circe-server-connected-hook
(setq +irc--defer-timer
(run-at-time +irc-defer-notifications nil
#'enable-circe-notifications)))
(add-hook 'circe-server-connected-hook #'enable-circe-notifications))
:config
(setq circe-notifications-watch-strings +irc-notifications-watch-strings
circe-notifications-emacs-focused nil
circe-notifications-alert-style
(cond (IS-MAC 'osx-notifier)
(IS-LINUX 'libnotify)
(circe-notifications-alert-style))))
(use-package! lui
:commands lui-mode
:config
(define-key lui-mode-map "\C-u" #'lui-kill-to-beginning-of-line)
(setq lui-fill-type nil)
(when (featurep! :tools flyspell)
(setq lui-flyspell-p t))
(after! evil
(defun +irc-evil-insert-h ()
"Ensure entering insert mode will put us at the prompt, unless editing
after prompt marker."
(when (> (marker-position lui-input-marker) (point))
(goto-char (point-max))))
(add-hook! 'lui-mode-hook
(add-hook 'evil-insert-state-entry-hook #'+irc-evil-insert-h
nil 'local))
(mapc (lambda (cmd) (push cmd +irc-scroll-to-bottom-on-commands))
'(evil-paste-after evil-paste-before evil-open-above evil-open-below)))
(defun +irc-preinput-scroll-to-bottom-h ()
"Go to the end of the buffer in all windows showing it.
Courtesy of esh-mode.el"
(when (memq this-command +irc-scroll-to-bottom-on-commands)
(let* ((selected (selected-window))
(current (current-buffer)))
(when (> (marker-position lui-input-marker) (point))
(walk-windows
(function
(lambda (window)
(when (eq (window-buffer window) current)
(select-window window)
(goto-char (point-max))
(select-window selected))))
nil t)))))
(add-hook! 'lui-mode-hook
(add-hook 'pre-command-hook #'+irc-preinput-scroll-to-bottom-h nil t))
;; enable a horizontal line marking the last read message
(add-hook 'lui-mode-hook #'enable-lui-track-bar)
(add-hook! 'lui-mode-hook
(defun +irc-init-lui-margins-h ()
(setq lui-time-stamp-position 'right-margin
lui-time-stamp-format +irc-time-stamp-format
right-margin-width (length (format-time-string lui-time-stamp-format))))
(defun +irc-init-lui-wrapping-a ()
(setq fringes-outside-margins t
word-wrap t
wrap-prefix (make-string (+ +irc-left-padding 3) ? )))))
(use-package! lui-logging
:after lui
:config (enable-lui-logging))
(use-package! lui-autopaste
:hook (circe-channel-mode . enable-lui-autopaste))

View File

@@ -0,0 +1,5 @@
;; -*- no-byte-compile: t; -*-
;;; app/irc/packages.el
(package! circe)
(package! circe-notifications)