;; -*- 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)

;;; 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-common-mode-hook nil
  "Hook run when entering any Lisp mode."
  :type 'hook
  :group 'lisp)

;;;; Environment & Location

(defun env/get-location ()
  "Return the physical location of the system, or `nil' if unknown"
  (if (executable-find "netctl")
      (catch 'found
        (mapc (lambda (line)
                (cond
                 ((string-prefix-p "* home" line) (throw 'found 'home))
                 ((string-prefix-p "* work" line) (throw 'found 'work))))
              (process-lines "netctl" "list"))
        nil)))

(defun env/get-system-type ()
  "Return the type of computer that is running"
  (cond
   ((string-prefix-p "prefect" system-name) 'desktop)
   ((string-prefix-p "server"  system-name) 'server)
   ((string-prefix-p "sheldon" system-name) 'laptop)))

(defvar env/location (env/get-location)
  "The type of location the system is located in
Values: `work', `home'")

(defvar env/system-type (env/get-system-type)
  "The type of computer Emacs is running on
Values: `desktop', `server', `laptop'")

(defun env/recheck-location ()
  (interactive)
  (setq env/location (env/get-location)))

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

;;;; Package Management
(eval-and-compile
  (setq package-archives '(("gnu"       . "http://elpa.gnu.org/packages/")
                           ("marmalade" . "http://marmalade-repo.org/packages/")
                           ("melpa"     . "http://melpa.milkbox.net/packages/")
                           ("org"       . "http://orgmode.org/elpa/")))
  (package-initialize))

(when (not package-archive-contents)
  (package-refresh-contents))

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

(mapc (lambda (package-name)
        (unless (package-installed-p package-name)
          (package-install package-name)))
      '(bind-key
        diminish
        use-package))

(require 'use-package)

;;;; Style

(use-package linum
  :defer t
  :config (setq linum-format " %4d "))

(use-package highlight-symbol
  :disabled t
  :config (setq highlight-symbol-idle-delay 0.2))

(use-package whitespace
  :defer t
  :config (setq whitespace-style
                '(face
                  space
                  tabs
                  trailing
                  newline
                  empty
                  space-after-tab
                  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)

(use-package solarized-theme
  :ensure t
  :config (load-theme 'solarized-light t))

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

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

  (cond
   ((eq window-system 'w32)
    (let* ((font-size 10)
           (font-list (font-family-list))
           (mono-face (cond
                       ((member "Liberation Mono" font-list)
                        "Liberation Mono")
                       ((member "Liberation Sans Mono" font-list)
                        "Liberation Sans Mono")
                       ((member "Consolas" font-list)
                        "Consolas")))
           (variable-face "Segoe UI")
           (default-font (concat mono-face "-" (number-to-string font-size))))
      (when mono-face
        (set-face-font 'default default-font)
        (set-face-font 'fixed-pitch default-font))
      (when variable-face
        (set-face-font 'variable-pitch (concat variable-face "-"
                                               (number-to-string (1+ font-size)))))))
   ((eq window-system 'ns)
    (let* ((font-size 14)
           (font-list (font-family-list))
           (mono-face (cond
                       ((member "Droid Sans Mono" font-list)
                        "Droid Sans Mono")))
           (variable-face "Helvetica Neue")
           (default-font (concat mono-face "-" (number-to-string font-size))))
      (when mono-face
        (set-face-font 'default default-font)
        (set-face-font 'fixed-pitch default-font))))))

(with-elapsed-timer "Setting up font styles"
  (let* ((font-height (face-attribute 'default :height))
         (small-font-height (max 1 (floor (* .917 font-height)))))
    (mapc (lambda (item)
            (put (car item) 'customized-face (cadr item))
            (face-spec-set (car item) (cadr item)))
          `((linum
             ((t (:height ,small-font-height
                          :foreground unspecified
                          :inherit fringe
                          :overline nil
                          :slant normal))))
            (vertical-border
             ((t (:foreground unspecified
                              :background unspecified
                              :inherit file-name-shadow))))
            (font-lock-comment-face
             ((t (:slant normal))))
            (font-lock-doc-face
             ((t (:slant normal))))
            (popup-face
             ((t (:background unspecified
                              :foreground unspecified
                              :inherit linum
                              :height ,font-height))))
            (popup-scroll-bar-foreground-face
             ((t (:background unspecified
                              :inherit region))))
            (popup-scroll-bar-background-face
             ((t (:background unspecified
                              :inherit popup-face))))
            (ac-completion-face
             ((t (:background unspecified
                              :foreground unspecified
                              :inherit popup-face))))
            (ac-candidate-face
             ((t (:background unspecified
                              :foreground unspecified
                              :inherit linum
                              :height ,font-height))))
            (ac-selection-face
             ((t (:background unspecified
                              :foreground unspecified
                              :inherit font-lock-variable-name-face
                              :inverse-video t))))
            (ac-candidate-mouse-face
             ((t (:background unspecified
                              :foreground unspecified
                              :inherit region))))
            (ac-dabbrev-menu-face
             ((t (:background unspecified
                              :foreground unspecified
                              :inherit popup-face))))
            (ac-dabbrev-selection-face
             ((t (:background unspecified
                              :foreground unspecified
                              :inherit ac-selection-face))))
            (flymake-warnline
             ((t (:background unspecified
                              :foreground unspecified
                              :inherit font-lock-preprocessor-face))))
            (org-table ((t (:inherit 'fixed-pitch))))
            (org-formula ((t (:foreground "Firebrick"
                                          :inherit 'fixed-pitch))))
            (org-done ((t (:weight normal
                                   :strike-through t))))
            (org-headline-done ((t (:strike-through t))))))))

;;;; 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

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

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

;;;; Communication

(use-package erc
  :defer t
  :config (progn
            (setq erc-user-full-name "Alan Pearce"
                  erc-email-userid "alan@alanpearce.co.uk"
                  erc-echo-notice-in-minibuffer t
                  erc-keywords '("alanpearce" "lethalrocks")
                  erc-autojoin-channels-alist
                  '(("freenode.net" "#emacs" "##freebsd" "#bufferbloat" "#openwrt" "#lojban" "#zfs" "#introverts")
                    ("what.cd" "#what.cd")
                    ("beusergroup.co.uk" "#be")))
            (add-to-list 'erc-modules 'scrolltobottom)
            (add-to-list 'erc-modules 'autojoin)
            (add-to-list 'erc-modules 'match)))

(setq message-send-mail-function 'smtpmail-send-it)
(setq smtpmail-smtp-server "external.home"
      smtpmail-smtp-service 587)

(use-package mu4e
  :if (and (eq env/location 'home)
           (eq env/system-type 'desktop))
  :load-path "/usr/share/emacs/site-lisp/mu4e"
  :commands (mu4e)
  :config (progn
            (setq mu4e-get-mail-command "true"
                  mu4e-update-interval 300
                  mu4e-sent-folder "/alanpearce/Sent"
                  mu4e-drafts-folder "/alanpearce/Drafts"
                  mu4e-refile-folder "/alanpearce/Archive")
            (bind-key "q" #'bury-buffer mu4e-main-mode-map)
            (bind-key "d" #'mu4e-headers-mark-for-delete mu4e-headers-mode-map)))

;;;; Completion

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

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

(use-package company
  :ensure t
  :commands (company-mode)
  :bind (("C-<tab>" . company-complete))
  :init (progn
          (add-hook 'prog-mode-hook #'company-mode)
          (setq company-idle-delay .3
                company-begin-commands '(self-insert-command))))

;;;; Dates & Times

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

;;;; Directory browsing
(use-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-bind-jump nil)
            (put 'dired-find-alternate-file 'disabled nil)))

(use-package dired+
  :defer t
  :ensure t
  :config (diredp-toggle-find-file-reuse-dir 1))

;;;; Documentation

(add-to-list 'Info-default-directory-list
             (concat user-emacs-directory
                     "info"))

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

;;;; 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)
(global-auto-revert-mode 1)

(add-hook 'before-save-hook #'delete-trailing-whitespace)

(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))
        (buffer (current-buffer)))
    (if (not (and filename (file-exists-p filename)))
        (ido-kill-buffer)
      (when (yes-or-no-p "Are you sure you want to remove this file? ")
        (if (vc-backend filename)
            (vc-delete-file filename)
          (delete-file filename)
          (kill-buffer buffer)
          (message "File '%s' successfully removed" filename))))))

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

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

(use-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") (nil nil "alan"))
                  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))))

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

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

(use-package auto-indent-mode
  :ensure t
  :commands (auto-indent-minor-mode
             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-global-mode)
            (defun lisp-auto-indent-mode ()
              (set (make-local-variable 'auto-indent-assign-indent-level) 2))
            (add-hook 'lisp-common-mode-hook #'lisp-auto-indent-mode)))

(use-package smart-tabs-mode
  :ensure t
  :commands (smart-tabs-mode
             smart-tabs-mode-enable
             smart-tabs-advice)
  :config (progn
            (smart-tabs-insinuate 'c 'javascript 'cperl 'python 'ruby)
            (add-hook 'php-mode-hook #'smart-tabs-mode-enable)))

;;;; 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" #'ucs-insert)
(bind-key "M-/" #'hippie-expand)

(unbind-key "s-h")
(unbind-key "s-n")
(unbind-key "s-p")
(unbind-key "s-w")

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

(set-register ?e `(file . ,*init-file*))

;; 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)
(minibuffer-depth-indicate-mode t)

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

(use-package lacarte
  :bind (("M-`" . lacarte-execute-menu-command)))

(use-package helm-config
  :ensure helm
  :bind (("C-x i"   . helm-imenu))
  :config (setq helm-idle-delay .1
                helm-input-idle-delay .1))

(use-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))
  :config (progn
            (setq ido-decorations (quote ("\n›" "" "\n " "\n …" "[" "]" " [No match]" " [Matched]" " [Not readable]" " [Too big]" " [Confirm]"))
                  ido-auto-merge-delay-time 99999
                  ido-enable-flex-matching t
                  ido-use-virtual-buffers t)

            (ido-init-completion-maps)
            (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)))

(use-package smex
  :ensure t
  :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)

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

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

(use-package haskell-mode
  :ensure t
  :mode (("\\.hs\\'" . haskell-mode)
         ("xmobarrc\\'" . haskell-mode)))

(use-package nginx-mode
  :ensure t
  :mode (("nginx.conf" . nginx-mode)))

(use-package lua-mode
  :ensure t
  :mode (("\\.lua\\'" . lua-mode)))

(use-package puppet-mode
  :mode (("\\.pp\\'" . puppet-mode))
  :config (progn
            (add-hook 'puppet-mode-hook #'autopair-mode)))

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

;;;; Planning

(use-package org
  :ensure org-plus-contrib
  :bind (("C-c C-a" . org-agenda-list)
         ("C-c a" . org-agenda)
         ("C-c l" . org-store-link)
         ("C-c r" . org-remember)
         ("C-c b" . org-iswitchb))
  :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-replace-disputed-keys 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")))))))

(use-package org-journal
  :config (progn
            (setq org-journal-date-format "%A, %d %B %Y")))

;;;; Programming

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

;;;; Projects

(use-package projectile
  :ensure t
  :bind (("C-c C-f" . projectile-find-file)
         ("s-x s-f" . projectile-find-file))
  :commands (projectile-global-mode))

(use-package project-persist
  :ensure t
  :commands (project-persist-mode)
  :bind (("C-c P d" . project-persist-delete)
         ("C-c P f" . project-persist-find)
         ("C-c P k" . project-persist-close)
         ("C-c P n" . project-persist-create)
         ("C-c P s" . project-persist-save))
  :config (progn
            (project-persist-mode t)

            (setq project-persist-auto-save-global t)

            (add-hook 'project-persist-before-load-hook #'kill-all-buffers)

            (defun emacs-process-p (pid)
              "If pid is the process ID of an emacs process, return t, else nil.
Also returns nil if pid is nil."
              (when pid
                (let ((attributes (process-attributes pid)) (cmd))
                  (dolist (attr attributes)
                    (if (string= "comm" (car attr))
                        (setq cmd (cdr attr))))
                  (if (and cmd (or (string= "emacs" cmd) (string= "emacs.exe" cmd))) t))))

            (defadvice desktop-owner (after pry-from-cold-dead-hands activate)
              "Don't allow dead emacsen to own the desktop file."
              (when (not (emacs-process-p ad-return-value))
                (setq ad-return-value nil)))

            (defun load-project-desktop ()
              "Load the project's desktop if available"
              (ignore-errors
                (let ((default-directory project-persist-current-project-settings-dir))
                  (desktop-read))))

            (defun kill-all-buffers ()
              "Kill all file-based buffers."
              (interactive)
              (mapc (lambda (buf)
                      (when (buffer-file-name buf)
                        (when (and (buffer-modified-p buf)
                                   (y-or-n-p (format "Buffer %s is modified - save it?" (buffer-name buf))))
                          (save-some-buffers nil buf))
                        (set-buffer-modified-p nil)
                        (kill-buffer buf)))
                    (buffer-list)))

            (add-hook 'project-persist-after-close-hook
                      (lambda ()
                        (kill-all-buffers)
                        (projectile-global-mode -1)))

            (add-hook 'project-persist-after-load-hook
                      (lambda ()
                        (setq default-directory (pp/settings-get 'root-dir))
                        (load-project-desktop)
                        (projectile-global-mode 1)))

            (add-hook 'project-persist-after-save-hook
                      (lambda ()
                        (message (format "Saving project desktop (%s)" project-persist-current-project-settings-dir))
                        (desktop-save project-persist-current-project-settings-dir)))
))

(use-package vc
  :config (progn
            (setq vc-follow-symlinks t)))

(use-package diff-hl
  :ensure t
  :init (global-diff-hl-mode))

(use-package magit
  :commands (magit-status)
  :bind (("C-x g" . magit-status)
         ("s-G" . magit-status))
  :init (add-hook 'magit-mode-hook #'magit-load-config-extensions))

;;;; Spelling
(setq ispell-program-name "aspell"
      ispell-dictionary "british")
;; (setq ispell-process-directory (expand-file-name "~/"))
;; If aspell is too slow
;; If it is still too slow, use ‘ultra’ instead of ‘fast’
;; (setq ispell-extra-args '(" --sug-mode=fast"))
(use-package ispell
  :bind (("<f8>" . ispell-word)))

;;;; Scripting

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

(use-package sh-script
  :defer t
  :config (setq sh-shell-file "/usr/bin/env zsh"))

(use-package ntcmd
  :mode (("\\`.cmd\\'" . ntcmd-mode)
         ("\\`.bat\\'" . ntcmd-mode)))

;;;; Shells & REPLs

(use-package eshell
  :bind ("C-c s" . eshell)
  :defer t
  :config (progn
            (setq eshell-directory-name "~/.emacs.d/eshell")
            (use-package em-smart
              :init (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)

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

(use-package multi-term
  :ensure t
  :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

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

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

(show-paren-mode t)

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

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

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

(use-package ap-functions
  :commands (ap/remove-extra-cr
             ap/byte-compile-get-dest)
  :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)))

(use-package ace-jump-mode
  :ensure t
  :bind (("C-c SPC" . ace-jump-mode))
  :config (progn
            (ace-jump-mode-enable-mark-sync)
            (setq ace-jump-word-mode-use-query-char nil
                  ace-jump-mode-scope 'window)))

(use-package autopair
  :ensure t
  :commands (autopair-mode
             autopair-on)
  :init (progn
          (add-hook 'prog-mode-hook #'autopair-on)
          (defun autopair-off ()
            (autopair-mode -1))
          (add-hook 'lisp-common-mode-hook #'autopair-off)
          (setq autopair-blink nil
                autopair-skip-whitespace nil)))

(use-package expand-region
  :ensure t
  :bind ("C-M-SPC" . er/expand-region))

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

(use-package multiple-cursors
  :ensure t
  :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)))

(use-package eldoc
  :defer t
  :config (progn
            (eldoc-add-command 'paredit-backward-delete 'paredit-close-round)))

(use-package paredit
  :ensure t
  :commands (paredit-mode)
  :init (progn
          (add-hook 'lisp-common-mode-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)))))

(use-package shuffle-lines
  :bind (("C-S-<up>" . move-line-up)
         ("C-S-<down>" . move-line-down)))

(use-package smart-forward
  :bind (("C-M-u" . smart-up)
         ("C-M-d" . smart-down)
         ("C-M-p" . smart-backward)
         ("C-M-n" . smart-forward)))

(use-package undo-tree
  :ensure t
  :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)

;;;; Lisps

(defun ap/lisp-setup ()
  (run-hooks 'lisp-common-mode-hook)
  (setq indent-tabs-mode nil)
  (local-set-key (kbd "RET") #'paredit-newline))

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

(add-hook 'emacs-lisp-mode-hook #'ap/lisp-setup)
(add-hook 'emacs-lisp-mode-hook #'turn-on-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)

(use-package bytecomp
  :defer t
  :config (setq byte-compile-dest-file-function #'ap/byte-compile-get-dest))

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

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

(use-package redshank
  :ensure t
  :defer t
  :init (progn
          (add-hook 'lisp-common-mode-hook #'turn-on-redshank-mode)))

(use-package geiser-base
  :defer t
  :config (use-package quack))

(use-package slime
  :ensure t
  :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" "^(use-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)

;;;; Programming

(use-package auto-async-byte-compile
  :ensure t
  :init (add-hook 'emacs-lisp-mode-hook #'auto-async-byte-compile-mode))

(use-package cc-mode
  :defer t
  :init (progn
          (add-hook 'c-mode-common-hook (lambda ()
                                          ;; (use-package mic-paren
                                          ;;   :init (paren-toggle-open-paren-context 1))
                                          (electric-indent-mode 1))))
  :config (progn
            (setq c-default-style '((java-mode . "java")
                                    (awk-mode . "awk")
                                    (other . "k&r"))
                  c-basic-offset 4)
            (c-set-offset 'case-label '+)
            (c-set-offset 'arglist-cont-nonempty #'ap/c-lineup-arglist)
            (defun ap/c-lineup-arglist (langelem)
              "Line up the current argument line under the first argument.

As a special case, if the indented line is inside a brace block
construct, the indentation is 0 only.  This is intended
as a \"DWIM\" measure in cases like macros that contains statement
blocks, e.g:

A_VERY_LONG_MACRO_NAME ({
    some (code, with + long, lines * in[it]);
});
<--> c-basic-offset

Works with: arglist-cont-nonempty, arglist-close."
              (save-excursion
                (let ((indent-pos (point)))

                  (if (c-block-in-arglist-dwim (c-langelem-2nd-pos c-syntactic-element))
                      0

                    ;; Normal case.  Indent to the token after the arglist open paren.
                    (goto-char (c-langelem-2nd-pos c-syntactic-element))
                    (if (and c-special-brace-lists
                             (c-looking-at-special-brace-list))
                        ;; Skip a special brace list opener like "({".
                        (progn (c-forward-token-2)
                               (forward-char))
                      (forward-char))

                    (let ((arglist-content-start (point)))
                      (c-forward-syntactic-ws)
                      (when (< (point) indent-pos)
                        (goto-char arglist-content-start)
                        (skip-chars-forward " \t"))
                      (vector (if (or (looking-at ",")
                                      (looking-at ")"))
                                  (- (current-column) 2)
                                (current-column))))))))))

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

;;;; Web Development

(use-package moz
  :load-path "moz-repl"
  :commands (moz-firefox-reload
             moz-reload-on-save-mode
             moz-minor-mode)
  :config (progn
            (define-minor-mode moz-reload-on-save-mode
              "Moz Reload On Save Minor Mode"
              nil " Reload" nil
              (if moz-reload-on-save-mode
                  ;; Edit hook buffer-locally.
                  (add-hook 'after-save-hook 'moz-firefox-reload nil t)
                (remove-hook 'after-save-hook 'moz-firefox-reload t)))

            (defun moz-firefox-reload ()
              "Reload Firefox"
              (interactive)
              (comint-send-string (inferior-moz-process) "BrowserReload();"))))

(use-package skewer-mode
  :ensure t
  :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)))

(use-package js2-mode
  :ensure t
  :mode ("\\.js\\'" . js2-mode)
  :config (progn
            (defun ap/javascript-setup ()
			  (if (featurep 'moz)
				  (moz-minor-mode))
              (autopair-mode -1)
              (auto-indent-mode -1))
            (add-hook 'js2-mode-hook #'ap/javascript-setup)
            (setq js2-basic-offset 4
                  js2-global-externs '("$"))))

(use-package mustache-mode
  :ensure t
  :mode (("\\.mustache" . mustache-mode)
         ("\\.mt\\'" . mustache-mode)
         ("\\.template\\'" . mustache-mode)))

(use-package jinja2-mode
  :ensure t
  :mode (("\\.j2\\'" . jinja2-mode)))

(use-package php-mode
  :mode ("\\.php\\'" . php-mode)
  :config (progn
            (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)
            (if (eq env/location 'home)
                (setq php-manual-url "http://docs.home/manual/en/"))
            (add-hook 'php-mode-hook (lambda ()
                                       (set (make-local-variable 'eldoc-documentation-function) #'my-php-eldoc-function)))
            (use-package eldoc-php
              :config (turn-on-eldoc-mode))))

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

(use-package emmet-mode
  :ensure t
  :config (progn
            (if (functionp 'web-mode)
                (add-hook 'web-mode-hook #'emmet-mode))))

(use-package web-mode
  :defer t
  :mode (("/views/.*\\.php\\'" . web-mode)
         ("/templates/.*\\.php\\'" . web-mode))
  :config (setq web-mode-code-indent-offset 4
                web-mode-css-indent-offset 4
                web-mode-markup-indent-offset 4))

;;;; Windows & Frames

(setq frame-title-format
      '(""
        (:eval (capitalize invocation-name)) ": "
        (:eval (if (buffer-file-name)
                   (abbreviate-file-name (buffer-file-name))
                 "%b"))))

(setq scroll-conservatively 100 ; Keep the cursor position when scrolling
      scroll-preserve-screen-position nil)

(when menu-bar-mode
  (menu-bar-mode -1))
(when scroll-bar-mode
  (scroll-bar-mode -1)
  (tooltip-mode -1)
  (tool-bar-mode -1))

(winner-mode 1)

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