Here is my configuration for the almighty emacs editor. I used to think it was long and comprehensive until I saw some of the more prolific configs that abound, so I guess it might be considered somewhat minimalist but I’ve been using emacs for many years and this works for me. The vast majority of my config is just loading packages for working with various programming languages, et cetera, but there are some tweaks included herein. My philosophy is to make few changes, only when there’s a clear value-add, and so this file has been built up incrementally over the course of several years. I believe this is the best way to leverage emacs’ strengths and mold it to yourself.
I do not use vim-style bindings because I am not a deranged lunatic.
This is a so-called “literate” code file, where the code and its documentation are intertwined as one file and separated at compile/runtime. This file is the primary configuration artifact and most of the sausage gets made here. There is also an early-init file that sets a few variables before the package system and GUI are loaded.
- Install emacs. Caveat: I primarily use emacs on macOS using this fork by JD Smith which is itself a fork of Mitsuharu Yamamoto’s
emacs-macfork (aka the railwaycat) fork, so your mileage may vary. I usually build with these flags:--with-natural-title-bar --with-mac-metal --with-native-comp --with-emacs-sexy-icon - Once emacs is installed, issue the following commands:
$ git clone git@github.com:nathanvy/dotemacs.git
$ cd .emacs.d/
$ make- Go make an espresso while
elpacabootstraps itself and begins pulling down packages.
First enable lexical binding, then set up elpaca which is a modern package manager that sidesteps the built-in package.el functionality, because package.el is a dumpster fire and apparently not even the emacs maintainers know how it works. Note that we use tree-sitter so technically we depend on emacs version 29+, but you could use the tree-sitter packages for prior versions. In that case change/delete the check below.
;; -*- lexical-binding: t; -*-
(when (version< emacs-version "29")
(error "This emacs requires a min version of 29 but you're running %s" emacs-version))
(setq user-full-name "Nathan Van Ymeren"
user-mail-address "n@0x85.org")
(setq use-short-answers t)
(tool-bar-mode -1)
(set-default-coding-systems 'utf-8)
(prefer-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-language-environment "English") Now because I’m running a fork, it sometimes doesn’t play nicely with packages that aren’t expecting a development version to be running. For example I get warnings elpaca about stale versions, so this is to suppress those:
(setq warning-suppress-types
'((elpaca core stale)))This is the elpaca installer:
(defvar elpaca-installer-version 0.11)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
:ref nil :depth 1 :inherit ignore
:files (:defaults "elpaca-test.el" (:exclude "extensions"))
:build (:not elpaca--activate-package)))
(let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory))
(build (expand-file-name "elpaca/" elpaca-builds-directory))
(order (cdr elpaca-order))
(default-directory repo))
(add-to-list 'load-path (if (file-exists-p build) build repo))
(unless (file-exists-p repo)
(make-directory repo t)
(when (<= emacs-major-version 28) (require 'subr-x))
(condition-case-unless-debug err
(if-let* ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
((zerop (apply #'call-process `("git" nil ,buffer t "clone"
,@(when-let* ((depth (plist-get order :depth)))
(list (format "--depth=%d" depth) "--no-single-branch"))
,(plist-get order :repo) ,repo))))
((zerop (call-process "git" nil buffer t "checkout"
(or (plist-get order :ref) "--"))))
(emacs (concat invocation-directory invocation-name))
((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
"--eval" "(byte-recompile-directory \".\" 0 'force)")))
((require 'elpaca))
((elpaca-generate-autoloads "elpaca" repo)))
(progn (message "%s" (buffer-string)) (kill-buffer buffer))
(error "%s" (with-current-buffer buffer (buffer-string))))
((error) (warn "%s" err) (delete-directory repo 'recursive))))
(unless (require 'elpaca-autoloads nil t)
(require 'elpaca)
(elpaca-generate-autoloads "elpaca" repo)
(load "./elpaca-autoloads")))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))
(elpaca elpaca-use-package
;; Enable use-package :ensure support for Elpaca.
(elpaca-use-package-mode)
(setq use-package-always-ensure t))I rarely if ever use Customize but when I do I prefer it to put its mess into its own tidy file. Also here I’ll load some secrets that I don’t want in version control.
(setq custom-file "~/.emacs.d/custom.el")
(when (file-exists-p custom-file)
(add-hook 'elpaca-after-init-hook (lambda () (load custom-file))))
(put 'upcase-region 'disabled nil)
(when (file-exists-p (expand-file-name "secrets.el" user-emacs-directory))
(load-file (expand-file-name "secrets.el" user-emacs-directory)))This sets up a centralized location where emacs will leave its temporary files instead of littering our filesystem.
(setq temporary-file-directory "~/.emacs.d/saves")
(setq backup-directory-alist
`((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms
`((".*" ,temporary-file-directory t)))
(message "Deleting old backup files...")
(let ((week (* 60 60 24 7))
(current (float-time (current-time))))
(dolist (file (directory-files temporary-file-directory t))
(when (and (backup-file-name-p file)
(> (- current (float-time (nth 5 (file-attributes file))))
week))
(message "%s" file)
(delete-file file))))Some utility functions:
(defun increment-number-at-point ()
(interactive)
(skip-chars-backward "0-9")
(or (looking-at "[0-9]+")
(error "No number at point"))
(replace-match (number-to-string (1+ (string-to-number (match-string 0))))))
(defun decrement-number-at-point ()
(interactive)
(skip-chars-backward "0-9")
(or (looking-at "[0-9]+")
(error "No number at point"))
(replace-match (number-to-string (1- (string-to-number (match-string 0))))))
(defun insert-line-below ()
"Insert a blank line below point"
(interactive)
(move-beginning-of-line nil)
(insert "\n")
(if electric-indent-inhibit
(let* ((indent-end (progn (crux-move-to-mode-line-start) (point)))
(indent-start (progn (move-beginning-of-line nil) (point)))
(indent-chars (buffer-substring indent-start indent-end)))
(forward-line -1)
(insert indent-chars))
(forward-line -1)
(indent-according-to-mode)))Now that we’re bootstrapped we can start pulling in stuff that we use to get other stuff done. We’ll start with some OS-specific stuff:
(when (eq system-type 'darwin)
(customize-set-variable 'native-comp-driver-options '("-Wl,-w")) ;;revisit in emacs 29
(use-package exec-path-from-shell
:config
(exec-path-from-shell-initialize)))
;; (when (eq system-type 'gnu/linux))
Because the emacs ecosystem is rather eclectic (derogatory), sometimes packages don’t load properly unless you use the clunky package.el infrastructure. One example of this is seq. For reasons I have not had time or energy to explore, seq just doesn’t get updated from 2.23 to 2.24+ if you install a recent magit. Maybe this is an elpaca problem, or maybe this is an emacs package ecosystem versioning problem, but either way I had to jump through these hoops to get magit to install properly via elpaca. Similarly, transient (which is what makes the fancy “menus” in magit, isn’t getting updated upon installing magit, which of course relies on transient pretty fucking heavily, and so unless you include the following, you have to deal with undefined symbols. Anyways apparently we need to manually specify certain dependencies to get things going at the time of this writing.
Interestingly enough straight.el didn’t require this much hand-holding, but it’s significantly slower so I don’t mind.
(defun +elpaca-unload-seq (e)
(and (featurep 'seq) (unload-feature 'seq t))
(elpaca--continue-build e))
(defun +elpaca-seq-build-steps ()
(append (butlast (if (file-exists-p (expand-file-name "seq" elpaca-builds-directory))
elpaca--pre-built-steps elpaca-build-steps))
(list '+elpaca-unload-seq 'elpaca--activate-package)))
(use-package seq :ensure `(seq :build ,(+elpaca-seq-build-steps)))
(use-package transient)
(use-package magit)
And some general utility packages. Transpose-frame lets us move frames around easily, and smex aka Smart M-x is just groovy.
(use-package transpose-frame)
(use-package smex)
(use-package projectile)
(use-package which-key
:config
(which-key-mode))There are lots of competing (or perhaps it would be better to say overlapping) packages in this space but I like good old ido. It does what I need. ido is built in but if you actually set `ido-everywhere = 1` you may discover it’s not actually everywhere so we add ido-completing-read+
(setq ido-enable-flex-matching t)
(ido-mode 1)
(ido-everywhere 1)
(use-package ido-completing-read+
:config
(ido-ubiquitous-mode 1))I stumbled upon prism-mode by accident after much mucking about with rainbow-delimiters and friends, and I’ve really come to prefer prism for coloring.
I shopped around for themes quite a bit because emacs by default is quite frankly hideous, and I spent quite some time embracing the glorious 80s aesthetic and for a while enjoyed a super dank synthwave type theme. Originally I had settled on the vscode-dark+ theme which I really liked and heartily recommend but sometimes you want to have more fun. Base16-based themes also get an honorable mention for being good. Lots of folks use solarized but I found it didn’t have enough contrast for me. These days I appear to have settled on nord.
We thank these themes for their prior service:
solarizedbase16and friendssynthwave-emacsdoom-outrun-electricdoom-laserwavetomorrow-nightvscode-dark
(column-number-mode t)
(show-paren-mode t)
(setq-default indent-tabs-mode nil)
(use-package nord-theme
:if (display-graphic-p)
:ensure (:host github :repo "nordtheme/emacs")
:config
(set-face-attribute 'default nil :family "Monaco")
(set-face-attribute 'fixed-pitch nil :family "Monaco")
(set-face-attribute 'variable-pitch nil :family "SF Pro Display" :height 140)
(load-theme 'nord t))
(use-package all-the-icons
:if (display-graphic-p))
(use-package mode-line-bell
:config (mode-line-bell-mode))
;; (use-package prism
;; :commands prism-mode
;; :init
;; (add-hook 'go-mode-hook #'prism-mode)
;; (add-hook 'csharp-mode-hook #'prism-mode)
;; (add-hook 'js-mode-hook #'prism-mode)
;; (add-hook 'js-jsx-mode-hook #'prism-mode)
;; (add-hook 'typescirpt-mode-hook #'prism-mode)
;; (add-hook 'c++-mode-hook #'prism-mode)
;; (add-hook 'emacs-lisp-mode-hook #'prism-mode)
;; (add-hook 'ielm-mode-hook #'prism-mode)
;; (add-hook 'lisp-mode-hook #'prism-mode)
;; (add-hook 'lisp-interaction-mode-hook #'prism-mode)
;; (add-hook 'scheme-mode-hook #'prism-mode)
;; (add-hook 'python-mode-hook #'prism-whitespace-mode))Parrot Mode needs no introduction, and no explanation.
(use-package parrot
:if (display-graphic-p)
:config (parrot-mode))Emacs and LSP together make for a fantastic editing experience and has deprecated a lot of previously-indispensable stuff so we’ll get it going along with company for completion. For pre-29 emacs this is where I also use-package‘d the tree-sitter packages and languages but with the release of 29.1 that’s no longer necessary as long as you compile emacs --with-tree-sitter
I develop C++ on a mac, so I’ve also got a setq in here to make sure clangd uses the homebrew version of LLVM rather than the one Apple ships by default, which tends to lag behind support for modern C++20 and whatnot.
(use-package lsp-mode
:init
;; set prefix for lsp-command-keymap (few alternatives - "C-l", "C-c l")
(setf lsp-keymap-prefix "C-c l")
:hook ((go-ts-mode . (lambda ()
(lsp-go-install-save-hooks)
(lsp)))
(csharp-ts-mode . lsp)
(ruby-base-mode . lsp)
(ess-r-mode . lsp)
(web-mode . lsp)
(js-ts-mode .lsp)
(js-jsx-mode . lsp)
(typescript-ts-mode . lsp)
(c++-ts-mode . lsp)
(c++-mode . lsp)
(php-ts-mode . lsp)
(python-ts-mode . (lambda ()
(require 'lsp-python-ms)
(lsp))))
:commands lsp lsp-deferred
:config
(setq lsp-disabled-clients
'((ruby-mode . (rubocop-ls)) (ruby-ts-mode . (rubocop-ls))))
(setq lsp-log-io nil)
(setq lsp-clients-clangd-executable "/opt/homebrew/opt/llvm/bin/clangd")
(add-to-list 'lsp-file-watch-ignored-directories "[/\\\\]\\.staging\\'"))
(use-package lsp-ui
:hook (lsp-mode . lsp-ui-mode))
(use-package flycheck
:init (global-flycheck-mode))
(use-package lsp-treemacs
:commands lsp-treemacs-errors-list)
(use-package company
:hook
(prog-mode . company-mode)
(tex-mode . company-mode)
(special-mode . company-mode)
(comint-mode . company-mode))
(setq treesit-language-source-alist
'((bash "https://github.com/tree-sitter/tree-sitter-bash")
(c "https://github.com/tree-sitter/tree-sitter-c")
(cpp "https://github.com/tree-sitter/tree-sitter-cpp")
(cmake "https://github.com/uyha/tree-sitter-cmake")
(css "https://github.com/tree-sitter/tree-sitter-css")
(csharp "https://github.com/tree-sitter/tree-sitter-c-sharp")
(lisp "https://github.com/theHamsta/tree-sitter-commonlisp")
(cuda "https://github.com/theHamsta/tree-sitter-cuda")
(elisp "https://github.com/Wilfred/tree-sitter-elisp")
(fortran "https://github.com/stadelmanma/tree-sitter-fortran")
(go "https://github.com/tree-sitter/tree-sitter-go")
(html "https://github.com/tree-sitter/tree-sitter-html")
(java "https://github.com/tree-sitter/tree-sitter-java")
(javascript "https://github.com/tree-sitter/tree-sitter-javascript" "master" "src")
(julia "https://github.com/tree-sitter/tree-sitter-julia")
(json "https://github.com/tree-sitter/tree-sitter-json")
(latex "https://github.com/latex-lsp/tree-sitter-latex")
(lua "https://github.com/Azganoth/tree-sitter-lua")
(make "https://github.com/alemuller/tree-sitter-make")
(markdown "https://github.com/ikatyang/tree-sitter-markdown")
(objc "https://github.com/jiyee/tree-sitter-objc")
(org "https://github.com/milisims/tree-sitter-org")
(perl "https://github.com/tree-sitter-perl/tree-sitter-perl")
(php "https://github.com/tree-sitter/tree-sitter-php" "master" "php/src")
(proto "https://github.com/mitchellh/tree-sitter-proto")
(python "https://github.com/tree-sitter/tree-sitter-python")
(R "https://github.com/r-lib/tree-sitter-r")
(ruby "https://github.com/tree-sitter/tree-sitter-ruby")
(rust "https://github.com/tree-sitter/tree-sitter-rust")
(scheme "https://github.com/6cdh/tree-sitter-scheme")
(sql "https://github.com/m-novikov/tree-sitter-sql")
(toml "https://github.com/tree-sitter/tree-sitter-toml")
(tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")
(typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
(yaml "https://github.com/ikatyang/tree-sitter-yaml")))Tree-sitter is awesome and much faster than regexps, but we don’t want to manually deal with enabling the <name>-ts-mode and wondering which are available so we’ll just use treesit-auto:
;;https://github.com/renzmann/treesit-auto
(use-package treesit-auto
:config
(setq treesit-auto-install t)
(global-treesit-auto-mode))In 2021 I started writing a lot of Go (golang) and there’s an awful lot of repetitive error checking when trying to follow the idiomatic style. I got annoyed at writing the same if construct hundreds of times so I decided it was finally time to install yasnippet. It comes with TAB bound to yas-expand by default which I don’t like, so I disabled it here by setting it to nil, and moved it to a different key combination at the end of this file.
(use-package yasnippet
:init
(yas-global-mode)
(define-key yas-minor-mode-map (kbd "<tab>") nil)
(define-key yas-minor-mode-map (kbd "TAB") nil))
I hated lisp at first but I’ve found that it’s really grown on me. It has its warts but all languages do. We don’t leverage LSP here since most lisp implementations predate Language Servers and provide their own analogous constructs that are more tightly integrated with the REPL anyway. Sly is a fork of SLIME and is more actively developed.
(use-package sly
:config
(setq inferior-lisp-program "sbcl --dynamic-space-size 2048")
(setq org-babel-lisp-eval-fn #'sly-eval)
(setq org-confirm-babel-evaluate nil))
(use-package paredit
:mode "paredit-mode"
:commands enable-paredit-mode
:init
(add-hook 'emacs-lisp-mode-hook #'enable-paredit-mode)
(add-hook 'eval-expression-minibuffer-setup-hook #'enable-paredit-mode)
(add-hook 'ielm-mode-hook #'enable-paredit-mode)
(add-hook 'lisp-mode-hook #'enable-paredit-mode)
(add-hook 'lisp-interaction-mode-hook #'enable-paredit-mode)
(add-hook 'scheme-mode-hook #'enable-paredit-mode))At the time of writing this paragraph I’m in an MBA program and for our analytics courses they inexplicably chose R over Python, because I guess they hate us. So here’s ess (Emacs Speaks Statistics). I haven’t bothered to set up polymode for doing “RMarkdown” shenanigans because org is good enough for me.
(use-package ess
:bind (:map ess-r-mode-map
("M-p" . " %>%"))
:config
(require 'ess-r-mode))In the course of writing assignments I ran into a problem where certain tidyverse packages were causing weird coloration in the inferior ESS R buffer, such that the text was basically unreadable on a dark background. After some digging it seems that the R process emits super leet haxors ANSI color codes, because you know why not?
The issue is this one: emacs-ess/ESS#1193
And the solution/workaround was:
(defun my-inferior-ess-init ()
"Workaround for https://github.com/emacs-ess/ESS/issues/1193"
(add-hook 'comint-preoutput-filter-functions #'xterm-color-filter -90 t)
(setq-local ansi-color-for-comint-mode nil))
(use-package xterm-color
:ensure (:host github :repo "atomontage/xterm-color")
:config
(add-hook 'inferior-ess-mode-hook #'my-inferior-ess-init))
Most of these are simple invocations of use-package and require no explanation.
(use-package php-mode
:ensure (:host github :repo "emacs-php/php-mode"))
(use-package php-ts-mode
:ensure (:host github :repo "emacs-php/php-ts-mode"))
(use-package yaml-mode
:mode "\\.yml\\'"
:ensure (:host github :repo "yoshiki/yaml-mode"))
(use-package web-mode)
(use-package terraform-mode
:ensure t)
(use-package glsl-mode
:ensure (:host github :repo "jimhourihan/glsl-mode"))
(use-package python)
(use-package lsp-python-ms
:after (lsp-mode python)
:init (setq lsp-python-ms-auto-install-server t))
(use-package ein
:ensure t
:init
(add-to-list 'auto-mode-alist '("\\.ipynb\\'" . ein:notebooklist-open))
)
(defun lsp-go-install-save-hooks ()
(add-hook 'before-save-hook #'lsp-format-buffer t t)
(add-hook 'before-save-hook #'lsp-organize-imports t t))
(use-package go-mode)
Some generally-useful stuff like Dashboard and packages like Org for writing prose comes here. If you read below and are confused, it’s not a typo: pdflatex needs to be invoked three times because of the way the standard LaTeX recipe works, which goes something like this:
$ latex <filename>$ bibtex <filename>$ latex <filename>$ latex <filename>
Basically, the first time you run it, latex writes citations and stuff like \label to a .aux file, which is what bibtex reads. BibTeX reads that file as well as the .bib file and uses that to format the references. When you run LaTeX a second time it also reads both .aux and .tex files and if bibtex generated any .bbl files it reads those as well, which is how it inserts the references into the output. The third run is what causes the citations and labels to get inserted into the output. If you have multiple bibliographies you’ll need more invocations of bibtex and latex and it quickly becomes a clusterfuck. Anyways this is why I have three calls to pdflatex in there.
(use-package dashboard
:config
(add-hook 'elpaca-after-init-hook #'dashboard-insert-startupify-lists)
(add-hook 'elpaca-after-init-hook #'dashboard-initialize)
(add-hook 'window-size-change-functions #'dashboard-resize-on-hook 100)
(add-hook 'window-setup-hook #'dashboard-resize-on-hook)
(setq dashboard-items '((recents . 20) (bookmarks . 20)))
(setq dashboard-banner-logo-title "Hacks and glory await!")
(setq recentf-exclude '("bookmarks"))
(setq dashboard-startup-banner (expand-file-name "~/.emacs.d/dashboard-logo.png" user-emacs-directory)))
(use-package org
:ensure nil
:init
(setf org-list-allow-alphabetical t)
(setf org-src-tab-acts-natively t)
(setf org-startup-truncated nil)
:config
(org-babel-do-load-languages 'org-babel-load-languages '((R . t)
(lisp . t)
(emacs-lisp . t)
(python . t)))
(set-face-attribute 'org-table nil :inherit 'fixed-pitch)
(set-face-attribute 'org-code nil :inherit 'fixed-pitch)
(set-face-attribute 'org-block nil :inherit 'fixed-pitch)
(set-face-attribute 'org-block-begin-line nil :inherit 'fixed-pitch)
(set-face-attribute 'org-block-end-line nil :inherit 'fixed-pitch)
(set-face-attribute 'org-block-begin-line nil :slant 'normal :underline nil :extend nil)
(set-face-attribute 'org-block-end-line nil :slant 'normal :overline nil :extend nil)
(setf org-html-preamble nil)
(setf org-html-postamble nil)
(setq org-latex-listings 'minted)
(setq org-latex-packages-alist '(("" "minted")))
(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")))
(use-package org-bullets
:init
(add-hook 'org-mode-hook (lambda ()
(org-bullets-mode 1))))
(use-package ox-rfc)
(use-package tex
:ensure (:host github :repo "emacs-straight/auctex"))
(use-package markdown-mode
:commands (markdown-mode gfm-mode)
:mode (("README\\.md\\'" . gfm-mode)
("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode))
:init (setq markdown-command "multimarkdown"))For writing prose or anything non-code I like to use Olivetti which adds some nice gutters on either side of the screen and pair it with variable pitch fonts.
(use-package olivetti
:init
(add-hook 'text-mode-hook (lambda ()
(olivetti-mode 1)
(olivetti-set-width 140)
(variable-pitch-mode 1))))I put package-local/namespaced binds with their use-package declarations but I decided to collect all my global custom keybinds into one section here at the end to keep better tabs on my C-c <single char> namespace.
(global-set-key (kbd "C-c d") 'lsp-find-definition)
(global-set-key (kbd "C-c g") 'rgrep)
(global-set-key (kbd "C-c f") 'ff-find-other-file)
(global-set-key (kbd "C-c i") 'flip-frame)
(global-set-key (kbd "C-c o") 'flop-frame)
(global-set-key (kbd "C-c r") 'rotate-frame-clockwise)
(global-set-key (kbd "C-c t") 'treemacs)
(global-set-key (kbd "C-c y") 'yas-expand)
(global-set-key (kbd "C-c n") 'parrot-rotate-next-word-at-point)
(global-set-key (kbd "C-c p") 'parrot-rotate-prev-word-at-point)
(global-set-key (kbd "C-c q") 'query-replace)
(global-set-key (kbd "C-c x") 'query-replace-regexp)
(global-set-key (kbd "M-x") 'smex)
(global-set-key (kbd "M-X") 'smex-major-mode-commands)
(global-set-key (kbd "M-o") 'insert-line-below)
;; This is the old M-x.
(global-set-key (kbd "C-c C-c M-x") 'execute-extended-command)
(global-set-key (kbd "C-c +") 'increment-number-at-point)
(global-set-key (kbd "C-c -") 'decrement-number-at-point)
(global-unset-key (kbd "M-t"))
(global-unset-key (kbd "C-t"))
(global-unset-key (kbd "<C-wheel-up>"))
(global-unset-key (kbd "<C-wheel-down>"))