Skip to content

Ebert-Hanke/emacs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 

Repository files navigation

Yet Another Emacs Config

Hi! My name is Micha and I write poetry in Emacs. I also use it to make a living as a developer for web stuff. This is my attempt to help new people start using Emacs for writing and coding. I have been using Emacs for some years now and this is what I have learned so far. I hope you enjoy Emacs as much as I do!

Thanks!

I am by no means an expert on Emacs. Everything I know I slowly cobbled together from different resources. There is a lot of good stuff out there about Emacs and many great people devoted lots of energy to compiling all that knowledge. Many of the things you find in this file are taken from one of these resources or based upon it so I want to give due credit and say thank you!

If anyone feels like contributing to this config it would be great! I am sure there is a lot in it which can be done better. Please let me know.

Also I would like to include more resources about Emacs, so please let me know, what might make sense.

How to use this config

Emacs relies on a config file which you find in the .emacs.d folder in your user folder (Linux & MacOS). In this folder a init.el file will hold your individual configuration. There is stuff that might be easier to set up in the “Options” menu in Emacs itself. This configurations will then be written by Emacs in your init.el under the entry (custom-set-variables. Just leave this block as it is. This configuration is written in Org Mode and uses Org Babel to have Emacs write an actual config file from your org mode config for you. In order to make this work you will need to place the following in your init.el. You can see that the next headline has a COMMENT in front of it, so it will be ignored by Org Babel. This makes sense if you want to make it really easy and just rename this file to setup.org so it becomes your config file as a whole. This might not be the most efficient in terms of startup times, since a large .org file can take a moment to load. You can toggle comments of org headlines with C-c ; and I will use this in the config to comment out things which are more meant for specific scenarios or taste. Just comment them back in, if you like to use them, or copy the bits and pieces you want to your own new config file. If you are reading this on my GitHub page, the headlines which are commented out will not be displayed. It makes more sense to get this file and open it in your Emacs.

Code Blocks in Org Mode

The code used to configure Emacs is written in Emacs Lisp a dialect of Lisp. To specify a code block in Org Mode to make it recognizable as such for formatting and also for Org Babel it is marked as source code block with a specified language by using the following: #+BEGIN_SRC and #+END_SRC. For Emacs Lisp and this config it will then look like this: #+BEGIN_SRC emacs-lisp and #+END_SRC as you can see above. For more information about Working with Source Code in Org Mode have a look here.

Editing Code Blocks

In order to edit code blocks in their respective mode and get proper syntax highlighting and formatting, you can do C-c ' on the code block to open a new buffer to edit. To close just do C-c ' again.

Comments in Emacs Lisp

Since this config will mostly be written in Org Mode, there will be a lot of explanation and links to further reading material in the prose parts but putting comments in the code blocks themselves is of course always good practice. For the Emacs Lisp Comment Syntax have a look here.

Emacs Packages & MELPA

Additions to the vanilla core of Emacs come in the form of Packages and can be added from different places, the most relevant being MELPA. In this config the use-package macro will be used. For more information on use-package check the repository here.

Your Config File

Next up on the list is the actual config file which we will call setup.org. If you want to give it another name, just change the reference in the init.el. Since this file will get compiled to setup.el you want to avoid naming conflicts and not call it init.org. For more information on the Files Emacs uses for Initialization have a look here. In the following you find a lot of config suggestions, which you could copy to your personal config. The beauty of Emacs is that you can make it the perfect tool for what you want to use it for, so there is obviously no config for every person. This is just meant as a sort of curated list of configurations and packages that you might want to evaluate and modify to your own liking. By doing this you hopefully find your way to making Emacs just perfect for you.

The Bits and Pieces for the Config

Tips & Tricks

If you ever want to comment out a whole code block for a particular part of your config you can use the command C-c ; on a subtree so everything under this will not get compiled by org babel.

General Settings

User Info

Specify your name and email address if you like.

(setq user-full-name "Your Full Name")
(setq user-mail-address "youremail@something.something")

Startup Screen

You might not want to see the startup screen every time you start Emacs.

(setq inhibit-startup-screen t)

Tool Bar, Menu Bar, Scroll Bar

Depending on your preference you might not want these parts in the interface. Personally I keep the menu bar around and disable the other two. Comment in the menu bar line to also disable it. You can check if you are in window-system to only enable certain config bits if it is the case.

(if window-system (scroll-bar-mode -1))
(tool-bar-mode -1)
;;(menu-bar-mode -1)

Yes or No

Emacs will often ask you stuff. If you don’t want to type “yes” or “no”, put this instead.

(defalias 'yes-or-no-p 'y-or-n-p)

UTF-8

You probably want to use UTF-8 so you should specify it.

(setq locale-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(prefer-coding-system 'utf-8)

Bell

Emacs will sometimes prompt you with an acoustic or visual signal to get your attention. I like to disable this.

(setq visible-bell nil)
(setq ring-bell-function 'ignore)

Scrolling Behaviour

For a smoother scrolling behavior with the cursor you can add this. For More Info on Scrolling check here.

(setq scroll-conservatively 1000)

Backups

Emacs can create backup files for you and if you want to use this feature I would recommend some config for it in order to avoid clutter. There is an argument for not having backups because we use Git but I think disk space is cheap and why not just have them. More Info about Backups can be found here.

;; specify your backup directory so your backups have a nice home
(setq backup-directory-alist `(("." . "~/.emacs-backups")))
;; backups should be made by copying which is safest (but maybe slower)
(setq backup-by-copying t)
;; define how backups should be handled
(setq delete-old-versions t ; delete old versions
kept-new-versions 6 ;  how many new versions to keep
kept-old-versions 2 ; how many old versions to keep
version-control t) ; version number the backup files

Convenience

Visit Your Config File

Since you might re-visit your configuration quite regularly to fiddle around with things, you might want a quick way to open it.

(defun config-visit ()
  (interactive)
  (find-file "~/.emacs.d/setup.org")) ; the path to your config file
(global-set-key (kbd "C-c e") 'config-visit) ; you can pick any key binding you like, here I chose "C-c e"

Reload Config

After you changed stuff in your config you might want to reload it. Keep in mind that some changes need a restart of Emacs so this will not always work.

(defun config-reload ()
  (interactive)
  (org-babel-load-file (expand-file-name "~/.emacs.d/setup.org"))) ; the path to your config file
(global-set-key (kbd "C-c u") 'config-reload) ; the key binding to execute this function, here I chose "C-c u"

Kill Current Buffer

To have a quick way to kill the current buffer you can use this.

(defun kill-curr-buffer ()
  (interactive)
  (kill-buffer (current-buffer)))
(global-set-key (kbd "C-x k") 'kill-curr-buffer) ; chose any key binding you like, I use "C-x k"

Aesthetics

Color Themes

There is a wide variety of color themes you can choose from and this is totally up to your own preference. I personally tried different things but keep coming back to the almighty Zenburn by Jani Nurminen so I give this as a starting point. Check the link above on different ways to install it. The easiest might be M-x package-install zenburn-theme from Emacs directly. To automatically load it on startup put this in your config:

(load-theme 'zenburn t)

Fonts

There are lots of great fonts to choose from. Here are some recommendations which might be more aimed at writing code. If you want to use Emacs for writing text you might want to choose something else unless you like writing in a monospaced font (as I do). Personally I use Monoid (more specifically Monoisome).

Once you installed the font of your desire on your system (or picked any already installed font on your system), you can set it via the menu bar Options -> Set Default Font and then Options -> Save Options. If you have disabled the menu bar (see above) you can still use the GUI picker by doing M-x menu-set-font.

Spaceline

So this one is a bit more involved and really more an aesthetical choice but if you like to have a different mode-line (the line below which gives you all sorts of usefull information) then you could use this mode-line from Spacemacs (which is an Emacs distribution). You find all the info about Spaceline and how to configure it here. As a starting point this is what I use at the moment:

(use-package spaceline
  :ensure t
  :config
  (require 'spaceline-config)
  (setq spaceline-buffer-encoding-abbrev-p nil)
  (setq spaceline-line-column-p nil)
  (setq spaceline-line-p nil)
  (setq powerline-default-separator (quote arrow))
  (spaceline-spacemacs-theme)
  (setq spaceline-nyan-cat-p t)
  (setq spaceline-buffer-position-p nil)
  (setq spaceline-projectile-root-p nil))

After changes it might be necessary to run M-x spaceline-compile

Mode Line

You can customize a lot of the info which is displayes in your mode-line. Here are some things to pick from or extend.

Time / Time Format

I like a clock in the mode-line and I like 24h format with date.

(setq display-time-24hr-format t)
(setq display-time-format "%H:%M / %d %b")
(display-time-mode 1)

Mail

The mode-line usually tells you if you have mail. If you do not want this, put the following.

(custom-set-variables '(display-time-mail-string ""))

Load Average

The load average time can be displayed. I don’t need that.

(setq display-time-default-load-average nil)

Diminish Mode

Emacs will show you all the active modes in the mode line which you might not want since it gets cluttered. To address this Will Mengarini created Diminish Mode.

When we diminish a mode, we are saying we want it to continue doing its work for us, but we no longer want to be reminded of it. It becomes a night worker, like a janitor; it becomes an invisible man; it remains a component, perhaps an important one, sometimes an indispensable one, of the mechanism that maintains the day-people’s world, but its place in their thoughts is diminished, usually to nothing. As we grow old we diminish more and more such thoughts, such people, usually to nothing. – Will Mengarini

So just define what becomes a silent “night worker”.

(use-package diminish
:ensure t
      :init
      (diminish 'which-key-mode)
      (diminish 'beacon-mode)
      (diminish 'visual-line-mode)
      (diminish 'autopair-mode)
      (diminish 'projectile-mode)    
      (diminish 'helm-mode)
      (diminish 'company-mode)
      (diminish 'flyspell-mode)
      (diminish 'flycheck-mode)
      (diminish 'rainbow-delimiters-mode)
      )
      ;; modify according to the modes which you actually use

Usability

Which Key

Which Key gives you a pop up with possible completions of the command you started. Very useful if you don’t remember an exact key binding.

;; which key
(use-package which-key
	:ensure t 
	:config
	(which-key-mode))

Dired

Emacs comes with a great file manager called Dired. To make this work better to my taste I customize the following:

;; reuse the dired buffer when you open something by pressing 'a'
(put 'dired-find-alternate-file 'disabled nil)
;; if you have a horizontal split open you can copy from one to the other
(setq dired-dwim-target t)

If Dired is not to your thing and you prefer something with a folder tree you might want to check out Treemacs.

Ivy, Counsel, Swiper

Many times you will search for stuff. To have an even better search you can use Swiper:

(use-package swiper
  :ensure t
  :bind ("C-s" . 'swiper))

Helm

(use-package helm
  :ensure t
  :bind
  ("C-x C-f" . 'helm-find-files)
  ("C-x C-b" . 'helm-buffers-list)
  ("M-x" . 'helm-M-x)
  ("C-x r b" . 'helm-bookmarks)
  :init
  (helm-mode 1))

(require 'helm-config)

Helm Projectile

(use-package helm-projectile
  :ensure t
  :config
  (helm-projectile-on))

Helm Tramp

(use-package helm-tramp
  :ensure t
  :config
  (setq tramp-default-method "ssh")
  (define-key global-map (kbd "C-c s") 'helm-tramp)
  (setq make-backup-files nil)
  (setq create-lockfiles nil)
  )

Helm Swiper

(use-package swiper-helm
:ensure t)

Ace Window

You can split windows in Emacs with C-x 3 (horizontally) and C-x 2 (vertically). To make a window full-size again use C-x 1. Ace Window makes changing between open windows fast and easy.

(use-package ace-window
  :ensure t
  :bind ("M-o" . ace-window))

Org Mode

A great thing to use with Emacs is Org Mode. I use it for all my writing, project management, time keeping, habit tracking, note taking, documentation and - of course - this config. Check the Org Manual or also the great video series by Rainer König. In the following you find some stuff I have found useful to configure.

one Note File to bind them …

I personally like to have one giant note file for all notes which I can access via key binding and just put in whatever it is I have to write down. I divide it into a few meaningful main categories and then create sub-categories as needed in the outline. To bring it up quickly I use:

;; modify filename / -path and keybinding to your liking
(global-set-key (kbd "<f6>") (lambda() (interactive)(find-file "~/orgfiles/misc.org")))

Org Bullets

In order to make the Org Mode outline structure with the asterisks look nicer I use Org Bullets and define some individual symbols for the levels 1 to 5.

(use-package org-bullets
:ensure t
:init
(setq org-bullets-bullet-list
;; any list of outline unicode characters can be enetered here for the different levels. enter them by using C-x 8 RET followed by the unicode number
'("" "" "" "" ""))
:config
(add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))))

Paths for Org and Agenda

For the Org Agenda and general Org Mode files, you should specify the paths which should be taken into account.

;; org directories, please set your own
(setq org-directory "/pathtoyourorgfiles")
;; agenda files, please set your own. multiple folders are possible
(setq org-agenda-files 
'("/filesforagenda" "/morefilesforagenda"))

Expand emacs-lisp code block

Since you will use a lot of emacs-lisp code blocks in this config you might want to make your life easier and add a snippet. By typing <s and pressing TAB you can quickly create a source code block. If you don’t want to specify the emacs-lisp part every time you can add a new shortcut with the following. Type <el and press TAB now for an emacs-lisp source code block.

;; necessary to use quick insertion of code blocks
(require 'org-tempo)  
;; add code block for emacs-lisp
(add-to-list 'org-structure-template-alist
               '("el" . "src emacs-lisp"))

Key bindings & Settings

Key bindings in Emacs are absolutely flexible, so always pick what works for you. Some functions you might use often don’t come with predefined key bindings so just choose some. Also there is a lot of setting variables you can use to fine tune the way Org Mode should work for you. Here are some. If you are not sure what a variable does bring up the documentation in Emacs with C-h v and then search for its name.

;; keybinding for linking things in .org documents
(define-key global-map "\C-cl" 'org-store-link)
;; keybinding to bring up the agenda view
(define-key global-map "\C-ca" 'org-agenda)
;; add a timestamp when a todo is changed to done
(setq org-log-done t)
;; soft wrap lines and indent for org mode
(with-eval-after-load 'org       
  (setq org-startup-indented t) 
  (add-hook 'org-mode-hook 'visual-line-mode))

Time Tracking

Org Mode provides you with a nice way to track time which I use a lot to keep track how many work hours I have spend on a project. Have a look at Clocking Work Time for the necessary commands you can use. To make this work better for my taste I specify some things.

;; adjust time format for clocksum in column view
(setq org-duration-format 'h:mm)
(setq org-time-clocksum-format (quote (:hours "%d" :require-hours t :minutes ":%02d" :require-minutes t)))
;; put all the clock entries into a drawer called CLOCKING
(setq org-clock-into-drawer "CLOCKING")

To get the most out of time tracking have a look at working with Tables in Org Mode. As a starting point here is some code you might find useful:

HeadlineTime
Total time16:21
My fancy Project16:21
\_ Timetrack16:21

This will sum up all your clock entries of the defined scope. If you want to update it press C-c C-x C-u.

Time BudgetTime Remaining
40:0023:39

This now will subtract the summed up time from a “time budget” you specify. I often find this useful for keeping track of projects. You could also easily calculate other stuff in, like your rate per hour or the like. For more information on Spreadsheet Functions in Org Mode have a look here. To update a table like this put the cursor on its formula and press C-c C-c.

ToDo States

You can use ToDo Items in Org Mode and you can specify the states they can have as well as the corresponding shortcuts to set them like this:

;;; org mode states (TODO changed to ACTIVE)
(setq org-todo-keywords
'((sequence "TODO(t)" "☛ ACTIVE(t)" "⚑ WAITING(w)" "|" "✔ DONE(d)" "✘ CANCELED(c)")))

Code

Many of you will use Emacs to write code. Depending on what you do, different packages and options will make sense. Since this is based on my config and the things I do (building stuff for the web), there is much left out. Hopefully others feel like contributing some starting points for other languages, environments and work flows.

Basic Stuff

Here are some usability things I personally like to have. Just use what you like and need.

;; highlight your active line in all modes
(when window-system (global-hl-line-mode t))
;; link matching parentheses
(show-paren-mode 1)
;; highlight columns / html elements in web-mode
(setq web-mode-enable-current-column-highlight t)
(setq web-mode-enable-current-element-highlight t)

Terminal / Shell

You will want a shell in Emacs. There is different ways to do this and all have their pros and cons. Some more information about this here.

(defvar my-term-shell "/bin/zsh") ; path to your shell of choice
(defadvice ansi-term (before force-bash)
  (interactive (list my-term-shell)))
(ad-activate 'ansi-term)
(global-set-key (kbd "C-c t") 'ansi-term) ; keybinding to open ansi-term

If you want more than one instance of ansi-term you can use this:

(use-package multi-term
  :ensure t
  :config
  (setq multi-term-program "/bin/zsh")) ; specify your shell

Magit

I am assuming that you use Git (if not you should consider it, actually also for non-code writing). One of my favorite things in Emacs is Magit - “A Git Porcelain inside Emacs” … and it is really amazing. I think it works somewhat intuitively and also gives you a lot of help inside, but the documentation is also really good, so check it out and you should be fine.

(use-package magit
:ensure t
:config
(global-set-key (kbd "C-x g") 'magit-status)
)

Projectile

In order to change quickly between your different projects (and also do a lot of other cool things in project scope) you can use Projectile. Check out the extensive Documentation to learn more about its functionality.

  (use-package projectile
    :ensure t
    :init
    (projectile-mode +1)
(define-key projectile-mode-map (kbd "s-p") 'projectile-command-map)
 (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)
(setq projectile-switch-project-action #'projectile-dired))
  (global-set-key (kbd "<f5>") 'projectile-compile-project)

Evil Mode

All flame wars may end, best of both worlds, peace. Evil Mode brings Vim modes and movements to Emacs.

(use-package evil
:ensure t
:config
(evil-mode 1)
(define-key evil-normal-state-map (kbd "C-u") 'evil-scroll-up))

Evil Escape

Pressing ESC to go back to normal mode is not really ergonomic Evil Escape Mode allows you to remap this to something else. I like to put it on the home row.

(use-package evil-escape
:ensure t
:config
(evil-escape-mode 1)
(setq-default evil-escape-key-sequence "jj")
(setq-default evil-escape-delay 0.2)
)

Relative Line Numbers

Since I use Evil Mode, I like to have relative line numbers. Check nlinum-relative for details.

(use-package nlinum-relative
:ensure t
    :config
    ;; define in which modes youd like line numbers
    (nlinum-relative-setup-evil)
    (add-hook 'prog-mode-hook 'nlinum-relative-mode)
    (add-hook 'web-mode-hook 'nlinum-relative-mode))

Autopair

If you type one brace or quote the other half shalt magically appear by the power of Autopair.

(use-package autopair
:ensure t
:config
(autopair-global-mode))

Rainbow Delimiters

Braces are colored according to nesting structure so you keep track of which is what. Check rainbow-delimiters for more details.

(use-package rainbow-delimiters
  :ensure t
  :init
  (add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
)

Expand Region

Easy selection of semantic regions in code. Expand a Region in your code.

(use-package expand-region
  :ensure t
  :config
  (global-set-key (kbd "C-=") 'er/expand-region)
  )

Markdown Mode

If possible nowadays I use Org but Markdown also comes in handy a lot of times and then you might want to use Markdown Mode if you have to edit markdown files. Keep in mind that you can always export markdown from Org Mode.

(use-package markdown-mode
  :ensure t
  :commands (markdown-mode gfm-mode)
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :init (setq markdown-command "multimarkdown"))

Web Mode

Web Mode is “an autonomous emacs major-mode for editing web templates” and if you are coding anything for the web it will be quite useful. It gives you proper semantic structure, syntax highlighting and more. I personally also prefer it for Vue.js. There is also a mode for Vue but that didn’t really work for me.

(use-package web-mode
  :ensure t
  :mode (("\\.erb\\'" . web-mode)
	 ("\\.mustache\\'" . web-mode)
	 ("\\.html?\\'" . web-mode)
         ("\\.php\\'" . web-mode)
         ("\\.vue\\'" . web-mode))
  :config (progn
            (setq web-mode-markup-indent-offset 2
		  web-mode-css-indent-offset 2
              web-mode-code-indent-offset 2)))

Emmet

Emmet makes your life so much easier (and writing code so much quicker) if you are doing web development. It can create many lines of nested markup with classes and all in no time. Have a look at the documentation how it works.

(use-package emmet-mode
:ensure t
:hook (web-mode css-mode)
)

JS2 Mode

For all your JavaScript concerns js2-mode is the right one to use. More details can be found in the description here.

(use-package js2-mode
  :ensure t
  :init
  (add-hook 'js2-mode-hook #'js2-imenu-extras-mode)
  :config
  (add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
  (setq js2-basic-offset 2)
  (setq indent-tabs-mode nil)
  )

JSON Mode

json-mode gives you syntax highlighting as well as some key bindings for working with JSON files in emacs.

(use-package json-mode
:ensure t
)

Flycheck

On the fly syntax checking for many languages. Check out the Quickstart Guide to set it up for your use case.

(use-package flycheck
  :ensure t
  :init 
  (global-flycheck-mode))

LSP Mode

LSP was originally developed for Visual Studio Code by Microsoft and is now an open standard. It is the driving force behind many IDE features in VSCode. If this might be the editor you are switching from to Emacs, fear not – all this (and more) can be had in Emacs. lsp-mode is a LSP client for Emacs and integrates with packages like company, flycheck and projectile. To make it work you need the appropriate LSP Servers installed on your system. For what I do (web development, JavaScript, Vue.js, Node.js) I use the following:

Installation is usually quite straight forward via npm.

;; set prefix for lsp-command-keymap (few alternatives - "C-l", "C-c l")
(setq lsp-keymap-prefix "C-l")

(use-package lsp-mode
    :hook (;; replace XXX-mode with concrete major-mode(e. g. python-mode)
            (web-mode . lsp)
            (js2-mode . lsp)            
            (js-mode . lsp)            
            (json-mode . lsp)            
;; if you want which-key integration
            (lsp-mode . lsp-enable-which-key-integration))
    :commands lsp)

;; optionally
(use-package lsp-ui :commands lsp-ui-mode)
;; if you are helm user
(use-package helm-lsp :commands helm-lsp-workspace-symbol)
;; if you are ivy user
(use-package lsp-ivy :commands lsp-ivy-workspace-symbol)
(use-package lsp-treemacs :commands lsp-treemacs-errors-list)

;; optionally if you want to use debugger
;;(use-package dap-mode)
;; (use-package dap-LANGUAGE) to load the dap adapter for your language

;; optional if you want which-key integration
(use-package which-key
    :config
    (which-key-mode))

Prettier

Prettier is an “opinionated code formatter” for JavaScript, HTML, CSS, SCSS, Vue and more. To use it you must first install it on your system by running npm install -g prettier. Once that is done you can use it in Emacs (and configure it to your liking) with prettier-js. Check out the documentation how to configure what you like best (tabs / spaces, indentation size, single / double quotes and the like).

(use-package prettier-js
:ensure t
)
(require 'prettier-js)
(add-hook 'js2-mode-hook 'prettier-js-mode)
(add-hook 'web-mode-hook 'prettier-js-mode)
(setq prettier-js-args '(
  "--single-quote" "false"
  "--prose-wrap" "never"
))
(defun enable-minor-mode (my-pair)
  "Enable minor mode if filename match the regexp.  MY-PAIR is a cons cell (regexp . minor-mode)."
  (if (buffer-file-name)
      (if (string-match (car my-pair) buffer-file-name)
      (funcall (cdr my-pair)))))
(add-hook 'web-mode-hook #'(lambda ()
                            (enable-minor-mode
                             '("\\.jsx?\\'" . prettier-js-mode))))

Email in Emacs

Intro

In order to spend more of your life in Emacs and make email in general suck less, you can use Emacs as your email client. There are a lot of different options and approaches to set this up. The one I want to outline here involves mbsync / isync, msmtp and Notmuch. This is of course an opinionated topic so feel free to do your own research online and check out options like offlineimap for syncing mail and mu4e as a mail frontend for Emacs. Generally speaking we need three things:

  1. a solution to sync our email from a server via IMAP to our computer as a local copy (mbsync, offlineimap, etc)
  2. a comfortable way to read and write, sort and filter email in Emacs (notmuch, mu4e, etc)
  3. a way to send email via SMTP (this can be done with Emacs onboard solution or msmtp)

Thanks

This took me a moment to figure out and other than all the documentation there were some really good resources / examples for setups I would like to point out … and say thank you!

Getting Mail

First you should install isync which is a tool which will sync your imap email account to a local folder on your harddrive. Depending on your OS there are different ways to do this. For Linux have a look here. For MacOS you can do brew install isync via Homebrew. Once that is done you need to set up a folder where you want your sinced emails to go. ~/Maildir for example. Next you need to set up a config for mbsync which is in the file ~/.mbsyncrc. The following can be used as a starting point. The manual for all options can be found here.

###  One Mail Account ###

IMAPAccount accountname
Host yourserver
Port 993
User yourusername	
# we dont want to store credentials in plain text so we encrypt the file and decrypt it here
# choose any path and filename and see below how to encrypt
PassCmd "gpg -q --for-your-eyes-only --no-tty -d ~/.yourimappassword.gpg"
SSLType IMAPS
SSLVersions TLSv1.2

# Remote
IMAPStore accountname-remote
Account accountname

# Local Storage
MaildirStore accountname-local
Path ~/Maildir/accountname/
Inbox ~/Maildir/accountname/INBOX
#if you want to download all subfolders
SubFolders Verbatim

# Channel
Channel accountname
Master :accountname-remote:
Slave :accountname-local:
Patterns *
# all mail and folders will be created in both places
Create Both
# all mail and folders will be permanently deletd in both places
# be careful here and test first if your setup works as expected
Expunge Both
CopyArrivalDate yes
# For help about the config parameters:
# https://isync.sourceforge.io/
# there are some empty lines here to separate blocks, keep them around or it wont work
# add as many accounts according to this schema below each other

For encryption of files in Emacs have a look at Keeping Secrets in Emacs by Mickey Petersen.

Now we can sync our imap account with the command mbsync -a. If everything works as expected it should take a while and then you have a local sync of all your email.

Reading Mail

In order to read mail we will use Notmuch. Have a look at the projects home page and see the section “Obtaining Notmuch” on how to install it for your operating system. After installing Notmuch we need to set it up to index all our synced email. We run notmuch setup in the terminal and tell Notmuch the path to our local email folder and some other optional information. This will build an initial database which will be used for tagging. The key concept of Notmuch is to not manipulate your email in any way. All it does it to reference additional data (tags) to the actual email files. This means you can not delete mail from Notmuch which might fee a bit weird but makes sense. What you can do is tag mail for deletion and the use a script to actually delete the mail. More on that later. Now we want to make Notmuch work in Emacs. Depending on the way you installed it, it might have come with the Emacs frontend out of the box. If the following does not work just install the notmuch package your favorite way. Here you find more information on the Emacs setup. The barebones should be:

(require 'notmuch)

Now you should be able to start Notmuch the usual way M-x notmuch. Feel free to define a global keybinding for this like so:

(global-set-key (kbd "C-c M") 'notmuch)

Once in the Notmuch starting screen you can press G to update email. For all the details how Notmuch works please check out the documentation on the project website. It is important to understand, that if you press G all it does is to reindex your local mail folder. It does not get the new mail from the server. This is the job of mbsync. To make this smoother we will now create a little script as a hook in Notmuch. See Notmuch Hooks for more info on this. We want to do the following in the terminal:

cd yourmaildir/.notmuch
mkdir hooks
cat > pre-new
#!/bin/zsh
mbsync -a

Change the #!/bin/zsh part to your shell and press Ctrl+D or Ctrl+C to exit. Now we need to make the file executable by:

chmod +x pre-new

When we press G in the Notmuch start screen now it will call mbsync to sync all mail from the server(s) and then have Notmuch index it.

Composing and Sending Mail

To send mail we will use msmtp. While Emacs comes equipped to send mail, I find this a bit easier if you want to use differen SMTP-servers for different identities. First step is to install msmtp on your system via the appropriate packet manager. Next we need to configure msmtp with a config file which should be ~/.msmtprc. The following should provide a good starting point. Check the msmtp website for more details on the configuration.

### one account ###
account youraccountname
host yourserveraddress
port 465
protocol smtp
auth on
user yourusername
from youremailaddress
passwordeval "gpg -q --for-your-eyes-only --no-tty -d ~/.secrets/.yoursmtppassword.gpg"
tls on
tls_starttls off
# add as many accounts according to this schema below each other

To compose email Notmuch uses a mode derived from Message Mode. Next up is the configuration part in Emacs.

;; change the directory to store the sent mail and drafts
;; this is set relative to your main maildir, where the .notmuch lives
(setq message-directory "yourmaildir/Drafts")
;; here you set the directory for the sent mail and the taglist for sent mail
(setq notmuch-fcc-dirs "youraccount/Sent -unread -inbox +sent")
;; add Cc and Bcc headers to the message buffer
(setq message-default-mail-headers "Cc: \nBcc: \n")
;; use to send mail msmtp
(setq message-send-mail-function 'message-send-mail-with-sendmail)
(setq sendmail-program "/usr/local/bin/msmtp")
;; tell msmtp to choose the SMTP server according to the from field in the outgoing email - taken from Adolfo Villafiorita
(setq message-sendmail-extra-arguments '("--read-envelope-from"))
(setq message-sendmail-f-is-evil 'T)
;; close buffer after send  
(setq message-kill-buffer-on-exit t)

Since this config is aimed at people having more than one email identity we need a solution for this. We are already telling msmtp to choose the right profile according to the from field in our email but in order for the email to be stored in the right sent folder we need to change the notmuch-fcc-dirs variable accordingly. To solve this we will use gnus-alias. Download this and put it in your loading path. It is important to specify your email identities in your .notmuch-config which was created in your home folder. Here under the section [user] you can add other_email=yourotheremail@something.com. If you have more than one email identity you dont need to specify the notmuch-fcc-dirs as seen above since it will be changed accordingly. To do this we add to our Emacs config:

;;; use gnus-alias to switch between identities

;; add gnus-alias to loading path and autoload
(add-to-list 'load-path "~/.emacs.d/plugins/gnus-alias/")
(autoload 'gnus-alias-determine-identity "gnus-alias" "" t)

;; define identities
;; define two identities, "home" and "work"
(setq gnus-alias-identity-alist
      '(("home"
         nil ;; does not refer to any other identity
         "Your Name <mail@something.com>" ;; Sender address
         nil ;; no organization header
         (("Fcc" . "accountone/Sent -unread -inbox +sent")) ; extra headers
         nil ;; no extra body text
         "~/.signature-home" ;; signature
         )
        ("job"
         nil ;; not referencing another identity
         "Your Other Name <moremail@else.com>"
         "Fancy Coding" ; organisation header
         (("Fcc" . "accounttwo/Sent -unread -inbox +sent")) ; extra headers
         nil ;; no extra body text
         "~/.signature-job"))) ; signature file

;; set your "job" identity as default
(setq gnus-alias-default-identity "job")

;; define rules to match identities for replies
(setq gnus-alias-identity-rules
      '(
        ("home"
         ("any" "mail@something.com" both) "hob")
        )
      ) 

;; hook into message setup
(add-hook 'message-setup-hook 'gnus-alias-determine-identity)

As you can see we switch the Fcc to specify the correct sent folder and also add and remove tags so sent mail will be treated as such. All the variables in gnus-alias are documented in detail so just inspect them with C-h v. For the rules you can do any regular expression to have more complex filter scenarios to choose the right identity.

It is also useful to have a quick way to switch identities when composing a message. Lets bind a key to this.

(define-key notmuch-message-mode-map (kbd "<f7>")
  'gnus-alias-select-identity)

Deleting Mail

It is my belief that email you never would likely go back to should be deleted. Since it is the philosphy of Notmuch to not alter your email in any way but only to reference it in a database, deleting email is not something Notmuch does. Hoever we can tag email for deletion and by default Notmuch hides email which is tagged with deleted. Of course you can still see the mail if you filter for this tag but for the moment it is out of sight. So we want a way to easily toggle this tag:

;; toggle the tag 'deleted'
(define-key notmuch-show-mode-map "d"
  (lambda ()
    "toggle todelete tag for message"
    (interactive)
    (if (member "deleted" (notmuch-show-get-tags))
        (notmuch-show-tag (list "-deleted"))
      (notmuch-show-tag (list "+deleted")))))

This of course can be used as a blueprint for any tagging you might want to do quickly. Now that we can mark emails for deletion we might also want to really delete the email files and sync up to our server. Be careful since this is pretty permanent. We use Notmuch to output all the files marked with the deleted tag and pipe this output into xargs to delete them. This could be a shell script or a cron job but I just like to call it from within Emacs. Of course you can do any other option. I also dont bind a key to this to make it a bit more of a conscious decision to call the function. Do how you see fit.

;; delete all messages with the tag deleted
(defun eh-delete-tagged-messages ()
  ;; filter all messages for tag deleted and delete them
  (interactive)
  (shell-command "notmuch search --output=files tag:deleted | xargs -L 1 rm"))

Email Workflow

Notmuch is an immensely powerful tool and you should make up your mind how you want to tackle your email situation with it. Have a look at the Notmuch documentation, especially Notmuch Search Terms, Initial Tagging and Hooks. For me the starting point to get excited about Notmuch was a talk by Vedang Manerikar who also kindly shares his setup. A big shoutout and thank you! So I hope you come up with a good setup for your needs!

Workflow Basics

In this section I want to give some ideas and starting point what you might want to do in terms of tagging and filtering with Notmuch. First up is defining some search based mailboxes in Emacs:

;; notmuch "mailboxes" (predefined searches)
(setq notmuch-saved-searches 
      '((:name "Private"
               :query "tag:private AND tag:unread"
               :key "p"
               :sort-order newest-first
               :search-type 'tree)
        (:name "Job"
               :query "tag:job AND tag:unread AND NOT tag:feed"
               :key "j"
               :sort-order newest-first
               :search-type 'tree)
        (:name "Feed"
               :query "tag:feed AND tag:unread"
               :key "f"
               :sort-order newest-first)
        (:name "Verwaltung"
               :query "tag:verwaltung AND tag:unread"
               :key "v"
               :sort-order newest-first
               :search-type 'tree)
        (:name "To Reply"
               :query "tag:toreply AND NOT tag:unread"
               :key "r"
               :search-type 'tree)
        (:name "All"
               :query "*"
               :key "a")
        (:name "Archive"
               :query "tag:archive"
               :key "A")))

This might give you an idea what could be done. To make this work nicely you might want to automatically assign tags to new mail depending on search criteria. Notmuch comes with a hook to do this but there is also a nice “initial tagging script” for Notmuch which is called afew which you might want to look into. For the moment we just stick with the hooks of Notmuch. We navigate to the hooks folder of notmuch where we already created our pre-new hook script and add another here like so in the terminal:

touch post-new
chmod +x post-new

Now we can open up the file and add some things as a starting point:

#! /bin/zsh
# specify the shell you want to use

# now for example you want to tag things which are coming from mailing lists with "feed"
notmuch tag +feed -- from:somemailinglist@something.com OR from:someothermailinglist@something.com
# or do a regular expression
notmuch tag +feed -- from:"*@something.com"
# now you could add more specific stuff
notmuch tag +emacs -- tag:feed AND from:someemacslist@something.com

# to separate your different identities maybe do
# mail to private
notmuch tag +private -- to:myprivateaddress@something.com

# mail to job, with filtering out the newsfeed stuff
notmuch tag +job -- to:mywork@something AND NOT tag:feed

This script now will be run everytime after getting new messages. Since you probably will change things around in the beginning you will want to run it manually to retag all existing mail according to your defined rules. Just navigate to your hooks directory and run like any shell script with ./post-new.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published