Skip to content

Latest commit

 

History

History
415 lines (331 loc) · 12 KB

README.org

File metadata and controls

415 lines (331 loc) · 12 KB

Emacs Config for Scala programmers

Table Of Contents

This file produces two directories in this directory. `.emacs.d` and `.sbt` These have a simple emacs configuration with sbt addons to make ensime work.

About

This file itself isn’t the config, it’s a literate version of the config. In order to generate the code version use M-x org-babel-tangle or C-c C-v t while in this buffer. Tangling this will produce to subdirectories, .sbt and .emacs.d, with a complete generated start point. Simply moving both directories to your home directory (i.e. mv ./.sbt ~/.sbt && mv ./.emacs.d ~/.emacs.d ) will get you bootstrapped.

Emacs

As it says on the tin

Emacs is the extensible, customizable, self-documenting real-time display editor.

That flexibility give great power but also makes getting started difficult. This is a basic configuration focused on making a workable starting evironment for programmers working in a mixed Scala and PHP environment with git as the version control system.

Init

This begins our section on basic emacs initialization and configuration. The idea is that while raw emacs is a power house it can take a while to get up to speed with it. We’re going to go through some basic setup with commentary so we can get to productive quickly.

We start our configuration by enabling and configuring the emacs package manager and using it to ensure that use-package is installed.

;; require is the equivalent of import, so we're importing emacs's
;; package configuration and management system
(require 'package)

;; find packages in melpa, melpa stable, or for org mode, orgmode over
;; elpa (or marmalade for that matter)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t)

(setq package-archive-priorities
  '(("melpa-stable" . 1)
    ("org" . 2)))

;; start the package manager
(package-initialize)

;; make sure all packages we use "ensure" so they're always loaded

(setq use-package-always-ensure t)

