;; -*- lexical-binding: t; -*-
;;;; Startup
;; Do not merge echo-area-message sexp
(setq inhibit-startup-echo-area-message "alan")
(setq inhibit-startup-screen t
      initial-scratch-message ""
      initial-major-mode 'text-mode
      user-mail-address "alan@alanpearce.co.uk"
      user-full-name "Alan Pearce"
      custom-file "~/.emacs.d/custom.el")

(load custom-file :noerror)

;;;; Helper Macros
(defmacro rename-modeline (mode new-name)
  `(defadvice ,mode (after rename-modeline activate)
     (setq mode-name ,new-name)))

;;; Allow lisps to use a common setup. I don't know why they don't have some lispy mode as their parent, but this is close enough
(defcustom lisp-mode-common-hook nil
  "Hook run when entering any Lisp mode."
  :type 'hook
  :group 'lisp)

;;;; Environment & Location

(defvar *init-file*
  (let ((init-file (or user-init-file
                       (expand-file-name "init.el" user-emacs-directory))))
    (expand-file-name "init.el"
                      (file-name-directory (file-truename init-file))))
  "Where the emacs init file really is, passing through symlinks.")

(defvar work-project-directory "~/work")
(defvar home-project-directory "~/projects")

;;;; Package Management
(add-to-list 'load-path (expand-file-name "elisp/" user-emacs-directory))

(eval-and-compile
  (add-to-list 'load-path (expand-file-name "~/.cask"))
  (require 'cask)
  (cask-initialize))

(unless (package-installed-p 'req-package)
  (package-refresh-contents)
  (package-install 'req-package))
(require 'req-package)
(setq use-package-verbose t)
(req-package pallet
  :defer 5
  :config (pallet-mode 1))

(req-package paradox
  :defer t
  :config (progn
            (setq paradox-automatically-star nil)))

(if (not (fboundp #'package-desc-archive))
    (defsubst package-desc-archive (desc)
      (aref desc (1- (length desc)))))

(defun package-list-installed ()
  (loop for pkg in package-activated-list
        collect (assq pkg package-archive-contents)))

(defun package-archive-stats ()
  (let ((archives (make-hash-table))
        (assoc '()))
    (dolist (arc package-archives)
      (puthash (car arc) 0 archives))
    (maphash (lambda (k v)
               (setq assoc (cons (cons k v) assoc)))
             (dolist (pkg (-filter #'identity (package-list-installed)) archives)
               (let ((pkg-arc (package-desc-archive (cdr pkg))))
                 (incf (gethash pkg-arc archives)))))
    assoc))

(defun package-show-installed-from-archive (archive)
  (interactive (list (helm-comp-read "Archive: " (mapcar #'car package-archives)
                                      :must-match t)))
  (let ((from-arc (mapcar #'car
                          (--filter (equalp (package-desc-archive (cdr it)) archive)
                                    (package-list-installed)))))
    (if (called-interactively-p 'any)
        (message "%s" from-arc)
      from-arc)))

(defun package-show-archive-stats ()
  (interactive)
  (message "%s" (package-archive-stats)))

;;;; Style

(req-package whitespace
  :defer t
  :config (setq whitespace-style
                '(face
                  space
                  tabs
                  trailing
                  newline
                  empty
                  tab-mark
                  space-before-tab
                  indentation
                  indentation::space
                  indentation::tabs)))

(global-font-lock-mode t)
;; Allow font-lock-mode to do background parsing
(setq jit-lock-stealth-time 1
      jit-lock-stealth-load 100
      jit-lock-chunk-size 1000
      jit-lock-defer-time 0.01)

(when (fboundp #'blink-cursor-mode)
  (blink-cursor-mode -1))

(setq x-underline-at-descent-line t)

(setq ring-bell-function
      (lambda ()
        (unless (memq this-command
                      '(isearch-abort abort-recursive-edit exit-minibuffer keyboard-quit undo-tree-undo))
          (ding))))

(req-package solarized-theme
  :config (progn
            (load-theme 'solarized-light t)))

(when (or (display-graphic-p)
          (daemonp))

  (defun use-variable-fonts ()
    (interactive)
    (variable-pitch-mode)
    (setq cursor-type 'bar))

  (defun ap/set-fonts (mono-face variable-face font-size)
    (when mono-face
      (let ((default-font (concat mono-face "-" (number-to-string font-size))))
        (add-to-list 'default-frame-alist `(font . ,default-font))
        (set-face-font 'fixed-pitch default-font)
        (set-frame-font default-font t t)))
    (when variable-face
      (set-face-font 'variable-pitch (concat variable-face "-"
                                             (number-to-string (1+ font-size))))))

  (cond
   ((eq window-system 'w32)
    (ap/set-fonts "Consolas" "Segoe UI" 10))
   ((eq system-type 'darwin)
    (ap/set-fonts "Monaco" "Helvetica" 12))))

(req-package rainbow-mode
  :commands (rainbow-turn-on
             rainbow-turn-off)
  :config (progn
            (add-hook 'xmonad-mode-hook #'rainbow-turn-on)))

(req-package highlight-stages
  :defer t
  :diminish highlight-stages-mode
  :init (progn
          (add-hook 'lisp-mode-common-hook #'highlight-stages-mode)))

;;;; Autosaves & Backups
(let ((backup-dir (expand-file-name "~/.emacs.d/backups/")))
  (unless (file-directory-p backup-dir)
    (make-directory backup-dir))
  (setq backup-directory-alist `((".*" . ,backup-dir))
        auto-save-file-name-transforms `((".*" ,temporary-file-directory t))
        backup-by-copying-when-linked t
        backup-by-copying-when-mismatch t))

;;;; Buffers

(req-package ibuffer
  :bind (("C-x C-b" . ibuffer))
  :config (progn
            (setq ibuffer-saved-filter-groups
                  (quote (("default"
                           ("org" (mode . org-mode))
                           ("emacs" (mode . emacs-lisp-mode))
                           ("zsh" (filename . "/zsh"))
                           ("server" (filename . "/su:root@server"))))))

            ;; Human-readable base-2 size column
            (define-ibuffer-column size-h
              (:name "Size" :inline t)
              (cond
               ((> (buffer-size) 1024)
                (format "%7.2fK" (/ (buffer-size) 1024.0)))
               ((> (buffer-size) 1048576)
                (format "%7.2fM" (/ (buffer-size) 1048576.0)))
               (t
                (format "%8d" (buffer-size)))))

            (setq ibuffer-formats
                  '((mark modified read-only " "
                          (name 18 18 :left :elide)
                          " "
                          (size-h 9 -1 :right)
                          " "
                          (mode 16 16 :left :elide)
                          " "
                          filename-and-process)))))

(req-package uniquify
  :config (progn
            (setq uniquify-buffer-name-style 'reverse
                  uniquify-separator "/"
                  uniquify-after-kill-buffer-p t
                  uniquify-ignore-buffers-re "^\\*")))

(req-package fancy-narrow
  :diminish fancy-narrow-mode
  :config (fancy-narrow-mode 1))

;;;; Communication

(setq gnutls-min-prime-bits nil)

;;;; Completion

(setq completion-styles '(basic initials partial-completion substring)
      completion-ignore-case t)

