legacy-dotfiles/emacs/.emacs.d/init-circe.org

9 KiB

I used to use ERC mostly because I didn't really use IRC at all and it was basically the first IRC client in Emacs that was presented to me, it being built-in and all. When I started to use IRC more and wanted to customize the way it looks more I was surprised to find that it wasn't all that easy. A friend of mine was using Circe and he helped me figure out how to get started with customizing Circe the way I wanted it. So now I use Circe.

Enable lexical binding

  ;; -*- lexical-binding: t -*-

Require the needed libraries

In order to keep compiler warnings to a minimum, require the libraries that are used in the configuration of Circe.

  (require 'auth-source)
  (require 'circe)
  (require 's)

Clean-up the display of messages

I once saw a very clean and simple weechat configuration on /r/unixporn and really wanted to have something similar. This was the start of my disappointment in ERC, I couldn't figure out how to change the way messages were printed. With a little help I did find out how to do it in Circe.

First we create a variable to store the length of the longest known nick, so we can properly align all messages. This variable should be buffer-local because each IRC chat will have different users with different length names. We start with a length of 0 because we don't know what the shortest nick there is going to be.

  (defvar oni:circe-longest-nick 0)
  (make-variable-buffer-local 'oni:circe-longest-nick)

Then we write the function that will print the most important messages, the ones people send, including me. Whenever we get a message or send a message, we check the length of the nick with the last recorded maximum length. If the new nick is longer that any previous ones we set this new length as the longest known length and adjust lui-fill-type accordingly. This ensures that continuation lines are indented to the correct column.

  (defun oni:circe-say-formatter (&rest keywords)
    (let* ((nick (plist-get keywords :nick))
           (len (length nick)))
      (when (> len oni:circe-longest-nick)
        (setq oni:circe-longest-nick len)
        (setq-local lui-fill-type (make-string (+ len 3) ?\ )))
      (plist-put keywords :nick (s-pad-left oni:circe-longest-nick " " nick))
      (lui-format "{nick}   {body}" keywords)))

I use this formatter both for messages I send myself and incoming messages, because they should basically look the same.

  (setq circe-format-self-say #'oni:circe-say-formatter
        circe-format-say #'oni:circe-say-formatter)

The rest of the formatting functions are basically the same, except they don't need to change the known size of nicks because they don't print the nick in the same column, instead they usually print something like *** to indicate that it is a system message and not a user message. We do pad whatever they print with the same number of spaces to keep them right-justified with the nicks.

  (defun oni:circe-action-formatter (&rest keywords)
    (format "%s   %s %s" (s-pad-left oni:circe-longest-nick " " "*")
            (plist-get keywords :nick)
            (plist-get keywords :body)))

  (defun oni:circe-server-message-formatter (&rest keywords)
    (format "%s   %s" (s-pad-left oni:circe-longest-nick " " "***")
            (plist-get keywords :body)))

  (defun oni:circe-server-join-in-channel-formatter (&rest keywords)
    (format "%s   Join: %s (%s) joined %s"
            (s-pad-left oni:circe-longest-nick " " "***")
            (plist-get keywords :nick)
            (plist-get keywords :userinfo)
            (plist-get keywords :channel)))

  (defun oni:circe-server-join-formatter (&rest keywords)
    (format "%s   %s joined the channel"
            (s-pad-left oni:circe-longest-nick " " "***")
            (plist-get keywords :nick)))

  (defun oni:circe-server-quit-formatter (&rest keywords)
    (format "%s   %s quit IRC: %s"
            (s-pad-left oni:circe-longest-nick " " "***")
            (plist-get keywords :nick)
            (plist-get keywords :reason)))

  (defun oni:circe-server-quit-channel-formatter (&rest keywords)
    (format "%s   %s left %s: %s"
            (s-pad-left oni:circe-longest-nick " " "***")
            (plist-get keywords :nick)
            (plist-get keywords :channel)
            (plist-get keywords :reason)))

  (defun oni:circe-server-part-formatter (&rest keywords)
    (format "%s   %s parted %s: %s"
            (s-pad-left oni:circe-longest-nick " " "***")
            (plist-get keywords :nick)
            (plist-get keywords :channel)
            (plist-get keywords :reason)))

  (defun oni:circe-server-nick-change-formatter (&rest keywords)
    (format "%s   %s is now known as %s"
            (s-pad-left oni:circe-longest-nick " " "***")
            (plist-get keywords :old-nick)
            (plist-get keywords :new-nick)))

  (setq circe-format-self-action #'oni:circe-action-formatter)
  (setq circe-format-action #'oni:circe-action-formatter)
  (setq circe-format-server-message #'oni:circe-server-message-formatter)
  (setq circe-format-server-join-in-channel
        #'oni:circe-server-join-in-channel-formatter)
  (setq circe-format-server-join #'oni:circe-server-join-formatter)
  (setq circe-format-server-quit #'oni:circe-server-quit-formatter)
  (setq circe-format-server-quit-channel
        #'oni:circe-server-quit-channel-formatter)
  (setq circe-format-server-part #'oni:circe-server-part-formatter)
  (setq circe-format-server-nick-change
        #'oni:circe-server-nick-change-formatter)

Automatically join some channels

I started using IRC because #mowedline was started and I felt obligated to join it as I was one of two known Mowedline users at the time. So now that's the one I'm usually active in. I do like to keep an eye on #emacs from time to time and #ninthfloor in case something happens there, though usually not.

  (defun oni:circe-nickserv-password (_)
    (let ((found (nth 0 (auth-source-search :max 1
                                            :host "irc.freenode.net"
                                            :require '(:secret)))))
      (when found
        (let ((secret (plist-get found :secret)))
          (if (functionp secret)
              (funcall secret)
            secret)))))

  (setq circe-network-options
        `(("Freenode"
           :nick "ryuslash"
           :channels ("#emacs" "#mowedline" "#ninthfloor")
           :nickserv-password oni:circe-nickserv-password)))

Clean-up the channel buffers further

When chatting with people in an IRC channel, there really isn't much need for any information in the mode-line. This is mostly because the channel I'm most active on always has its own window. Visual line mode is very handy to have in chats, in case I type very long lines. And the wrap-prefix is set so that when I do type long lines, they are filled nicely to the circe prompt.

  (defun oni:remove-mode-line ()
    (setq mode-line-format nil))

  (defun oni:set-circe-prompt-wrap-prefix ()
    (setq wrap-prefix "  "))

  (add-hook 'circe-channel-mode-hook #'oni:remove-mode-line)
  (add-hook 'circe-channel-mode-hook #'oni:set-circe-prompt-wrap-prefix)
  (add-hook 'circe-channel-mode-hook 'visual-line-mode)

Show #mowedline in a frame without minibuffer

When I'm chatting on #mowedline I do so in a separate small window. This window needs no minibuffer as I do very little actualy Emacsy things in it. Just typing a little and reading. So far I only do this with #mowedline. In order to specifically show it in a frame without a minibuffer I use display-buffer-alist to specify how to show it. The function called dynamically binds default-frame-alist to add a minibuffer element with the value nil (meaning, no minibuffer). I can't do this in the regular default-frame-alist because I want all other frames to show up with a minibuffer. It then creates a new frame and switches to the given buffer in it.

  (defun oni:display-in-minibufferless-frame (buffer _)
    (let ((default-frame-alist default-frame-alist))
      (push '(minibuffer . nil) default-frame-alist)
      (let ((frame (make-frame)))
        (select-frame frame)
        (switch-to-buffer buffer))))

  (add-to-list 'display-buffer-alist
               '("^\#mowedline$" oni:display-in-minibufferless-frame))

Provide the right feature

In order to be able to use (require 'circe-init) we must first provide it.

  (provide 'circe-init)