;; we're going to be using use-package to define and load
;; packages. it's a code based configuration with a bunch of fancy
;; extensions
(when
    (not package-archive-contents)
  (package-refresh-contents)
  (package-install 'use-package))

Making Emacs more friendly

The one real issue with emacs is that the key bindings that are the defaults only made sense back when you interacted with it via a real serial tty device. In those days succinctness was a virtue and the number of key bindings was limited. Today neither of those constraints exist but history has cemented bindings. ErgoEamcs is a site run by a emacs user who has spent the time and effort in redesigning keybindings to be ergonometric as possible. see Ergoemacs-mode for more information

(use-package ergoemacs-mode
  :pin melpa-stable
  :init
  (setq ergoemacs-theme nil)              ;use default bindings theme
  (setq ergoemacs-keyboard-layout "us")     ;assume us querty keyboard
  :config
  (ergoemacs-mode 1))

Version Control

Lets setup git now. Magit will install it’s documentation in info mode so we’re all good on learning how it works.

(use-package with-editor
    :pin melpa-stable)

(use-package git-commit
    :pin melpa-stable)

(use-package magit
  :pin melpa-stable
  :commands magit-status magit-blame
  :init
  (setq magit-auto-revert-mode nil)
  (setq magit-last-seen-setup-instructions "1.4.0")
  :bind (("s-g" . magit-status)
         ("s-b" . magit-blame)))

(use-package magithub
  :after magit
  :config (magithub-feature-autoinject t))

(use-package magit-find-file
  ;; s-f is the key ⌘ + f (so cmd-f)
  :bind (("s-f" . magit-find-file-completing-read)))

;; invoke M-x git-timemachine in a git versioned file to enable moving
;; through version space.
(use-package git-timemachine)

Scala programming

Lets handle scala coding right away here at the top level of our initialization.

  (use-package expand-region
    :commands 'er/expand-region
    :bind ("C-=" . er/expand-region))

  (use-package counsel-projectile
    :config
    (counsel-projectile-on))

  ;; the packages listed here before ensime aren't strictly speaking
  ;; needing to be included manually. I've had issues in the past with
  ;; pinning them to stable when ensime is unpinned.
  (use-package sbt-mode
    :pin melpa
    :commands sbt-start sbt-command)

  (use-package scala-mode
    :pin melpa
    :interpreter ("scala" . scala-mode))

;; packages
;; loads key-chord and adds a :chord symbol for use-package.
  (use-package use-package-chords
    :config (key-chord-mode 1))

  ;; elisp string functions
  (use-package s)
  (use-package string-inflection
    :bind ("s-i" . string-inflection-all-cycle))

  ;; these next packages don't describe modes or features rather they're
  ;; packages of elisp function designed to make coding better.  API for
  (use-package dash)
  (use-package dash-functional)
  (use-package m-buffer)
  (use-package f)
  (use-package multiple-cursors)

OMG crazy!

;; stackoverflow is great but why leave emacs to search it?
(use-package sx
  :init (require 'bind-key)
  :config
  (bind-keys
   :prefix "C-c s"
   :prefix-map my-sx-map
   :prefix-docstring "Global keymap for SX."
   ("q" . sx-tab-all-questions)
   ("i" . sx-inbox)
   ("o" . sx-open-link)
   ("u" . sx-tab-unanswered-my-tags)
   ("a" . sx-ask)
   ("s" . sx-search)))

(use-package company
  :diminish company-mode)

(use-package ivy
  :pin melpa-stable
  :bind
  (:map ivy-mode-map
        ("C-'" . ivy-avy))
  :diminish (ivy-mode . "")
  :config
  ;; (ivy-mode 1)
  ;; add ‘recentf-mode’ and bookmarks to ‘ivy-switch-buffer’.
  (setq ivy-use-virtual-buffers t)
  ;; number of result lines to display
  (setq ivy-height 10)
  ;; does not count candidates
  (setq ivy-count-format "")
  ;; no regexp by default
  (setq ivy-initial-inputs-alist nil)
  ;; configure regexp engine.
  (setq ivy-re-builders-alist
        ;; allow input not in order
        '((t   . ivy--regex-ignore-order))))

(use-package counsel-projectile
  :config
  (counsel-projectile-on))

Ensime

The defacto development environment for scala in emacs is ensime which relies on scala-mode and sbt-mode.

(use-package popup
  :pin melpa-stable)

(use-package ensime
  :pin melpa-stable
  :init
  (put 'ensime-auto-generate-config 'safe-local-variable #'booleanp)
  (setq
    ensime-startup-snapshot-notification nil
    ensime-startup-notification nil)
  :config
  (require 'ensime-expand-region)
  (add-hook 'git-timemachine-mode-hook (lambda () (ensime-mode 0))))

JS

So both SBT and emacs are quite happy in the javascript soup.

;; rjsx mode brings js-mode with it and ads the ability to format jsx
;; files as well as stright javascript
(use-package rjsx-mode)
(use-package js2-refactor)
(use-package js2-mode
  :init (add-hook 'js2-mode-hook #'(js2-refactor-mode)))

PHP`

Blech, but it’s a thing we need so…

(use-package composer)
(use-package php-mode)
;; turn this back on when it works again
;; (use-package php+-mode)
(use-package phpunit)
(use-package psysh)

Custom

Emacs can use a fil hold auto-generated custom settings. It’s not required but using it means you can customize each instance of emacs if you want (the default) or not.

(setq custom-file (prog1
  (expand-file-name "custom.el" user-emacs-directory)
  (f-touch (expand-file-name "custom.el" user-emacs-directory))))

Hooks

Emacs modes almost all have hooks. These serve a bit of a different purpose than :init or :config in use-package

(add-hook 'ensime-mode-hook
          (lambda ()
            (let ((backends (company-backends-for-buffer)))
              (setq company-backends
                    (push '(ensime-company company-yasnippet) backends)))))


;; start code
(defun company-backends-for-buffer ()
  "Calculate appropriate `company-backends' for the buffer.
For small projects, use TAGS for completions, otherwise use a
very minimal set."
  (projectile-visit-project-tags-table)
  (cl-flet ((size () (buffer-size (get-file-buffer tags-file-name))))
    (let ((base '(company-keywords company-dabbrev-code company-yasnippet)))
      (if (and tags-file-name (<= 20000000 (size)))
          (list (push 'company-etags base))
        (list base)))))

;; given that I have to work with eclipse users it's the only way to
;; stay sane.
(defun fix-format-buffer ()
  "indent, untabify and remove trailing whitespace for a buffer"
  (interactive)
  (save-excursion
    (delete-trailing-whitespace)
    (indent-region (point-min) (point-max))
    (untabify (point-min) (point-max))))

(defun contextual-backspace ()
  "Hungry whitespace or delete word depending on context."
  (interactive)
  (if (looking-back "[[:space:]\n]\\{2,\\}" (- (point) 2))
      (while (looking-back "[[:space:]\n]" (- (point) 1))
        (delete-char -1))
    (cond
     ((and (boundp 'smartparens-strict-mode)
           smartparens-strict-mode)
      (sp-backward-kill-word 1))
     ((and (boundp 'subword-mode)
           subword-mode)
      (subword-backward-kill 1))
     (t
      (backward-kill-word 1)))))

(global-set-key (kbd "C-<backspace>") 'contextual-backspace)

(defun eshell-here()
  "Opens up a new shell in the directory associated with the
current buffer's file. The eshell is renamed to match that
directory to make multiple eshell windows easier."
  (interactive)
  (let*((parent(if(buffer-file-name)
                   (file-name-directory(buffer-file-name))
                 default-directory))
        (height(/(window-total-height) 3))
        (name  (car(last(split-string parent "/" t)))))
    (split-window-vertically(- height))
    (other-window 1)
    (eshell "new")
    (rename-buffer(concat "*eshell: " name "*"))

    (insert(concat "ls"))
    (eshell-send-input)))

(global-set-key(kbd "C-!") 'eshell-here)

Themes

Just because emacs is a tool for coding doesn’t mean you can’t have fun with it. Maybe you don’t like black text on white. Lets add a theme and a game.

(use-package cyberpunk-theme)

(use-package 2048-game)

Good luck figuring out how to use them!

SBT

Ensime

The defacto development environment for scala in emacs is ensime which relies on scala-mode and sbt-mode. In order to use ensime we need to add it’s emacs mode but we also need to add the project generator plugin to our sbt projects. The easiest way to do that is to add the plugin to all sbt projects globally. Since we’re here lets also add some other fun (and useful) plugins.

addSbtPlugin("io.spray" % "sbt-revolver" % "0.8.0")
addSbtPlugin("com.eed3si9n" % "sbt-dirty-money" % "0.1.0")
addSbtPlugin("org.ensime" % "sbt-ensime" % "1.12.11")

Sbt Globals

import org.ensime.EnsimeKeys._
import org.ensime.EnsimeCoursierKeys._

// if this isn't set then ensime will create 2.11 and 2.12 specific
// directories for you in your tree :(
ensimeIgnoreMissingDirectories in ThisBuild := true

// allow C-c to interrupt the running app NOT kill sbt
cancelable in Global := true