/literate_emacs

Literate Emacs configuration with EXWM

Primary LanguageEmacs Lisp

Emacs literate configuration

Literate Emacs configuration style. This is part of my journey for Teach myself programming in 10 years. I’m using this configuration on a system running Arch Linux and Emacs 26.3, that’s why this configuration will be using the latest features and not check for the system it’s running on.

Every emacs user should write his own configuration file and steal as many code as he wants from here (or elsewhere).

(unless (equal user-login-name "wand")
  (warn "Please don't load Wand init file, it probably won't work for you.")
  (with-current-buffer " *load*"
    (goto-char (point-max))))

Very nice text about Why should you share your dotfiles. I will try to record all relevant configuration of my current box in the literate style here.

Really trying to avoid another Emacs Bankruptcy. o/

Summary

Packages

Emacs facility to download and install “packages” that implement additional features. You can find information about a specific package by using C-h P that prompts for the name and shows more details.

There is a very detail package in Emacs help system that you can find on info:emacs#Packages.

I always start a new configuration setup with a naive mindset that I will not install thousands of external packages, however they are so good and make our life so much easier that is hard to avoid them altogether.

initialization

Let’s initialize the package system.

(require 'package)

(unless (bound-and-true-p package--initialized)
  (package-initialize))

Despite the fact that GNU Elpa, the standard repository, of Emacs packages maintained by the core team already have many different packages, I like to use another external repository called Melpa which is currently maintained by the community and curated by Purcell’s and his team.

(add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives
             '("melpa-stable" . "https://stable.melpa.org/packages/") t)

We need to refresh the archives to make this change to take place.

(unless (file-exists-p "~/.emacs.d/elpa/archives/melpa")
  (package-refresh-contents))

Also, by default Emacs also automatically loads all installed packages in subsequent Emacs session. I want to disable it.

(setq package-enable-at-startup nil)

use-package

When you have more than a dozen packages, it makes the process of managing them very difficult without any additional help. And by my experience the only real issue is due to performance because you will inevitably have many external packages loaded in situations where you don’t need it. Fortunately, Jon Wiegley made our lives easier by creating use-package, please look for C-h P use-package to more details.

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package)
  (package-install 'delight))

(require 'use-package)

We can add new keywords to use-package, stolen from here.

(defmacro bk-use-package-keywords-add (keyword)
  "Add new keyword as placeholder"
  `(progn
     (add-to-list 'use-package-keywords ,keyword 'append)
     (defun ,(intern (format "use-package-normalize/%s" keyword)) (&rest _))
     (defun ,(intern (format "use-package-handler/%s" keyword)) (&rest _))))

(bk-use-package-keywords-add :about)
(bk-use-package-keywords-add :homepage)

custom packages

Some old packages simply are not in any repository, they are only elisp files distributed over the web. I will place these files inside a folder called lisps.

(setq site-lisps-dir (expand-file-name "lisps" user-emacs-directory))

(dolist (project (directory-files site-lisps-dir t "\\w+"))
  (when (file-directory-p project)
    (add-to-list 'load-path project)))

Dependencies

List of external packages that I rely on in my daily basis

extra

scrot

Scrot (SCReenshOT) is a screenshot capturing utility that uses the imlib2 library to acquire and save images. By default, the captured file is saved with a date-stamped filename in the current directory, although you can also explicitly specify the name of the captured images when the command is run.

Generic command to help us out here!

(defun bk/scrot-cmd (cmd name folder)
  "Scrot CMD to be executed and saving to the correct picture NAME in the FOLDER.
Folder is a symbol recognizing the folder name."
  (interactive)
  (let* ((folder-path (cl-case folder
                       (:window "/home/wand/Pictures/window/")
                       (:region "/home/wand/Pictures/region/")
                       ))
         (filepath (concat folder-path name ".png"))
         (scrot-cmd (format "scrot %s %s -e 'xclip -selection c -t image/png < $f'" cmd filepath)))
    (start-process-shell-command "pt" nil scrot-cmd)))

Capture the print screen of the current window

(defun bk/print-window ()
  "Print current window."
  (interactive)
  (let ((print-name (read-from-minibuffer "Print name: ")))
    (bk/scrot-cmd "" print-name :window)))

Print screens are way to serious, right? Take that region

(defun bk/print-region ()
  "Print screen interactively."
  (interactive)
  (let ((print-name (read-from-minibuffer "Print name: ")))
    (bk/scrot-cmd "-s" print-name :region)))

(eval-after-load 'exwm
  '(exwm-input-set-key (kbd "<print>") #'bk/print-region))

I also need to go fast to these folders, no more: C-x C-j /home C-s Pictures RET {window,region} o.O

(set-register ?w '(file . "~/Pictures/window"))
(set-register ?r '(file . "~/Pictures/region"))

screensavers

I use the external package called xscreensaver which is amazing. You can lock the screen by pressing s-l or calling M-x bk/lock-screen.

Emacs zone is also an happy surprise for me. It seems like this is a default mode to ‘zones’ Emacs out by choosing one of its random modes to obfuscate the current buffer, which can then be used as a screensaver.

I will add some configuration for this.

(use-package zone
  :ensure nil
  :config
  (defvar zone--window-config nil)
  (defadvice zone (before zone-ad-clean-ui)
    "Maximize window before `zone' starts."
    (setq zone--window-config (current-window-configuration))
    (delete-other-windows)
    (when (and (eq window-system 'x) (executable-find "xtrlock"))
      (start-process "xtrlock" nil "xtrlock")))
  (defadvice zone (after zone-ad-restore-ui)
    "Restore window configuration."
    (when zone--window-config
      (set-window-configuration zone--window-config)
      (setq zone--window-config nil)))
  (ad-activate 'zone))

I also installed xtrlock so when I activate zone I also lock my screen. In order to unlock you just need to start typing the correct password and press RET.

dunst

Dunst is a lightweight replacement for the notification-daemons provided by most desktop environments. Dunst allows for the use of HTML markup in notifications, some examples are bold, italics, strike-though, and underline.

The relevant bits of my .dunstrc.

[global]
font = Source Code Pro Medium

[urgency_low]
# IMPORTANT: colors have to be defined in quotation marks.
# Otherwise the "#" and following would be interpreted as a comment.
frame_color = "#3B7C87"
foreground = "#3B7C87"
background = "#191311"
timeout = 8

[urgency_normal]
frame_color = "#5B8234"
foreground = "#5B8234"
background = "#191311"
timeout = 10

[urgency_critical]
frame_color = "#B7472A"
foreground = "#B7472A"
background = "#191311"
timeout = 12

mpv

mpv is a free (as in freedom) media player for the command line. It supports a wide variety of media file formats, audio, and video codecs, and subtitle types.

On screen controller, while mpv strives for minimalism and provides no real GUI, it has a small controller on top of the video for basic control.

browsers

qutebrowser

A keyboard-driven, vim-like browser based on PyQt5 web browser with a minimal GUI.

I met this project back at the university in 2012 and is hard to remember but I think it was the first time that I talked to other programmers online with attempts to report bugs and errors for the maintainers of this browser. Very nice project.

The cheat sheet is very important.

The following file is not my complete config.py file for qutebrowser, only the diff from defaults. If you want to create a default config file, you should use :config-write-py --default.

# Always restore open sites when qutebrowser is reopened.
# Type: Bool
c.auto_save.session = False

# Show javascript alerts
# Type: Bool
c.content.javascript.alert = False

# Allow websites to record audio/video
c.content.media_capture = 'ask'

# Allow websites to lock your mouse
c.content.mouse_lock = True

# Allow websites to show notifications
c.content.notifications = False

## Open a new window for every tab.
## Type: Bool
c.tabs.tabs_are_windows = True

google chrome

You know, that time when the internet tells you: “you can’t see this page without a google-based product today”

Operating System

Functionalities that interface directly with the underlying operating system.

async

Asynchronous bytecode compilation and various other actions makes Emacs look SIGNIFICANTLY less often which is a good thing.

(use-package async
  :ensure t
  :defer t
  :init
  (dired-async-mode 1)
  (async-bytecomp-package-mode 1)
  :custom (async-bytecomp-allowed-packages '(all)))

path

Teach Emacs about the PATH of the underlying OS.

(setenv "PATH" (concat (getenv "PATH") ":/home/wand/private/scripts"))
(setq exec-path (append exec-path '("/home/wand/private/scripts")))

(setenv "PATH" (concat (getenv "PATH") ":/usr/local/bin"))
(setq exec-path (append exec-path '("/usr/local/bin")))

(setenv "LD_LIBRARY_PATH" (concat (getenv "LD_LIBRARY_PATH") ":/usr/local/lib"))
(setq exec-path (append exec-path '("/usr/local/lib")))

I’ve been using qutebrowser as my main browser for more than one year now. Idk, I like keyboard centric products.

(setq browse-url-browser-function 'browse-url-generic
      browse-url-generic-program "qutebrowser")

security

Fix old security Emacs problems

(eval-after-load "enriched"
  '(defun enriched-decode-display-prop (start end &optional param)
     (list start end)))

compilation

(use-package emacs
  :ensure nil
  :config
  (setq compilation-always-kill t
        compilation-ask-about-save nil
        compilation-context-lines 10
        compilation-window-height 100
        compilation-scroll-output 'first-error))

system monitor

A tiny system monitor that can be enabled or disabled at runtime, useful for checking performance with power-hungry processes in ansi-term.

(use-package symon
  :ensure t
  :defer t)

Built in htop.

(setq proced-auto-update-flag t
      proced-auto-update-interval 1
      proced-descend t)

garbage collection

Garbage collection shouldn’t happen during startup, as what will slow Emacs down. Do it later.

Change the default values.

(defvar file-name-handler-alist-old file-name-handler-alist)

(setq-default gc-cons-threshold 402653184
              file-name-handler-alist nil
              gc-cons-percentage 0.6
              auto-window-vscroll nil
              message-log-max 16384)

(add-hook 'after-init-hook
          `(lambda ()
             (setq file-name-handler-alist file-name-handler-alist-old
                   gc-cons-threshold 800000
                   gc-cons-percentage 0.1)
             (garbage-collect)) t)

Ease the font caching during GC.

(setq inhibit-compacting-font-caches t)

Emacs can inform us when the garbage collection is happening. I do not want to see this anymore… it was useful to understand the behavior for configuration.

(setq garbage-collection-messages nil)

collector magic hack

Enforce a sneaky GC strategy to minimize GC interference with the activity. During normal use a high GC threshold is set, when idling GC is immediately triggered and a low threshold is set.

(use-package gcmh
  :ensure t
  :disabled t
  :init
  (setq gcmh-verbose nil)
  :config
  (gcmh-mode 1))

manage external services

Very interesting package that help us to have some instances of external processes running and keep track of it all. I often need to enable the VPN of my company to work remotely, this suits nicely.

(use-package prodigy
  :ensure t
  :config
  (prodigy-define-service
    :name "Captalys VPN"
    :command "captalys-vpn"
    :tags '(captalys)
    :stop-signal 'sigkill
    :kill-process-buffer-on-stop t)

  (prodigy-define-service
    :name "Blog"
    :command "lein ring server"
    :cwd "~/bartuka-blog"
    :stop-signal 'sigkill
    :tags '(blog)
    :kill-process-buffer-on-stop t)

  (prodigy-define-tag
    :name 'captalys
    :ready-message "Initialization Sequence Completed")
  (prodigy-define-tag
    :name 'blog
    :ready-message "Started server on port 3000"))

Aesthetics

Look and fill of several aspects of Emacs: mode-line, fonts, specific faces, fringe, and more.

cleaning

Since I never use the mouse with GNU Emacs, I prefer not to use invasive graphical elements.

(when window-system
  (menu-bar-mode -1)
  (tool-bar-mode -1)
  (scroll-bar-mode -1))

Emacs convention is to show help and other inline documentation in the message area. Show help there instead of OS tooltip.

(when (display-graphic-p)
  (tooltip-mode -1))

Let’s remove some crunchy messages at startup time.

(setq inhibit-splash-screen t
      inhibit-startup-echo-area-message t)

Enabling some builtin modes that are very helpful e.g. highlight the positions of open/close of parenthesis, prettify symbols for now basically converts a fn to a lambda symbol, but I intend to expand the list of converted symbols.

(show-paren-mode t)
(global-prettify-symbols-mode t)
(blink-cursor-mode 0)
(use-package simple
  :ensure nil
  :delight auto-fill-mode
  :config
  (add-hook 'text-mode-hook #'auto-fill-mode))

theme and faces

The color theme is always a complicated matter. I’ve been trying several ones, most recently I had settle with Protesilaos modus-{operandi,vivendi} packages, but now I want to try dakrone for a while. deprecated already. I will be using the default white one.

Find out what face something at point have.

(defun what-face (pos)
  (interactive "d")
  (let ((face (or (get-char-property (point) 'read-face-name)
                  (get-char-property (point) 'face))))
    (if face (message "Face: %s" face) (message "No face at %d" pos))))

Change the highlight color for selection text.

(set-face-attribute 'region nil :background "#D5F0D5")

Make cursor the width of the character it is under.

(setq x-stretch-cursor t)

Allow only one theme at a time

(setq custom-theme-allow-multiple-selections nil)

Set the custom theme path

(setq custom-theme-directory (concat user-emacs-directory "themes"))

(dolist
    (path (directory-files custom-theme-directory t "\\w+"))
  (when (file-directory-p path)
    (add-to-list 'custom-theme-load-path path)))

buffer based theming

(use-package load-theme-buffer-local
  :ensure nil
  :about I am using a custom version located at /lisps folder of this setup.
  :commands (load-theme-buffer-local))

download themes

organic green

A light theme with a light-green background.

I enjoyed so much this theme, that I started contributing to the source code. Right now, I am modernizing the structure and color pallets to be more organized and comprehensive.

cyberpunk

Real dark theme.

(use-package cyberpunk-theme
  :ensure t
  :disabled t
  :config
  (load-theme 'cyberpunk t))

zenburn

(use-package zenburn-theme
  :ensure t
  :disabled t
  :config
  (load-theme 'zenburn t))

monokai

(use-package monokai-theme
  :ensure t
  :defer t)

tomorrow night

(use-package color-theme-sanityinc-tomorrow
  :ensure t
  :defer t)

default theme

Let’s activate the default theme, I might change this very often.

(add-hook 'after-init-hook
  (lambda ()
    (interactive)
    (load-theme 'organic-green t)))

time

(use-package time
  :ensure nil
  :init
  (setq display-time-default-load-average nil
        display-time-format "%Hh%M "
        display-time-day-and-date t)
  :config
  (display-time-mode t))

font

redefine the size of the font.

(when (member "Monaco" (font-family-list))
  (set-face-attribute 'default nil :font "Monaco" :height 120)
  (setq default-frame-alist '((font . "Monaco-12"))))

mode line

menu

This package implements a menu that lists enabled minor-modes, as well as commonly but not currently enabled minor-modes. It can be used to toggle local and global minor-modes, to access mode-specific menus, and to get help about modes.

(use-package minions
  :ensure t
  :config
  (minions-mode 1))

fringe

Control the fringe around the frame.

(fringe-mode '(10 . 1))

Preview line numbers when prompting for line number.

(define-advice goto-line (:before (&rest _) preview-line-number)
  "Preview line number when prompting for goto-line."
  (interactive
   (lambda (spec)
     (if (and (boundp 'display-line-numbers)
              (not display-line-numbers))
         (unwind-protect
             (progn (display-line-numbers-mode)
                    (advice-eval-interactive-spec spec))
           (display-line-numbers-mode -1))
       (advice-eval-interactive-spec spec)))))

Defaults

Many changes in the default behavior of Emacs, not able to group anywhere else.

I ran into this little tidbit while reading Sacha Chu’a posts from Emacs. You can find the whole discussion here but the idea is that next-line defun triggers line-move-partial which leads to excessive processing. By setting the variable below, the speed of using next-line gets very cut down.

(setq auto-window-vscroll nil)

Do not clutter my init.el file with customized variables.

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (file-exists-p custom-file)
  (load custom-file))

Show current key-sequence in minibuffer, like vim does. Any feedback after typing is better UX than no feedback at all.

(setq echo-keystrokes 0.2)

Allow pasting selection outside of Emacs

(setq x-select-enable-clipboard t)

Say you copied a link from your web browser, then switched to Emacs to paste it somewhere. Before you do that, you notice something you want to kill. Doing that will place the last kill to the clipboard, thus overriding the thing you copied earlier. We can have a kill ring solution:

(setq save-interprogram-paste-before-kill t)
(setq custom-safe-themes t)

(defalias 'cquit 'cider-quit)
(defalias 'yes-or-no-p 'y-or-n-p)

;; built in htop

Don’t use tabs to indent and fix some indentation settings

(use-package emacs
  :ensure nil
  :config
  (setq-default tab-always-indent 'complete)
  (setq-default indent-tabs-mode nil
                tab-width 4
                fill-column 70))

Enable some built in modes to add critical functionality to Emacs. More explanation about them will follow in future.

(delete-selection-mode t)
(pending-delete-mode t)
(column-number-mode 1)
(global-auto-revert-mode)

;; real emacs knights don't use shift to mark things
(setq shift-select-mode nil)

set warning of opening large files to 100MB

(setq-default large-file-warning-threshold 100000000)

do not add double space after periods Real sentence in Emacs : emacs

(setq-default sentence-end-double-space nil)

more defaults

(setq-default user-mail-address "iagwanderson@gmail.com"
              user-full-name "Wanderson Ferreira"
              disabled-command-function nil)

ediff

(setq ediff-diff-options "-w")
(setq ediff-split-window-function 'split-window-horizontally)
(setq ediff-window-setup-function 'ediff-setup-windows-plain)

word wrapping

Word wrapping

(setq-default word-wrap t
              truncate-lines t
              truncate-partial-width-windows nil
              sentence-end-double-space nil
              delete-trailing-lines nil
              require-final-newline t
              tabify-regexp "^\t* [ \t]+")

Favor hard-wrapping in text modes

(defun bk/auto-fill ()
  "My autofill setup for text buffers."
  (auto-fill-mode t)
  (delight 'auto-fill-mode))

(add-hook 'text-mode-hook #'bk/auto-fill)

backup

This enables file backups to N versions of saves, as opposed to only backing up the very first save. I don’t re-launch emacs that often so this is necessary to get useful backups.

(setq backup-directory-alist `(("." . ,(concat user-emacs-directory "backups")))
      vc-make-backup-files t
      version-control t
      kept-old-versions 0
      kept-new-versions 10
      delete-old-versions t
      backup-by-copying t)

recentf

This is a built-in mode that keeps track of the files you have opened allowing you go back to them faster. It can also integrate with a completion framework to populate a virtual buffers list.

(use-package recentf
  :ensure nil
  :init
  (setq recentf-max-saved-items 50
	     recentf-max-menu-items 15
	     recentf-show-file-shortcuts-flag nil
	     recentf-auto-cleanup 'never)
  :config
  (recentf-mode t))

save place

Save place remembers your location in a file when saving files.

(require 'saveplace)
(setq save-place-mode (expand-file-name "saveplace" user-emacs-directory))
(save-place-mode 1)

save history

Keeps a record of actions involving the minibuffer. This is of paramount important to a fast and efficient workflow involving any completion framework that leverages the built-in mechanisms.

(use-package savehist
  :ensure nil
  :config
  (setq savehist-file "~/.emacs.d/savehist"
        history-length 30000
        history-delete-duplicates nil
        savehist-additional-variables '(search-ring
                                        regexp-search-ring)
        savehist-save-minibuffer-history t)
  (savehist-mode 1))

uniquify

Uniquify buffer names dependent on file name. Emacs’s traditional method for making buffer names unique adds <2>, <3>, etc to the end of (all but one of) the buffers. This settings change the default behavior.

(use-package uniquify
  :ensure nil
  :config
  (setq uniquify-buffer-name-style 'post-forward-angle-brackets
	     uniquify-separator " * "
	     uniquify-after-kill-buffer-p t
	     uniquify-strip-common-suffix t
	     uniquify-ignore-buffers-re "^\\*"))

extended command

smex is an improved version of extended-command or M-x

(use-package smex
  :ensure t
  :config
  (smex-initialize))

case switch

(use-package fix-word
  :ensure t
  :config
  (global-set-key (kbd "M-u") #'fix-word-upcase)
  (global-set-key (kbd "M-l") #'fix-word-downcase)
  (global-set-key (kbd "M-c") #'fix-word-capitalize))

registers

Emacs registers are compartments where you can save text, rectangles, positions, and other things for later use. Once you save text or a rectangle in a register, you can copy it into the buffer once or many times; once you save a position in a register, you can jump back to that position once or many times.

For more information: `C-h r’ and then letter i to search for registers and the amazing video from Protesilaos.

The prefix to all commands of registers is C-x r

commanddescription
M-x view-register Rsee what register R contains
C-x r ssave region to register
C-x r iinsert text from a register
C-x r nrecord a number defaults to 0
C-x r +increment a number from register
C-x r SPCrecord a position into register
C-x r jjump to positions or windows config
C-x r wsave a window configuration
C-x r fsave a frame configuration

Important note: the data saved into the register is persistent as long as you don’t override it.

The way to specify a number, is to use an universal argument e.g. C-u <number> C-x n

Clean all the registers you saved.

(defun bk/clear-registers ()
  "Remove all saved registers."
  (interactive)
  (setq register-alist nil))
(set-register ?e '(file . "~/.emacs.d/README.org"))
(set-register ?t '(file . "~/org/todo.org"))
(set-register ?c '(file . "~/.emacs.d/docs/cheatsheet.org"))

abbreviation

 (use-package abbrev
   :ensure nil
   :delight abbrev-mode
   :config
   (setq-default abbrev-mode t))

 (defun bk/add-region-local-abbrev (start end)
   "Go from START to END and add the selected text to a local abbrev."
   (interactive "r")
   (if (use-region-p)
	(let ((num-words (count-words-region start end)))
	  (add-mode-abbrev num-words)
	  (deactivate-mark))
     (message "No selected region!")))

 (global-set-key (kbd "C-x a l") 'bk/add-region-local-abbrev)

 (defun bk/add-region-global-abbrev (start end)
   "Go from START to END and add the selected text to global abbrev."
   (interactive "r")
   (if (use-region-p)
	(let ((num-words (count-words-region start end)))
	  (add-abbrev global-abbrev-table "Global" num-words)
	  (deactivate-mark))
     (message "No selected region!")))

 (global-set-key (kbd "C-x a g") 'bk/add-region-global-abbrev)

imenu

Change some defaults of imenu.

(require 'imenu)

(setq imenu-auto-rescan 1
      imenu-auto-rescan-maxout 600000
      imenu-max-item-length 600
      imenu-use-markers t
      imenu-max-items 200)

The objectives of this package is to provide a way to choose buffer indexes in a specific mode. What is a buffer index? Basically we have a function that will find “interesting” positions in your buffer that you might want to jump there, something like function definitions, headlines in outline mode, class definitions, etc.

(use-package imenu-anywhere
  :ensure t)

ibuffer

It provides a way of filtering and then grouping the list of buffers that you currently have open. About the configuration below:

DefaultExplanation
ibuffer-expertStop asking for confirmation after every action in Ibuffer
ibyffer-auto-modeKeeps the buffer list up to date
(use-package ibuffer
  :ensure nil
  :init
  (setq ibuffer-expert t)
  (setq ibuffer-show-empty-filter-groups nil)
  (setq ibuffer-saved-filter-groups
        '(("Main"
           ("Directories" (mode . dired-mode))
           ("Rest" (mode . restclient-mode))
           ("Docker" (or
                      (mode . docker-compose-mode)
                      (mode . dockerfile-mode)))
           ("Programming" (or
                           (mode . clojure-mode)
                           (mode . emacs-lisp-mode)
                           (mode . sql-mode)
                           (mode . python-mode)))
           ("Browser" (or
                       (name . "qutebrowser:\*")
                       ))
           ("Slack" (name . "*Slack"))
           ("Org" (mode . org-mode))
           ("Markdown" (or
                        (mode . markdown-mode)
                        (mode . gfm-mode)))
           ("Git" (or
                   (mode . magit-blame-mode)
                   (mode . magit-cherry-mode)
                   (mode . magit-diff-mode)
                   (mode . magit-log-mode)
                   (mode . magit-process-mode)
                   (mode . magit-status-mode)))
           ("Emacs" (or
                     (name . "^\\*Help\\*$")
                     (name . "^\\*Custom.*")
                     (name . "^\\*Org Agenda\\*$")
                     (name . "^\\*info\\*$")
                     (name . "^\\*scratch\\*$")
                     (name . "^\\*Backtrace\\*$")
                     (name . "^\\*Messages\\*$"))))))
  :config
  (add-hook 'ibuffer-mode-hook
            (lambda ()
              (ibuffer-auto-mode 1)
              (ibuffer-switch-to-saved-filter-groups "Main"))))

Package ibuffer-vc let you filter the Ibuffer by projects definitions (in my case, every folder that has a .git folder inside is considered a project).

(use-package ibuffer-vc
  :ensure t
  :after ibuffer
  :config
  (define-key ibuffer-mode-map (kbd "/ V") 'ibuffer-vc-set-filter-groups-by-vc-root))

Increasing the width of each column in ibuffer. Some buffers names are very large in EXWM.

 (setq ibuffer-formats
	   '((mark modified read-only " "
		   (name 60 60 :left :elide) ; change: 60s were originally 18s
		   " "
		   (size 9 -1 :right)
		   " "
		   (mode 16 16 :left :elide)
		   " " filename-and-process)
	     (mark " "
		   (name 16 -1)
		   " " filename)))

minibuffer

The following setting prevent the minibuffer to grow, therefore it will be always 1 line height.

(setq resize-mini-windows nil)
(setq max-mini-window-height 1)

get more space for editing

Stole this from Sacha Chua’s configuration, sometimes you want to be able to do fancy things with the text that you are entering into the minibuffer. Sometimes you just want to be able to read it, specially when it comes to lots of text. This binds C-M-e in a minibuffer.

(use-package miniedit
  :ensure t
  :config
  (miniedit-install))

calendar

(use-package calendar
  :ensure nil
  :hook (calendar-today-visible . calendar-mark-today)
  :config
  (setq calendar-latitude -23.5475
        calendar-longitude -46.63611
        calendar-location-name "Sao_Paulo, Brazil"
        calendar-mark-holidays-flag t))

webjump

Provide a nice keyboard interface to web pages of your choosing.

Adding urban dictionary to webjump.

(eval-after-load "webjump"
  '(add-to-list 'webjump-sites '("Urban Dictionary" . [simple-query
							 "www.urbandictionary.com"
							 "http://www.urbandictionary.com/define.php?term="
							 ""])))

(global-set-key (kbd "C-c j") 'webjump)

authentication source

Auth Source is a generic interface for common backends such as your operating sysetm’s keychain and your local ~/.authinfo file. Auth Source solves the problem of mapping passwords and usernames to hosts.

Debugging auth issues

(setq auth-source-debug t)

We need to tell auth-source where to look for secrets.

(setq auth-sources '((:source "~/.emacs.d/secrets/.authinfo")))

GPG

(use-package pinentry :ensure t)
(use-package epa
  :ensure nil
  :config
  (setq epa-pinentry-mode 'loopback)
  (pinentry-start))

enable the disabled commands

(put 'downcase-region 'disabled nil)
(put 'upcase-region 'disabled nil)
(put 'narrow-to-region 'disabled nil)

Hydras

This package has a very nice name once you know what it does! This is a package that can be used to tie related commands into a family of short bindings with a common prefix - a Hydra.

(use-package hydra
  :ensure t)

Help and Info

info

(use-package info
  :ensure t
  :bind ("C-h C-i" . info-lookup-symbol)
  :config
  (add-hook 'Info-mode-hook
            #'(lambda ()
                (setq buffer-face-mode '(:family "Bookerly"))
                (buffer-face-mode)
                (text-scale-adjust 1))))

customization to info-mode

(eval-after-load 'Info-mode
  '(progn
     <<info-hydras>>
     <<info-define>>))

define keys

(define-key Info-mode-map "w" 'forward-word)
(define-key Info-mode-map "b" 'backward-word)
(define-key Info-mode-map "t" 'hydra-info-to/body)
(define-key Info-mode-map "u" 'Info-history-back)
(define-key Info-mode-map "H" 'Info-history-back)

hydras

(defun ora-Info-hook ())

(defun ora-open-info (topic bname)
  "Open info on TOPIC in BNAME."
  (if (get-buffer bname)
      (progn
        (switch-to-buffer bname)
        (unless (string-match topic Info-current-file)
          (Info-goto-node (format "(%s)" topic))))
    (info topic bname)))

(defhydra hydra-info-to (:hint nil :color teal)
    "
_o_rg e_l_isp _e_macs _h_yperspec"
    ("o" (ora-open-info "org" "*org info*"))
    ("l" (ora-open-info "elisp" "*elisp info*"))
    ("e" (ora-open-info "emacs" "*emacs info*"))
    ("h" (ora-open-info "gcl" "*hyperspec*")))

Editing Text

defaults

See also bidi-paragraph-direction; setting that non-nil might speed up redisplay.

(setq bidi-paragraph-direction 'left-to-right)

key bindings

Default movement keys

move and mark by paragraph

Use M-{ and M-} to move forward or backward by paragraph. Use M-h to mark (highlight) the current paragraph.

very large files

Since I am using EXWM, I might open very large files, there is a package to help Emacs handle this kind of files.

(use-package vlf
  :ensure t
  :defer t)

I found a good paper about log files in Emacs where they mention vlf package. This paper is very worth reading nevertheless.

eldoc

(use-package eldoc
  :ensure nil
  :delight eldoc-mode
  :init
  (setq eldoc-idle-delay 0.1
        eldoc-echo-area-use-multiline-p nil)
  (eldoc-mode 1)
  :config
  (add-hook 'prog-mode-hook 'turn-on-eldoc-mode))

subword

(use-package subword
  :ensure nil
  :delight subword-mode)

extra features

change inner

(use-package change-inner
  :homepage https://github.com/magnars/change-inner.el
  :about vim's `ci' command, building on expand-region
  :ensure t)

expand region

Expand or reduce region selection semantically. Supports all languages that I work with inside Emacs.

(use-package expand-region
  :homepage https://github.com/magnars/expand-region.el
  :about Extension to increase selected region by semantic units
  :ensure t
  :requires hydra
  :init
  (setq er--show-expansion-message t
        expand-region-fast-keys-enabled nil)
  :bind (("C-=" . er/expand-region)
         ("C-c =" . bk/expand-region/body))
  :config

  (defhydra bk/expand-region (:color pink :hint nil)
    "
 ^Expand/Discard^                ^Mark^
─^──────────────^────────────────^────^─────────────────
 _e_ or _+_: expand region         _(_:      inside pairs
 _r_ or _-_: reduce region         _)_:      around pairs
 _g_:      exit hydrant          _q_ or _'_: inside quotes
 _G_:      discard region, exit  _Q_ or _\"_: around quotes
 ^ ^    ^ ^                        _p_:      paragraph"
    ("e" er/expand-region)
    ("+" er/expand-region)
    ("r" er/contract-region)
    ("-" er/contract-region)
    ("p" er/mark-paragraph)
    ("(" er/mark-inside-pairs)
    (")" er/mark-outside-pairs)
    ("q" er/mark-inside-quotes)
    ("'" er/mark-inside-quotes)
    ("Q" er/mark-outside-quotes)
    ("\"" er/mark-outside-quotes)
    ("g" ignore :exit t)
    ("G" #'(lambda () (interactive) (deactivate-mark)) :exit t)))

jump to char

(use-package avy
  :homepage https://github.com/abo-abo/avy
  :about Jump to things in Emacs tree-style
  :ensure t
  :config
  (avy-setup-default)
  :bind (("C-." . avy-goto-char-timer)))

avy-zap is a nice package to use zap to char with avy. Basically deleting everything from point to another char.

(use-package avy-zap
  :ensure t
  :bind (("M-z" . avy-zap-to-char-dwim)
         ("M-Z" . avy-zap-up-to-char-dwim)))

move through edit points

Emacs leaves a trail of breadcrumbs (the mark ring) through which we can navigate to hop around to places you’ve been in the buffer. A nice alternative is to move round through points at which you made edits in a buffer.

(use-package goto-chg
  :ensure t
  :config
  (global-set-key (kbd "C-c b ,") 'goto-last-change)
  (global-set-key (kbd "C-c b .") 'goto-last-change-reverse))

Now we can use C-c b , and C-c b . to go back and forth through the edit points in your buffer. It takes you through your undo history without undoing anything.

highlights

symbols

Very often is useful to highlight some symbols.

(use-package highlight-symbol
  :ensure t
  :delight highlight-symbol-mode
  :hook
  ((highlight-symbol-mode . highlight-symbol-nav-mode)
   (prog-mode . highlight-symbol-mode))
  :custom
  (highlight-symbol-highlight-single-occurrence nil)
  (highlight-symbol-idle-delay 0.25)
  (highlight-symbol-on-navigation-p t))

browse kill ring

(use-package browse-kill-ring
  :ensure t
  :commands browse-kill-ring)

utf-8

No one knows why this is not the default already.

(prefer-coding-system 'utf-8)
(setq locale-coding-system 'utf-8)
(set-language-environment "UTF-8")
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)

multiple cursors

Multiple cursors is a very nice package that lets you create several cursors that all do the same thing as you type.

(use-package multiple-cursors
  :ensure t
  :bind
  (("C->" . mc/mark-next-like-this)
   ("C-<" . mc/mark-previous-like-this)
   ("S-<mouse-1>" . mc/add-cursor-on-click)
   ("C-c m" . bk/hydra-multiple-cursors/body))
  :requires hydra
  :config

  (defhydra bk/hydra-multiple-cursors (:hint nil :color pink)
    "
 ^Select^                 ^Discard^                     ^Edit^               ^Navigate^
─^──────^─────────────────^───────^─────────────────────^────^───────────────^────────^─────────
 _M-s_: split lines       _M-SPC_:  discard current      _&_: align           _(_: cycle backward
 _s_:   select regexp     _b_:      discard blank lines  _#_: insert numbers  _)_: cycle forward
 _n_:   select next       _d_:      remove duplicated    ^ ^                  ^ ^
 _p_:   select previous   _q_ or _g_: exit hydrant       ^ ^                  ^ ^
 _C_:   select next line  _G_:      exit mc mode"
    ("M-s" mc/edit-ends-of-lines)
    ("s" mc/mark-all-in-region-regexp)
    ("n" mc/mark-next-like-this-word)
    ("p" mc/mark-previous-like-this-word)
    ("&" mc/vertical-align-with-space)
    ("(" mc/cycle-backward)
    (")" mc/cycle-forward)
    ("M-SPC" mc/remove-current-cursor)
    ("b" mc/remove-cursors-on-blank-lines)
    ("d" mc/remove-duplicated-cursors)
    ("C" mc/mark-next-lines)
    ("#" mc/insert-numbers)
    ("q" mc/remove-duplicated-cursors :exit t)
    ("g" mc/remove-duplicated-cursors :exit t)
    ("G" mc/keyboard-quit :exit t)))

(use-package mc-extras
  :ensure t
  :after multiple-cursors)

To use mc/edit-lines you need to highlight the lines on which you wish to have cursors and use C-c m c. Now you can edit away and press enter when you are done to exit multiple cursors.

There is this amazing video from magnars showing off multiple cursors features.

However, occasionally the best way to get the cursors where you want them is with the mouse. With the following code, C-S-<left mouse click> adds a new cursor.

custom functions

Several helper functions to ease the day-to-day work of editing text.

smart move to beginning of visible line (or not)

Very nice default.

;; `C-a' first takes you to the first non-whitespace char as
;; `back-to-indentation' on a line, and if pressed again takes you to
;; the actual beginning of the line.
(defun smarter-move-beginning-of-line (arg)
  "Move depending on ARG to beginning of visible line or not.
  From https://emacsredux.com/blog/2013/05/22/smarter-navigation-to-the-beginning-of-a-line/."
  (interactive "^p")
  (setq arg (or arg 1))
  (when (/= arg 1)
    (let ((line-move-visual nil))
      (forward-line (1- arg))))
  (let ((orig-point (point)))
    (back-to-indentation)
    (when (= orig-point (point))
      (move-beginning-of-line 1))))

(global-set-key [remap move-beginning-of-line] 'smarter-move-beginning-of-line)

unfill paragraph

I used it sometimes when yanking text written in Emacs to paste in other external apps such as gmail and I don’t want the “break line” to be at 70th column there.

(defun unfill-paragraph ()
  "Takes a multi-line paragraph and makes it into a single line of text."
  (interactive)
  (let ((fill-column (point-max)))
    (fill-paragraph nil)))

unfill region

(defun unfill-region (beg end)
  "Unfill the region, joining text paragraphs into a single logical line."
  (interactive "*r")
  (let ((fill-column (point-max)))
    (fill-region beg end)))

duplicate line or region

(defun duplicate-current-line-or-region (arg)
  "Duplicates the current line or region ARG times.
If there's no region, the current line will be duplicated."
  (interactive "p")
  (save-excursion
    (if (region-active-p)
        (duplicate-region arg)
      (duplicate-current-line arg))))

(defun duplicate-region (num &optional start end)
  "Duplicates the region bounded by START and END NUM times.
If no START and END is provided, the current region-beginning
region-end is used."
  (interactive "p")
  (let* ((start (or start (region-beginning)))
         (end (or end (region-end)))
         (region (buffer-substring start end)))
    (goto-char start)
    (dotimes (i num)
      (insert region))))

(defun duplicate-current-line (num)
  "Duplicate the current line NUM times."
  (interactive "p")
  (when (eq (point-at-eol) (point-max))
    (goto-char (point-max))
    (newline)
    (forward-char -1))
  (duplicate-region num (point-at-bol) (1+ (point-at-eol))))

Let’s bind the top level function to a sensible key.

(global-set-key (kbd "C-c 2") 'duplicate-current-line-or-region)

web paste

(use-package webpaste
  :ensure t
  :config
  (setq webpaste-provider-priority '("ix.io" "dpaste.org")))

Completions

minibuffer

The optimal way of using Emacs is through searching and narrowing selection candidates. Spend less time worrying about where things are on the screen and more on how fast you can bring them into focus.

Minibuffer is the place for extended command interaction. Whether it is about input to a prompt, performing search, executing functions or dealing with buffers.

General config for the minibuffers

(use-package minibuffer
  :ensure nil
  :init
  (setq completion-styles '(basic partial-completion)
        completion-category-defaults nil
        completion-cycle-threshold 3
        completion-flex-nospace nil
        completion-ignore-case t
        read-buffer-completion-ignore-case t
        read-file-name-completion-ignore-case t
        completions-format 'vertical
        enable-recursive-minibuffers t
        read-answer-short t
        resize-mini-windows t)
  :config
  (defun bk/describe-symbol-at-point (&optional arg)
    "Get help (documentation) for the symbol at point."
    (interactive "P")
    (let ((symbol (symbol-at-point)))
      (when symbol
        (describe-symbol symbol)))
    (when arg
      (let ((help (get-buffer-window "*Help*")))
        (when help
          (if (not (eq (selected-window) help))
              (select-window help)
            (select-window (get-mru-window)))))))
  
  (defun bk/focus-minibuffer ()
    "Focus the active minibuffer."
    (interactive)
    (let ((mini (active-minibuffer-window)))
      (when mini
        (select-window mini))))

  (defun bk/completions-kill-save-symbol ()
    "Add symbol-at-point to the kill ring."
    (interactive)
    (kill-new (thing-at-point 'symbol)))

  (file-name-shadow-mode t)
  (minibuffer-depth-indicate-mode t)
  (minibuffer-electric-default-mode t)

  :bind (:map completion-list-mode-map
              ("h" . bk/describe-symbol-at-point)
              ("w" . bk/completions-kill-save-symbol)
              ("n" . next-line)
              ("p" . previous-line)
              ("f" . next-completion)
              ("b" . previous-completion)
              ("M-v" . bk/focus-minibuffer)))

ivy

Yeah, I migrated from Ido to Ivy. Let’s enjoy new features, speed, and comfort that Ivy provides.

(use-package ivy
  :ensure t
  :init
  (setq ivy-use-virtual-buffers t
      ivy-height 13
      ivy-wrap t
      ivy-magic-slash-non-match-action nil
      ivy-initial-inputs-alist nil
      ivy-on-del-error-function #'ignore
      ivy-use-selectable-prompt t
      ivy-display-style 'fancy
      ivy-count-format "%d/%d "
      ivy-virtual-abbreviate 'full
      ivy-extra-directories '("./"))
  :config
  (ivy-mode +1))

Counsel

(use-package counsel
  :ensure t
  :bind
  (("M-x". counsel-M-x)
   ("C-x C-m" . counsel-M-x)
   ("C-x C-i" . counsel-imenu)
   ("C-c g p" . counsel-git-grep)
   ("C-x C-r" . counsel-recentf)))

icomplete

(use-package icomplete
  :ensure nil
  :config
  (icomplete-mode +1))

company mode

Company is a text completion framework for Emacs. The name stands for “complete anything”. It uses pluggable back-ends and front-ends to retrieve and display completion candidates.

(use-package company
  :ensure t
  :delight company-mode
  :init
  (setq company-show-numbers t
        company-idle-delay 0.25
        company-require-match 'never
        company-dabbrev-downcase nil
        company-dabbrev-ignore-case t
        company-minimum-prefix-length 2
        company-transformers '(company-sort-by-occurrence))
  :config
  (add-hook 'after-init-hook 'global-company-mode))

Also, we numbered all the candidates and the following code will enable us to choose the candidate based on its number. This solution was stolen from link with some customization and simplification to provide only what I saw useful.

(defun ora-company-number ()
  "Choose the candidate based on his number at candidate list."
  (interactive)
  (let* ((k (this-command-keys))
         (re (concat "^" company-prefix k)))
    (if (cl-find-if (lambda (s) (string-match re s)) company-candidates)
        (self-insert-command)
      (company-complete-number (string-to-number k)))))

(defun ora-activate-number ()
  "Activate the number-based choices in company."
  (interactive)
  (let ((map company-active-map))
    (mapc
     (lambda (x)
       (define-key map (format "%d" x) 'ora-company-number))
     (number-sequence 0 9))
    ;; (define-key map (kbd "<return>") nil)
    ))

(eval-after-load 'company
  '(ora-activate-number))

completion help

(use-package company-quickhelp
  :ensure t
  :after company
  :init
  (setq company-quickhelp-delay nil)
  :config
  (company-quickhelp-mode t)
  :bind (:map company-active-map
              ("M-h" . company-quickhelp-manual-begin)))

company roam backend

(use-package company-org-roam
  :ensure t
  :after company
  :config
  (push 'company-org-roam company-backends))

hippie expand

Hippie Expand is a more feature complete completion engine than the default dabbrev engine. The main feature I use over dabbrev is that is supports a wide range of backends for finding completions - dabbrev only looks at currently open buffers.

 (setq hippie-expand-try-functions-list
	   '(try-expand-dabbrev
	     try-expand-dabbrev-all-buffers
	     try-expand-dabbrev-from-kill
	     try-complete-file-name-partially
	     try-complete-file-name
	     try-expand-all-abbrevs
	     try-expand-list
	     try-expand-line
	     try-complete-lisp-symbol-partially
	     try-complete-lisp-symbol))

Then we override dabbrev-expand’s keybinding to use hippie-expand instead.

(define-key (current-global-map) [remap dabbrev-expand] 'hippie-expand)

Window

A window is an area of the screen that is used to display a buffer. In Emacs Lisp, windows are represented by a special Lisp object type.

Windows are grouped into frames. Each frame contains at least one window; the user can subdivide it into multiple, non-overlapping windows to view several buffers at once.

Emacs uses the word “window” with a different meaning than in graphical desktop environments and window systems, such as the X Window System. When Emacs is run on X, each of its graphical X windows is an Emacs frame. When Emacs is run on a text terminal, the frame fills the entire terminal screen.

scrolling

Improve the scroll experience on Emacs

(defun bk/scroll-up ()
  "Scroll only specific amount of lines. I don't like the defaults of whole screen."
  (interactive)
  (scroll-up-command 8))

(defun bk/scroll-down ()
  "Scroll only specific amount of lines. I don't like the defaults of whole screen."
  (interactive)
  (scroll-down-command 8))

(global-set-key (kbd "C-v") #'bk/scroll-up)
(global-set-key (kbd "M-v") #'bk/scroll-down)

popwin

popwin is a popup window manager for Emacs which makes you free from the hell of annoying buffers such like *Help*, *Completions*, and etc.

(use-package popwin
  :ensure t
  :config
  (push '("*cider-error*" :dedicated t :position bottom :stick t :noselect nil :height 0.4)
        popwin:special-display-config)
  (push '("*cider-doc*" :dedicated t :position bottom :stick t :noselect nil :height 0.4)
        popwin:special-display-config)
  (global-set-key (kbd "C-z") popwin:keymap)
  (popwin-mode 1))
KeyCommand
bpopwin:popup-buffer
lpopwin:popup-last-buffer
opopwin:display-buffer
C-bpopwin:switch-to-last-buffer
C-ppopwin:original-pop-to-last-buffer
C-opopwin:original-display-last-buffer
SPCpopwin:select-popup-window
spopwin:stick-popup-window
0popwin:close-popup-window
f, C-fpopwin:find-file
epopwin:messages
C-upopwin:universal-display
1popwin:one-window

ace window

Ease the task of changing window quickly.

(use-package ace-window
  :ensure t
  :config
  (global-set-key (kbd "C-c o") 'ace-window))

Don’t popup certain buffers

  (add-to-list 'display-buffer-alist
		   (cons "\\*Async Shell Command\\*.*"
			 (cons #'display-buffer-no-window nil)))
keyFunction
saw-swap-window
2aw-split-window-vert
3aw-split-window-horz
xaw-delete-window
??aw-dispatch-help

custom functions

Toggle window from:

Window A ++++++++ Window B

to

Window A : Window B

(defun toggle-window-split ()
  (interactive)
  (if (= (count-windows) 2)
      (let* ((this-win-buffer (window-buffer))
         (next-win-buffer (window-buffer (next-window)))
         (this-win-edges (window-edges (selected-window)))
         (next-win-edges (window-edges (next-window)))
         (this-win-2nd (not (and (<= (car this-win-edges)
                     (car next-win-edges))
                     (<= (cadr this-win-edges)
                     (cadr next-win-edges)))))
         (splitter
          (if (= (car this-win-edges)
             (car (window-edges (next-window))))
          'split-window-horizontally
        'split-window-vertically)))
    (delete-other-windows)
    (let ((first-win (selected-window)))
      (funcall splitter)
      (if this-win-2nd (other-window 1))
      (set-window-buffer (selected-window) this-win-buffer)
      (set-window-buffer (next-window) next-win-buffer)
      (select-window first-win)
      (if this-win-2nd (other-window 1))))))

(global-set-key (kbd "C-x |") 'toggle-window-split)

When splitting windows open the previous buffer in it.

(defun bk/vsplit-last-buffer ()
  "Split the window vertically and display the previous buffer."
  (interactive)
  (split-window-vertically)
  (other-window 1 nil)
  (switch-to-next-buffer))

(defun bk/hsplit-last-buffer ()
  "Split the window horizontally and display the previous buffer."
  (interactive)
  (split-window-horizontally)
  (other-window 1 nil)
  (switch-to-next-buffer))

(global-set-key (kbd "C-x 2") 'bk/vsplit-last-buffer)
(global-set-key (kbd "C-x 3") 'bk/hsplit-last-buffer)

clean it up after some time.

(use-package midnight
  :ensure t
  :config
  (midnight-delay-set 'midnight-delay "4:00am")
  (midnight-mode +1))

perspective

Perspective offers a new way to manage the display of windows and buffers.

(use-package perspective
  :ensure t
  :init
  (setq persp-sort 'access)
  :config
  (persp-mode +1))

same window

Same window buffers

(add-to-list 'same-window-buffer-names "*SQL*")
(add-to-list 'same-window-buffer-names "*Help*")
(add-to-list 'same-window-buffer-names "*Apropos*")
(add-to-list 'same-window-buffer-names "*Process List*")

winner

Winner is a built-in tool that keeps a record of buffer and window layout changes. It then allows us to move back and forth in the history of said changes. The mnemonic is related to Emacs default commands to operating on windows (C-x 4) and the undo operations with [uU] letter.

There are some buffers that winner will not restore, I list them in the winner-boring-buffers.

(use-package winner
  :ensure nil
  :hook (after-init . winner-mode)
  :init
  (setq winner-dont-bind-my-keys t)
  (setq winner-boring-buffers
	  '("*Completions*"
	    "*Compile-Log*"
	    "*inferior-lisp*"
	    "*Fuzzy Completions*"
	    "*Apropos*"
	    "*Help*"
	    "*cvs*"
	    "*Buffer List*"
	    "*Ibuffer*"
	    "*esh command on file*"))
  :bind (("C-x 4 u" . winner-undo)
	   ("C-x 4 U" . winner-redo)))

resize

(use-package windresize
  :ensure t
  :commands (windresize))

Alerts

alert

Alert is a growl-workalike for Emacs which uses a common notification interface and multiple, selectable styles, whose use is fully customized by the user.

(use-package alert
  :config
  (setq alert-default-style 'libnotify
        alert-log-messages t))

user configuration

Several packages uses Alert for sending notifications, so you have full control over them by customizing alert-user-configuration.

slack notifications

This was stolen from endless parentheses and adapt accordingly.

Shuts up!

(eval-after-load 'alert
  '(add-to-list 'alert-user-configuration
                '(((:category . "slack"))
                  log nil)))

Channels that I wish to only log the messages in the Alert buffer.

(eval-after-load 'alert
  '(add-to-list 'alert-user-configuration
                '(((:title . "\\(beginners\\|datomic\\|clojure\\|clojurescript\\|off-topic\\|datascript\\|core-async\\)")
                   (:category . "slack"))
                  log nil)))

However, there are a couple of important channels I would like to be notified about anything, so add a rule for them.

(eval-after-load 'alert
  '(add-to-list 'alert-user-configuration
                '(((:title . "\\(reitit\\|sql\\)")
                   (:category . "slack"))
                  libnotify nil)))

There are a few channel where I only need to pay attention if explicitly mentioned.

(eval-after-load 'alert
  '(add-to-list 'alert-user-configuration
                '(((:message . "@bartuka\\|Wanderson")
                   (:title . "\\(beginners\\)")
                   (:category . "slack"))
                  libnotify nil)))

telegram notifications

Let’s start by telling alert not to notify anything.

(eval-after-load 'alert
  '(add-to-list 'alert-user-configuration
                '(((:mode . "telega-chat-mode"))
                  log nil)))

However, if someone explicitly mention me, tell me pls.

(eval-after-load 'alert
  '(add-to-list 'alert-user-configuration
                '(((:message . "@bartuka\\|Wanderson")
                   (:mode . "telega-chat-mode"))
                  libnotify nil)))

custom functions

Some packages are too noisy.

(defun suppress-messages (func &rest args)
  "Suppress message output from FUNC."
  (cl-flet ((silence (&rest args1) (ignore)))
    (advice-add 'message :around #'silence)
    (unwind-protect
        (apply func args)
      (advice-remove 'message #'silence))))

Dired

Dired is very smart and usually finds the correct intent for some situations, and all of this is able through the DWIM variable. For example, if two buffers are open in the “dired” mode in different folders, if you git M to rename a file, it will move the file from folder A to B.

(setq dired-dwim-target t)

Add the following to have file sizes given in “human-readable” format.

(setq dired-listing-switches "-alh")

Omit certain files.

(setq dired-omit-files
      (rx (or (seq bol (? ".") "#")
              (seq bol "." eol))))

hide details

(add-hook 'dired-mode-hook
          (lambda ()
            (dired-hide-details-mode)
            (dired-sort-toggle-or-edit)))

guidelines

Group of guidelines to help me remember dired functionalities

mark files in dired

A very nice feature is to be able to edit Dired buffers as regular Emacs buffers. You can make several activities bearable using it, for more details follow this guide.

You can mark in Dired buffer based on a search using % m. By using the letter t we can toggle the marked files. There is also the command k that hide all the mark file from the current view.

You can always go back by pressing the better g

chorddescription
% mmark files based on search
ttoggle mark
khide marked files
grebuild the original tree
ilist the content of a sub-directory
C-x udired undo

replace text in multiple files

Start dired and mark files as described in Mark files in Dired, then use Q to run query-replace on all marked files.

functions

Some custom functions for Dired.

(require 'dired-x)

(defun bk/dired-xdg-open ()
  "Open the file at point with xdg-open."
  (interactive)
  (let ((file (dired-get-filename nil t)))
    (message "Opening %s..." file)
    (call-process "xdg-open" nil 0 nil file)
    (message "Opening %s done" file)))

(eval-after-load 'dired
  '(define-key dired-mode-map (kbd "O") 'bk/dired-xdg-open))
  (defun bk/dired-directories-first ()
"Sort dired listings with directories first."
(save-excursion
  (let (buffer-read-only)
    (forward-line 2)
    (sort-regexp-fields t "^.*$" "[ ]*." (point) (point-max)))
  (set-buffer-modified-p nil)))

  (advice-add 'dired-readin :after #'bk/dired-directories-first)

M-up is nicer in dired if it moves to the third line - straight to the “..”, which M-down is nicer if it moves to the last file and finally C-a moving back to start of files.

(defun dired-back-to-top ()
  (interactive)
  (beginning-of-buffer)
  (next-line 2)
  (dired-back-to-start-of-files))

(defun dired-back-to-bottom ()
  (interactive)
  (end-of-buffer)
  (next-line -1)
  (dired-back-to-start-of-files))

(defun dired-back-to-start-of-files ()
  (interactive)
  (backward-char (- (current-column) 2)))

Let’s bind the functions defined above so it can take effect in dired.

(eval-after-load 'dired
  '(progn
     (define-key dired-mode-map (kbd "M-p") 'dired-back-to-top)
     (define-key dired-mode-map (kbd "M-n") 'dired-back-to-bottom)
     (define-key dired-mode-map (kbd "C-a") 'dired-back-to-start-of-files)))

Bookmark

(require 'bookmark)
(setq bookmark-default-file (expand-file-name "bookmarks" user-emacs-directory)
      bookmark-save-flag 1)

TRAMP

If TRAMP makes backup files, they should be better be kept locally than remote.

(setq tramp-backup-directory-alist backup-directory-alist)

Version Control

Sane config for ediff which is basically removing noisy highlights, avoiding crazy multi-frames setup, ignoring some whitespaces and windows should be side-by-side.

(use-package ediff
  :init
  (setq ediff-highlight-all-diffs nil)
  (setq ediff-window-setup-function 'ediff-setup-windows-plain)
  (setq ediff-diff-options "-w")
  (setq ediff-split-window-function 'split-window-horizontally))

magit

A git porcelain inside Emacs. Magit is an interface to the version control system Git, implemented as an Emacs package. Magit aspires to be a complete Git porcelain, look for more info at here.

(use-package magit
  :ensure t
  :init
  <<magit-setup>>
  :config
  (add-to-list 'magit-no-confirm 'stage-all-changes))

magit setup

(setq magit-diff-refine-hunk t
      magit-revert-buffers 'silent
      magit-commit-arguments '("--verbose")
      magit-process-popup-time 10)

magit hydra

And what about another hydra? Magit deserves everything.

(defhydra hydra-magit (:color blue)
  ("q" nil "quit" :column "Magit")
  ("b" magit-blame "blame" :column "Do")
  ("c" magit-clone "clone" :column "Do")
  ("i" magit-init "init" :column "Do")
  ("s" magit-status "status" :column "Do")
  ("t" git-timemachine "time-travel" :column "TimeMachine"))

(global-set-key (kbd "C-c g g") 'hydra-magit/body)

forge

Very detailed manual about working with Github and Gitlab from Magit.

(use-package forge
  :ensure t)

git config

gitconfig is a major mode for editing gitconfig files.

     (use-package gitconfig-mode
	:ensure t
	:config
	(require 'gitconfig-mode))

git ignore

git-modes has a major mode for editing gitignore files.

     (use-package gitignore-mode
	:ensure t
	:config
	(require 'gitignore-mode))

time machine

(use-package git-timemachine :ensure t)

visual identification

Show differences between local and remote repo.

(use-package diff-hl
  :ensure t
  :init
  (setq diff-hl-side 'left)
  :config
  (add-hook 'dired-mode-hook 'diff-hl-dired-mode)
  (diff-hl-flydiff-mode)
  (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)
  (global-diff-hl-mode)
  
  (custom-set-faces
   '(diff-hl-change ((t (:background "#3a81c3"))))
   '(diff-hl-insert ((t (:background "#7ccd7c"))))
   '(diff-hl-delete ((t (:background "#ee6363"))))))

miscellaneous

(use-package browse-at-remote :ensure t)
(use-package gitconfig-mode :ensure t)
(use-package gitignore-templates :ensure t)

custom functions

visit pull request

(defun bk/visit-pull-request-url ()
  "Visit the current branch's PR on Github."
  (interactive)
  (browse-url
   (format "https://github.com/%s/pull/new/%s"
           (replace-regexp-in-string
            "\\`.+github\\.com:\\(.+\\)\\.git\\'" "\\1"
            (magit-get "remote"
                       (magit-get-remote)
                       "url"))
           (magit-get-current-branch))))

Search

wgrep

(use-package wgrep
  :ensure t)

visual regexp

(use-package visual-regexp
  :ensure t
  :bind (("C-c r" . vr/replace)
         ("C-c %" . vr/query-replace)
         ("<C-m> /" . vr/mark)))

grep fullscreen

This function help me day by day, every single version of my setup had this beauty in it.

(defun bk/rgrep-fullscreen (regexp &optional files dir confirm)
  "Open grep in full screen, saving windows and searching for REGEXP.
in FILES and DIR without CONFIRM."
  (interactive
   (progn
     (grep-compute-defaults)
     (let* ((regexp (grep-read-regexp))
            (files (grep-read-files regexp))
            (dir (ido-read-directory-name "Base directory: "
                                          nil default-directory t))
            (confirm (equal current-prefix-arg '(4))))
       (list regexp files dir confirm))))
  (window-configuration-to-register ?$)
  (rgrep regexp files dir confirm)
  (switch-to-buffer "*grep*")
  (delete-other-windows)
  (goto-char (point-min)))

(defun rgrep-quit-window ()
  "Simply jump to the register where all your windows are."
  (interactive)
  (kill-buffer)
  (jump-to-register ?$))

(defun rgrep-goto-file-and-close-rgrep ()
  "Go to file and close rgrep window."
  (interactive)
  (compile-goto-error)
  (kill-buffer "*grep*")
  (delete-other-windows)
  (message "Type C-x r j $ to return to pre-rgrep windows."))

ripgrep

(use-package rg
  :ensure t
  :config
  (rg-define-search bk/search-git-root-or-dir
    :query ask
    :format regexp
    :files "everything"
    :dir (let ((vc (vc-root-dir)))
    	     (if vc
    		 vc
    	       default-directory))
    :confirm prefix
    :flags ("--hidden -g !.git"))
  :bind
  ("M-s g" . bk/search-git-root-or-DIR))

isearch

You can invoke it using C-s and typing your desired search string. Also, if you want to use the regexp flavour you can use M-C-s.

Run C-h k C-s yo get an awesome help menu with all the extra keys you can use with isearch. These are the ones I use the most:

KeybindingsDescription
C-ssearch forward
C-rsearch backward
M-C-ssearch forward using regexp
M-C-rsearch backward using regexp
C-s C-wsearch word at point
M-sis a prefix while in isearch mode
(while isearch activated) M-rturn your regular isearch into regexp mode
M-s .search for thing at point
M-s oget the results in occur buffer
M-s h rhighlight regexp
M-s h uundo the highlight
C-s M-rtoggle regexp search

occur

Let’s use an occur snippet from (or emacs. It will offer as the default candidate:

  • the current region, if it’s active
  • the current symbol, otherwise
  (defun occur-dwim ()
    "Call `occur' with a sane default."
    (interactive)
    (push (if (region-active-p)
		 (buffer-substring-no-properties
		  (region-beginning)
		  (region-end))
	       (let ((sym (thing-at-point 'symbol)))
		 (when (stringp sym)
		   (regexp-quote sym))))
	     regexp-history)
    (call-interactively 'occur))

  (global-set-key (kbd "M-s o") 'occur-dwim)

google this

Artur Malabarba has a nice package called google-this which provides a set of functions for querying google from emacs.

(use-package google-this
  :ensure t
  :delight google-this-mode
  :config
  (google-this-mode 1))

This package provides a set of functions under the prefix C-c /. The simplest is C-c / RET which prompts you for a search in the minibuffer, with a default search based on the text around the point.

KeysFunction
C-c / SPCgoogle-this-region
C-c / agoogle-this-ray
C-c / cgoogle-this-translate-query-or-region
C-c / egoogle-this-error
C-c / fgoogle-this-forecast
C-c / ggoogle-this-lucky-search
C-c / igoogle-this-lucky-and-insert-url
C-c / mgoogle-maps
C-c / ngoogle-this-noconfirm
C-c / rgoogle-this-cpp-reference
C-c / sgoogle-this-symbol
C-c / tgoogle-this
C-c / wgoogle-this-word
C-c / <return>google-this-search

Shell

eshell mode

change defaults

(use-package eshell
  :ensure nil
  :config
  (setq eshell-hist-ignoredups t
        eshell-ls-initial-args "-h"))

clear buffer

(defun eshell-clear-buffer ()
  "Clear the terminal buffer."
  (interactive)
  (let ((inhibit-read-only t))
    (erase-buffer)
    (eshell-send-input)))

(add-hook 'eshell-mode-hook
          (lambda ()
            (local-set-key (kbd "C-l") 'eshell-clear-buffer)))

bookmark

(use-package eshell-bookmark
  :ensure t
  :config
  (add-hook 'eshell-mode-hook 'eshell-bookmark-setup))

aliases

(require 'em-alias)
(add-hook 'eshell-mode-hook
          (lambda ()
            (eshell/alias "e" "find-file $1")
            (eshell/alias "ee" "find-file-other-window $1")))

shell mode

(use-package shell
  :bind (:map shell-mode-map
              ("<s-up>" . comint-previous-input)
              ("<s-down>" . comint-next-input))
  :init
  (dirtrack-mode)
  (setq explicit-shell-file-name "/bin/bash")
  :config
  (add-hook 'after-save-hook
            'executable-make-buffer-file-executable-if-script-p))

General Programming

Sometimes I place some TODO and FIXME words in the middle of my code so I can come back to it latter and work on the subjects. The following snippet will highlight these words to help me identify them.

(add-hook 'prog-mode-hook (defun bk--add-watchwords ()
                            (font-lock-add-keywords
                             nil `(("\\<\\(FIX\\(ME\\))?\\|TODO\\)"
                                    1 font-lock-warning-face t)))))

There is a package to help cycle quotes when in strings.

(use-package cycle-quotes
  :ensure t
  :commands (cycle-quotes))

toggle between src and tests

Toggle Test allows you to quickly switch between test and test subject. This is very useful tool to have when you are TDDing.

  • Allows you to quickly navigate between test and source files
  • It creates the test or source file if it not exists
  • If there are many choices, the package present them to us
  • This is language agnostic, therefore you need to configure it.
  • You can work with multiple projects at the same time.
(use-package toggle-test
  :ensure t
  :init
  (setq tgt-open-in-new-window nil)
  :config
  (global-set-key (kbd "s-t") 'tgt-toggle)

  ;; the setup lives in a .dir-locals.el now.
  ;;(put 'tgt-projects 'safe-local-variable #'listp)
  (add-to-list 'tgt-projects '((:root-dir "/home/wand/platform/banker")
                               (:src-dirs "src")
                               (:test-dirs "test")
                               (:test-suffixes "_test"))))

This is an example of .dir-locals.el content file.

((nil . ((tgt-projects . (((:root-dir "~/foo/bar") (:src-dirs "src") (:test-dirs "test") (:test-suffixes "_test")))))))

whitespaces

Control your whitespaces!

(require 'whitespace)
(setq whitespace-style '(trailing lines space-before-tab
                  indentation space-after-tab))
(setq whitespace-line-column 100)
(whitespace-mode +1)

A less intrusive ‘delete-trailing-whitespaces’ on save.

(use-package ws-butler
  :ensure t
  :delight ws-butler-mode
  :config
  (add-hook 'prog-mode-hook #'ws-butler-mode))

hungry delete

Hungry delete mode

(use-package hungry-delete
  :ensure t
  :config
  (add-hook 'prog-mode-hook 'hungry-delete-mode))

parenthesis

paredit

Paredit is great, it brings structural editing to lisps, maintaining the syntactical correctness of your code.

Follow this animated guide to paredit to learn more.

(use-package paredit
  :ensure t
  :pin melpa
  :bind (:map paredit-mode-map
              ("[")
              ("M-k" . paredit-raise-sexp)
              ("M-I" . paredit-splice-sexp)
              ("C-c ( n" . paredit-add-to-next-list)
              ("C-c ( p" . paredit-add-to-previous-list))
  :bind (:map emacs-lisp-mode-map ("<return>" . paredit-newline))
  :bind (:map lisp-mode-map ("<return>" . paredit-newline))
  :config
  (add-hook 'emacs-lisp-mode-hook 'enable-paredit-mode)
  (add-hook 'lisp-mode-hook 'enable-paredit-mode)
  (add-hook 'clojure-mode-hook 'enable-paredit-mode)
  (add-hook 'cider-mode-hook 'enable-paredit-mode))

DISABLED smartparens

Smartparens is a minor mode that deals with parens pairs and tries to be smart about it. I am a paredit user for a long time, I decided to switch to smartparens to experiment this new package which seems to have many more features than paredit. I’ve been here for almost 6 months now, I am yet not sold out in the smartparens idea, for some reason I like the feel of paredit. Still need to dedicate more time to use this in its fullest.

(use-package smartparens
  :ensure t
  :delight smartparens-strict-mode
  :disabled t
  :init
  (setq sp-highlight-pair-overlay nil
        sp-base-key-bindings 'paredit
        sp-autoskip-closing-pair 'always
        sp-hybrid-kill-entire-symbol nil)
  :config
  (add-hook 'lisp-mode-hook #'smartparens-strict-mode)
  (add-hook 'emacs-lisp-mode-hook #'smartparens-strict-mode)

  (with-eval-after-load "smartparens"
    ;; remove some pairs
    (sp-pair "'" nil :actions :rem)
    (sp-pair "`" nil :actions :rem)

    ;; include new wrap of pairs
    (sp-pair "(" ")" :wrap "M-(")
    (sp-pair "[" "]" :wrap "M-[")

    (sp-use-paredit-bindings)

    (sp-local-tag 'markdown-mode "c" "```clojure" "```")
    (sp-local-tag 'markdown-mode "e" "```elisp" "```")
    (sp-local-tag 'markdown-mode "b" "```bash" "```")
    (sp-local-tag 'markdown-mode "p" "```python" "```")

    (define-key smartparens-mode-map (kbd "M-p") 'sp-prefix-pair-object)))

I stole this from here, where Alvaro Ramirez is exemplifying a nice feature of Xcode about auto-indent after opening parens. For sure we should be able to do that in smartparens:

(defun indent-between-pair (&rest _ignored)
  (newline)
  (indent-according-to-mode)
  (forward-line -1)
  (indent-according-to-mode))

(eval-after-load 'smartparens-strict-mode
  '(progn
     (sp-local-pair 'prog-mode "{" nil :post-handlers '((indent-between-pair "RET")))
     (sp-local-pair 'prog-mode "[" nil :post-handlers '((indent-between-pair "RET")))
     (sp-local-pair 'prog-mode "(" nil :post-handlers '((indent-between-pair "RET")))))

folding

folding based on indentation and syntax

Origami is a text folding minor mode for Emacs.

(use-package origami
  :ensure t
  :commands (origami-toggle-node))

There is a vast set of commands to enable the manipulation of folds.

functiondesctiption
origami-toggle-nodetoggle open or closed a fold node
origami-show-only-nodeclose everything but the folds at point
origami-recursively-toggle-nodeacts like org-mode header collapsing
origami-undoundo the last undone folding operation
origami-redoredo the last undone folding operation

Let’s build a hydra for it too.

(defhydra bk/hydra-origami (:color red)
  "
  _o_pen node    _n_ext fold       toggle _f_orward
  _c_lose node   _p_revious fold   toggle _a_ll
  "
  ("o" origami-open-node)
  ("c" origami-close-node)
  ("n" origami-next-fold)
  ("p" origami-previous-fold)
  ("f" origami-forward-toggle-node)
  ("a" origami-toggle-all-nodes)
  ("t" origami-toggle-node))

(global-set-key (kbd "C-x @") 'bk/hydra-origami/body)

fold regions based on selection

Syntax based folding is great and all but sometimes I need to fold some random piece of text and Vimish fold is great for that.

(use-package vimish-fold
  :ensure t
  :commands (vimish-fold-toggle
             vimish-fold))

Let’s make a hydra for vimish fold.

(defhydra bk/hydra-vimish-fold (:color red :hint nil)
  "
 _f_: fold  _u_: unfold  _r_: refold  _t_: toggle  _d_: delete    _n_: next      _q_: quit
          _U_: Unfold  _R_: Refold  _T_: Toggle  _D_: Delete    _p_: previous
  "
  ("f" vimish-fold)
  ("u" vimish-fold-unfold)
  ("r" vimish-fold-refold)
  ("t" vimish-fold-toggle)
  ("d" vimish-fold-delete)
  ("U" vimish-fold-unfold-all)
  ("R" vimish-fold-refold-all)
  ("T" vimish-fold-toggle-all)
  ("D" vimish-fold-delete-all)
  ("n" vimish-fold-next-fold)
  ("p" vimish-fold-previous-fold)
  ("q" nil :color blue))

Bind the hydra to a key.

(global-set-key (kbd "C-c @") 'bk/hydra-vimish-fold/body)

go to definition

dumb jump

Dump jump is a simple package that uses the grep or ag to jump to the source of the symbol at point. This is the fallback when language specific navigation is not possible.

(use-package dumb-jump
  :ensure t
  :bind (("C-c ." . dumb-jump-go))
  :config
  (dumb-jump-mode 1))

documentation

Zeal is a nice little app that stores documents offline for reference inspired by Dash which only works on Mac. Let’s bring that to Emacs.

You need to install Zeal in your machine, yaourt -S zeal.

(use-package zeal-at-point
  :ensure t
  :bind (("C-c I" . zeal-at-point))
  :config
  (add-hook 'clojure-mode-hook
            (lambda ()
              (setq zeal-at-point-docset "clojure"))))

programming languages

clojure

Unfortunately, Emacs does not have a builtin major mode for Clojure, however we have a great community that support any programming language available in the world as a major mode of emacs rsrs.

The intent of a major mode is basically provide font-lock, indentation, navigation and refactoring for the target programming language.

prettify symbols

Pretty symbols for Clojure removed from spacemacs clojure layer.

(defun clojure/fancify-symbols (mode)
  "Pretty symbols for Clojure's anonymous functions and sets,
   like (λ [a] (+ a 5)), ƒ(+ % 5), and ∈{2 4 6}."
  (font-lock-add-keywords mode
    `(("(\\(fn\\)[\[[:space:]]"
       (0 (progn (compose-region (match-beginning 1)
                                 (match-end 1) "λ"))))
      ("(\\(partial\\)[\[[:space:]]"
       (0 (progn (compose-region (match-beginning 1)
                                 (match-end 1) "Ƥ"))))
      ("(\\(comp\\)[\[[:space:]]"
       (0 (progn (compose-region (match-beginning 1)
                                 (match-end 1) ""))))
      ("\\(#\\)("
       (0 (progn (compose-region (match-beginning 1)
                                 (match-end 1) "ƒ"))))
      ("\\(#\\){"
       (0 (progn (compose-region (match-beginning 1)
                                 (match-end 1) "")))))))

clojure mode

At the clojure-mode website recommends us to use the MELBA Stable bundle because the MELPA version is following a development branch of the library. As this mode is very important for me right now, I would like to stick to the more stable branch.

(use-package clojure-mode
  :ensure t
  :delight (clojure-mode "λ")
  :init
  (setq clojure-align-forms-automatically nil)
  :config

  (defadvice clojure-test-run-tests (before save-first activate)
    (save-buffer))

  (defadvice nrepl-load-current-buffer (before save-first activate)
    (save-buffer))

  (add-hook 'clojure-mode-hook #'eldoc-mode)
  (add-hook 'clojure-mode-hook #'subword-mode)

  (dolist (m '(clojure-mode clojurescript-mode
                            clojurec-mode
                            clojurex-mode))
    (clojure/fancify-symbols m)))

The previous setting clojure-align-forms-automatically makes the following example a default behavior and you don’t need to manually align the values. **NOTE**: this is an experiment, 90% of the time this happened to me, that was the default behavior I wanted. Let’s see how much the other 10% will annoy me now. EDIT: These 10% are really bad, I turned it off again.

(def my-map
  {:a-key 1
   :other-key 2})

;; after C-c SPC
(def my-map
  {:a-key     1
   :other-key 2})

clojure refactor

There are several incredible examples of refactoring in the clojure-mode website.

  1. TODO: Study refactoring support in clojure-mode.

Provides additional refactoring support, but as we see from the clojure-mode github page, all these extra functionalities are migrating to the clojure mode package.

(use-package clj-refactor
  :ensure t
  :delight clj-refactor-mode
  :after clojure-mode
  :init
  (setq cljr-favor-prefix-notation nil
        cljr-auto-sort-ns nil)
  (setq cljr-magic-require-namespaces '(("io" . "clojure.java.io")
                                        ("set" . "clojure.set")
                                        ("walk" . "clojure.walk")
                                        ("zip" . "clojure.zip")
                                        ("time" . "clj-time.core")
                                        ("log" . "clojure.tools.logging")
                                        ("json" . "cheshire.core")
                                        ("client" . "org.httpkit.client")
                                        ("http" . "clj-http.core")
                                        ("a" . "clojure.core.async")
                                        ("jdbc" . "next.jdbc")
                                        ("s" . "clojure.spec.alpha")
                                        ("gen" . "clojure.spec.gen.alpha")))
  :config
  (add-hook 'clojure-mode-hook (lambda ()
                                 (clj-refactor-mode t)
                                 (cljr-add-keybindings-with-prefix "C-c C-m"))))

eval sexp fu

(use-package eval-sexp-fu
  :ensure t
  :config
  (use-package cider-eval-sexp-fu
    :ensure t))

additional font lock

We also improved the font-locking for built-in methods and macros of clojure.

(use-package clojure-mode-extra-font-locking
  :ensure t
  :after clojure-mode
  :config
  (require 'clojure-mode-extra-font-locking))

Now comes the real deal for Clojure development, CIDER extends Emacs with support for interactive programming in Clojure. It basically connects the buffer to a nREPL and communicate back-and-forth to provide functionalities such as code completion, documentation, navigation, debugging, running tests, and many more.

  1. TODO: Study cider mode

cider

(use-package cider
  :ensure t
  :pin melpa-stable
  :after clojure-mode
  :init
  (setq cider-mode-line " CIDER"
        cider-repl-result-prefix ";; => "
        cider-save-file-on-load nil
        cider-prompt-for-symbol nil
        cider-repl-use-clojure-font-lock t
        cider-repl-display-in-current-window t
        cider-repl-use-pretty-printing t
        cider-auto-select-error-buffer t
        cider-auto-select-test-report-buffer nil
        cider-test-show-report-on-success t
        cider-repl-pop-to-buffer-on-connect nil
        cider-stacktrace-default-filters '(tooling dup))
  :config
  (add-hook 'cider-mode-hook #'eldoc-mode)
  (add-hook 'cider-repl-mode-hook #'cider-company-enable-fuzzy-completion)
  (add-hook 'cider-mode-hook #'cider-company-enable-fuzzy-completion))

When cider is not connected, I usually use some commands that makes no sense in clojure-mode and receive a non-sense error message that I never understand what is happening or even worse it just hands without no feedback.

I will borrow the idea from Alex Baranosky and create a dummy function to provide some useful feedback message to my future self.

(defun bk/nrepl-warn-when-not-connected ()
  (interactive)
  (message "Oops! You're not connected to an nREPL server. Please run M-x cider or M-x cider-jack-in to connect"))

And bind this to the most common keys that requires cider activated.

(define-key clojure-mode-map (kbd "C-x C-e") 'bk/nrepl-warn-when-not-connected)
(define-key clojure-mode-map (kbd "C-c C-k") 'bk/nrepl-warn-when-not-connected)
(define-key clojure-mode-map (kbd "C-c C-z") 'bk/nrepl-warn-when-not-connected)

kaocha runner

Package for running Kaocha tests via CIDER

(use-package kaocha-runner
  :ensure t
  :after clojure-mode)

custom functions

bk/repl for testing ideas fast

Often I need to fire a repl and investigate some properties better, I have a temp project setup in my machine a simple lein new temp where I have some libraries already in the project.clj dependency available. The following function helps me get there quickly and require some frequent namespaces.

(defun bk/repl ()
  "Start an interactive repl in a temp project"
  (interactive)
  (cider-jack-in '(:project-dir "/home/wand/temp"))
  (add-hook 'cider-connected-hook
        (lambda ()
      (cider-repl-set-ns "user")
      (cider-nrepl-sync-request:eval "(require '[clj-time.core :as t])")
      (cider-nrepl-sync-request:eval "(require '[clj-http.core :as client])")
      (cider-nrepl-sync-request:eval "(require '[org.httpkit.client :as http])")
      (cider-nrepl-sync-request:eval "(require '[clojure.core.async :as a])")
      (cider-nrepl-sync-request:eval "(require '[cheshire.core :as json])"))))
great users with a nice random clojure docstring

Let’s make a nice usage of babashka scripting for clojure and print a random doc-string message in the initial of my Emacs session.

(let ((clj-docstring (shell-command-to-string "docstring.clj")))
  (when clj-docstring
    (setq initial-scratch-message clj-docstring)))

The docstring.clj content is pretty small and it required babashka to be installed, the content:

#!/usr/bin/env bb

(require '[clojure.repl])

(defmacro random-doc []
  (let [sym (-> (ns-publics 'clojure.core) keys rand-nth)]
    (if (:doc (meta (resolve sym)))
      `(clojure.repl/doc ~sym)
      `(random-doc))))

(random-doc)

I added the new file to my PATH variable. That’s all.

(defun bk/clj-random-docstring ()
  "Random doc-string into new buffer."
  (interactive)
  (let ((docstring (shell-command-to-string "docstring.clj"))
        (buffer-name "*Clojure Random Docs*"))
    (when (get-buffer buffer-name)
      (kill-buffer buffer-name))
    (get-buffer-create buffer-name)
    (with-current-buffer buffer-name (insert docstring))
    (switch-to-buffer-other-window buffer-name)
    (special-mode)))
clojure display error buffer
(defun bk/cider-display-error-buffer (&optional arg)
  "Displays the *cider-error* buffer in the current window.
If called with a prefix argument, uses the other window instead."
  (interactive "P")
  (let ((buffer (get-buffer cider-error-buffer)))
    (when buffer
      (funcall (if (equal arg '(4))
                   'switch-to-buffer-other-window
                 'switch-to-buffer)
               buffer))))

Clojure rocks!

common lisp

(use-package slime
  :ensure t
  :config
  (setq inferior-lisp-program "sbcl")
  (slime-setup '(slime-fancy slime-quicklisp slime-asdf)))

elisp

Some general packages to improve the already great experience with Elisp. There are already incredible advice’s on code development in Elisp at the official documentation: here.

nameless

Hide package namespace in your emacs-lisp code. Check the github page from Malabarba.

(use-package nameless
  :ensure t
  :config
  (add-hook 'emacs-lisp-mode-hook #'nameless-mode))

tips and tricks

Most of this section was stolen from John Wiegley talk:

  1. master paredit
  2. C-M-x will evaluate the definition while your cursor is anywhere inside the forms
  3. M-; will open an eval option in the minibuffer
  4. C-x C-e pp-eval-last-sexp
  5. Enable, debug-on-error if necessary
    1. enable edebug
      1. SPC move you forward
      2. ? will provide which keys you have available
      3. you can call (debug) inside any form

python

(defun bk/elpy-setup ()
  (pyvenv-activate "~/miniconda3")
  (delete `elpy-module-django elpy-modules)
  (delete `elpy-module-highlight-indentation elpy-modules))

(use-package elpy
  :ensure t
  :config
  (add-hook 'python-mode-hook #'elpy-enable)
  (add-hook 'python-mode-hook #'bk/elpy-setup))

(use-package py-autopep8
  :ensure t
  :after elpy
  :init
  (setq py-autopep8-options '("--max-line-length=250"))
  :config
  (add-hook 'elpy-mode-hook 'py-autopep8-enable-on-save))

sql

(use-package sqlup-mode
  :ensure t
  :config
  (add-hook 'sql-mode-hook 'sqlup-mode)
  (add-hook 'sql-interactive-hook 'sqlup-mode)
  (add-to-list 'sqlup-blacklist "name"))

This Emacs library provides commands and a minor mode for easily reformating SQL using external programs such as pgformatter which can be installed in Arch Linux using yaourt -S pgformatter-git

(use-package sqlformat
  :ensure t
  :init
  (setq sqlformat-command 'pgformatter)
  :config
  (add-hook 'sql-mode-hook 'sqlformat-on-save-mode))

Indentation is also important

(use-package sql-indent
  :ensure t
  :delight sql-mode "Σ "
  :after (:any sql sql-interactive-mode)
  :config
  (add-hook 'sql-mode-hook 'sqlind-minor-mode))

latex

(use-package tex
  :defer t
  :ensure auctex
  :config
  (setq TeX-view-program-selection '((output-pdf "PDF Tools"))
        TeX-view-program-list '(("PDF Tools" TeX-pdf-tools-sync-view))
        TeX-source-correlate-start-server t)

  (add-hook 'TeX-after-compilation-finished-functions
            #'TeX-revert-document-buffer))
(use-package reftex
  :ensure t
  :config
  (setq reftex-cite-prompt-optional-args t))

(setq TeX-auto-save t
      TeX-parse-self t
      TeX-save-query nil
      TeX-PDF-mode t)
(add-hook 'LaTeX-mode-hook 'visual-line-mode)
(add-hook 'LaTeX-mode-hook 'flyspell-mode)
(add-hook 'LaTeX-mode-hook 'Latex-math-mode)
(add-hook 'LaTeX-mode-hook 'turn-on-reftex)

(with-eval-after-load 'tex
  (add-to-list 'safe-local-variable-values
               '(TeX-command-extra-options . "-shell-escape")))

tla plus

Using formal math to specify programs? I have to check this out!!

(use-package tla-mode
  :ensure nil
  :mode "\.tla$")

linters

Flycheck is a modern on-the-fly syntax checking extension for GNU Emacs, intended as replacement for the older Flymake.

(use-package flycheck
  :ensure t
  :config
  (setq flycheck-check-syntax-automatically '(mode-enabled save)))

(use-package flycheck-clj-kondo :ensure t)

A very important command you should remember is C-c ! v or (M-x flycheck-verify-setup) that can help you verify for your current mode if everything is fine with your linter and it’s backend.

The following package implements a minor-mode for displaying errors from Flycheck right below their reporting location, using overlays.

(use-package flycheck-inline
  :ensure t
  :after flycheck
  :config
  (add-hook 'flycheck-mode-hook #'flycheck-inline-mode))

Integrate Unified Modeling Language with flycheck to automatically check the syntax of your plantuml files on the fly.

(use-package flycheck-plantuml
  :ensure t
  :after flycheck
  :config
  (flycheck-plantuml-setup))

unified modeling language

The UML is a general-purpose, developmental, modeling language in the field of software engineering that is intended to provide a standard way to visualize the design of a system.

  1. any activities (jobs)
  2. individual components of the system
  3. how the system will run
  4. how entities interact with others
  5. external user interfaces

The UML diagrams represent two different views of a system model

  • Static (or structural) view: emphasizes the static structure of the system using objects, attributes, operations and relationships. It includes class diagrams and composite structure diagrams.
  • Dynamic (or behavioral) view: emphasizes the dynamic behavior of the system by showing collaborations among objects and changes to the internal states of objects. This view includes sequence diagrams, activity diagrams and state machine diagrams.

Let’s see a very interesting cheatsheet now:

./images/uml-1.png

./images/uml-2.png

./images/uml-3.png

The internal setup in order to use it will happen though PlantUML which has an specific syntax but is very easy to pick it up, follow examples at the official documentation at webpage.

(use-package plantuml-mode
  :ensure t
  :mode ("\\.plantuml\\'" "\\.puml\\'")
  :init
  (setq org-plantuml-jar-path "/home/wand/plantuml.jar")
  :config
  (require 'ob-plantuml))

how do I do it?

Do you find yourself constantly Googling for how to do basic programming tasks? Suppose you want to know how to format a date in bash. Why open your browser and read through blogs when you can just M-x howdoi RET format date bash RET.

(defun bk/howdoi ()
  "Call `howdoi' and ask for help"
  (interactive)
  (let* ((query (read-from-minibuffer "Query: "))
         (docstring (shell-command-to-string (concat "howdoi " query)))
         (buffer-name "*How do I do it?*"))
    (when (get-buffer buffer-name)
      (kill-buffer buffer-name))
    (get-buffer-create buffer-name)
    (with-current-buffer buffer-name (insert docstring))
    (switch-to-buffer-other-window buffer-name)
    (special-mode)))

Do not forget to install the command line utility. yaourt -S howdoi

custom functions

align blocks

Emacs has the built-in command align-regexp to align via regular expression. However, it is not very straightforward, especially since I don’t know much Emacs regular expressions. Hence, let’s use some wrapper functions to ease the life in the moment of danger =)

(defun bk/align-whitespace (start end)
  "Align columns by whitespace"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\)\\s-" 1 0 t))

(defun bk/align-ampersand (start end)
  "Align columns by ampersand"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\)&" 1 1 t))

(defun bk/align-quote-space (start end)
  "Align columns by quote and space"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\).*\\s-\"" 1 0 t))

(defun bk/align-equals (start end)
  "Align columns by equals sign"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\)=" 1 0 t))

(defun bk/align-comma (start end)
  "Align columns by comma"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\)," 1 1 t))

(defun bk/align-dot (start end)
  "Align columns by dot"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\)\\\." 1 1 t))

(defun bk/align-colon (start end)
  "Align columns by equals sign"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\):" 1 0 t))

Additional Major Modes

I started to craft a emacs theme, and to do that I had to enable rainbow-mode to help me identifying RGB colors. Very useful in this circumstances.

(use-package rainbow-mode
  :ensure t
  :commands (rainbow-mode))

rest client

(use-package restclient
  :ensure t
  :config
  (add-to-list 'auto-mode-alist '("\\.restclient\\'" . restclient-mode)))

(use-package company-restclient
  :ensure t
  :after company
  :config
  (add-to-list 'company-backends 'company-restclient))

create a tmp restclient buffer

(defun bk/restclient ()
  "Open the restclient buffer."
  (interactive)
  (with-current-buffer (get-buffer-create "*restclient*")
    (restclient-mode)
    (pop-to-buffer (current-buffer))))

edn

Emacs lisp library for reading and writing the data format edn.

(use-package edn
  :ensure t)

markdown

(use-package markdown-mode
  :ensure t
  :config
  (add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))
  (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))
  (add-to-list 'auto-mode-alist '("README\\.md\\'" . gfm-mode)))
     (eval-after-load 'markdown-mode
	 '(progn
	    ;; `pandoc' is better than obsolete `markdown'
	    (when (executable-find "pandoc")
	      (setq markdown-command "pandoc -f markdown"))))

Edit markdown code block like Org.

(use-package edit-indirect
  :ensure t
  :defer t)

json

(use-package json-mode
  :ensure t
  :config
  (add-to-list 'auto-mode-alist '("\\.json\\'" . json-mode)))

xml

(require 'nxml-mode)

(push '("<\\?xml" . nxml-mode) magic-mode-alist)

;; pom files should be treated as xml files
(add-to-list 'auto-mode-alist '("\\.pom$" . nxml-mode))

(setq nxml-child-indent 4
      nxml-attribute-indent 4
      nxml-auto-insert-xml-declaration-flag nil
      nxml-bind-meta-tab-to-complete-flag t
      nxml-slash-auto-complete-flag t)

yaml

Unfortunately, I have to deal with YAML files on my daily basis.

(use-package yaml-mode
  :ensure t
  :config
  (add-hook 'yaml-mode-hook 'whitespace-mode)
  (add-hook 'yaml-mode-hook 'subword-mode))

makefile

(use-package make-mode
  :ensure t
  :mode (("Makefile" . makefile-gmake-mode)))

PDF

PDF Tools is, among other things, a replacement of DocView for PDF files. The key difference is that pages are not pre-rendered by e.g. ghostscript and stored in the file-system, but rather created on-demand and stored in memory.

(use-package pdf-tools
  :ensure t
  :defer 1
  :magic ("%PDF" . pdf-view-mode)
  :init (pdf-tools-install :no-query))

(use-package pdf-view
  :ensure nil
  :after pdf-tools
  :bind (:map pdf-view-mode-map
		("C-s" . isearch-forward)
		("d" . pdf-annot-delete)
		("h" . pdf-annot-add-highlight-markup-annotation)
		("t" . pdf-annot-add-text-annotation))
  :custom
  (pdf-view-display-size 'fit-page)
  (pdf-view-resize-factor 1.1)
  (pdf-view-use-unicode-ligther nil))

Org mode

Org mode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system.

literate programming

Literate Programming is the art of preparing programs for human readers.

“Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to humans what we want the computer to do.” - Donald E. Knuth, 1984.

what is noweb?

noweb is designed to meet the needs of literate programmers while remaining as simple as possible.

The gist of using noweb is that in your source blocks you have labels like <<imports>>, that refer to other named code blocks that get substituted in place of the label.

table of contents

(use-package toc-org
  :ensure t
  :init
  (setq toc-org-max-depth 3)
  :config
  (add-hook 'org-mode-hook 'toc-org-mode))

I need to control the window that pops up when I open the Org Src buffer to edit code.

(setq org-src-window-setup 'current-window)

configuration

defaults

When using RET over a link, please go to it.

(setq org-return-follows-link t)

Please, disable flycheck from org-src buffers. We always have errors in there related to some emacs-lisp checkers. Here is how to disable it.

(defun disable-flycheck-in-org-src-block ()
  (setq-local flycheck-disabled-checkers '(emacs-lisp-checkdoc)))

(add-hook 'org-src-mode-hook 'disable-flycheck-in-org-src-block)

manipulating sections

Let’s enable Org Speed Keys. The main purpose of Speed Keys is to speed up the execution of the most common tasks you do in Org Mode - like outline navigation, visibility cycling, and structure editing. They also support basic clock commands and meta data editing, however, in order to use them, the cursor needs to be at the beginning of a headline.

(setq org-use-speed-commands t)

List of most randy commands:

KeyDescription
#toggle COMMENT-in for an org-header
stoggles narrowing to a subtree i.e. hide the rest of the doc
I/Oclock In/Out to the task defined by the current heading
ujumping upwards to the parent heading
cfor cycling structure below current heading, or C cycling global
iinsert a new same-level heading below current heading
wrefile current heading
tcycle through the available TODO states
^sort children of the current subtree
n/pfor next/previous visible heading
f/bfor next/previous same-level heading
D/Umove a heading Down/Up
L/Rrecursively promote (move leftwards) or demote (more rightwards)
1,2,3to mark a heading with priority

seamless Navigation Between Source Blocks

Toggle editing org-mode source blocks.

(global-set-key (kbd "s-e") #'org-edit-special)
(define-key org-src-mode-map (kbd "s-e") #'org-edit-src-exit)

capture

(require 'org-capture)
(setq org-directory "/home/wand/org"
      org-confirm-babel-evaluate nil
      org-agenda-files (list "/home/wand/org/todo.org"
                             "/home/wand/gcal-captalys.org"))

(setq org-todo-keywords
      '((sequence "TODO(t)" "|" "DOING(d)" "|" "DONE(D)" "|" "CANCELLED(C)")
        (sequence "STUDY(s)" "|" "STUDIED(S)")
        (sequence "ACT(a)" "|" "ACTED(A)")))

(setq org-capture-templates
      '(("c" "Capture some concise actionable item and exist" entry
         (file+headline "todo.org" "Task list without a defined date")
         "* TODO [#B] %^{Title}\n :PROPERTIES:\n :CAPTURED: %U\n :END:\n\n %i %l" :immediate-finish t)
        ("t" "Task of importance with a tag, deadline, and further editable space" entry
         (file+headline "todo.org" "Task list with a date")
         "* %^{Scope of task||TODO [#A]|STUDY [#A]|Act on} %^{Title} %^g\n DEADLINE: %^t\n :PROPERTIES:\n :CONTEXT: %a\n:CAPTURED: %U\n :END:\n\n %i %?")))

(setq org-agenda-window-setup 'only-window)

 ;;; after calling the `org-todo', the org mode tries to store some
 ;;; sort of a "note" using `org-store-log-note' function. I want that
 ;;; every modification done in my todo file save the file right after.
(advice-add 'org-deadline :after (lambda (&rest _rest) (org-save-all-org-buffers)))
(advice-add 'org-schedule :after (lambda (&rest _rest) (org-save-all-org-buffers)))
(advice-add 'org-todo :after (lambda (&rest _rest) (org-save-all-org-buffers)))
(advice-add 'org-store-log-note :after (lambda (&rest _rest) (org-save-all-org-buffers)))

babel

(org-babel-do-load-languages
 'org-babel-load-languages
 '((emacs-lisp . t)
   (ledger . t)
   (sql . t)
   (clojure . t)))

structure templates

The “Easy Templates” as often is mentioned, is the standard way in Emacs to handle inline code blocks when writing in literate programming style.

You can find all the different available templates by `C-h v org-structure-template-alist`.

This works in Emacs 26, but I am deprecating this now because of my new usage of Emacs 27 and the new way of defining structure templates.

;; (add-to-list 'org-structure-template-alist
;;      (list "elisp" (concat "#+BEGIN_SRC emacs-lisp\n"
;;                "?\n"
;;                "#+END_SRC")))
(add-to-list 'org-structure-template-alist
             '("elisp" . "src emacs-lisp"))

reveal.js

There an exhaustive documentation about Reveal.js in the github repository, please follow the link if more is necessary.

(use-package org-re-reveal
  :ensure t
  :after org
  :custom
  (org-reveal-mathjax t)
  (org-reveal-root "http://cdn.jsdelivr.net/reveal.js/3.0.0/"))

habits

Emacs org-habits function enables you:

  • to specify any number of habits, each with different, sometimes less regular frequencies
  • to track when you perform a habit and when you should next perform it and finally
  • to visualize if you are performing according to the specifications you have set
    (add-to-list 'org-modules 'org-habit t)
    
    (setq org-treat-insert-todo-heading-as-state-change t
          org-log-into-drawer t)
    
        

[documentation] repeated tasks

Highlights from the official documentation on repeated tasks. Some tasks need to be repeated again and again, Org helps to organize such tasks using a so-called repeater in a DEADLINE, SCHEDULE or plain timestamps.

** TODO Pay the rent
   DEADLINE: <2020-04-21 Tue +1m>

The +1m is repeater; the intended interpretation is that the task has a deadline on 2020/04/21 and repeats itself every (one) month.

repeaterperiodicity
yyearly
wweekly
ddaily
hhourly

journal

Let’s use 750words to write a personal journal. As part of the process, I want my journal entries to be fully encrypted because privacy is important.

  • org-journal-encrypt-journal, if set to t has the effect of transparently encrypting/decrypting the journal files as they are written to disk.
  • org-journal-enable-encryption, if set to t, enables integration with org-crypt, so it automatically adds a :crypt tag to new journal entries. This has the effect of automatically encrypting those entries upon save, replacing them with a blob of gpg-encrypted text which has to be further decrypted with org-decrypt-entry in order to read or edit them again… too much for now.
(use-package org-journal
  :ensure t
  :init
  (setq org-journal-dir "/home/wand/.journal"
        org-journal-file-format "%Y/%m/%Y%m%d"
        org-journal-date-format "%A, %Y-%m-%d"
        org-journal-encrypt-journal t
        org-journal-enable-encryption nil
        org-journal-enable-agenda-integration t)
  :bind
  ("C-c j" . org-journal-new-entry))

wc-mode allows counting characters and words, both on demand and continuously. It also allows setting up a word/character goal.

(use-package wc-mode
  :ensure t
  :hook (org-journal-mode . wc-mode)
  :config
  (setq wc-word-goal 750))

what is a personal journal?

  • This is between you and you
  • It’s all about writing, and getting into your brain
  • It’s not blogging or status updating
  • Way to think out loud without having to worry about half-formed ideas, random tangents, private stuff, and all the other things our heads that we often filter out before ever voicing them or writing about them
  • It’s a daily brain dump

writeroom

A package to help us concentrate in the writing by removing everything from the screen and centralizing the text being written.

(use-package writeroom-mode
  :ensure t
  :init
  (setq writeroom-width 150))

presentation

epresent is a simple presentation mode for Emacs Org-mode. Epresent leverages exiting Org-mode features like inline image display, inline latex fragments, and in-buffer code fontification to generate very nice looking presentations directly from within Emacs.

(use-package epresent
  :ensure t)
  • call epresent-run on an org-buffer
  • press t / 1 to view the top level of the presentation
  • navigate the presentation with n/f, p/b
  • scroll with k and l
  • use c and C to navigate between code blocks, e to edit them, x to make it run, and s / S to toggle their visibility
  • quit with q

agenda

defaults

(with-eval-after-load "org"
  (require 'org-agenda)
  (setq org-agenda-fontify-priorities t
        org-agenda-include-diary t
        org-agenda-inhibit-startup t
        org-agenda-log-mode-items '(closed clock state)
        org-agenda-ndays 1
        org-agenda-persistent-filter t
        org-agenda-show-all-dates t))

DISABLED - gmail agenda

I am following this post from pragmatic emacs to setup gcalcli to handle your google agenda. Let’s see if I can make this through. ==FAILED== I will try something else =)

Let’s see if is possible to sync Google Calendar with Org mode. The Org-gcal library enable you to fetch, post, edit and sync events from your calendar.

There is a bit of setup outside Emacs to make it work, you can follow the step-by-step guide on Org Gcal Readme page.

(use-package org-gcal
  :ensure t
  :disabled true
  :config
  (setq org-gcal-client-id (auth-source-pick-first-password
                            :host "gcal.com"
                            :user "client-id")
        org-gcal-client-secret (auth-source-pick-first-password
                                :host "gcal.com"
                                :user "client-secret")
        org-gcal-file-alist '(("wanderson.ferreira@captalys.com.br" . "~/gcal-captalys.org"))
        org-gcal-notify-p nil))

There are a couple of commands to remember:

commanddescription
org-gcal-syncsync between org and gcal, before syncing, execute `org-gcal-fetch’
org-gcal-fetchfetch google calendar events and populate org-gcal-file-alist
org-gcal-post-at-pointpost/edit org block at point to google calendar
org-gcal-delete-at-pointdelete gcal event at point
org-gcal-refresh-tokenrefresh the oauth token, it expires in 3600s you should refresh in regular basis.

I got these two hooks from Zemansky to sync things semi-automatically.

;; (advice-add 'org-gcal-sync :around #'suppress-messages)
;; (add-hook 'org-agenda-mode-hook (lambda () (org-gcal-sync)))
;;  not very good integration! many callback errors are getting back.

The other way around, integrating Org bullets to Gmail seems to work but with very basic functionalities. I wish I could create a full appointment entry with invitations, location, and correct duration. For now, I will keep using Google’s UI to do that.

beamer

(require 'ox-beamer)
(require 'ox-latex)

(setq org-export-allow-bind-keywords t)
(setq org-latex-listings 'minted)

(add-to-list 'org-latex-packages-alist '("" "minted"))

(org-babel-do-load-languages
 'org-babel-load-languages
 '((python . t) (C . t) (ruby . t) (js . t)))

(setq org-latex-pdf-process
      '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
        "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
        "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))

tips and tricks

compute difference between two dates.

From M-x tpis (@iLemming) in Org-mode

  • use C-c ! to enter timestamps
  • use S-<arrows> to move between days
  • use M-<arrows> to move between months
  • Place - between them (to make it a range)
  • C-c C-y to org-evaluate-time-range
  • with a prefix argument inserts it into the buffer.

insert template for documenting a meeting

(defun bk/meeting-minute ()
  "Insert Org template to create a meeting minute."
  (interactive)
  (insert-file "~/.emacs.d/docs/org-template-meeting.org"))

export to asciidoc

(use-package ox-asciidoc
  :ensure t
  :config
  (require 'ox-asciidoc))

Second brain

Org Roam

(use-package org-roam
  :ensure t
  :hook (after-init . org-roam-mode)
  :init
  (setq org-roam-directory "/home/wand/all/permanent")
  (setq org-roam-dailies-capture-templates
        '(("d" "daily" plain (function org-roam-capture--get-point) ""
           :file-name "dailies/%<%Y-%m-%d>"
           :unnarrowed t
           :head "#+TITLE: %<%Y-%m-%d>\n#+STARTUP: showall\n#+roam_tags: fleeting")))

  (setq org-roam-capture-templates
        '(("l" "literature" plain #'org-roam-capture--get-point "%?"
           :file-name "literature/%<%Y%m%d%H%M%S>-${slug}"
           :head "#+title: ${title}\n#+created_at: %U\n#+STARTUP: showall\n#+roam_tags: literature"
           :unnarrowed t)

          ("p" "permanent" plain #'org-roam-capture--get-point "%?"
           :file-name "%<%Y%m%d%H%M%S>-${slug}"
           :head "#+title: ${title}\n#+created_at: %U\n#+STARTUP: showall\n#+roam_tags: permanent"
           :unnarrowed t)))
  :bind (("C-c n c" . org-roam-capture)
         ("C-c n t" . org-roam-dailies-today)
         :map org-roam-mode-map
         ("C-c n f" . org-roam-find-file)
         :map org-mode-map
         ("C-c n i" . org-roam-insert)
         ("C-c n I" . org-roam-insert-immediate)))

Protocol

(use-package org-roam-server
  :ensure nil
  :config
  (require 'org-roam-protocol))

Enable local files

(setq org-roam-server-enable-access-to-local-files t
      org-roam-server-webserver-prefix "/home/wand"
      org-roam-server-webserver-address "127.0.0.1:8887/"
      org-roam-server-webserver-supported-extensions '("pdf" "mp4" "ogv" "mkv"))

Tip of the day

I really like this idea from Prelude, about giving the user some useful tip about Emacs. I will try to reproduce some here.

First, the list of useful tips.

(defvar bk-tips
  '("Press <C-c ;> to activate avy char-based navigation."
    ))

Then the function to display it.

(defun bk-tip-of-the-day ()
  "Display a random entry from `bk-tips'."
  (interactive)
  (when (and bk-tips (not (window-minibuffer-p)))
    (random t)
    (message
     (concat "Bartuka tip: " (nth (random (length bk-tips)) bk-tips)))))

Run from time to time.

(run-at-time 10 (* 10 60) #'bk-tip-of-the-day)

Projects

Let’s define a nice hydra that I’ve been using for a while now.

(use-package projectile
  :ensure t
  :delight '(:eval (concat " " (projectile-project-name)))
  :requires hydra
  :init
  (setq projectile-completion-system 'ivy
        projectile-indexing-method 'hybrid
        projectile-enable-caching t
        projectile-sort-order 'access-time)
  :config
  (defhydra bk/hydra-projects (:color blue)
    ("q" nil "quit" :column "Projectile")

    ("b" projectile-switch-to-buffer "list" :column "Buffers")
    ("K" projectile-kill-buffers "kill all" :column "Buffers")
    ("S" projectile-save-project-buffers "save all" :column "Buffers")

    ("d" projectile-find-dir "directory" :column "Find")
    ("D" projectile-dired "root" :column "Find")
    ("f" projectile-find-file "file" :column "Find")
    ("p" projectile-switch-project "project" :column "Find")

    ("r" projectile-replace "replace" :column "Search")
    ("R" projectile-replace-regexp "regexp replace" :column "Search")
    ("g" bk/rgrep-fullscreen "grep" :column "Search"))

  (projectile-mode t)
  :bind
  ("C-c p" . bk/hydra-projects/body))

Spelling

correct your spelling errors on the fly

(defun bk/spell-buffer-pt-BR ()
  "Spell check in portuguese."
  (interactive)
  (ispell-change-dictionary "pt_BR")
  (flyspell-buffer))

(defun bk/spell-buffer-en ()
  "Spell check in english."
  (interactive)
  (ispell-change-dictionary "en_US")
  (flyspell-buffer))

(use-package flyspell
  :ensure nil
  :delight flyspell-mode
  :config
  (add-hook 'prog-mode-hook 'flyspell-prog-mode)
  (add-hook 'text-mode-hook 'flyspell-mode)
  (define-key flyspell-mode-map (kbd "C-.") nil))

There is a nice package to help correcting previous words that improve upon the flyspell-auto-correct-previous-word function

 (use-package flyspell-correct
   :ensure t
   :commands (flyspell-correct-word-generic
		  flyspell-correct-previous-word-generic)
   :config
   (require 'flyspell-correct-ido)
   (setq flyspell-correct-interface #'flyspell-correct-ido)
   :bind (:map flyspell-mode-map
		   ("C-;" . flyspell-correct-wrapper)))

By default the flyspell-correct-wrapper is the most convenient way to use the package because it will jump to the first misspelled word before the point and prompts for correction and gets you back. Calling it with C-u gives ability to correct multiple misspelled words in one run. With C-u C-u changes direction and C-u C-u C-u changes direction and enables multiple corrections.

using a grammar & style checker

Let’s install a grammar and style checker. We get the offline tool from the link, then relocate it as follows.

(use-package langtool
  :ensure t
  :config
  (setq langtool-language-tool-jar "/home/wand/.emacs.d/var/LanguageTool-4.5/languagetool-commandline.jar"))

Now we can run langtool-check on a grammatically incorrect text which colors errors in red, when we click on them we get the reason why; then we may invoke langtool-correct-buffer to quickly use the suggestions to fix each correction, and finally invoke language-check-done to stop any remaining red coloring.

Let’s verify if our installation is working by using a piece of incorrect text from Language Tool website:

LanguageTool offers spell and grammar checking. Just paste your text here
and click the 'Check Text' button. Click the colored phrases for details
on potential errors. or use this text too see an few of of the problems
that LanguageTool can detecd. What do you thinks of grammar checkers?
Please not that they are not perfect. Style issues get a blue marker:
It's 5 P.M. in the afternoon. The weather was nice on Thursday, 27 June 2017
--uh oh, that's the wrong date ;-)
;; ;; Quickly check, correct, then clean up /region/ with M-^

;; (add-hook 'langtool-error-exists-hook
;;   (lambda ()
;;     (langtool-correct-buffer)
;;     (langtool-check-done)
;;   ))

;; (global-set-key "\M-^" 'langtool-check)

synonyms

Synosaurus is a thesaurus frontend for Emacs with pluggable backends. It has basically three commands:

KeyCommandsDescription
C-c C-s lsynosaurus-lookupquery you for a word
C-c C-s rsynosaurus-choose-and-replace
C-c C-s isynosaurus-choose-and-insert
(use-package synosaurus
  :ensure t
  :init (synosaurus-mode)
  :config
  (setq synosaurus-choose-method 'popup)
  :bind
  ("M-#" . synosaurus-choose-and-replace))

The thesaurus is powered by the Wordnet wn tool, which can be invoked without an internet connection.

yaourt -S wordnet-common

Let’s use Wordnet as a dictionary via the wordnut package.

(use-package wordnut
  :ensure t
  :bind
  ("M-!" . wordnut-lookup-current-word))

Some keys you can use inside the *WordNut* buffer.

KeyDescription
EnterLookup a word under the cursor
oA tooltip w/ a sense for the current lexical category
/new search
l, rmove backward/forward in history
hview history
qhide buffer
M-up, M-downmove between sections
SpacePage Down
b, BackspacePage Up

translate

To assist in language learning, it may be nice to have Emacs interface to Google translate e.g. invoke google-translate-at-point.

(use-package google-translate
  :ensure t
  :config
  (require 'google-translate-smooth-ui)
  (global-set-key (kbd "s-g t") #'google-translate-smooth-translate))

;; temporary fix for error args-out-of-range
;; https://github.com/atykhonov/google-translate/issues/98
(defun google-translate-json-suggestion (json)
  "Retrieve from JSON (which returns by the
`google-translate-request' function) suggestion. This function
does matter when translating misspelled word. So instead of
translation it is possible to get suggestion."
  (let ((info (aref json 7)))
    (if (and info (> (length info) 0))
        (aref info 1)
      nil)))

typing

Practice touch typing using speed-type.

(use-package speed-type
  :ensure t)

Running M-x speed-type-region on a region of text, or M-x speed-type-buffer on a whole buffer, or just M-x speed-type-text will produce the selected region, buffer, or random text for practice.

A better alternative is to use Typing of Emacs which is far more interactive.

(use-package typing
  :ensure t)

There are a few external websites that can help you with that too, Typing.io is the most recommended for Programmers. Check it out!

Snippets

Yasnippet is a template system for Emacs. It allows you to type an abbreviation and automatically expand it into function templates.

(use-package yasnippet
  :ensure t
  :delight yas-minor-mode
  :config
  (yas-global-mode +1)
  (define-key yas-minor-mode-map (kbd "<tab>") nil)
  (define-key yas-minor-mode-map (kbd "TAB") nil)
  (define-key yas-minor-mode-map (kbd "C-c y") #'yas-expand))

But since some specific version, yasnippet does not bundles snippets directly, you have to get them from third-party packages.

  ;;; a snippet collection maintained by AndreaCrotti.
(use-package yasnippet-snippets :ensure t)
(use-package clojure-snippets :ensure t :defer t)

I want to rely more on snippets on my day-to-day, therefore I need way to visualize if there is an existent snippet for a particular situation. You can do that with `M-x yas/describe-table’.

I will place that in my cheatsheet too and a nice shortcut: C-c s.

(global-set-key (kbd "C-c s")
		     (lambda ()
		       (interactive)
		       (yas/describe-tables)
		       (other-window 1)))

Jump to end of snippet definition

(define-key yas-keymap (kbd "<return>") 'yas-exit-all-snippets)

Docker

 (use-package docker
   :ensure t
   :bind
   ("C-c d" . docker))

 (use-package docker-tramp
   :ensure t)

 (use-package dockerfile-mode
   :ensure t
   :config
   (add-to-list 'auto-mode-alist '("Dockerfile\\'" . dockerfile-mode))
   (add-to-list 'auto-mode-alist '("DockerfileDev\\'" . dockerfile-mode)))

 (use-package docker-compose-mode
   :ensure t
   :config
   (add-to-list 'auto-mode-alist '("docker-compose[^/]*\\.yml\\'" . docker-compose-mode)))

 (defun bk/dockerfile-add-build-args ()
   "Add env variables to your docker build."
   (interactive)
   (let* ((vars (read-from-minibuffer "sequence of <envName>=<envValue>: "))
	   (split-vars (split-string vars " ")))
     (setq dockerfile-build-args nil)
     (dolist (v split-vars)
	(add-to-list 'dockerfile-build-args v))
     (setq docker-build-history-args vars)))


 (defun bk/docker-compose-custom-envs ()
   "Add usual env variables to Emacs environment."
   (interactive)
   (let* ((idu (shell-command-to-string "id -u"))
	   (idg (shell-command-to-string "id -g"))
	   (uid (string-join (vector (string-trim idu) ":" (string-trim idg)))))
     (setenv "WEBSERVER_PORT" "3000")
     (setenv "CURRENT_UID" uid)
     (message "setenv WEBSERVER_PORT=3000 CURRENT_UID=$(id -u):$(id -g) done!")))

 (defun bk/docker-cleanup-buffers ()
   "Delete all the docker buffers created."
   (interactive)
   (kill-matching-buffers "docker" nil t))

Social Networks

rss feed

I like to read about programming, but Emacs and Clojure are by far the most interesting communities I know so far, therefore, my feeds have many links from these subjects.

(use-package elfeed
  :ensure t
  :commands (elfeed elfeed-update)
  :bind
  ("C-x w" . elfeed))
(setq-default elfeed-search-filter "@3-week-ago +unread")

Using the minions mode I am able to correctly see which modes are enabled at a specific buffer. I found several global modes enabled at elfeed-search which might be consuming resources.

(defun bk/elfeed-disable-mode-setup ()
  (interactive)
  (abbrev-mode -1)
  (company-mode -1)
  (smart-shift-mode -1)
  (yas-minor-mode -1)
  (dired-async-mode -1)
  (diff-hl-flydiff-mode -1)
  (global-auto-revert-mode -1))

(add-hook 'elfeed-show-mode-hook 'bk/elfeed-disable-mode-setup)
(add-hook 'elfeed-search-update-hook 'bk/elfeed-disable-mode-setup)

load config

Lazy loading all the parts of my elfeed setup.

(require 'elfeed)
(require 'cl-lib)
(require 'elfeed-search)
(require 'elfeed-db)

<<elfeed-basic-config>>
<<elfeed-mode-disabled>>
<<elfeed-newsletters>>
<<elfeed-scoring>>
<<elfeed-filters>>
<<elfeed-automatic-update>>
<<elfeed-starred>>
<<elfeed-youtube>>

elfeed newsletters

Configure the Elfeed RSS reader with an Org-mode file. Yet, I tried to maintain my boring long list of feeds, but this does not make sense at all. Hard to find info, not repeat yourself, and even tagging.

(use-package elfeed-org
  :ensure t
  :after elfeed
  :init
  (setq rmh-elfeed-org-files (list "~/.emacs.d/elfeed.org")
        rmh-elfeed-org-tree-id "elfeed")
  :config
  (elfeed-org))

filter

By default, s run a live filter and you can type something like “Xah” to dynamically narrow the list of stories to those containing that string. The only problem is that you need an extra whitespace before the word, ” Xah”, let’s fix that.

(defun bk/elfeed-search-live-filter-space ()
  "Insert space when running elfeed filter"
  (interactive)
  (let ((elfeed-search-filter (concat elfeed-search-filter " ")))
    (elfeed-search-live-filter)))

(define-key elfeed-search-mode-map (kbd "/") #'bk/elfeed-search-live-filter-space)
(define-key elfeed-search-mode-map "h"
  (lambda ()
    (interactive)
    (elfeed-search-set-filter (default-value 'elfeed-search-filter))))

Tagging automatic as podcasts

(defun ime-elfeed-pocast-tagger (entry)
  (when (elfeed-entry-enclosures entry)
    (elfeed-tag entry 'podcast)))

(add-hook 'elfeed-new-entry-hook #'ime-elfeed-pocast-tagger)

automatic Update

Toggle automatic update of elfeed newsletters.

(defvar bk--update-elfeed-timer nil)

(defun bk/toggle-update-elfeed ()
  "Toggle automatic elfeed update from 25/25 min."
  (interactive)
  (let ((repeat-rate (* 60 25)))
    (if bk--update-elfeed-timer
        (progn
          (cancel-timer bk--update-elfeed-timer)
          (setq bk--update-elfeed-timer nil)
          (message "Elfeed automatic update disabled..."))
      (setq bk--update-elfeed-timer
            (run-at-time 5 repeat-rate 'elfeed-update))
      (message "Elfeed automatic update enabled..."))))

(add-hook 'after-init-hook 'bk/toggle-update-elfeed)

Enable visual-line-mode in elfeed buffers.

(add-hook 'elfeed-show-mode-hook 'visual-line-mode)

star and unstar

This was got from pragmatic emacs.

(defun bk/elfeed-star ()
  "Apply starred to all selected entries."
  (interactive)
  (let* ((entries (elfeed-search-selected))
         (tag (intern "starred")))
    (cl-loop for entry in entries do (elfeed-tag entry tag))
    (mapc #'elfeed-search-update-entry entries)
    (unless (use-region-p) (forward-line))))

(defun bk/elfeed-unstar ()
  "Remove starred tag from all selected entries."
  (interactive)
  (let* ((entries (elfeed-search-selected))
         (tag (intern "starred")))
    (cl-loop for entry in entries do (elfeed-untag entry tag))
    (mapc #'elfeed-search-update-entry entries)
    (unless (use-region-p) (forward-line))))

(defface elfeed-search-starred-title-face
  '((t :foreground "#f77"))
  "Marks a starred Elfeed entry.")

(push '(starred elfeed-search-starred-title-face)
      elfeed-search-face-alist)

I bind these to the keys “*” to add a star and “8” to remove the star.

(define-key elfeed-search-mode-map (kbd "*") 'bk/elfeed-star)
(define-key elfeed-search-mode-map (kbd "8") 'bk/elfeed-unstar)

Now you can look for the starred feeds by pressing “S”.

(defalias 'elfeed-toggle-star (elfeed-expose #'elfeed-search-toggle-all 'star))
(define-key elfeed-search-mode-map (kbd "S") #'elfeed-toggle-star)

youtube

(defun ambrevar/elfeed-play-with-mpv ()
  "Play entry link with mpv."
  (interactive)
  (let ((entry (if (eq major-mode 'elfeed-show-mode) elfeed-show-entry (elfeed-search-selected :single)))
        (quality-arg "")
        (quality-val (completing-read "Max height resolution (0 for unlimited): " '("0" "480" "720") nil nil)))
    (setq quality-val (string-to-number quality-val))
    (message "Opening %s with height≤%s with mpv..." (elfeed-entry-link entry) quality-val)
    (when (< 0 quality-val)
      (setq quality-arg (format "--ytdl-format=[height<=?%s]" quality-val)))
    (start-process "elfeed-mpv" nil "mpv" quality-arg (elfeed-entry-link entry))))

(defun ambrevar/elfeed-open-with-eww ()
  "Open in eww with `eww-readable'."
  (interactive)
  (let ((entry (if (eq major-mode 'elfeed-show-mode) elfeed-show-entry (elfeed-search-selected :single))))
    (eww  (elfeed-entry-link entry))
    (add-hook 'eww-after-render-hook 'eww-readable nil t)))

(defvar ambrevar/elfeed-visit-patterns
  '(("youtu\\.?be" . ambrevar/elfeed-play-with-mpv)
    ("phoronix" . ambrevar/elfeed-open-with-eww))
  "List of (regexps . function) to match against elfeed entry link to know
whether how to visit the link.")

(defun ambrevar/elfeed-visit-maybe-external ()
  "Visit with external function if entry link matches `ambrevar/elfeed-visit-patterns',
visit otherwise."
  (interactive)
  (let ((entry (if (eq major-mode 'elfeed-show-mode)
                   elfeed-show-entry
                 (elfeed-search-selected :single)))
        (patterns ambrevar/elfeed-visit-patterns))
    (while (and patterns (not (string-match (caar patterns) (elfeed-entry-link entry))))
      (setq patterns (cdr patterns)))
    (cond
     (patterns
      (funcall (cdar patterns)))
     ((eq major-mode 'elfeed-search-mode)
      (call-interactively 'elfeed-search-show-entry))
     (t (elfeed-show-visit)))))

(define-key elfeed-search-mode-map "v" #'ambrevar/elfeed-play-with-mpv)
(define-key elfeed-search-mode-map "c" 'elfeed-search-untag-all-unread)
(define-key elfeed-show-mode-map "b" #'ambrevar/elfeed-visit-maybe-external)

score

(defun score-elfeed-entry (entry)
  (let ((title (elfeed-entry-title entry))
        (content (elfeed-deref (elfeed-entry-content entry)))
        (score 0))
    (cl-loop for (pattern n) in '(("software\\|programming\\|design\\|systems" 1)
                               ("clojure" 1)
                               ("emacs.*clojure\\|clojure.*emacs" 2))
          if (string-match pattern title)
          do (incf score n)
          if (string-match pattern content)
          do (incf score n))
    (message "%s - %s" title score)
    (setf (elfeed-meta entry :my/score) score)
    (cond
     ((= score 1)
      (elfeed-tag entry 'relevant))
     ((= score 2)
      (elfeed-tag entry 'important))
     ((> score 2)
      (elfeed-tag entry 'urgent)))
    entry))

(defface relevant-elfeed-entry
  `((t :background ,(color-lighten-name "LightBlue1" 40)))
  "Maks a relevant Elfeed entry.")

(defface important-elfeed-entry
  `((t :background ,(color-lighten-name "orange1" 40)))
  "Marks a important Elfeed entry.")

(defface urgent-elfeed-entry
  `((t :background ,(color-lighten-name "OrangeRed2" 40)))
  "Marks an urgent Elfeed entry.")

(add-hook 'elfeed-new-entry-hook 'score-elfeed-entry)

(push '(relevant relevant-elfeed-entry) elfeed-search-face-alist)
(push '(important important-elfeed-entry) elfeed-search-face-alist)
(push '(urgent urgent-elfeed-entry) elfeed-search-face-alist)

(define-key elfeed-search-mode-map (kbd "U")
  (lambda () (interactive)
    (elfeed-search-set-filter "@6-months-ago +unread +urgent")))

(define-key elfeed-search-mode-map (kbd "I")
  (lambda () (interactive)
    (elfeed-search-set-filter "@6-months-ago +unread +important")))

(define-key elfeed-search-mode-map (kbd "R")
  (lambda () (interactive)
    (elfeed-search-set-filter "@6-months-ago +unread +relevant")))

(define-key elfeed-search-mode-map (kbd "C")
  (lambda () (interactive)
    (elfeed-search-set-filter "@6-months-ago +unread")))

slack

Slack from Emacs? :O Why not? I am having a terrible time configuring all my workspaces lately. Therefore, it sounds like a perfect opportunity to leverage the best tool for the job once again.

(use-package slack
  :ensure t
  :disabled t
  :init
  (setq slack-buffer-emojify t
        slack-prefer-current-team t
        slack-request-timeout 30
        slack-buffer-create-on-notify t
        slack-buffer-function #'switch-to-buffer
        slack-completing-read-function #'ido-completing-read)
  :config
  (slack-register-team
   :name "captalysdev"
   :default t
   :modeline-enabled t
   :visible-threads t
   :token (auth-source-pick-first-password
           :host "slack.com"
           :user "captalysdev")
   :subscribed-channels '(onboarding
                          geral dev
                          atlas garantias-e-cobranca)
   :full-and-display-names t)

  (slack-register-team
   :name "clojurians"
   :token (auth-source-pick-first-password
           :host "slack.com"
           :user "clojurians")
   :subscribed-channels '(beginners reitit sql))

  :config
  ;; go to any channel with `C-x j`
  (define-key ctl-x-map "j" #'slack-select-rooms)
  (define-key slack-mode-map (kbd "C-;") ":+1:"))

Bring up the mentions menu with `@’, and insert a space afterwards.

(eval-after-load 'slack
  '(define-key slack-mode-map "@"
     (defun endless/slack-message-embed-mention ()
       (interactive)
       (call-interactively #'slack-message-embed-mention)
       (insert " "))))

CRUD on messages

(eval-after-load 'slack
  '(progn
     (define-key slack-mode-map (kbd "C-c C-d") #'slack-message-delete)
     (define-key slack-mode-map (kbd "C-c C-e") #'slack-message-edit)
     (define-key slack-mode-map (kbd "C-c C-k") #'slack-channel-leave)))

Circe is a client for IRC in Emacs. It tries to have sane defaults, and integrates well with the rest of the editor.

(use-package circe :ensure t)

Emojify is an Emacs extension to display emojis.

(use-package emojify
  :ensure t
  :delight emojify-mode
  :config
  (setq emojify-display-style 'image
        emojify-emoji-styles '(unicode)
        emojify-point-entered-behaviour 'echo)
  (global-emojify-mode 1))

How to use Slack on emacs? Some terminology from the website:

FunctionDescription
iman IM (instant message) is a direct message between you and exactly one other user
channelA channel is a slack channel which you are a member of
groupAny chat (direct message or channel) which isn’t an IM is a group
slack-register-teamset team configuration and create team
slack-change-current-teamchange slack-current-team var
slack-startdo authorize and initialze
slack-ws-closeturn off websocket connection
slack-group-selectselect group from list
slack-im-selectselect direct message from list
slack-channel-selectselect channel from list
slack-group-list-updateupdate group list
slack-channel-list-updateupdate channel list
slack-message-embed-mentiouse to mention to user
slack-file-uploaduploads a file

custom functions

take me directly to a specific chat room

(defun bk/slack-move-direct-to-specific-room (team channel)
  "Open the buffer of the specified CHANNEL in a TEAM, without leaving the current TEAM."
  (let* ((alist (mapcar #'(lambda (team) (cons (slack-team-name team)
                                          (oref team token)))
                        (hash-table-values slack-teams-by-token)))
         (token (cdr (-first (lambda (v) (equalp (car v) team)) alist)))
         (team (slack-team-find-by-token token))
         (room (-first (lambda (room) (equalp (slack-room-name room team) channel))
                       (slack-team-channels team))))
    (when team
      (slack-team-connect team)
      (slack-room-display room team))))

(defun bk/slack-move-to-reitit ()
  "Take me to reitit discussions."
  (interactive)
  (bk/slack-move-direct-to-specific-room "clojurians" "reitit"))

(defun bk/slack-move-to-beginners ()
  "Take me to beginners discussions."
  (interactive)
  (bk/slack-move-direct-to-specific-room "clojurians" "beginners"))

telegram

One more chat service that we need to stay in touch with friends and community.

(use-package telega
  :ensure t
  :delight (telega-chat-mode "Telegram")
  :init
  (setq telega-animation-play-inline nil
        telega-chat-reply-prompt "R>> "
        telega-chat-use-markdown-version 2)
  :config
  (custom-set-faces
   '(telega-msg-heading ((t (:overline t :weight bold))))))

I will also enable the contrib packages provides in the github repo. I copied the interesting ones to me in the lisps/ folder.

(require 'telega-alert)
(telega-alert-mode t)

(require 'telega-dired-dwim)


(use-package all-the-icons :ensure t)
(require 'telega-url-shorten)
(add-hook 'telega-load-hook 'global-telega-url-shorten-mode)

Enabling emoji completions in chat buffer

Take a look at the mode-line in chat buffers

(setq telega-chat-mode-line-format
      '((:eval
         (telega-chatbuf-mode-line-unread))
        (:eval
         (telega-chatbuf-mode-line-marked))
        (:eval
         (telega-chatbuf-mode-line-members nil))
        (:eval
         (telega-chatbuf-mode-line-pinned-msg 20))))

spotify

(use-package helm-spotify-plus
  :ensure t
  :defer t)

twitter

The possible features includes:

  • viewing various timelines
  • posting tweets
  • following and removing users
  • marking tweets as favorites

Use Twitter from within Emacs!

(use-package twittering-mode
  :ensure t
  :config
  (setq twittering-timer-interval 3600
        twittering-icon-mode t
        twittering-use-master-password t))

How to use:

  1. Execute M-x twit to run twittering-mode.
  2. Basic key bindings are as follows:
    1. V : open or switch to another timeline by timeline-spec
    2. u : post a reply to the pointed tweet
    3. RET : post an organic retweet
    4. C-c RET : post an official/native retweet
    5. d : send a direct message
    6. C-c C-w : delete the pointed tweet
    7. j : go to next tweet

More on usage here.

Weather

Weather forecast stolen from pragmatic emacs.

(use-package wttrin
  :ensure t
  :init
  (setq wttrin-default-cities '("São Paulo"
                                "London"))
  :config
  (require 'wttrin))

By default wttrin prompts you to choose the city from your list when it starts. This function starts with the first city on your list. I also have a problem with color-theme because I use a too light one.

(defun bk/weather ()
  "Open the weather in your first city in `wttrin-default-cities'."
  (interactive)
  (wttrin-query (car wttrin-default-cities))
  (load-theme-buffer-local 'deeper-blue
                           (get-buffer "*wttr.in - São Paulo*")))

Pomodoro

For many people, time is an enemy. We race against the clock to finish assignments and meet deadlines. The Pomodoro technique teaches you to work with time, instead of struggling against it.

  1. Choose a task you would like to get done
  2. Set the pomodoro for 25 minutes
  3. Work on the task until the Pomodoro rings
  4. When the Pomodoro rings, put a checkmark on a paper
  5. Take a short break (5 minutes in my setup)
  6. Every 4 pomodoros, take a longer break (15 minutes in my setup)
(require 'pomidor)

Financial

Ledger mode is a major-mode for editing files in the format used by the ledger command-line accounting system. It also provides automated support for some ledger workflows, such as reconciling transactions, or running certain reports.

(use-package ledger-mode
  :ensure t
  :mode ("\\.dat\\'"
         "\\.ledger\\'"
         "\\ledger\\'")
  :custom (ledger-clear-whole-transactions t)
  :config
  (require 'ledger-mode))

NOTE: in order to use this mode, ledger must be installed in your system.

Some help functions from here.

(defun bk/clean-leader-on-save ()
  (interactive)
  (if (eq major-mode 'ledger-mode)
      (let ((curr-line (line-number-at-pos)))
        (ledger-mode-clean-buffer)
        (line-move (- curr-line 1)))))

(defun bk/ledger-increment-date ()
  (interactive)
  (bk/ledger-change-date 1))

(defun bk/ledger-decrement-date ()
  (interactive)
  (bk/ledger-change-date -1))

(defun bk/ledger-change-date (num)
  (save-excursion
    (ledger-navigate-beginning-of-xact)
    (let* ((beg (point))
           (end (re-search-forward ledger-iso-date-regexp))
           (xact-date (filter-buffer-substring beg end)))
      (delete-region beg end)
      (insert
       (format-time-string
        "%Y/%m/%d"
        (time-add (bk/encoded-date xact-date)
                  (days-to-time num)))))))

(defun bk/encoded-date (date)
  (string-match "\\([0-9][0-9][0-9][0-9]\\)/\\([0-9][0-9]\\)/\\([0-9][0-9]\\)" date)
  (let* ((fixed-date
          (concat (match-string 1 date) "-"
                  (match-string 2 date) "-"
                  (match-string 3 date)))
         (d (parse-time-string fixed-date)))
    (encode-time 0 0 0 (nth 3 d) (nth 4 d) (nth 5 d))))

(add-hook 'before-save-hook 'bk/clean-leader-on-save)
(eval-after-load 'ledger-mode
  '(progn
     (define-key ledger-mode-map (kbd "C-M-.") 'bk/ledger-increment-date)
     (define-key ledger-mode-map (kbd "C-M-,") 'bk/ledger-decrement-date)))

Linter for the ledger mode. Very very useful to understand if you filled everything’s right.

(use-package flycheck-ledger
  :ensure t
  :config
  (add-hook 'ledger-mode-hook 'flycheck-mode))

You can use C-c C-b to popup the calc mode and perform some math with the number at point to fix it.

Register to get into the ledger file quickly.

(set-register ?l '(file . "~/.ledger"))

ledger explanation

  • Double entry system: All money has a source and destination account
  • Five accounts
    • Assets : what you have
    • Expenses : what you expends
    • Income : what you earns
    • Liabilities : what you owe
    • Equity : what you worth
  • Net worth = Assets - Liabilities
  • Net income = Income - Expenses

plaintext accounting with the ledger ecosystem

Youtube

  • **Why accounting?**
    • To know what you have
      • across all accounts
      • had at some point in the past
      • how much you can spend
    • legally required for most businesses
  • **Double-entry accounting**
    • account: label describing an amount of something
      • assets : bank accounts, wallet, investments
      • income : paychecks, dividends, interest
      • expenses : groceries, taxes, donations
      • liabilities : mortgage, credit cards, student loans
      • equity : for everything else like opening balances
    • debit
      • the deduction of value from an account
    • credit
      • the addition of value to an account
    • transaction
      • a collection of credits and debits with a timestamp to describe when the transaction is effective
      • all transaction must balance, equals to 0 at the end
  • **Why ledger?**
    • intimate knowledge of every transaction
    • track everything
    • its just text

Guru

Let’s force learning proper Emacs

(use-package guru-mode
  :ensure t
  :config
  (guru-mode 1))

If you think that this is too aggressive, please don’t! rsrs I am kidding, you can use the option guru-warn-only to t and be ashamed of yourself.

Advice

When you need to modify a function defined in another library.

Adivice feature lets you add to the existing definition of a function, by advising the function. This is a cleaner method than redefining the whole function.

pop to mark

When popping the mark, continue popping until the cursor actually moves. Also, if the last command was a copy - skip past all the expand-region craft.

   (defadvice pop-to-mark-command (around ensure-new-position activate)
     (let ((p (point)))
	 (when (eq last-command 'save-region-or-current-line)
	   ad-do-it
	   ad-do-it
	   ad-do-it)
	 (dotimes (i 10)
	   (when (= p (point)) ad-do-it))))

   (setq set-mark-command-repeat-pop t)

yank indent

This was stolen from here. The idea is to indent yanked regions in specific modes that you can define.

(defvar yank-indent-modes '(prog-mode
                            js2-mode)
  " Modes in which to indent regions that are yanked (or yank-popped)")

(defvar yank-advised-indent-threshold 1000
  " Threshhold (# chars) over which indentation does not automatically occur.")

(defun yank-advise-indent-function (beg eng)
  "Do indentation, as long as the region isn't too large."
  (if (<= (- end beg) yank-advised-indent-threshold)
      (indent-region beg end nil)))

(defadvice yank (after yank-indent activate)
  "If current mode is one of `yank-indent-modes', indent yanked text."
  (if (and (not (ad-get-arg 0))
           (member major-mode yank-indent-modes))
      (let ((transient-mark-mode nil))
        (yank-advise-indent-function (region-beginning) (region-end)))))

(defadvice yank-pop (after yank-pop-indent activate)
  "If the current mode is one of `yank-indent-modes', indent yanked text."
  (if (and (not (ad-get-arg 0))
           (member major-mode yank-indent-modes))
      (let ((transient-mark-mode nil))
        (yank-advise-indent-function (region-beginning) (region-end)))))

(defun yank-unindented ()
  (interactive)
  (yank 1))

Recording

Tool for capturing screen-casts directly from Emacs.

  1. To use it, simply call M-x camcorder-record
  2. A new smaller frame will popup and recording starts
  3. When you’re finished, git F12

You can also convert the file to a gif by issuing the command M-x comcorder-convert-to-gif

(use-package camcorder
  :ensure t)

timing

Count up time and show remainder time at mode-line.

;; (use-package stopwatch
;;     :ensure nil)

Logs

commands logs

(use-package command-log-mode
  :ensure t
  :commands command-log-mode)

Custom Functions

shell

kubernetes

(defun eshell/kg (&rest args)
  "Find status of pods in ARGS."
  (let* ((env (car args)))
    (with-current-buffer "*eshell*"
      (insert "kubectl get pods -n " env)
      (eshell-send-input))))

(defun get-pod-name (pod env)
  "Get POD name from correct ENV."
  (let ((res (eshell-command-result (concat "kubectl get pods -n " env))))
    (string-match (concat pod ".*") res 0)
    (car (split-string (match-string 0 res) " "))))

(defun eshell/kl (&rest args)
  "Get logs from PODS and ENVS in ARGS."
  (let* ((pod (car args))
         (env (car (cdr args)))
         (pod-name (get-pod-name pod env)))
    (with-current-buffer "*eshell*"
      (insert "kubectl logs -n " env " " pod-name " " pod "-" env " -f")
      (eshell-send-input))))

keys i use to work with

(defun bk/load-private-keys ()
  "Personal keys I use to work and whatnot"
  (interactive)
  (let ((client-key (shell-command-to-string "base64 /home/wand/CaptalysPlatform.key"))
        (qi-key (shell-command-to-string "base64 /home/wand/qitech_captalys_homolog.key.pub")))
    (setenv "CLIENT_PRIVATE_KEY" client-key)
    (setenv "QI_PUBLIC_KEY" qi-key))
  (message "CLIENT_PRIVATE_KEY and QI_PUBLIC_KEY were loaded"))

;; load them!!
(bk/load-private-keys)

http-based

find my current ip

(defvar url-http-end-of-headers)
(defun bk/ip ()
  "Find my current public IP address."
  (interactive)
  (let* ((endpoint "https://api.ipify.org")
   	   (myip (with-current-buffer (url-retrieve-synchronously endpoint)
   		   (buffer-substring (+ 1 url-http-end-of-headers) (point-max)))))
    (kill-new myip)
    (message "IP: %s" myip)))

editing

what? sudo!

(defun bk/sudo-edit (&optional arg)
  "Function to edit file with super-user with optional ARG."
  (interactive "P")
  (if (or arg (not buffer-file-name))
      (find-file (concat "/sudo:root@localhost:" (read-file-name "File: ")))
    (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))

eval and replace

(defun eval-and-replace ()
  "Replace the preceding sexp with its value."
  (interactive)
  (backward-kill-sexp)
  (condition-case nil
      (prin1 (eval (read (current-kill 0)))
             (current-buffer))
    (error (message "invalid expression")
           (insert (current-kill 0)))))

insert today’s date

(defun bk/insert-today-date ()
  "Insert today date as YYYY-MM-DD."
  (interactive)
  (insert (format-time-string "%Y/%m/%d")))

kill all the comments

(defun comment-kill-all ()
  "Function to kill all comments in a buffer."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (comment-kill (save-excursion
                    (goto-char (point-max))
                    (line-number-at-pos)))))

buffer

go to scratch buffer

(defun bk/scratch-buffer ()
  "Function to change buffer to scratch buffer."
  (interactive)
  (let ((buf (get-buffer "*scratch*")))
    (if buf
        (switch-to-buffer buf)
      (switch-to-buffer (get-buffer-create "*scratch*"))
      (lisp-interaction-mode))))

(global-set-key (kbd "C-c b s") #'bk/scratch-buffer)

kill buffer and the file associated

 (defun bk/kill-buffer-and-file (buffer-name)
   "Removes file connected to current buffer and kills buffer."
   (interactive "bKill buffer and its file:")
   (let* ((buffer (get-buffer buffer-name))
   	   (filename (buffer-file-name buffer)))
     (if (not (and filename (file-exists-p filename)))
   	  (error "Buffer '%s' is not visiting a file!" buffer-name)
   	(delete-file filename)
   	(kill-buffer buffer))))

rename current buffer and file associated

(defun bk/rename-current-buffer-file ()
  "Renames current buffer and file it is visiting."
  (interactive)
  (let* ((name (buffer-name))
         (filename (buffer-file-name))
         (new-name (read-file-name "New name: " filename)))
    (if (get-buffer new-name)
        (error "A buffer named '%s' already exists!" new-name)
      (rename-file filename new-name 1)
      (rename-buffer new-name)
      (set-visited-file-name new-name)
      (set-buffer-modified-p nil)
      (message "File '%s' sucessfully renamed to '%s'"
               name (file-name-nondirectory new-name)))))

miscellaneous

generate password

(defun generate-password ()
  "Generate a 16-digit password."
  (interactive)
  (kill-new
   (s-trim (shell-command-to-string
            " openssl rand -base64 32 | tr -d /=+ | cut -c -16")))
  (message "Password in kill ring!"))

Keys

free keys?

Which keys are free?

(use-package free-keys
  :ensure t
  :commands free-keys)

hint

(use-package which-key
  :ensure t
  :delight which-key-mode
  :init
  (setq which-key-use-C-h-commands t
        which-key-separator " - "
        which-key-show-prefix 'echo
        which-key-popup-type 'side-window)
  :config
  (which-key-mode))

global definitions

(global-set-key (kbd "M-i") 'change-inner)
(global-set-key (kbd "M-o") 'change-outer)
(global-set-key (kbd "C-c e") 'eshell)
(global-set-key (kbd "C-c C-k") 'eval-buffer)
(global-set-key (kbd "C-x C-b") 'ibuffer)
(global-set-key (kbd "C-c t") 'org-capture)
(global-set-key (kbd "C-c a") 'org-agenda)
(global-set-key (kbd "C-x p") 'pop-to-mark-command)

;; by default C-x k prompts to select which buffer should be selected.
(global-set-key (kbd "C-x k") (lambda ()
             (interactive)
             (kill-buffer (current-buffer))))

cast

Show current command and its key in the mode line

(use-package keycast
  :homepage https://github.com/tarsius/keycast
  :ensure t
  :defer t)

key frequency

Monitor my new habits key-wise.

(use-package keyfreq
  :ensure t
  :init
  (setq keyfreq-excluded-commands
        '(self-insert-command
          abort-recursive-edit
          forward-char
          backward-char
          previous-line
          next-line
          org-self-insert-command))
  :config
  (keyfreq-mode +1)
  (keyfreq-autosave-mode +1))

Emms

GNU/Emms is the Emacs multimedia system. Emms displays and plays multimedia from within Emacs using a variety of external players and from different sources.

(use-package emms
  :ensure t
  :init
  (setq emms-seek-seconds 10)
  :config
  (require 'emms-setup)
  (require 'emms-player-mpv)
  (emms-standard)
  (emms-default-players))

mode line cycle

Display the emms mode line as a ticker. I am listening to several podcasts where the whole link of the podcast is displayed at the mode-line. I can’t see the time-elapsed listen to stuff, this is bad.

A package to solve this problem:

(use-package emms-mode-line-cycle
  :ensure t
  :after emms
  :config
  (emms-mode-line 1)
  (emms-playing-time 1)
  (emms-mode-line-cycle 1))

Exwm

Emacs as my full operating system is just too great. I have a small problem recently with this setup, I use an 60% Anne Pro 2 keyboard and its well known to be very but very buggy. The experience of the keyboard itself is incredible, but the firmware behind it is just unbearable, for some reason after hitting some keys my Emacs was halting. oO yes, complete halt for no reason whatsoever, seems like XKB and this firmware is not getting along nicely.

lxde

I found an alternative to fix this issue by using EXWM alongside with LXDE. There are two files: ~/.config/lxsession/LXDE/autostart

@pcmanfm --desktop-off --profile LXDE
@compton -b &

~/.config/lxsession/LXDE/desktop.conf

[Session]
window_manager=emacs
disable_autostart=no
polkit/command=lxpolkit
clipboard/command=lxclipboard
xsettings_manager/command=build-in
proxy_manager/command=build-in
keyring/command=ssh-agent
quit_manager/command=lxsession-logout
lock_manager/command=lxlock
terminal_manager/command=lxterminal
quit_manager/image=/usr/share/lxde/images/logout-banner.png
quit_manager/layout=top

[GTK]
iXft/Antialias=1
iXft/Hinting=1
sXft/HintStyle=hintslight
sXft/RGBA=rgb
sNet/ThemeName=Clearlooks
sNet/IconThemeName=nuoveXT2
iNet/EnableEventSounds=1
iNet/EnableInputFeedbackSounds=1
sGtk/ColorScheme=
sGtk/FontName=Sans 10
iGtk/ToolbarStyle=3
iGtk/ToolbarIconSize=3
iGtk/ButtonImages=1
iGtk/MenuImages=1
iGtk/CursorThemeSize=18
sGtk/CursorThemeName=DMZ-White

[Mouse]
AccFactor=20
AccThreshold=10
LeftHanded=0

[Keyboard]
Delay=500
Interval=30
Beep=1

[State]
guess_default=true

[Dbus]
lxde=true

[Environment]
menu_prefix=lxde-

And don’t forget to change the content of ~/.xinitrc to exec startlxde.

additional Functions

Ok, now let’s start with EXWM configuration.

(defun bk/keepmenu ()
  "Call password manager."
  (interactive)
  (start-process-shell-command "pwd" nil "keepmenu"))

(defun bk/lock-screen ()
  (interactive)
  (start-process-shell-command "lock" nil "xscreensaver-command -lock"))

(defun bk/qutebrowse ()
  (interactive)
  (start-process-shell-command "browser" nil "qutebrowser"))

exwm basics

(use-package exwm
  :ensure t
  :disabled true
  :init
  (setq exwm-workspace-number 4
        exwm-workspace-show-all-buffers nil
        exwm-layout-show-all-buffers t)
  :config
  (display-battery-mode t)

  ;; setting floating window boarder
  (setq exwm-floating-border-width 3)

  (require 'exwm)
  (exwm-input-set-simulation-keys
   '(([?\C-p] . [up])
     ([?\C-n] . [down])
     ([?\C-f] . [right])
     ([?\C-b] . [left])
     ([?\C-s] . [\C-f])
     ([?\M-w] . [\C-c])
     ([?\C-y] . [\C-v])
     ([?\C-w] . [\C-x])))

  (setq exwm-input-global-keys
        `(([?\s-r] . exwm-reset)
          ([?\s-w] . exwm-workspace-switch)
          ,@(mapcar (lambda (i)
                      `(,(kbd (format "s-%d" i)) .
                        (lambda ()
                          (interactive)
                          (exwm-workspace-switch-create ,i))))
                    (number-sequence 0 9))))

  (exwm-input-set-key (kbd "s-p") #'bk/keepmenu)
  (exwm-input-set-key (kbd "s-d") #'dmenu)
  (exwm-input-set-key (kbd "C-c l") #'bk/lock-screen)

  (exwm-enable)

  (require 'exwm-config)
  (exwm-config-ido)

  ;; universal Get-me-outta-here
  (push ?\C-g exwm-input-prefix-keys)
  (exwm-input-set-key (kbd "C-g") #'keyboard-quit))

window manager

Now that I am using Emacs as my window manager I can use the meta keys to provide operations over the windows itself, in other days, there were reserved to i3wm operations.

(eval-after-load "exwm"
  '(progn
     (exwm-input-set-key (kbd "s-x") #'exwm-input-toggle-keyboard)
     (exwm-input-set-key (kbd "s-h") #'windmove-left)
     (exwm-input-set-key (kbd "s-j") #'windmove-down)
     (exwm-input-set-key (kbd "s-k") #'windmove-up)
     (exwm-input-set-key (kbd "s-l") #'windmove-right)))

I made a change in qutebrowser so every tab open is a new window of qutebrowser, therefore I can search for the tabs using C-x b from Emacs.

:set -t tabs.tabs_are_window true

More settings for qutebrowser can be found here.

Disabling floating window

(eval-after-load "exwm"
  '(setq exwm-manage-force-tiling t))

edit text fields

Ag has a very nice package to help us out in EXWM, the feature is similar in usage to Org-SRC-Blocks, therefore you press C-c ' on input text boxes of other external apps and another window pops up so you can have all the Emacs under the finger while editing.

(use-package exwm-edit
  :ensure t
  :after (exwm)
  :config
  (exwm-input-set-key (kbd "C-s-e") #'exwm-edit--compose))

app runner

(use-package dmenu
  :ensure t
  :after (exwm)
  :commands (dmenu))

multiple screens

Stolen from reddit answer, however the original function is heavily customized to the authors setup, therefore I modified the appropriate bits.

After some time working with the code I decided to try to make a package out of it. However, there are several moving peaces yet, however, is already useful for me.

Work in Progress nevertheless…

(eval-after-load "exwm"
  '(progn
     (require 'exwm-monitors)

     (exwm-monitors-define-screen-info
      :name "eDP1"
      :width 1600
      :height 900)

     (exwm-monitors-define-spec
      :name "home"
      :pred (list :only '("eDP1" "HDMI1"))
      :action (list '("eDP1" :off)
                    '("HDMI1" :right)))

     (defun bk/turn-x1-carbon-on ()
       (interactive)
       (exwm-monitors-define-spec
        :name "home"
        :pred (list :only '("eDP1" "HDMI1"))
        :action (list '("eDP1" :auto)

                      '("HDMI1" :right)))
       (exwm-monitors-initial-setup))

     (exwm-monitors-initial-setup)))

system package

Oh, this is nice! I can control pacman from Emacs.

(use-package system-packages
  :ensure t
  :config
  (setq system-packages-use-sudo t))

notification daemon

I need a notification daemon to alert me about all the statefull things changing around me. For now, I will use Dunst.

(call-process-shell-command "nohup dunst >/dev/null &" nil 0)

There is a function to send an alert to the daemon.

(defun dunst-alert (header string &rest objects)
  "Send an alert to the Dunst daemon."
  (let ((string (funcall #'format string objects))
        (command (format "notify-send -a \"%s\" \"%s\"" header string)))
    (call-process-shell-command command nil 0)))

window behaviour

In stock Emacs, EXWM uses char mode and line mode to distinguish between using the keyboard to control an application vs using the keyboard to control the application’s buffer.

Rename buffers to match the X11 window class or title:

(defun exwm-rename-buffer ()
  (interactive)
  (exwm-workspace-rename-buffer
   (concat exwm-class-name ":"
           (if (<= (length exwm-title) 30) exwm-title
             (concat (substring exwm-title 0 29))))))

(add-hook 'exwm-update-class-hook 'exwm-rename-buffer)
(add-hook 'exwm-update-title-hook 'exwm-rename-buffer)

Window dividers make Emacs look far less sloppy, and provide divisions between windows that are significantly more visible. The color is grabbed from the mode line for consistency.

(eval-after-load "exwm"
  '(progn
     (setq window-divider-default-right-width 3)
     (let ((color (face-background 'mode-line)))
       (dolist (face '(window-divider-first-pixel
                       window-divider-last-pixel
                       window-divider))
         (set-face-foreground face color)))
     (window-divider-mode 1)))

key bindings

“Global key bindings” in EXWM work essentially anywhere, including buffers that are currently in char mode. The bindings below should be fairly straightforward.

Regular keys to control audio using the package pulseaudio.

(use-package pulseaudio-control
  :ensure t
  :after (exwm)
  :config
  (exwm-input-set-key
   (kbd "<XF86AudioLowerVolume>")
   #'pulseaudio-control-decrease-volume)

  (exwm-input-set-key
   (kbd "<XF86AudioRaiseVolume>")
   #'pulseaudio-control-increase-volume)

  (exwm-input-set-key
   (kbd "<XF86AudioMute>")
   #'pulseaudio-control-toggle-current-sink-mute))

Control the backlight level

(use-package emacs
  :ensure nil
  :after (exwm)
  :config
  (exwm-input-set-key
   (kbd "<XF86MonBrightnessDown>")
   (lambda () (interactive)
     (start-process-shell-command "bdown" nil "xbacklight -dec 10")))

  (exwm-input-set-key
   (kbd "<XF86MonBrightnessUp>")
   (lambda () (interactive)
     (start-process-shell-command "bdown" nil "xbacklight -inc 10"))))

More binding definitions

(defun bk/qutebrowser ()
  "Open the browser"
  (interactive)
  (start-process-shell-command "brw" nil "qutebrowser"))

(defun bk/fix-caps-and-key-rate ()
  "Capslock is another ctrl and key rate need to be higher"
  (interactive)
  (start-process-shell-command "caps" nil "setxkbmap -layout us -variant alt-intl -option ctrl:nocaps")
  (start-process-shell-command "krate" nil "xset r rate 300 50")
  (message "Ctrl and key rate fixed!"))

(use-package emacs
  :ensure nil
  :after (exwm)
  :config
  (exwm-input-set-key (kbd "s-q") #'bk/qutebrowser)
  (exwm-input-set-key (kbd "C-c k") #'bk/fix-caps-and-key-rate))

System tray

(defun exwm-bk/nm-applet ()
  (interactive)
  (start-process-shell-command "applt" nil
                               "nm-applet"))

(defun exwm-bk/bluetooth ()
  (interactive)
  (start-process-shell-command "bl-applt" nil
                               "blueman-applet"))

(defun exwm-bk/xscreensaver ()
  (interactive)
  (start-process-shell-command "xs" nil
                               "xscreensaver -no-splash"))

(defun exwm-bk/key-rate ()
  (interactive)
  (start-process-shell-command "key-rate" nil
                               "xset r rate 300 50"))

(defun exwm-bk/nocaps ()
  (interactive)
  (start-process-shell-command "nocaps" nil
                               "setxkbmap -layout us -variant alt-intl -option ctrl:nocaps"))

(use-package exwm-systemtray
  :ensure nil
  :after (exwm)
  :init
  (setq exwm-systemtray-height 20)
  :config
  (exwm-systemtray-enable)
  (add-hook 'exwm-init-hook 'exwm-bk/nm-applet t)
  (add-hook 'exwm-init-hook 'exwm-bk/bluetooth t)
  (add-hook 'exwm-init-hook 'exwm-bk/xscreensaver)
  (add-hook 'exwm-init-hook 'exwm-bk/key-rate)
  (add-hook 'exwm-init-hook 'exwm-bk/nocaps))

Logging out with LXDE

(defun exwm-logout ()
  (interactive)
  (recentf-save-list)
  (save-some-buffers)
  (start-process-shell-command "logout" nil "lxsession-logout"))

important commands

KeysCommandDescription
-exwm-workspace-move-windowsend current window to a specific workspace

References

  1. EXWM User Guide
  2. EXWM config with support for external monitor
  3. Reddit EXWM Configs?
  4. Ag layer for EXWM
  5. Another great emacs.d focusing on EXWM
  6. Emacs Everywhere - by u/ambrevar. Very nice post on reddit.
  7. https://ambrevar.xyz/emacs/
  8. Literate emacs - by wasamasa
  9. Far from sane literate Emacs - by farlado
  10. Pragmatic Emacs - Amazing blog with lots of Emacs customizations