(req-package smart-tab
  :commands (global-smart-tab-mode)
  :init (global-smart-tab-mode)
  :config (progn
            (nconc smart-tab-completion-functions-alist '((php-mode . php-complete-function)))
            (diminish 'smart-tab-mode "")))

(req-package company
  :commands (company-mode)
  :diminish "Cmpl"
  :bind (("C-<tab>" . company-complete))
  :init (progn
          (add-hook 'prog-mode-hook #'company-mode)
          (setq company-backends '(company-tern (php-extras-company company-elisp company-bbdb company-nxml company-css company-eclim company-semantic company-clang company-xcode company-cmake company-capf company-gtags company-dabbrev-code company-etags company-keywords)
                                                company-oddmuse company-files company-dabbrev)
                company-idle-delay .3
                company-begin-commands '(self-insert-command)
                company-auto-complete #'company-explicit-action-p
                company-auto-complete-chars '(?\ ?\( ?\) ?.)
                company-tooltip-align-annotations t
                company-dabbrev-downcase nil)))

;;;; Dates & Times

(req-package calendar
  :defer t
  :config (progn
            (setq calendar-week-start-day 1)
            (calendar-set-date-style 'iso)))

(defun insert-date (prefix)
  "Insert the current date. With prefix-argument, use British format. With
   two prefix arguments, write out the day and month name."
  (interactive "P")
  (let ((format (cond
                 ((not prefix) "%Y-%m-%d")
                 ((equal prefix '(4)) "%d/%m/%Y")
                 ((equal prefix '(16)) "%A, %d %B %Y"))))
    (insert (format-time-string format))))

(defun insert-datetime (prefix)
  "Insert the current date and time."
  (interactive "P")
  (let ((format (cond
                 ((not prefix) "%Y-%m-%d %H:%M:%S")
                 ((equal prefix '(4)) "%Y-%m-%dT%H:%M:%SZ"))))
    (insert (format-time-string format))))

;;;; Directory browsing
(req-package dired
  :defer t
  :config (progn
            (bind-key "<return>" #'dired-find-file dired-mode-map)
            (bind-key "^" (lambda () (interactive) (find-alternate-file "..")) dired-mode-map)
            (setq dired-dwim-target t
                  dired-recursive-copies 'top
                  dired-recursive-deletes 'top
                  dired-listing-switches "-alh"
                  dired-bind-jump nil)
            (when (and (eq system-type 'darwin) (executable-find "gls"))
              (setq insert-directory-program (executable-find "gls")))
            (put 'dired-find-alternate-file 'disabled nil)))

(req-package dired-k
  :require dired
  :init (progn
          (add-hook 'dired-initial-position-hook #'dired-k))
  :config (progn
            (setq dired-k-human-readable t)
            (bind-key "g" #'dired-k dired-mode-map)))

(req-package dired-x
  :require dired)

(req-package dired+
  :require dired
  :config (progn
            (diredp-toggle-find-file-reuse-dir 1)
            (unbind-key "C-h C-m" dired-mode-map)
            (bind-key "." #'diredp-describe-file dired-mode-map)
            (defun turn-on-dired-omit-mode ()
              (interactive)
              (dired-omit-mode 1))
            (add-hook 'dired-mode-hook #'turn-on-dired-omit-mode)
            (setq dired-omit-files "#\\|\\.$"
                  dired-omit-verbose nil
                  dired-find-subdir t)))

(req-package dired-subtree
  :defer t
  :require dired
  :config (progn
            (setq dired-subtree-use-backgrounds nil)
            (defun dired-subtree-maybe-up ()
              "Jump up one subtree or directory"
              (interactive)
              (let ((ov (dired-subtree--get-ov)))
                (if ov
                    (progn (goto-char (overlay-start ov))
                           (dired-previous-line 1))
                  (dired-up-directory))))
            (bind-key "^" #'dired-subtree-maybe-up dired-mode-map))
  :init (progn
          (bind-key "i" #'dired-subtree-insert dired-mode-map)))

;;;; Documentation

(req-package helm-dash
  :defer t
  :init (progn
          (defmacro ap/create-helm-dash-hook (mode docsets)
            (let* ((mode-s (symbol-name mode))
                   (fun (intern (concat "helm-dash-hook-" mode-s)))
                   (hook (intern (concat mode-s "-mode-hook"))))
              `(progn
                 (defun ,fun ()
                   (when (require 'helm-dash nil :noerror)
                     (-each (-difference ',docsets
                                         (helm-dash-installed-docsets))
                       #'helm-dash-install-docset)
                     (setq-local helm-dash-docsets ',docsets)))
                 (add-hook (quote ,hook) (function ,fun)))))
          (ap/create-helm-dash-hook nginx ("Nginx"))
          (ap/create-helm-dash-hook ansible ("Ansible"))
          (ap/create-helm-dash-hook php ("PHP" "Symfony"))
          (ap/create-helm-dash-hook twig ("Twig"))
          (ap/create-helm-dash-hook js2 ("JavaScript" "NodeJS" "jQuery" "Express"))
          (ap/create-helm-dash-hook markdown ("Markdown"))
          (ap/create-helm-dash-hook saltstack ("SaltStack"))
          (ap/create-helm-dash-hook clojure ("Clojure"))
          (ap/create-helm-dash-hook sql ("PostgreSQL" "MySQL"))))

(req-package which-func
  :init (which-function-mode)
  :config (setq which-func-modes t))

(req-package discover-my-major
  :bind ("C-h C-m" . discover-my-major))

;;;; Files

(prefer-coding-system 'utf-8-auto-unix)
(set-default-coding-systems 'utf-8-auto-unix)
(setq-default buffer-file-coding-system 'utf-8-auto-unix)
(setq create-lockfiles nil)
(if (eq system-type 'darwin)
    (setq delete-by-moving-to-trash t))

(req-package autorevert
  :init (progn
          (global-auto-revert-mode 1)
          (setq auto-revert-verbose nil)))

(defun rename-current-buffer-file ()
  "Renames current buffer and file it is visiting."
  (interactive)
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (if (not (and filename (file-exists-p filename)))
        (error "Buffer '%s' is not visiting a file!" name)
      (let ((new-name (read-file-name "New name: " filename)))
        (if (get-buffer new-name)
            (error "A buffer named '%s' already exists!" new-name)
          (cond
           ((vc-backend filename) (vc-rename-file filename new-name))
           (t (rename-file filename new-name t)
              (rename-buffer new-name)
              (set-visited-file-name new-name)
              (set-buffer-modified-p nil)
              (message "File '%s' successfully renamed to '%s'"
                       name (file-name-nondirectory new-name)))))))))

(defun delete-current-buffer-file ()
  "Removes file connected to current buffer and kills buffer."
  (interactive)
  (let ((filename (buffer-file-name)))
    (if (not (and filename (file-exists-p filename)))
        (kill-this-buffer)
      (when (yes-or-no-p "Are you sure you want to remove this file? ")
        (delete-file filename)
        (kill-this-buffer)
        (message "File '%s' successfully removed" filename)))))

(defun my-create-non-existent-directory ()
  (let ((parent-directory (file-name-directory buffer-file-name)))
    (when (and (not (file-exists-p parent-directory))
               (y-or-n-p (format "Directory `%s' does not exist! Create it?" parent-directory)))
      (make-directory parent-directory t))))
(add-to-list 'find-file-not-found-functions #'my-create-non-existent-directory)

(defun kill-or-delete-this-buffer-dwim (&optional arg)
  "Kills current buffer. With prefix arg, delete it."
  (interactive "P")
  (if (equal arg '(4))
      (delete-current-buffer-file)
    (if server-buffer-clients
        (server-edit)
      (let ((buf (buffer-name)))
        (when (equalp buf "*HTTP Response*")
          (other-window 1))
        (kill-buffer buf)))))

(req-package ws-butler
  :if window-system
  :config (ws-butler-global-mode 1))
(if (daemonp)
    (add-hook #'before-make-frame-hook (lambda () (ws-butler-global-mode 1))))

(req-package fasd
  :bind ("C-x C-/" . fasd-find-file)
  :init (progn
          (global-fasd-mode 1)))

(req-package recentf
  :init (progn (setq recentf-auto-cleanup 'never
                     recentf-save-file (expand-file-name "recentf" user-emacs-directory))
               (recentf-mode 1)))

(req-package password-store
  :config (progn
            (setq password-store-password-length 16)))

(req-package saveplace
  :config (progn (setq-default save-place t)
                 (setq save-place-file (expand-file-name ".saveplace" user-emacs-directory))))

(req-package tramp
  :defer t
  :config (progn
            (setq tramp-default-method (if (eq system-type 'windows-nt) "plinkx" "ssh")
                  tramp-default-user-alist '(("\\`su\\(do\\)?\\'" nil "root"))
                  tramp-backup-directory-alist backup-directory-alist
                  backup-enable-predicate (lambda (name)
                                            (and (normal-backup-enable-predicate name)
                                                 (not (let ((method (file-remote-p name 'method)))
                                                        (when (stringp method)
                                                          (member method '("su" "sudo")))))))
                  tramp-shell-prompt-pattern "\\(?:^\\|
\\)[^#$%>\n]*#?[#$%>›] *\\(\\[[0-9;]*[a-zA-Z] *\\)*")
            (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" (concat "/" tramp-default-method ":%h:")))
            (add-to-list 'tramp-default-proxies-alist `(,(regexp-quote (system-name)) nil nil))
            (add-to-list 'tramp-default-proxies-alist '("localhost" nil nil))
            (add-to-list 'tramp-default-proxies-alist '("router" nil nil))))

(req-package tramp-sh
  :require tramp
  :defer t
  :config (progn
            (add-to-list 'tramp-remote-path "/usr/local/sbin")
            (add-to-list 'tramp-remote-path "~/bin")))

(req-package ediff
  :defer t
  :config (progn
            (setq ediff-split-window-function 'split-window-horizontally
                  ediff-window-setup-function 'ediff-setup-windows-plain)))

;;;; Indentation

(setq-default tab-width 4
              indent-tabs-mode t)
(setq tab-stop-list
      ;; (mapcar (lambda (x)
      ;;           (* 4 x))
      ;;         (number-sequence 1 (/ 120 4)))
      '(4 8 12 16 20 24 28 32 36 40 44 48 52 56 60 64 68 72 76 80 84 88 92 96 100 104 108 112 116 120)
      tab-always-indent 'complete)

(req-package auto-indent-mode
  :config (progn
            (setq auto-indent-key-for-end-of-line-then-newline "<C-return>"
                  auto-indent-key-for-end-of-line-insert-char-then-newline "<C-S-return>"
                  auto-indent-blank-lines-on-move nil
                  auto-indent-assign-indent-level 4
                  auto-indent-backward-delete-char-behavior nil
                  auto-indent-delete-trailing-whitespace-on-save-file t
                  auto-indent-mode-untabify-on-yank-or-paste nil
                  auto-indent-known-indent-level-variables
                  (remq 'lisp-body-indent auto-indent-known-indent-level-variables))
            (add-to-list 'auto-indent-disabled-modes-list 'jinja2-mode)
            (add-to-list 'auto-indent-disabled-modes-list 'yaml-mode)
            (add-to-list 'auto-indent-disabled-modes-list 'saltstack-mode)
            (add-to-list 'auto-indent-disabled-modes-list 'nix-mode)
            (auto-indent-global-mode)))

(req-package smart-tabs-mode
  :commands (smart-tabs-mode
             smart-tabs-mode-enable
             smart-tabs-advice)
  :config (progn
            (add-hook 'php-mode-hook (lambda ()
                                       (smart-tabs-mode indent-tabs-mode)))
            (smart-tabs-insinuate 'c 'javascript 'cperl 'python)))

;;;; Keybindings

(when (eq system-type 'darwin)
  (set-keyboard-coding-system nil)
  (setq mac-option-modifier 'meta
        mac-right-option-modifier 'left
        mac-control-modifier 'control
        mac-right-control-modifier 'left
        mac-command-modifier 'super
        mac-right-command-modifier 'left
        mac-function-modifier 'hyper))

(unbind-key "<f4>")
(bind-key "<f5>" #'compile)
(bind-key "<f6>" #'kmacro-start-macro-or-insert-counter)
(bind-key "<f7>" #'kmacro-end-or-call-macro)

(bind-key "<apps>" #'execute-extended-command)

(unbind-key "C-z")
(bind-key "C-<tab>" #'other-window)

(bind-key "C-x C-r" #'revert-buffer)
(bind-key "C-x C-j" #'delete-indentation)
(unbind-key "C-x C-c")

(bind-key "C-c i" #'insert-char)
(bind-key "M-/" #'hippie-expand)

(unbind-key "s-h")
(unbind-key "s-n")
(unbind-key "s-p")
(unbind-key "s-w")
(bind-key "s-k" #'kill-or-delete-this-buffer-dwim)

(bind-key "s-x" (define-prefix-command 'super-x-map))

(defun switch-to-dotfiles ()
  (interactive)
  (projectile-persp-switch-project (expand-file-name "dotfiles" home-project-directory)))
(bind-key "s-," #'switch-to-dotfiles)
(set-register ?e `(file . ,*init-file*))
(set-register ?z `(file . ,(expand-file-name ".config/zsh/zshrc" "~")))

(req-package discover
  :config (global-discover-mode))

;; Enable narrowing functions C-x n
(put 'narrow-to-defun  'disabled nil)
(put 'narrow-to-page   'disabled nil)
(put 'narrow-to-region 'disabled nil)

;;;; Minibuffer

(setq enable-recursive-minibuffers t)
(setq minibuffer-prompt-properties '(read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt))
(minibuffer-depth-indicate-mode t)

(if (daemonp)
    (defalias 'exit-emacs #'delete-frame)
  (defalias 'exit-emacs #'save-buffers-kill-emacs))

(req-package helm-config
  :demand t
  :ensure helm
  :bind (("C-x i" . helm-semantic-or-imenu))
  :config (progn
            (setq helm-idle-delay .1
                  helm-input-idle-delay 0)
            (when (fboundp #'helm-adaptive-mode)
              (helm-adaptive-mode 1))))

(req-package helm-files
  :defer t
  :config (progn
            (define-key helm-read-file-map (kbd "<tab>") #'helm-execute-persistent-action)
            (define-key helm-read-file-map (kbd "TAB") #'helm-execute-persistent-action)
            (define-key helm-read-file-map (kbd "C-i") #'helm-execute-persistent-action)
            (define-key helm-read-file-map (kbd "C-z") #'helm-select-action)))

(req-package ido
  :bind (("C-x b"   . ido-switch-buffer))
  :init (progn
          (setq ido-save-directory-list-file (expand-file-name "ido-state" user-emacs-directory))
          (bind-key* "C-x C-f" #'ido-find-file)
          (ido-mode 1)
          (ido-everywhere 1))
  :config (progn
            (setq ido-auto-merge-delay-time 99999
                  ido-enable-flex-matching t)

            (ido-init-completion-maps)
            (defadvice ido-find-file (after find-file-sudo activate)
              "Find file as root if necessary."
              (unless (and buffer-file-name
                           (file-writable-p buffer-file-name))
                (find-alternate-file (concat "/sudo::" buffer-file-name))))
            (defun ido-manual-merge ()
              (interactive)
              (ido-initiate-auto-merge (current-buffer)))
            (bind-key "C-c C-s" #'ido-manual-merge ido-file-dir-completion-map)))

(req-package ido-completing-read+
  :require ido
  :config (progn
            (setq ido-cr+-fallback-function #'helm-completing-read
                  ido-cr+-max-items 2000)))

(defun ap/ido-projectile-switch-buffer-dwim (force-ido)
  (interactive "p")
  (if (and (projectile-project-p) (eq force-ido 1))
      (call-interactively #'projectile-switch-to-buffer)
    (call-interactively #'ido-switch-buffer)))

(bind-key "s-x b" #'ap/ido-projectile-switch-buffer-dwim)
(bind-key "s-x s-b" #'ap/ido-projectile-switch-buffer-dwim)

(req-package ido-vertical-mode
  :require ido
  :init (progn
          (setq ido-vertical-decorations '("\n❯ "
                                             ""
                                             "\n  "
                                             "\n  …"
                                             "["
                                             "]"
                                             " [No match]"
                                             " [Matched]"
                                             " [Not readable]"
                                             " [Too big]"
                                             " [Confirm]"
                                             "\n❯ "
                                             "")
                ido-vertical-define-keys 'C-n-C-p-up-down-left-right))
  :config (ido-vertical-mode 1))

(req-package flx-ido
  :require ido
  :init (progn
          (flx-ido-mode 1)
          (setq flx-ido-threshhold 1000)))

(req-package smex
  :require ido
  :bind (("M-x" . smex)
         ("<apps>" . smex)
         ("<menu>" . smex)
         ("M-X" . smex-major-mode-commands)
         ("C-c M-x" . execute-extended-command))
  :config (progn
            (setq smex-key-advice-ignore-menu-bar t
                  smex-auto-update nil)
            (defun smex-update-after-load (_unused)
              (if (boundp 'smex-cache)
                  (smex-update)))
            (add-hook 'after-load-functions 'smex-update-after-load))
  :init (progn
          (setq smex-history-length 100
                smex-save-file (concat user-emacs-directory
                                       "smex-items"))
          (smex-initialize)))

;;;; Modeline

(column-number-mode t)
(size-indication-mode t)

(defun fill-line-format (line-format)
  (max 0
       (- (window-width)
          (length (format-mode-line line-format)))))

(defvar mode-line-size
  `((size-indication-mode
     ((-3 ,(propertize
            "%p"
            'local-map mode-line-column-line-number-mode-map
            'mouse-face 'mode-line-highlight
            ;; XXX needs better description
            'help-echo "Size indication mode\n\
mouse-1: Display Line and Column Mode Menu"))
      " "
      (-4 "%I")))))

(setq-default
 mode-line-modes (let ((recursive-edit-help-echo "Recursive edit, type C-M-c to get out"))
                   (list (propertize "%[" 'help-echo recursive-edit-help-echo)
                         `(:propertize ("" mode-name)
                                       help-echo "Major mode\n\
mouse-1: Display major mode menu\n\
mouse-2: Show help for major mode\n\
mouse-3: Toggle minor modes"
                                       mouse-face mode-line-highlight
                                       local-map ,mode-line-major-mode-keymap)
                         '("" mode-line-process)
                         `(:propertize ("" minor-mode-alist)
                                       mouse-face mode-line-highlight
                                       help-echo "Minor mode\n\
mouse-1: Display minor mode menu\n\
mouse-2: Show help for minor mode\n\
mouse-3: Toggle minor modes"
                                       local-map ,mode-line-minor-mode-keymap)
                         (propertize "%n" 'help-echo "mouse-2: Remove narrowing from buffer"
                                     'mouse-face 'mode-line-highlight
                                     'local-map (make-mode-line-mouse-map
                                                 'mouse-2 #'mode-line-widen))
                         (propertize "%]" 'help-echo recursive-edit-help-echo)
                         " "))
 mode-line-buffer-identification (list (propertize "%b"
                                                   'face 'mode-line-buffer-id))

 mode-line-position `((line-number-mode
                       ((column-number-mode
                         ,(propertize
                           "%l:%c"
                           'local-map mode-line-column-line-number-mode-map
                           'mouse-face 'mode-line-highlight
                           'help-echo "Line number and Column number\n\
mouse-1: Display Line and Column Mode Menu")
                         (6 ,(propertize
                              "L%l"
                              'local-map mode-line-column-line-number-mode-map
                              'mouse-face 'mode-line-highlight
                              'help-echo "Line Number\n\
mouse-1: Display Line and Column Mode Menu"))))
                       ((column-number-mode
                         (5 ,(propertize
                              "C%c"
                              'local-map mode-line-column-line-number-mode-map
                              'mouse-face 'mode-line-highlight
                              'help-echo "Column number\n\
mouse-1: Display Line and Column Mode Menu"))))))
 mode-line-format `("%e"
                    " "
                    mode-line-modes
                    mode-line-misc-info
                    (vc-mode vc-mode)
                    mode-line-end-spaces)
 header-line-format `("%e"
                      mode-line-front-space
                      mode-line-mule-info
                      mode-line-client
                      mode-line-modified
                      mode-line-auto-compile
                      mode-line-remote " "
                      mode-line-position " "
                      mode-line-size
                      " ⎸ "
                      mode-line-buffer-identification
                      ))

;;;; Modes

;;;; systemd files
(add-to-list 'auto-mode-alist '("\\.service\\'" . conf-mode))
(add-to-list 'auto-mode-alist '("\\.target\\'" . conf-mode))
(add-to-list 'auto-mode-alist '("\\.socket\\'" . conf-mode))

(add-to-list 'auto-mode-alist '("\\.envrc\\'" . sh-mode))

(req-package xrdb-mode
  :mode (("\\.Xdefaults\\'" . xrdb-mode)
         ("\\.Xresources\\'" . xrdb-mode)))

(req-package haskell-mode
  :mode (("\\.hs\\'" . haskell-mode)))

(req-package nix-mode
  :mode (("\\.nix\\'" . nix-mode))
  :config (progn
            (setq-local indent-tabs-mode nil)))

(define-derived-mode xmonad-mode haskell-mode "XM")
(add-to-list 'auto-mode-alist '("xmobarrc\\'" . xmonad-mode))
(add-to-list 'auto-mode-alist '("xmonad.hs\\'" . xmonad-mode))

(req-package nginx-mode
  :defer t
  :mode (("/nginx/servers/" . nginx-mode)
         ("/nginx/.*\\.d/" . nginx-mode))
  :config (progn
            (setq nginx-indent-tabs-mode t)))

(req-package lua-mode
  :defer t)

(req-package ledger-mode
  :mode ("\\.ledger\\'" . ledger-mode)
  :config (progn
            (defun setup-ledger-mode ()
              (setq-local indent-tabs-mode nil))
            (add-hook 'ledger-mode-hook #'setup-ledger-mode)
            (add-to-list 'smart-tab-disabled-major-modes 'ledger-mode)
            (setq ledger-use-iso-dates t
                  ledger-post-use-completion-engine 'ido
                  ledger-reconcile-default-commodity "€"
                  ledger-clear-whole-transactions t
                  ledger-narrow-on-reconcile t
                  ledger-default-date-format "%Y-%m-%d")))

(req-package ruby-mode
  :mode (("\\.rb\\'" . ruby-mode)
         ("\\.cap\\'" . ruby-mode)))

(req-package yaml-mode
  :mode (("/group_vars/.*" . yaml-mode)
         ("/host_vars/.*" . yaml-mode)))

(define-derived-mode ansible-mode yaml-mode "Ansible")
(add-to-list 'auto-mode-alist '("\\(?:ansible.+\\|roles/.+/\\(?:tasks\\|handlers\\)\\)/.+\\.yml\\'" . ansible-mode))

(define-derived-mode saltstack-mode yaml-mode "Salt")
(add-to-list 'auto-mode-alist '("\\.sls\\'" . saltstack-mode))

;;;; Planning

(req-package org
  :bind (("C-c C-a" . org-agenda-list)
         ("C-c a" . org-agenda)
         ("C-c l" . org-store-link))
  :init (setq org-replace-disputed-keys t)
  :config (progn
            (setq org-directory "~/org"
                  org-agenda-files `(,org-directory)

                  org-default-notes-file (concat org-directory "/notes")

                  ;; Fewer asterisks, doesn't change indentation
                  org-hide-leading-stars t

                  org-startup-indented t

                  ;; ‘Remember’: new items at top
                  org-reverse-note-order t

                  org-modules '(org-habit
                                org-checklist)

                  ;; Add time done to ‘done’ tasks
                  org-log-done 'time

                  ;; Allow refiling into any org file
                  org-refile-targets '((org-agenda-files :maxlevel . 3))

                  org-list-allow-alphabetical t

                  org-pretty-entities t

                  org-table-duration-custom-format 'seconds

                  org-export-have-math t

                  org-blank-before-new-entry '((heading . t)
                                               (plain-list-item . auto))
                  org-fontify-done-headline t

                  org-todo-keywords '((sequence "TODO" "STARTED" "|" "DONE")
                                      (sequence "TOLEARN" "LEARNING" "LEARNED" "|" "MASTERED")
                                      (sequence "|" "CANCELLED")))
            (set-register ?o `(file . ,(expand-file-name "organiser.org" org-directory)))
            (defadvice org-clock-in (after wicked activate)
              "Mark STARTED when clocked in"
              (save-excursion
                (catch 'exit
                  (org-back-to-heading t)
                  (if (looking-at org-outline-regexp) (goto-char (1- (match-end 0))))
                  (if (looking-at (concat " +" org-todo-regexp "\\( +\\|[ \t]*$\\)"))
                      (org-todo "STARTED")))))))

(req-package org-babel
  :require org
  :defer t
  :config (org-babel-do-load-languages
           'org-babel-load-languages
           '((ledger . t))))

(req-package org-journal
  :require org
  :defer t
  :config (progn
            (setq org-journal-date-format "%A, %d %B %Y")))

(req-package org-mobile
  :require org
  :defer t
  :config (progn
            (setq org-mobile-directory "~/Mobile/Org")))

;;;; Programming

(req-package cedet
  :disabled t
  :config (progn
            (semantic-load-enable-code-helpers)
            (global-semantic-idle-completions-mode t)
            (global-semantic-highlight-func-mode t)
            (global-semantic-show-unmatched-syntax-mode t)
            (global-semantic-decoration-mode t)))

(req-package flycheck
  :diminish " ✓"
  :init (global-flycheck-mode))

(req-package go-mode
  :mode (("\\.go\\'" . go-mode)))

(when (file-exists-p "src/code.google.com/p/go.tools/cmd/oracle/oracle.el")
  (req-package oracle
    :load-path ,(expand-file-name "src/code.google.com/p/go.tools/cmd/oracle/oracle.el" (getenv "GOPATH"))
    :init (progn
            (add-hook 'go-mode-hook #'go-oracle-mode))))

(req-package company-go
  :require go-mode
  :config (progn
            (setq company-go-show-annotation t)
            (defun ap/company-go-setup ()
              (set (make-local-variable 'company-backends)
                   '(company-go)))
            (add-hook 'go-mode-hook #'ap/company-go-setup)))

(req-package go-eldoc
  :require go-mode
  :config (progn
            (add-hook 'go-mode-hook #'go-eldoc-setup)))

(req-package go-projectile
  :require (go-mode go-eldoc projectile)
  :config (progn
            (setq go-projectile-switch-gopath 'maybe)))

(req-package ggtags
  :commands turn-on-ggtags-mode
  :config (progn
            (bind-key "q" #'ggtags-navigation-mode-abort ggtags-navigation-mode-map))
  :init (progn
          (defun turn-on-ggtags-mode ()
            (interactive)
            (ggtags-mode 1))
          (add-hook 'c-mode-common-hook #'turn-on-ggtags-mode)))

(req-package auto-compile
  :defer t
  :init (add-hook 'emacs-lisp-mode-hook #'auto-compile-on-save-mode))

(req-package cc-mode
  :defer t
  :init (progn
          (add-hook 'c-mode-common-hook #'electric-indent-mode))
  :config (progn
              (setq c-default-style '((java-mode . "java")
                                    (awk-mode . "awk")
                                    (other . "k&r"))
                  c-basic-offset 4)
            (c-set-offset 'case-label '+)))

(req-package quickrun
  :bind (("C-c C-e" . quickrun)))

;;;; Projects

(req-package dash
  :demand t
  :init (setq dash-enable-fontlock t)
  :config (progn
            (dash--enable-fontlock 'dash-enable-font-lock t)))

(req-package projectile
  :bind (("C-c C-f" . projectile-find-file)
         ("s-x s-f" . projectile-find-file)
         ("C-x g" . projectile-vc)
         ("s-G"   . projectile-vc))
  :init (projectile-global-mode)
  :diminish projectile-mode
  :config (progn
            (defun ap/subfolder-projects (dir)
              (--map (file-relative-name it dir)
                     (-filter (lambda (subdir)
                                (--reduce-from (or acc (funcall it subdir)) nil
                                               projectile-project-root-files-functions))
                              (-filter #'file-directory-p (directory-files dir t "\\<")))))

            (defun ap/-add-known-subfolder-projects (dir)
              (-map #'projectile-add-known-project (--map (concat (file-name-as-directory dir) it) (ap/subfolder-projects dir))))

            (defun ap/add-known-subfolder-projects ()
              (interactive)
              (ap/-add-known-subfolder-projects (ido-read-directory-name "Add projects under: ")))

            (defun ap/open-subfolder-project (from-dir &optional arg)
              (let ((project-dir (projectile-completing-read "Open project: "
                                                             (ap/subfolder-projects from-dir))))
                (projectile-switch-project-by-name (expand-file-name project-dir from-dir) arg)))

            (defun ap/open-work-project (&optional arg)
              (interactive "P")
              (ap/open-subfolder-project work-project-directory arg))

            (defun ap/open-home-project (&optional arg)
              (interactive "P")
              (ap/open-subfolder-project home-project-directory arg))


            (setq projectile-switch-project-action #'projectile-dired
                  projectile-remember-window-configs t
                  projectile-completion-system 'helm)))

(req-package helm-projectile
  :ensure projectile
  :require projectile
  :defer t)

(req-package projector
  :require projectile
  :bind (("s-z" . projector-open-project-shell)))

(req-package perspective
  :bind (("s-p" . persp-switch))
  :init (progn
          (persp-mode)))

(req-package persp-projectile
  :require (projectile perspective))

(req-package vc
  :defer t
  :bind (("C-x v C" . vc-resolve-conflicts))
  :config (progn
            (setq vc-follow-symlinks t)))

(req-package diff-hl
  :init (progn
          (global-diff-hl-mode)
          (add-hook 'magit-refresh-file-buffer-hook #'diff-hl-update)))

(req-package magit
  :commands (magit-status)
  :config (progn (rename-modeline magit-status-mode (char-to-string (-find #'char-displayable-p '(11942 5848 177))))
                 (setq magit-last-seen-setup-instructions "1.4.0"
                       magit-completing-read-function #'magit-ido-completing-read))
  :init (add-hook 'magit-mode-hook #'magit-load-config-extensions))

;;;; Spelling
(req-package ispell
  :bind (("<f8>" . ispell-word))
  :config (progn
            (setq ispell-program-name "aspell"
                  ispell-dictionary "british")))

;;;; Scripting

(add-hook 'after-save-hook
          #'executable-make-buffer-file-executable-if-script-p)

(req-package sh-script
  :mode (("\\.zsh\\'" . shell-script-mode))
  :config (setq sh-shell-file "/usr/bin/env zsh"))

;;;; Shells & REPLs

(req-package eshell
  :bind ("C-c s" . eshell)
  :config (progn
            (setq eshell-directory-name "~/.emacs.d/eshell")
            (add-hook 'eshell-load-hook (lambda ()
                                          (bind-key "C-c C-l" #'helm-eshell-history eshell-mode-map)))
            (req-package em-smart
              :config (progn
                        (setq eshell-where-to-jump 'begin
                              eshell-review-quick-commands nil
                              eshell-smart-space-goes-to-end t)
                        (eshell-smart-initialize)))))

(autoload #'eshell/cd "em-dirs")
(defun eshell-goto-current-dir (&optional arg)
  (interactive "P")
  (let ((dir default-directory))
    (eshell arg)
    (eshell/cd dir)))
(bind-key "C-c S" #'eshell-goto-current-dir)

(req-package shell
  :defer t
  :config (define-key shell-mode-map
            (kbd "C-d") 'comint-delchar-or-eof-or-kill-buffer))

(req-package comint
  :defer t
  :config (bind-key "C-c C-l" #'helm-comint-input-ring comint-mode-map))

(req-package multi-term
  :if (not (eq system-type 'windows-nt))
  :bind ("C-`" . multi-term-dedicated-toggle))

(defun comint-delchar-or-eof-or-kill-buffer (arg)
  (interactive "p")
  (if (null (get-buffer-process (current-buffer)))
      (kill-buffer)
    (comint-delchar-or-maybe-eof arg)))

;;;; Text editing

(bind-key "C-M-a" #'backward-paragraph text-mode-map)
(bind-key "C-M-e" #'forward-paragraph text-mode-map)

;; Enable upcase and downcase-region
(put 'upcase-region 'disabled nil)
(put 'downcase-region 'disabled nil)
(setq sentence-end-double-space t
      line-move-visual nil)

(req-package align
  :config (progn
            (add-to-list 'align-rules-list
                         '(colon-key-value
                           (regexp . ":\\(\\s-*\\)")
                           (modes  . '(js2-mode))))))

(setq x-select-enable-clipboard t)
(if (functionp 'x-cut-buffer-or-selection-value)
    (setq interprogram-paste-function 'x-cut-buffer-or-selection-value))

;; replace highlighted text rather than just inserting at point
(delete-selection-mode t)

(bind-key "S-SPC" #'set-mark-command)

(defun fc/isearch-yank-symbol ()
  "Yank the symbol at point into the isearch minibuffer.

C-w does something similar in isearch, but it only looks for
the rest of the word. I want to look for the whole string. And
symbol, not word, as I need this for programming the most."
  (interactive)
  (isearch-yank-string
   (save-excursion
     (when (and (not isearch-forward)
                isearch-other-end)
       (goto-char isearch-other-end))
     (thing-at-point 'symbol))))
(bind-key "C-d" #'fc/isearch-yank-symbol isearch-mode-map)

(req-package subword
  :init (global-subword-mode t))

(req-package misc
  :bind (("M-z" . zap-up-to-char)
         ("M-Z" . zap-to-char)))

(req-package typopunct
  :config (progn
            (typopunct-change-language 'english t)
            (defconst typopunct-minus (decode-char 'ucs #x2212))
            (defadvice typopunct-insert-typographical-dashes
                (around minus-or-pm activate)
              (cond
               ((or (eq (char-before) typopunct-em-dash)
                    (looking-back "\\([[:blank:]]\\|^\\)\\^"))
                (delete-char -1)
                (insert typopunct-minus))
               ((looking-back "[^[:blank:]]\\^")
                (insert typopunct-minus))
               (t ad-do-it)))
            (define-key typopunct-map "+" 'typopunct-insert-mp)

            (defconst typopunct-ellipsis (decode-char 'ucs #x2026))
            (defconst typopunct-middot   (decode-char 'ucs #xB7)) ; or 2219
            (defun typopunct-insert-ellipsis-or-middot (arg)
              "Change three consecutive dots to a typographical ellipsis mark."
              (interactive "p")
              (cond
               ((and (= 1 arg)
                     (eq (char-before) ?^))
                (delete-char -1)
                (insert typopunct-middot))
               ((and (= 1 arg)
                     (eq this-command last-command)
                     (looking-back "\\.\\."))
                (replace-match "")
                (insert typopunct-ellipsis))
               (t
                (self-insert-command arg))))
            (define-key typopunct-map "." 'typopunct-insert-ellipsis-or-middot)

            (defconst typopunct-times (decode-char 'ucs #xD7))
            (defun typopunct-insert-times (arg)
              (interactive "p")
              (if (and (= 1 arg) (looking-back "\\([[:blank:]]\\|^\\)\\^"))
                  (progn (delete-char -1)
                         (insert typopunct-times))
                (self-insert-command arg)))
            (define-key typopunct-map "x" 'typopunct-insert-times)

            (defadvice typopunct-insert-quotation-mark (around wrap-region activate)
              (let* ((lang (or (get-text-property (point) 'typopunct-language)
                               typopunct-buffer-language))
                     (omark (if single
                                (typopunct-opening-single-quotation-mark lang)
                              (typopunct-opening-quotation-mark lang)))
                     (qmark (if single
                                (typopunct-closing-single-quotation-mark lang)
                              (typopunct-closing-quotation-mark lang))))
                (cond
                 (mark-active
                  (let ((skeleton-end-newline nil)
                        (singleo (typopunct-opening-single-quotation-mark lang))
                        (singleq (typopunct-closing-single-quotation-mark lang)))
                    (if (> (point) (mark))
                        (exchange-point-and-mark))
                    (save-excursion
                      (while (re-search-forward (regexp-quote (string omark)) (mark) t)
                        (replace-match (regexp-quote (string singleo)) nil nil)))
                    (save-excursion
                      (while (re-search-forward (regexp-quote (string qmark)) (mark) t)
                        (replace-match (regexp-quote (string singleq)) nil nil)))
                    (skeleton-insert (list nil omark '_ qmark) -1)))
                 ((looking-at (regexp-opt (list (string omark) (string qmark))))
                  (forward-char 1))
                 (t ad-do-it)))))
  :init (progn
          (add-hook 'text-mode-hook #'typopunct-mode)))

(req-package ap-functions
  :commands (ap/remove-extra-cr)
  :bind (("C-x r M-w" . copy-rectangle)
         ("M-!" . shell-execute)))

(when (boundp 'x-select-request-type)
  (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)))

(req-package markdown-mode
  :defer t
  :config (progn
            (add-hook 'markdown-mode-hook #'turn-on-auto-fill)))

(req-package avy
  :bind* (("M-g g" . avy-goto-line)
          ("M-g M-g" . avy-goto-line)
          ("C-|" . avy-goto-line)
          ("C-c SPC" . avy-goto-char))
  :config (progn
            (avy-setup-default)
            (setq avy-all-windows nil)))

(use-package ace-jump-buffer
  :bind ("s-b" . ace-jump-buffer))

(req-package ace-window
  :bind (("s-s" . ace-window))
  :config (progn
            (setq aw-dispatch-always t
                  aw-dispatch-alist '((?k aw-delete-window " Ace - Delete Window")
                                      (?K aw-delete-window)
                                      (?m aw-swap-window " Ace - Swap Window")
                                      (?f aw-flip-window)
                                      (?v aw-split-window-vert " Ace - Split Vert Window")
                                      (?b aw-split-window-horz " Ace - Split Horz Window")
                                      (?m delete-other-windows " Ace - Maximize Window")
                                      (?l delete-other-windows)
                                      (?, winner-undo)
                                      (?. winner-redo))
                  aw-keys '(?a ?r ?s ?t ?n ?e ?i ?o))))

(req-package expand-region
  :bind ("C-M-SPC" . er/expand-region))

(req-package goto-chg
  :bind ("C-x SPC" . goto-last-change))

(req-package helm-swoop
  :bind (("C-=" . helm-swoop)
         ("C-c C-=" . helm-multi-swoop))
  :init (progn
          (bind-key "C-=" #'helm-swoop isearch-mode-map)
          (bind-key "C-=" #'helm-multi-swoop-all-from-helm-swoop)))

(req-package multiple-cursors
  :defer 1
  :config (progn
            (bind-key "C-." #'mc/mark-next-like-this)
            (bind-key "C-," #'mc/mark-previous-like-this)
            (bind-key "M-<f3>" #'mc/mark-all-like-this-dwim)
            (bind-key "C-<f3>" #'mc/mark-more-like-this-extended)
            (bind-key "C-S-L" #'mc/edit-lines)))

(req-package eldoc
  :commands (eldoc-mode)
  :diminish eldoc-mode
  :config (progn
            (setq eldoc-idle-delay 0.1)
            (eldoc-add-command 'paredit-backward-delete 'paredit-close-round)))

(req-package paredit
  :diminish "()"
  :commands (paredit-mode)
  :init (progn
          (add-hook 'lisp-mode-common-hook #'enable-paredit-mode)
          (put #'paredit-forward-delete 'delete-selection 'supersede)
          (put #'paredit-backward-delete 'delete-selection 'supersede)
          (add-hook 'minibuffer-setup-hook #'conditionally-enable-paredit-mode)
          (defun conditionally-enable-paredit-mode ()
            "enable paredit-mode during eval-expression"
            (if (eq this-command 'eval-expression)
                (paredit-mode 1)))))

(req-package smartparens-config
  :config (progn
            (sp-use-smartparens-bindings)
            (fset 'wrap-with-paren "\C-](") ;; `sp-select-next-thing-exchange'
            (bind-key "C-(" #'wrap-with-paren sp-keymap)
            (bind-key "C-)" #'sp-forward-slurp-sexp sp-keymap)
            (bind-key "M-<backspace>" #'backward-kill-word sp-keymap)
            (bind-key "M-?" #'sp-convolute-sexp sp-keymap)
            (bind-key "C-M-t" #'sp-transpose-sexp sp-keymap)
            (bind-key "M-r" #'sp-raise-sexp sp-keymap)
            (bind-key "M-s" #'sp-splice-sexp sp-keymap)
            (bind-key "M-S" #'sp-split-sexp sp-keymap)
            (bind-key "M-J" #'sp-join-sexp sp-keymap)
            (bind-key "M-<up>" #'sp-splice-sexp-killing-backward sp-keymap)
            (bind-key "M-<down>" #'sp-splice-sexp-killing-forward sp-keymap)
            (bind-key "C-M-S-k" #'sp-kill-hybrid-sexp sp-keymap)
            (bind-key "C-S-<right>" #'sp-slurp-hybrid-sexp sp-keymap)
            (sp-with-modes '(web-mode twig-mode)
              (sp-local-pair "{%" "%}")
              (sp-local-pair "{{" "}}"))
            (show-smartparens-global-mode t)
            (smartparens-global-strict-mode t)
            (add-hook 'lisp-mode-common-hook #'turn-off-smartparens-mode)))

(req-package move-text
  :config (move-text-default-bindings))

(req-package undo-tree
  :config (progn
            (global-undo-tree-mode)
            ;; Keep region when undoing in region
            (defadvice undo-tree-undo (around keep-region activate)
              (if (use-region-p)
                  (let ((m (set-marker (make-marker) (mark)))
                        (p (set-marker (make-marker) (point))))
                    ad-do-it
                    (goto-char p)
                    (set-mark m)
                    (set-marker p nil)
                    (set-marker m nil))
                ad-do-it)))
  :diminish undo-tree-mode)

(req-package visual-regexp
  :bind (("C-c r" . vr/replace)
         ("C-c q" . vr/query-replace)
         ("C-c m" . vc/mc-mark)))

;;;; Lisps

(defun ap/lisp-setup ()
  (run-hooks 'lisp-mode-common-hook)
  (setq indent-tabs-mode nil))

(defun set-common-lisp-indentation ()
    (set (make-local-variable 'lisp-indent-function)
         #'common-lisp-indent-function))

(rename-modeline emacs-lisp-mode "ξ")
(add-to-list 'auto-mode-alist '("/Cask\\'" . emacs-lisp-mode))
(add-hook 'emacs-lisp-mode-hook #'ap/lisp-setup)
(add-hook 'emacs-lisp-mode-hook #'eldoc-mode)

(add-hook 'scheme-mode-hook #'ap/lisp-setup)
(add-hook 'lisp-mode-hook #'ap/lisp-setup)
(add-hook 'lisp-mode-hook #'set-common-lisp-indentation)

(req-package elisp-slime-nav
  :commands elisp-slime-nav-mode
  :diminish elisp-slime-nav-mode)

(req-package geiser
  :commands (geiser-mode
             geiser
             run-geiser
             run-racket))

(req-package clojure-mode
  :defer t
  :init (progn
          (add-hook 'cider-repl-mode-hook (lambda () (highlight-changes-mode -1)))
          (add-hook 'clojure-mode-hook #'ap/lisp-setup)))

(req-package clj-refactor
  :defer t
  :require clojure-mode
  :config (progn
            (cljr-add-keybindings-with-prefix "C-c C-m"))
  :init (progn
          (defun turn-on-clj-refactor-mode ()
            (clj-refactor-mode 1))
          (add-hook 'clojure-mode-hook #'turn-on-clj-refactor-mode)))

(req-package cider
  :require clojure-mode
  :defer t
  :config (progn
            (setq nrepl-hide-special-buffers t)
            (unbind-key "C-c C-f" cider-mode-map)
            (add-hook 'cider-mode-hook #'cider-turn-on-eldoc-mode)))

(req-package redshank
  :diminish " Λ"
  :defer t
  :init (progn
          (add-hook 'lisp-mode-common-hook #'turn-on-redshank-mode)))

(req-package ielm
  :defer t
  :config (progn
            (add-hook 'ielm-mode-hook (lambda ()
                                        (run-hooks 'lisp-mode-common-hook)))))

(req-package slime
  :commands (slime)
  :config (progn
            (let ((ql-slime-helper (expand-file-name "~/quicklisp/slime-helper.el")))
              (if (file-exists-p ql-slime-helper)
                  (load ql-slime-helper))
              (slime-setup))
            (setq inferior-lisp-program (executable-find "sbcl"))))

(defun imenu-elisp-sections ()
  (setq imenu-prev-index-position-function nil)
  (add-to-list 'imenu-generic-expression '("Sections" "^;;;; \\(.+\\)$" 1) t)
  (add-to-list 'imenu-generic-expression '("Packages" "^(req-package\\s-+\\(\\(\\sw\\|\\s_\\)+\\)$" 1) t))

(defun init-narrow-to-section ()
  (interactive)
  (save-excursion
    (beginning-of-line)
    (unless (looking-at "^;;;;")
      (re-search-backward "^;;;;" nil t))
    (push-mark)
    (forward-line)
    (re-search-forward "^;;;;" nil t)
    (forward-line -1)
    (narrow-to-region (region-beginning) (region-end))))

(defun init-imenu (p)
  (interactive "P")
  (find-file-existing *init-file*)
  (widen)
  (helm-imenu)
  (if p (init-narrow-to-section)))

(add-hook 'emacs-lisp-mode-hook 'imenu-elisp-sections)

(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)))))

(bind-key "C-c e" #'eval-and-replace)

;;;; Web Development

(req-package skewer-mode
  :defer t
  :init (progn
          (add-hook 'js2-mode-hook #'skewer-mode)
          (add-hook 'html-mode-hook #'skewer-html-mode)
          (add-hook 'css-mode-hook #'skewer-css-mode)))

(req-package js2-mode
  :mode ("\\.js\\'" . js2-mode)
  :config (progn
            (defun ap/javascript-setup ()
              (autopair-mode -1)
              (auto-indent-mode -1))
            (defun ap/js2-prev-error ()
              (interactive)
              (js2-next-error -1))
            (bind-key "M-g M-n" #'js2-next-error js2-mode-map)
            (bind-key "M-g M-p" #'ap/js2-prev-error js2-mode-map)
            (add-hook 'js2-mode-hook #'ap/javascript-setup)
            (setq js2-basic-offset 4
                  js2-include-node-externs t)))

(req-package json-mode
  :mode ("\\.json\\'" . json-mode))

(req-package restclient
  :mode ("\\.api\\'" . restclient-mode)
  :config (progn
            (defun imenu-restclient-sections ()
              (setq imenu-prev-index-position-function nil)
              (add-to-list 'imenu-generic-expression '("Services" "^## ?\\(.+\\)$" 1) t)
              (add-to-list 'imenu-generic-expression '("Calls" "^# ?\\(.+\\)$" 1) t))
            (add-hook restclient-mode-hook #'imenu-restclient-sections)))

(req-package tern
  :commands ap/enable-tern
  :config (progn
            (setq tern-command (list (executable-find "tern")))
            (defun ap/enable-tern ()
              (tern-mode 1))
            (add-hook 'js2-mode-hook #'ap/enable-tern)))

(req-package tern-company
  :require (tern company))

(add-to-list 'auto-mode-alist '("composer\\.lock" . js-mode))

(req-package scss-mode
  :defer t
  :config (progn
            (setq scss-compile-at-save nil)))

(req-package jinja2-mode
  :mode (("\\.j2\\'" . jinja2-mode)
         ("\\.jinja\\'" . jinja2-mode)))

(req-package php-mode
  :mode ("\\.php\\'" . php-mode)
  :config (progn
            (setq php-template-compatibility nil)
            (bind-key "C-h C-f" #'php-search-documentation php-mode-map)
            (unbind-key "C-c C-f" php-mode-map)
            (unbind-key "C-." php-mode-map)
            (setq php-mode-coding-style "Symfony2")
            (add-hook 'php-mode-hook #'turn-on-eldoc-mode)
            (add-hook 'php-mode-hook #'php-enable-symfony2-coding-style)))

(req-package sgml-mode
  :defer t
  :config (setq sgml-basic-offset 4))

(req-package emmet-mode
  :commands (emmet-mode)
  :diminish (emmet-mode . " >")
  :init (progn
          (if (functionp 'web-mode)
              (add-hook 'web-mode-hook #'emmet-mode))))

(req-package web-mode
  :mode (("/views/.*\\.php\\'" . web-mode)
         ("/layouts/.*\\.html\\'" . web-mode)
         ("/templates/.*\\.php\\'" . web-mode)
         ("\\.ejs\\'" . web-mode))
  :config (setq web-mode-code-indent-offset 4
                web-mode-css-indent-offset 4
                web-mode-markup-indent-offset 4
                web-mode-style-padding 0
                web-mode-script-padding 0
                web-mode-comment-style 2
                web-mode-enable-auto-pairing nil))

(define-derived-mode twig-mode web-mode "Twig")
(add-to-list 'auto-mode-alist '("\\.html\\.twig\\'" . twig-mode))

(req-package mmm-auto
  :config (progn
            (mmm-add-classes
             '((php-sql
                :submode sql-mode
                :front "<<<SQL[\r\n]+"
                :back "SQL;?"
                :face mmm-code-submode-face)))
            (mmm-add-mode-ext-class 'php-mode "\\.php$" 'php-sql)
            (mmm-add-classes
             '((markdown-toml
                :submode toml-mode
                :face mmm-declaration-submode-face
                :front "\\`+++[\n\r]+"
                :back "^+++$")
               (markdown-lisp
                :submode common-lisp-mode
                :face mmm-code-submode-face
                :front "^{{% highlight cl %}}[\r\n]+"
                :back "^{{% /highlight %}}$")
               (markdown-shell
                :submode shell-script-mode
                :face mmm-code-submode-face
                :front "^{{% highlight sh %}}[\r\n]+"
                :back "^{{% /highlight %}}$")))
            (mmm-add-mode-ext-class 'markdown-mode nil 'markdown-toml)
            (mmm-add-mode-ext-class 'markdown-mode nil 'markdown-lisp)
            (mmm-add-mode-ext-class 'markdown-mode nil 'markdown-shell))
  :init (setq mmm-global-mode 'maybe))

;;;; Windows & Frames

(setq frame-title-format
      '((:eval (if (and (fboundp #'projectile-project-p)
                        (projectile-project-p))
                   (projectile-project-name)))
        ": "
        (:eval (if (buffer-file-name)
                   (buffer-name)
                 "%b"))))

(setq scroll-conservatively 100 ; Keep the cursor position when scrolling
      scroll-margin 1
      scroll-preserve-screen-position t
      mouse-wheel-scroll-amount '(1 ((shift) . 1) ((control)))
      split-height-threshold 100)

(when (and menu-bar-mode (not (eq window-system 'ns)))
  (menu-bar-mode -1))
(when scroll-bar-mode
  (scroll-bar-mode -1)
  (tooltip-mode -1)
  (tool-bar-mode -1))

(defun toggle-window-dedicated ()
  "Toggle whether the current active window is dedicated or not"
  (interactive)
  (message
   "Window '%s' is %s"
   (current-buffer)
   (if (let ((window (get-buffer-window (current-buffer))))
         (set-window-dedicated-p window
                                 (not (window-dedicated-p window))))
       "dedicated"
     "normal")))

(req-package popwin
  :if (and (>= emacs-major-version 24)
           (>  emacs-minor-version 3))
  :config (progn
            (popwin-mode 1)
            (add-to-list 'popwin:special-display-config '("^*helm.+*$" :regexp t :height 20))))

(req-package winner
  :init (progn
          (winner-mode 1)
          (setq winner-boring-buffers '("*Completions*" "*Help*" "*Apropos*" "*Buffer List*" "*info*" "*Compile-Log*"))))

(req-package windmove
  :bind (("S-<left>"  . windmove-left)
         ("S-<right>" . windmove-right)
         ("S-<up>"    . windmove-up)
         ("S-<down>"  . windmove-down)))

(if (eq system-type 'darwin)
    (setq ns-pop-up-frames nil))

(req-package-finish)

(unless (daemonp)
  (require 'server)
  (if (server-running-p server-name)
      (message "Server already appears to be running")
    (server-start)))