;; -*- lexical-binding: t; -*-

(load! "utils")
(require 'f)
(require 'predd)

(undefine-key! :keymaps 'doom-leader-map "/")

(defmacro find-file-in! (path &optional project-p)
  "Returns an interactive function for searching files."
  `(lambda () (interactive)
     (let ((default-directory ,path))
       (call-interactively
        ',(command-remapping
           (if project-p
               #'projectile-find-file
             #'find-file))))))

(defun dired-mode-p () (eq 'dired-mode major-mode))

(defun grfn/dired-minus ()
  (interactive)
  (if (dired-mode-p)
      (dired-up-directory)
    (when buffer-file-name
      (-> (buffer-file-name)
          (f-dirname)
          (dired)))))

(defmacro define-move-and-insert
    (name &rest body)
  `(defun ,name (count &optional vcount skip-empty-lines)
     ;; Following interactive form taken from the source for `evil-insert'
     (interactive
      (list (prefix-numeric-value current-prefix-arg)
            (and (evil-visual-state-p)
                 (memq (evil-visual-type) '(line block))
                 (save-excursion
                   (let ((m (mark)))
                     ;; go to upper-left corner temporarily so
                     ;; `count-lines' yields accurate results
                     (evil-visual-rotate 'upper-left)
                     (prog1 (count-lines evil-visual-beginning evil-visual-end)
                       (set-mark m)))))
            (evil-visual-state-p)))
     (atomic-change-group
       ,@body
       (evil-insert count vcount skip-empty-lines))))

(define-move-and-insert grfn/insert-at-sexp-end
  (when (not (equal (get-char) "("))
    (backward-up-list))
  (forward-sexp)
  (backward-char))

(define-move-and-insert grfn/insert-at-sexp-start
  (backward-up-list)
  (forward-char))

(define-move-and-insert grfn/insert-at-form-start
  (backward-sexp)
  (backward-char)
  (insert " "))

(define-move-and-insert grfn/insert-at-form-end
  (forward-sexp)
  (insert " "))

(load! "splitjoin")

(defun +hlissner/install-snippets ()
  "Install my snippets from https://github.com/hlissner/emacs-snippets into
private/hlissner/snippets."
  (interactive)
  (doom-fetch :github "hlissner/emacs-snippets"
              (expand-file-name "snippets" (doom-module-path :private 'hlissner))))

(defun +hlissner/yank-buffer-filename ()
  "Copy the current buffer's path to the kill ring."
  (interactive)
  (if-let* ((filename (or buffer-file-name (bound-and-true-p list-buffers-directory))))
      (message (kill-new (abbreviate-file-name filename)))
    (error "Couldn't find filename in current buffer")))

(defmacro +def-finder! (name dir)
  "Define a pair of find-file and browse functions."
  `(progn
     (defun ,(intern (format "+find-in-%s" name)) ()
       (interactive)
       (let ((default-directory ,dir)
             projectile-project-name
             projectile-require-project-root
             projectile-cached-buffer-file-name
             projectile-cached-project-root)
         (call-interactively #'projectile-find-file)))
     (defun ,(intern (format "+hlissner/browse-%s" name)) ()
       (interactive)
       (let ((default-directory ,dir))
         (call-interactively (command-remapping #'find-file))))))

(+def-finder! templates +file-templates-dir)
(+def-finder! snippets +grfn-snippets-dir)
(+def-finder! dotfiles (expand-file-name ".dotfiles" "~"))
(+def-finder! doomd (expand-file-name ".doom.d" "~"))
(+def-finder! notes +org-dir)
(+def-finder! home-config (expand-file-name "code/system/home" "~"))
(+def-finder! system-config (expand-file-name "code/system/system" "~"))

(defun +grfn/paxedit-kill (&optional n)
  (interactive "p")
  (or (paxedit-comment-kill)
      (when (paxedit-symbol-cursor-within?)
        (paxedit-symbol-kill))
      (paxedit-implicit-sexp-kill n)
      (paxedit-sexp-kill n)
      (message paxedit-message-kill)))

;;;

(evil-set-command-property 'flycheck-next-error :repeat nil)
(evil-set-command-property 'flycheck-prev-error :repeat nil)
(evil-set-command-property 'flycheck-previous-error :repeat nil)
(evil-set-command-property 'smerge-next :repeat nil)
(evil-set-command-property 'smerge-prev :repeat nil)

;;;

(map!
 [remap evil-jump-to-tag] #'projectile-find-tag
 [remap find-tag]         #'projectile-find-tag
 ;; ensure there are no conflicts
 :nmvo doom-leader-key nil
 :nmvo doom-localleader-key nil)

(undefine-key! :keymaps 'doom-leader-map "/")

(map!
 ;; --- Global keybindings ---------------------------
 ;; Make M-x available everywhere
 :gnvime "M-x" #'execute-extended-command
 :gnvime "A-x" #'execute-extended-command
 ;; Emacs debug utilities
 :gnvime "M-;" #'eval-expression
 :gnvime "M-:" #'doom/open-scratch-buffer
 ;; Text-scaling
 "M-+"       (λ! (text-scale-set 0))
 "M-="       #'text-scale-increase
 "M--"       #'text-scale-decrease
 ;; Simple window navigation/manipulation
 "C-`"       #'doom/popup-toggle
 "C-~"       #'doom/popup-raise
 "M-t"       #'+workspace/new
 "M-T"       #'+workspace/display
 "M-w"       #'delete-window
 "M-W"       #'+workspace/close-workspace-or-frame
 "M-n"       #'evil-buffer-new
 "M-N"       #'make-frame
 "M-1"       (λ! (+workspace/switch-to 0))
 "M-2"       (λ! (+workspace/switch-to 1))
 "M-3"       (λ! (+workspace/switch-to 2))
 "M-4"       (λ! (+workspace/switch-to 3))
 "M-5"       (λ! (+workspace/switch-to 4))
 "M-6"       (λ! (+workspace/switch-to 5))
 "M-7"       (λ! (+workspace/switch-to 6))
 "M-8"       (λ! (+workspace/switch-to 7))
 "M-9"       (λ! (+workspace/switch-to 8))
 "M-0"       #'+workspace/switch-to-last
 ;; Other sensible, textmate-esque global bindings
 :ne "M-r"   #'+eval/buffer
 :ne "M-R"   #'+eval/region-and-replace
 :ne "M-b"   #'+eval/build
 :ne "M-a"   #'mark-whole-buffer
 :ne "M-c"   #'evil-yank
 :ne "M-q"   (if (daemonp) #'delete-frame #'save-buffers-kill-emacs)
 :ne "M-f"   #'swiper
 :ne "C-M-f" #'doom/toggle-fullscreen
 :n  "M-s"   #'save-buffer
 :m  "A-j"   #'+hlissner:multi-next-line
 :m  "A-k"   #'+hlissner:multi-previous-line
 :nv "C-SPC" #'+evil:fold-toggle
 :gnvimer "M-v" #'clipboard-yank
 ;; Easier window navigation
 :en "C-h"   #'evil-window-left
 :en "C-j"   #'evil-window-down
 :en "C-k"   #'evil-window-up
 :en "C-l"   #'evil-window-right
 :n "U" #'undo-tree-visualize

 "C-x p"     #'doom/other-popup

 :n "K" #'+lookup/documentation
 :n "g d" #'+lookup/definition


 ;; --- <leader> -------------------------------------
 (:leader
   :desc "Ex command"              :nv ";"  #'evil-ex
   :desc "M-x"                     :nv ":"  #'execute-extended-command
   :desc "Pop up scratch buffer"   :nv "x"  #'doom/open-scratch-buffer
   :desc "Org Capture"             :nv "X"  #'org-capture
   :desc "Org Capture"             :nv "a"  #'org-capture

   ;; Most commonly used
   :desc "Find file in project"    :n "SPC" #'projectile-find-file
   :desc "Switch workspace buffer" :n ","   #'persp-switch-to-buffer
   :desc "Switch buffer"           :n "<"   #'switch-to-buffer
   :desc "Browse files"            :n "."   #'find-file
   :desc "Toggle last popup"       :n "~"   #'doom/popup-toggle
   :desc "Eval expression"         :n "`"   #'eval-expression
   :desc "Blink cursor line"       :n "DEL" #'+doom/blink-cursor
   :desc "Jump to bookmark"        :n "RET" #'bookmark-jump

   ;; C-u is used by evil
   :desc "Universal argument"      :n "u"  #'universal-argument
   :desc "window"                  :n "w"  evil-window-map

   (:desc "previous..." :prefix "["
     :desc "Text size"             :nv "[" #'text-scale-decrease
     :desc "Buffer"                :nv "b" #'doom/previous-buffer
     :desc "Diff Hunk"             :nv "d" #'git-gutter:previous-hunk
     :desc "Todo"                  :nv "t" #'hl-todo-previous
     :desc "Error"                 :nv "e" #'flycheck-previous-error
     :desc "Workspace"             :nv "w" #'+workspace/switch-left
     :desc "Smart jump"            :nv "h" #'smart-backward
     :desc "Spelling error"        :nv "s" #'evil-prev-flyspell-error
     :desc "Spelling correction"   :n  "S" #'flyspell-correct-previous-word-generic
     :desc "Git conflict"          :n  "n" #'smerge-prev)

   (:desc "next..." :prefix "]"
     :desc "Text size"             :nv "]" #'text-scale-increase
     :desc "Buffer"                :nv "b" #'doom/next-buffer
     :desc "Diff Hunk"             :nv "d" #'git-gutter:next-hunk
     :desc "Todo"                  :nv "t" #'hl-todo-next
     :desc "Error"                 :nv "e" #'flycheck-next-error
     :desc "Workspace"             :nv "w" #'+workspace/switch-right
     :desc "Smart jump"            :nv "l" #'smart-forward
     :desc "Spelling error"        :nv "s" #'evil-next-flyspell-error
     :desc "Spelling correction"   :n  "S" #'flyspell-correct-word-generic
     :desc "Git conflict"          :n  "n" #'smerge-next)

   (:desc "search" :prefix "/"
     :desc "Swiper"                :nv "/" #'swiper
     :desc "Imenu"                 :nv "i" #'imenu
     :desc "Imenu across buffers"  :nv "I" #'imenu-anywhere
     :desc "Online providers"      :nv "o" #'+lookup/online-select)

   (:desc "workspace" :prefix "TAB"
     :desc "Display tab bar"          :n "TAB" #'+workspace/display
     :desc "New workspace"            :n "n"   #'+workspace/new
     :desc "Load workspace from file" :n "l"   #'+workspace/load
     :desc "Load last session"        :n "L"   (λ! (+workspace/load-session))
     :desc "Save workspace to file"   :n "s"   #'+workspace/save
     :desc "Autosave current session" :n "S"   #'+workspace/save-session
     :desc "Switch workspace"         :n "."   #'+workspace/switch-to
     :desc "Kill all buffers"         :n "x"   #'doom/kill-all-buffers
     :desc "Delete session"           :n "X"   #'+workspace/kill-session
     :desc "Delete this workspace"    :n "d"   #'+workspace/delete
     :desc "Load session"             :n "L"   #'+workspace/load-session
     :desc "Next workspace"           :n "]"   #'+workspace/switch-right
     :desc "Previous workspace"       :n "["   #'+workspace/switch-left
     :desc "Switch to 1st workspace"  :n "1"   (λ! (+workspace/switch-to 0))
     :desc "Switch to 2nd workspace"  :n "2"   (λ! (+workspace/switch-to 1))
     :desc "Switch to 3rd workspace"  :n "3"   (λ! (+workspace/switch-to 2))
     :desc "Switch to 4th workspace"  :n "4"   (λ! (+workspace/switch-to 3))
     :desc "Switch to 5th workspace"  :n "5"   (λ! (+workspace/switch-to 4))
     :desc "Switch to 6th workspace"  :n "6"   (λ! (+workspace/switch-to 5))
     :desc "Switch to 7th workspace"  :n "7"   (λ! (+workspace/switch-to 6))
     :desc "Switch to 8th workspace"  :n "8"   (λ! (+workspace/switch-to 7))
     :desc "Switch to 9th workspace"  :n "9"   (λ! (+workspace/switch-to 8))
     :desc "Switch to last workspace" :n "0"   #'+workspace/switch-to-last)

   (:desc "buffer" :prefix "b"
     :desc "New empty buffer"        :n "n" #'evil-buffer-new
     :desc "Switch workspace buffer" :n "b" #'switch-to-buffer
     :desc "Switch buffer"           :n "B" #'switch-to-buffer
     :desc "Kill buffer"             :n "k" #'doom/kill-this-buffer
     :desc "Kill other buffers"      :n "o" #'doom/kill-other-buffers
     :desc "Save buffer"             :n "s" #'save-buffer
     :desc "Pop scratch buffer"      :n "x" #'doom/open-scratch-buffer
     :desc "Bury buffer"             :n "z" #'bury-buffer
     :desc "Next buffer"             :n "]" #'doom/next-buffer
     :desc "Previous buffer"         :n "[" #'doom/previous-buffer
     :desc "Sudo edit this file"     :n "S" #'doom/sudo-this-file)

   (:desc "code" :prefix "c"
     :desc "List errors"               :n  "x" #'flycheck-list-errors
     :desc "Evaluate buffer/region"    :n  "e" #'+eval/buffer
                                       :v  "e" #'+eval/region
     :desc "Evaluate & replace region" :nv "E" #'+eval:replace-region
     :desc "Build tasks"               :nv "b" #'+eval/build
     :desc "Jump to definition"        :n  "d" #'+lookup/definition
     :desc "Jump to references"        :n  "D" #'+lookup/references
     :desc "Open REPL"                 :n  "r" #'+eval/open-repl
                                       :v  "r" #'+eval:repl)

   (:desc "file" :prefix "f"
     :desc "Find file"                  :n "." #'find-file
     :desc "Sudo find file"             :n ">" #'doom/sudo-find-file
     :desc "Find file in project"       :n "/" #'projectile-find-file
     :desc "Find file from here"        :n "?" #'counsel-file-jump
     :desc "Find other file"            :n "a" #'projectile-find-other-file
     :desc "Open project editorconfig"  :n "c" #'editorconfig-find-current-editorconfig
     :desc "Find file in dotfiles"      :n "d" #'+find-in-dotfiles
     :desc "Find file in system config" :n "s" #'+find-in-system-config
     :desc "Find file in home config"   :n "h" #'+find-in-home-config
     :desc "Browse dotfiles"            :n "D" #'+hlissner/browse-dotfiles
     :desc "Find file in emacs.d"       :n "e" #'+find-in-doomd
     :desc "Browse emacs.d"             :n "E" #'+hlissner/browse-doomd
     :desc "Recent files"               :n "r" #'recentf-open-files
     :desc "Recent project files"       :n "R" #'projectile-recentf
     :desc "Yank filename"              :n "y" #'+hlissner/yank-buffer-filename)

   (:desc "git" :prefix "g"
     :desc "Git status"            :n  "S" #'magit-status
     :desc "Git blame"             :n  "b" #'magit-blame
     :desc "Git time machine"      :n  "t" #'git-timemachine-toggle
     :desc "Git stage hunk"        :n  "s" #'git-gutter:stage-hunk
     :desc "Git revert hunk"       :n  "r" #'git-gutter:revert-hunk
     :desc "Git revert buffer"     :n  "R" #'vc-revert
     ;; :desc "List gists"            :n  "g" #'+gist:list
     :desc "Git grep"              :n  "g" #'counsel-projectile-rg
     :desc "Checkout Branch"       :n  "c" #'counsel-git-checkout
     :desc "Next hunk"             :nv "]" #'git-gutter:next-hunk
     :desc "Previous hunk"         :nv "[" #'git-gutter:previous-hunk

     (:desc "smerge" :prefix "m"
       :desc "Keep Current" :n "SPC" #'smerge-keep-current
       :desc "Keep All"     :n "a" #'smerge-keep-all
       :desc "Keep Upper"   :n "u" #'smerge-keep-upper
       :desc "Keep Lower"   :n "l" #'smerge-keep-lower))

   (:desc "help" :prefix "h"
     :n "h" help-map
     :desc "Apropos"               :n  "a" #'apropos
     :desc "Reload theme"          :n  "R" #'doom//reload-theme
     :desc "Find library"          :n  "l" #'find-library
     :desc "Toggle Emacs log"      :n  "m" #'doom/popup-toggle-messages
     :desc "Command log"           :n  "L" #'global-command-log-mode
     :desc "Describe function"     :n  "f" #'describe-function
     :desc "Describe key"          :n  "k" #'describe-key
     :desc "Describe char"         :n  "c" #'describe-char
     :desc "Describe mode"         :n  "M" #'describe-mode
     :desc "Describe variable"     :n  "v" #'describe-variable
     :desc "Describe face"         :n  "F" #'describe-face
     :desc "Describe DOOM setting" :n  "s" #'doom/describe-setting
     :desc "Describe DOOM module"  :n  "d" #'doom/describe-module
     :desc "Find definition"       :n  "." #'+lookup/definition
     :desc "Find references"       :n  "/" #'+lookup/references
     :desc "Find documentation"    :n  "h" #'+lookup/documentation
     :desc "What face"             :n  "'" #'doom/what-face
     :desc "What minor modes"      :n  ";" #'doom/what-minor-mode
     :desc "Info"                  :n  "i" #'info
     :desc "Toggle profiler"       :n  "p" #'doom/toggle-profiler)

   (:desc "insert" :prefix "i"
     :desc "From kill-ring"        :nv "y" #'counsel-yank-pop
     :desc "From snippet"          :nv "s" #'yas-insert-snippet)

   (:desc "notes" :prefix "n"
     :desc "Agenda"                 :n  "a" #'org-agenda
     :desc "Find file in notes"     :n  "n" #'+find-in-notes
     :desc "Store link"             :n  "l" #'org-store-link
     :desc "Browse notes"           :n  "N" #'+hlissner/browse-notes
     :desc "Org capture"            :n  "x" #'+org-capture/open
     :desc "Create clubhouse story" :n  "c" #'org-tracker-create-issue
     :desc "Archive subtree"        :n  "k" #'org-archive-subtree
     :desc "Goto clocked-in note"   :n  "g" #'org-clock-goto
     :desc "Clock Out"              :n  "o" #'org-clock-out)


   (:desc "open" :prefix "o"
     :desc "Default browser"       :n  "b" #'browse-url-of-file
     :desc "Debugger"              :n  "d" #'+debug/open
     :desc "Terminal in project"   :n  "T" #'+term/open-popup-in-project

     :desc "Slack IM"              :n  "i" #'slack-im-select
     :desc "Slack Channel"         :n  "c" #'slack-channel-select
     :desc "Slack Group"           :n  "g" #'slack-group-select
     :desc "Slack Unreads"         :n  "u" #'slack-select-unread-rooms
     :desc "Slack Threads"         :n  "r" #'slack-all-threads

     :desc "Email"                 :n "m" #'notmuch-jump-search

     (:desc "ERC" :prefix "e"
       :desc "Channel" :n "c" #'erc-switch-to-buffer)

     ;; applications
     :desc "APP: elfeed"           :n "E" #'=rss
     :desc "APP: twitter"          :n "T" #'=twitter

     (:desc "spotify" :prefix "s"
       :desc "Search track"  :n "t" #'counsel-spotify-search-track
       :desc "Search album"  :n "a" #'counsel-spotify-search-album
       :desc "Search artist" :n "A" #'counsel-spotify-search-artist)

     ;; macos
     (:when IS-MAC
       :desc "Reveal in Finder"          :n "o" #'+macos/reveal-in-finder
       :desc "Reveal project in Finder"  :n "O" #'+macos/reveal-project-in-finder
       :desc "Send to Transmit"          :n "u" #'+macos/send-to-transmit
       :desc "Send project to Transmit"  :n "U" #'+macos/send-project-to-transmit
       :desc "Send to Launchbar"         :n "l" #'+macos/send-to-launchbar
       :desc "Send project to Launchbar" :n "L" #'+macos/send-project-to-launchbar))

   (:desc "Email" :prefix "M"
     :desc "Compose" :n "m" #'+notmuch/compose)

   (:desc "project" :prefix "p"
     :desc "Browse project"          :n  "." (find-file-in! (doom-project-root))
     :desc "Find file in project"    :n  "/" #'projectile-find-file
     :desc "Run cmd in project root" :nv "!" #'projectile-run-shell-command-in-root
     :desc "Switch project"          :n  "p" #'projectile-switch-project
     :desc "Recent project files"    :n  "r" #'projectile-recentf
     :desc "List project tasks"      :n  "t" #'+ivy/tasks
     :desc "Pop term in project"     :n  "o" #'+term/open-popup-in-project
     :desc "Invalidate cache"        :n  "x" #'projectile-invalidate-cache)

   (:desc "quit" :prefix "q"
     :desc "Quit"                   :n "q" #'evil-save-and-quit
     :desc "Quit (forget session)"  :n "Q" #'+workspace/kill-session-and-quit)

   (:desc "remote" :prefix "r"
     :desc "Upload local"           :n "u" #'+upload/local
     :desc "Upload local (force)"   :n "U" (λ! (+upload/local t))
     :desc "Download remote"        :n "d" #'+upload/remote-download
     :desc "Diff local & remote"    :n "D" #'+upload/diff
     :desc "Browse remote files"    :n "." #'+upload/browse
     :desc "Detect remote changes"  :n ">" #'+upload/check-remote)

   (:desc "snippets" :prefix "s"
     :desc "New snippet"            :n  "n" #'yas-new-snippet
     :desc "Insert snippet"         :nv "i" #'yas-insert-snippet
     :desc "Find snippet for mode"  :n  "s" #'yas-visit-snippet-file
     :desc "Find snippet"           :n  "S" #'+find-in-snippets)

   (:desc "toggle" :prefix "t"
     :desc "Flyspell"               :n "s" #'flyspell-mode
     :desc "Flycheck"               :n "f" #'flycheck-mode
     :desc "Line numbers"           :n "l" #'doom/toggle-line-numbers
     :desc "Fullscreen"             :n "f" #'doom/toggle-fullscreen
     :desc "Indent guides"          :n "i" #'highlight-indentation-mode
     :desc "Indent guides (column)" :n "I" #'highlight-indentation-current-column-mode
     :desc "Impatient mode"         :n "h" #'+impatient-mode/toggle
     :desc "Big mode"               :n "b" #'doom-big-font-mode
     :desc "Evil goggles"           :n "g" #'+evil-goggles/toggle))


 ;; --- vim-vinegar
 :n "-" #'grfn/dired-minus
 (:after dired-mode
         (:map dired-mode-map
        "-" #'grfn/dired-minus))

 (:map smartparens-mode-map
   :n "g o" #'sp-raise-sexp)

 ;; --- vim-sexp-mappings-for-regular-people
 (:after paxedit
   (:map paxedit-mode-map
     :i ";"                          #'paxedit-insert-semicolon
     :i "("                          #'paxedit-open-round
     :i "["                          #'paxedit-open-bracket
     :i "{"                          #'paxedit-open-curly
     :n [remap evil-yank-line]       #'paxedit-copy
     :n [remap evil-delete-line]     #'+grfn/paxedit-kill
     :n "g o"                        #'paxedit-sexp-raise
     :n [remap evil-join-whitespace] #'paxedit-compress
     :n "g S"                        #'paxedit-format-1
     :n "g k"                        #'paxedit-backward-up
     :n "g j"                        #'paxedit-backward-end))

 ;; --- vim-splitjoin
 :n [remap evil-join-whitespace] #'+splitjoin/join
 :n "gS"                         #'+splitjoin/split

 ;; --- Personal vim-esque bindings ------------------
 :n  "zx" #'doom/kill-this-buffer
 :n  "ZX" #'bury-buffer
 :n  "]b" #'doom/next-buffer
 :n  "[b" #'doom/previous-buffer
 :n  "]w" #'+workspace/switch-right
 :n  "[w" #'+workspace/switch-left
 :m  "gt" #'+workspace/switch-right
 :m  "gT" #'+workspace/switch-left
 :m  "gd" #'+lookup/definition
 :m  "gD" #'+lookup/references
 :m  "K" #'+lookup/documentation
 :n  "gp" #'+evil/reselect-paste
 :n  "gr" #'+eval:region
 :n  "gR" #'+eval/buffer
 :v  "gR" #'+eval:replace-region
 :v  "@"  #'+evil:macro-on-all-lines
 :n  "g@" #'+evil:macro-on-all-lines
 ;; repeat in visual mode (FIXME buggy)
 :v  "."  #'evil-repeat
 ;; don't leave visual mode after shifting
 ;; :v  "<"  #'+evil/visual-dedent  ; vnoremap < <gv
 ;; :v  ">"  #'+evil/visual-indent  ; vnoremap > >gv
 ;; paste from recent yank register (which isn't overwritten)
 :v  "C-p" "\"0p"

 (:map evil-window-map ; prefix "C-w"
   ;; Navigation
   "C-h"     #'evil-window-left
   "C-j"     #'evil-window-down
   "C-k"     #'evil-window-up
   "C-l"     #'evil-window-right
   "C-w"     #'ace-window
   ;; Swapping windows
   "H"       #'+evil/window-move-left
   "J"       #'+evil/window-move-down
   "K"       #'+evil/window-move-up
   "L"       #'+evil/window-move-right
   "C-S-w"   #'ace-swap-window
   ;; Window undo/redo
   "u"       #'winner-undo
   "C-u"     #'winner-undo
   "C-r"     #'winner-redo
   "o"       #'doom/window-enlargen
   ;; Delete window
   "c"       #'+workspace/close-window-or-workspace
   "C-C"     #'ace-delete-window
   ;; Popups
   "p"       #'doom/popup-toggle
   "m"       #'doom/popup-toggle-messages
   "P"       #'doom/popup-close-all)


 ;; --- Plugin bindings ------------------------------
 ;; auto-yasnippet
 :i  [C-tab] #'aya-expand
 :nv [C-tab] #'aya-create

 ;; company-mode (vim-like omnicompletion)
 :i "C-SPC"  #'+company/complete
 (:prefix "C-x"
   :i "C-l"   #'+company/whole-lines
   :i "C-k"   #'+company/dict-or-keywords
   :i "C-f"   #'company-files
   :i "C-]"   #'company-etags
   :i "s"     #'company-ispell
   :i "C-s"   #'company-yasnippet
   :i "C-o"   #'company-capf
   :i "C-n"   #'company-dabbrev-code
   :i "C-p"   #'+company/dabbrev-code-previous)
 (:after company
   (:map company-active-map
     ;; Don't interfere with `evil-delete-backward-word' in insert mode
     "C-w"        nil
     "C-o"        #'company-search-kill-others
     "C-n"        #'company-select-next
     "C-p"        #'company-select-previous
     "C-h"        #'company-quickhelp-manual-begin
     "C-S-h"      #'company-show-doc-buffer
     "C-S-s"      #'company-search-candidates
     "C-s"        #'company-filter-candidates
     "C-SPC"      #'company-complete-common
     "C-h"        #'company-quickhelp-manual-begin
     [tab]        #'company-complete-common-or-cycle
     [backtab]    #'company-select-previous
     [escape]     (λ! (company-abort) (evil-normal-state 1)))
   ;; Automatically applies to `company-filter-map'
   (:map company-search-map
     "C-n"        #'company-search-repeat-forward
     "C-p"        #'company-search-repeat-backward
     "C-s"        (λ! (company-search-abort) (company-filter-candidates))
     [escape]     #'company-search-abort))

 ;; counsel
;  (:after counsel
;    (:map counsel-ag-map
;      [backtab]  #'+ivy/wgrep-occur      ; search/replace on results
;      "C-SPC"    #'ivy-call-and-recenter ; preview))

 ;; evil-commentary
 ;; :n  "gc"  #'evil-commentary

 ;; evil-exchange
 :n  "gx"  #'evil-exchange

 ;; evil-magit
 (:after evil-magit
   :map (magit-status-mode-map magit-revision-mode-map)
   :n "C-j" nil
   :n "C-k" nil)

 ;; Smerge
 :n "]n" #'smerge-next
 :n "[n" #'smerge-prev

 ;; evil-mc
 (:prefix "gz"
   :nv "m" #'evil-mc-make-all-cursors
   :nv "u" #'evil-mc-undo-all-cursors
   :nv "z" #'+evil/mc-make-cursor-here
   :nv "t" #'+evil/mc-toggle-cursors
   :nv "n" #'evil-mc-make-and-goto-next-cursor
   :nv "p" #'evil-mc-make-and-goto-prev-cursor
   :nv "N" #'evil-mc-make-and-goto-last-cursor
   :nv "P" #'evil-mc-make-and-goto-first-cursor
   :nv "d" #'evil-mc-make-and-goto-next-match
   :nv "D" #'evil-mc-make-and-goto-prev-match)
 (:after evil-mc
   :map evil-mc-key-map
   :nv "C-n" #'evil-mc-make-and-goto-next-cursor
   :nv "C-N" #'evil-mc-make-and-goto-last-cursor
   :nv "C-p" #'evil-mc-make-and-goto-prev-cursor
   :nv "C-P" #'evil-mc-make-and-goto-first-cursor)

 ;; evil-multiedit
 :v  "R"     #'evil-multiedit-match-all
 :n  "M-d"   #'evil-multiedit-match-symbol-and-next
 :n  "M-D"   #'evil-multiedit-match-symbol-and-prev
 :v  "M-d"   #'evil-multiedit-match-and-next
 :v  "M-D"   #'evil-multiedit-match-and-prev
 :nv "C-M-d" #'evil-multiedit-restore
 (:after evil-multiedit
   (:map evil-multiedit-state-map
     "M-d" #'evil-multiedit-match-and-next
     "M-D" #'evil-multiedit-match-and-prev
     "RET" #'evil-multiedit-toggle-or-restrict-region)
   (:map (evil-multiedit-state-map evil-multiedit-insert-state-map)
     "C-n" #'evil-multiedit-next
     "C-p" #'evil-multiedit-prev))

 ;; evil-snipe
 (:after evil-snipe
   ;; Binding to switch to evil-easymotion/avy after a snipe
   :map evil-snipe-parent-transient-map
   "C-;" (λ! (require 'evil-easymotion)
             (call-interactively
              (evilem-create #'evil-snipe-repeat
                             :bind ((evil-snipe-scope 'whole-buffer)
                                    (evil-snipe-enable-highlight)
                                    (evil-snipe-enable-incremental-highlight))))))

 ;; evil-surround
 :v  "S"  #'evil-surround-region
 :o  "s"  #'evil-surround-edit
 :o  "S"  #'evil-Surround-edit

 ;; expand-region
 :v  "v"  #'er/expand-region
 :v  "V"  #'er/contract-region

 ;; flycheck
 :m  "]e" #'flycheck-next-error
 :m  "[e" #'flycheck-previous-error
 (:after flycheck
   :map flycheck-error-list-mode-map
   :n "C-n" #'flycheck-error-list-next-error
   :n "C-p" #'flycheck-error-list-previous-error
   :n "j"   #'flycheck-error-list-next-error
   :n "k"   #'flycheck-error-list-previous-error
   :n "RET" #'flycheck-error-list-goto-error)

 ;; flyspell
 :m  "]S" #'flyspell-correct-word-generic
 :m  "[S" #'flyspell-correct-previous-word-generic

 ;; git-gutter
 :m  "]d" #'git-gutter:next-hunk
 :m  "[d" #'git-gutter:previous-hunk

 ;; git-timemachine
 (:after git-timemachine
   (:map git-timemachine-mode-map
     :n "C-p" #'git-timemachine-show-previous-revision
     :n "C-n" #'git-timemachine-show-next-revision
     :n "[["  #'git-timemachine-show-previous-revision
     :n "]]"  #'git-timemachine-show-next-revision
     :n "q"   #'git-timemachine-quit
     :n "gb"  #'git-timemachine-blame))

 ;; gist
 (:after gist
   :map gist-list-menu-mode-map
   :n "RET" #'+gist/open-current
   :n "b"   #'gist-browse-current-url
   :n "c"   #'gist-add-buffer
   :n "d"   #'gist-kill-current
   :n "f"   #'gist-fork
   :n "q"   #'quit-window
   :n "r"   #'gist-list-reload
   :n "s"   #'gist-star
   :n "S"   #'gist-unstar
   :n "y"   #'gist-print-current-url)

 ;; helm
 (:after helm
   (:map helm-map
     "ESC"        nil
     "C-S-n"      #'helm-next-source
     "C-S-p"      #'helm-previous-source
     "C-u"        #'helm-delete-minibuffer-contents
     "C-w"        #'backward-kill-word
     "C-r"        #'evil-paste-from-register ; Evil registers in helm! Glorious!
     "C-b"        #'backward-word
     [left]       #'backward-char
     [right]      #'forward-char
     [escape]     #'helm-keyboard-quit
     [tab]        #'helm-execute-persistent-action)

   (:after helm-files
     (:map helm-generic-files-map
       :e "ESC"     #'helm-keyboard-quit)
     (:map helm-find-files-map
       "C-w" #'helm-find-files-up-one-level
       "TAB" #'helm-execute-persistent-action))

   (:after helm-ag
     (:map helm-ag-map
       "<backtab>"  #'helm-ag-edit)))

 ;; hl-todo
 :m  "]t" #'hl-todo-next
 :m  "[t" #'hl-todo-previous

 ;; ivy
 (:after ivy
   :map ivy-minibuffer-map
   [escape] #'keyboard-escape-quit
   "C-SPC" #'ivy-call-and-recenter
   "TAB" #'ivy-partial
   "M-v" #'yank
   "M-z" #'undo
   "C-r" #'evil-paste-from-register
   "C-k" #'ivy-previous-line
   "C-j" #'ivy-next-line
   "C-l" #'ivy-alt-done
   "C-w" #'ivy-backward-kill-word
   "C-u" #'ivy-kill-line
   "C-b" #'backward-word
   "C-f" #'forward-word)

 ;; neotree
 (:after neotree
   :map neotree-mode-map
   :n "g"         nil
   :n [tab]       #'neotree-quick-look
   :n "RET"       #'neotree-enter
   :n [backspace] #'evil-window-prev
   :n "c"         #'neotree-create-node
   :n "r"         #'neotree-rename-node
   :n "d"         #'neotree-delete-node
   :n "j"         #'neotree-next-line
   :n "k"         #'neotree-previous-line
   :n "n"         #'neotree-next-line
   :n "p"         #'neotree-previous-line
   :n "h"         #'+neotree/collapse-or-up
   :n "l"         #'+neotree/expand-or-open
   :n "J"         #'neotree-select-next-sibling-node
   :n "K"         #'neotree-select-previous-sibling-node
   :n "H"         #'neotree-select-up-node
   :n "L"         #'neotree-select-down-node
   :n "G"         #'evil-goto-line
   :n "gg"        #'evil-goto-first-line
   :n "v"         #'neotree-enter-vertical-split
   :n "s"         #'neotree-enter-horizontal-split
   :n "q"         #'neotree-hide
   :n "R"         #'neotree-refresh)

 ;; realgud
 (:after realgud
   :map realgud:shortkey-mode-map
   :n "j" #'evil-next-line
   :n "k" #'evil-previous-line
   :n "h" #'evil-backward-char
   :n "l" #'evil-forward-char
   :m "n" #'realgud:cmd-next
   :m "b" #'realgud:cmd-break
   :m "B" #'realgud:cmd-clear
   :n "c" #'realgud:cmd-continue)

 ;; rotate-text
 :n  "gs"  #'rotate-text

 ;; smart-forward
 :m  "g]" #'smart-forward
 :m  "g[" #'smart-backward

 ;; undo-tree -- undo/redo for visual regions
 :v "C-u" #'undo-tree-undo
 :v "C-r" #'undo-tree-redo

 ;; yasnippet
 (:after yasnippet
   (:map yas-keymap
     "C-e"           #'+snippets/goto-end-of-field
     "C-a"           #'+snippets/goto-start-of-field
     "<M-right>"     #'+snippets/goto-end-of-field
     "<M-left>"      #'+snippets/goto-start-of-field
     "<M-backspace>" #'+snippets/delete-to-start-of-field
     [escape]        #'evil-normal-state
     [backspace]     #'+snippets/delete-backward-char
     [delete]        #'+snippets/delete-forward-char-or-field)
   (:map yas-minor-mode-map
     :i "<tab>" yas-maybe-expand
     :v "<tab>" #'+snippets/expand-on-region))


 ;; --- Major mode bindings --------------------------

 ;; Markdown
 (:after markdown-mode
   (:map markdown-mode-map
     ;; fix conflicts with private bindings
     "<backspace>" nil
     "<M-left>"    nil
     "<M-right>"   nil))

 ;; Rust
 (:after rust
   (:map rust-mode-map
     "g RET" #'cargo-process-test))

 ;; Elixir
 (:after alchemist
   (:map elixir-mode-map
     :n "K"     #'alchemist-help-search-at-point
     :n "g RET" #'alchemist-project-run-tests-for-current-file
     :n "g \\"  #'alchemist-mix-test-at-point
     :n "g SPC" #'alchemist-mix-compile))

 ;; Haskell
 (:after haskell-mode
   (:map haskell-mode-map
     ;; :n "K"     #'intero-info
     :n "K"     #'lsp-describe-thing-at-point
     ;; :n "g d"   #'lsp-ui-peek-find-definitions
     :n "g d"   #'lsp-ui-peek-find-definitions
     :n "g R"   #'lsp-find-references
     ;; :n "g SPC" #'intero-repl-load
     ;; :n "g y"   #'lsp-ui-
     ))

 ;; Javascript
 ;; (:after rjsx-mode
 ;;   (:map rjsx-mode-map
 ;;     :n "g d" #'flow-minor-jump-to-definition
 ;;     :n "K"   #'flow-minor-type-at-pos))

 (:after js2-mode
   (:map js2-mode-map
     :n "g d" #'flow-minor-jump-to-definition
     :n "K"   #'flow-minor-type-at-pos))

 ;; Elisp
 (:map emacs-lisp-mode-map
   :n "g SPC" #'eval-buffer
   :n "g RET" (λ! () (ert t)))


 ;; --- Custom evil text-objects ---------------------
 :textobj "a" #'evil-inner-arg                    #'evil-outer-arg
 :textobj "B" #'evil-textobj-anyblock-inner-block #'evil-textobj-anyblock-a-block
 :textobj "i" #'evil-indent-plus-i-indent         #'evil-indent-plus-a-indent
 :textobj "I" #'evil-indent-plus-i-indent-up      #'evil-indent-plus-a-indent-up
 :textobj "J" #'evil-indent-plus-i-indent-up-down #'evil-indent-plus-a-indent-up-down


 ;; --- Built-in plugins -----------------------------
 (:after comint
   ;; TAB auto-completion in term buffers
   :map comint-mode-map [tab] #'company-complete)

 (:after debug
   ;; For elisp debugging
   :map debugger-mode-map
   :n "RET" #'debug-help-follow
   :n "e"   #'debugger-eval-expression
   :n "n"   #'debugger-step-through
   :n "c"   #'debugger-continue)

 (:map help-mode-map
   :n "[["  #'help-go-back
   :n "]]"  #'help-go-forward
   :n "o"   #'ace-link-help
   :n "q"   #'quit-window
   :n "Q"   #'+ivy-quit-and-resume)

 (:after vc-annotate
   :map vc-annotate-mode-map
   :n "q"   #'kill-this-buffer
   :n "d"   #'vc-annotate-show-diff-revision-at-line
   :n "D"   #'vc-annotate-show-changeset-diff-revision-at-line
   :n "SPC" #'vc-annotate-show-log-revision-at-line
   :n "]]"  #'vc-annotate-next-revision
   :n "[["  #'vc-annotate-prev-revision
   :n "TAB" #'vc-annotate-toggle-annotation-visibility
   :n "RET" #'vc-annotate-find-revision-at-line))

;; evil-easymotion
(after! evil-easymotion
  (let ((prefix (concat doom-leader-key " /")))
    ;; NOTE `evilem-default-keybinds' unsets all other keys on the prefix (in
    ;; motion state)
    (evilem-default-keybindings prefix)
    (evilem-define (kbd (concat prefix " n")) #'evil-ex-search-next)
    (evilem-define (kbd (concat prefix " N")) #'evil-ex-search-previous)
    (evilem-define (kbd (concat prefix " s")) #'evil-snipe-repeat
                   :pre-hook (save-excursion (call-interactively #'evil-snipe-s))
                   :bind ((evil-snipe-scope 'buffer)
                          (evil-snipe-enable-highlight)
                          (evil-snipe-enable-incremental-highlight)))
    (evilem-define (kbd (concat prefix " S")) #'evil-snipe-repeat-reverse
                   :pre-hook (save-excursion (call-interactively #'evil-snipe-s))
                   :bind ((evil-snipe-scope 'buffer)
                          (evil-snipe-enable-highlight)
                          (evil-snipe-enable-incremental-highlight)))))


;;
;; Keybinding fixes
;;

;; This section is dedicated to "fixing" certain keys so that they behave
;; properly, more like vim, or how I like it.

(map! (:map input-decode-map
        [S-iso-lefttab] [backtab]
        (:unless window-system "TAB" [tab])) ; Fix TAB in terminal

      ;; I want C-a and C-e to be a little smarter. C-a will jump to
      ;; indentation. Pressing it again will send you to the true bol. Same goes
      ;; for C-e, except it will ignore comments and trailing whitespace before
      ;; jumping to eol.
      :i "C-a" #'doom/backward-to-bol-or-indent
      :i "C-e" #'doom/forward-to-last-non-comment-or-eol
      :i "C-u" #'doom/backward-kill-to-bol-and-indent

      ;; Emacsien motions for insert mode
      :i "C-b" #'backward-word
      :i "C-f" #'forward-word

      ;; Highjacks space/backspace to:
      ;;   a) balance spaces inside brackets/parentheses ( | ) -> (|)
      ;;   b) delete space-indented blocks intelligently
      ;;   c) do none of this when inside a string
      ;; :i "SPC"                          #'doom/inflate-space-maybe
      ;; :i [remap delete-backward-char]   #'doom/deflate-space-maybe
      ;; :i [remap newline]                #'doom/newline-and-indent

      (:map org-mode-map
       :i [remap doom/inflate-space-maybe] #'org-self-insert-command
       "C-c C-x C-i" #'org-clock-in
       "C-c C-x <C-i>" #'org-clock-in)

      (:map org-agenda-mode-map
       "C-c C-x C-i" #'org-agenda-clock-in
       "C-c C-x <C-i>" #'org-agenda-clock-in)

      ;; Restore common editing keys (and ESC) in minibuffer
      (:map (minibuffer-local-map
             minibuffer-local-ns-map
             minibuffer-local-completion-map
             minibuffer-local-must-match-map
             minibuffer-local-isearch-map
             evil-ex-completion-map
             evil-ex-search-keymap
             read-expression-map)
        ;; [escape] #'abort-recursive-edit
        "C-r" #'evil-paste-from-register
        "C-a" #'move-beginning-of-line
        "C-w" #'doom/minibuffer-kill-word
        "C-u" #'doom/minibuffer-kill-line
        "C-b" #'backward-word
        "C-f" #'forward-word
        "M-z" #'doom/minibuffer-undo)

      (:map messages-buffer-mode-map
        "M-;" #'eval-expression
        "A-;" #'eval-expression)

      (:map tabulated-list-mode-map
        [remap evil-record-macro] #'doom/popup-close-maybe)

      (:after view
        (:map view-mode-map "<escape>" #'View-quit-all)))

(defun +sexp-transpose ()
  (interactive)
  (case evil-this-operator
    ('evil-shift-right (paxedit-transpose-forward))
    ('evil-shift-left  (paxedit-transpose-backward))))

;; (defun nmap (&rest keys-and-ops)
;;   (->>
;;    (seq-partition keys-and-ops 2)
;;    (seq-map
;;     (lambda (k-op)
;;       (let* ((k (car k-op))
;;              (op (cadr k-op))
;;              (prefix (substring k 0 1))
;;              (prefix-sym (lookup-key evil-normal-state-map prefix))
;;              (keyseq (substring k 1)))
;;         (list keyseq prefix-sym op))))
;;    (seq-group-by #'car)
;;    (seq-map
;;     (lambda (k-ops)
;;       (let* ((keyseq           (car k-ops))
;;              (ops              (cdr k-ops))
;;              (existing-binding (lookup-key evil-operator-state-map keyseq))
;;              (handler (λ! ()
;;                           (if-let
;;                               ((oplist
;;                                 (seq-find (lambda (op)
;;                                             (equal (nth 1 op)
;;                                                    evil-this-operator))
;;                                           ops)))
;;                               (message "calling oplist")
;;                               (->> oplist (nth 2) funcall)
;;                             (when existing-binding
;;                               (funcall existing-binding))))))
;;         (if existing-binding
;;             (progn
;;               (define-key evil-operator-state-map
;;                 (vector 'remap existing-binding)
;;                 handler)
;;               (define-key evil-motion-state-map
;;                 (vector 'remap existing-binding)
;;                 handler))
;;           (define-key evil-operator-state-map keyseq handler)))))))

;; (nmap
;;  ">e" #'paxedit-transpose-forward
;;  "<e" #'paxedit-transpose-backward)

(require 'paxedit)
(require 'general)
(general-evil-setup t)

(nmap
  ">" (general-key-dispatch 'evil-shift-right
        "e" 'paxedit-transpose-forward
        ")" 'sp-forward-slurp-sexp
        "(" 'sp-backward-barf-sexp
        "I" 'grfn/insert-at-sexp-end
        ;; "a" 'grfn/insert-at-form-end
        ))

(nmap
  "<" (general-key-dispatch 'evil-shift-left
        "e" 'paxedit-transpose-backward
        ")" 'sp-forward-barf-sexp
        "(" 'sp-backward-slurp-sexp
        "I" 'grfn/insert-at-sexp-start
        ;; "a" 'grfn/insert-at-form-start
        ))


(defmacro saving-excursion (&rest body)
  `(λ! () (save-excursion ,@body)))

(nmap "c" (general-key-dispatch 'evil-change
            "r c" (saving-excursion (string-inflection-lower-camelcase))
            "r C" (saving-excursion (string-inflection-camelcase))
            "r m" (saving-excursion (string-inflection-camelcase))
            "r s" (saving-excursion (string-inflection-underscore))
            "r u" (saving-excursion (string-inflection-upcase))
            "r -" (saving-excursion (string-inflection-kebab-case))
            "r k" (saving-excursion (string-inflection-kebab-case))
            ;; "r ." (saving-excursion (string-inflection-dot-case))
            ;; "r ." (saving-excursion (string-inflection-space-case))
            ;; "r ." (saving-excursion (string-inflection-title-case))
            ))


(predd-defmulti eval-sexp (lambda (form) major-mode))

(predd-defmethod eval-sexp 'clojure-mode (form)
  (cider-interactive-eval form))

(predd-defmethod eval-sexp 'emacs-lisp-mode (form)
  (pp-eval-expression form))

(predd-defmulti eval-sexp-region (lambda (_beg _end) major-mode))

(predd-defmethod eval-sexp-region 'clojure-mode (beg end)
  (cider-interactive-eval nil nil (list beg end)))

(predd-defmethod eval-sexp-region 'emacs-lisp-mode (beg end)
  (pp-eval-expression (read (buffer-substring beg end))))

(predd-defmulti eval-sexp-region-context (lambda (_beg _end _context) major-mode))

(predd-defmethod eval-sexp-region-context 'clojure-mode (beg end context)
  (cider--eval-in-context (buffer-substring beg end)))

(defun pp-eval-context-region (beg end context)
  (interactive "r\nxContext: ")
  (let* ((inner-expr (read (buffer-substring beg end)))
         (full-expr (list 'let* context inner-expr)))
    (pp-eval-expression full-expr)))

(predd-defmethod eval-sexp-region-context 'emacs-lisp-mode (beg end context)
  (pp-eval-context-region beg end context))

(predd-defmulti preceding-sexp (lambda () major-mode))

(predd-defmethod preceding-sexp 'clojure-mode ()
  (cider-last-sexp))

(predd-defmethod preceding-sexp 'emacs-lisp-mode ()
  (elisp--preceding-sexp))

(defun eval-sexp-at-point ()
  (interactive)
  (let ((bounds (bounds-of-thing-at-point 'sexp)))
    (eval-sexp-region (car bounds)
                      (cdr bounds))))

(defun eval-last-sexp (_)
  (interactive)
  (eval-sexp (preceding-sexp)))

;;;

(defun cider-insert-current-sexp-in-repl (&optional arg)
  "Insert the expression at point in the REPL buffer.
If invoked with a prefix ARG eval the expression after inserting it"
  (interactive "P")
  (cider-insert-in-repl (cider-sexp-at-point) arg))

(evil-define-operator fireplace-send (beg end)
  (cider-insert-current-sexp-in-repl nil nil (list beg end)))

(defun +clojure-pprint-expr (form)
  (format "(with-out-str (clojure.pprint/pprint %s))"
          form))

(defun cider-eval-read-and-print-handler (&optional buffer)
  "Make a handler for evaluating and reading then printing result in BUFFER."
  (nrepl-make-response-handler
   (or buffer (current-buffer))
   (lambda (buffer value)
     (let ((value* (read value)))
       (with-current-buffer buffer
         (insert
          (if (derived-mode-p 'cider-clojure-interaction-mode)
              (format "\n%s\n" value*)
            value*)))))
   (lambda (_buffer out) (cider-emit-interactive-eval-output out))
   (lambda (_buffer err) (cider-emit-interactive-eval-err-output err))
   '()))

(defun cider-eval-and-replace (beg end)
  "Evaluate the expression in region and replace it with its result"
  (interactive "r")
  (let ((form (buffer-substring beg end)))
    (cider-nrepl-sync-request:eval form)
    (kill-region beg end)
    (cider-interactive-eval
     (+clojure-pprint-expr form)
     (cider-eval-read-and-print-handler))))

(defun cider-eval-current-sexp-and-replace ()
  "Evaluate the expression at point and replace it with its result"
  (interactive)
  (apply #'cider-eval-and-replace (cider-sexp-at-point 'bounds)))

;;;

(evil-define-operator fireplace-eval (beg end)
  (eval-sexp-region beg end))

(evil-define-operator fireplace-replace (beg end)
  (cider-eval-and-replace beg end))

(evil-define-operator fireplace-eval-context (beg end)
  (eval-sexp-region-context beg end))

;;; fireplace-esque eval binding
(nmap :keymaps 'cider-mode-map
  "c" (general-key-dispatch 'evil-change
        "p" (general-key-dispatch 'fireplace-eval
              "p" 'cider-eval-sexp-at-point
              "c" 'cider-eval-last-sexp
              "d" 'cider-eval-defun-at-point
              "r" 'cider-test-run-test)
        "q" (general-key-dispatch 'fireplace-send
              "q" 'cider-insert-current-sexp-in-repl
              "c" 'cider-insert-last-sexp-in-repl)
        "x" (general-key-dispatch 'fireplace-eval-context
              "x" 'cider-eval-sexp-at-point-in-context
              "c" 'cider-eval-last-sexp-in-context)
        "!" (general-key-dispatch 'fireplace-replace
              "!" 'cider-eval-current-sexp-and-replace
              "c" 'cider-eval-last-sexp-and-replace)
        "y" 'cider-copy-last-result))

;;;

(nmap :keymaps 'emacs-lisp-mode-map
  "c" (general-key-dispatch 'evil-change
        "p" (general-key-dispatch 'fireplace-eval
              "p" 'eval-sexp-at-point
              "c" 'eval-last-sexp
              "d" 'eval-defun
              "r" 'cider-test-run-test)
        "x" (general-key-dispatch 'fireplace-eval-context
              "x" 'cider-eval-sexp-at-point-in-context
              "c" 'cider-eval-last-sexp-in-context)
        "!" (general-key-dispatch 'fireplace-replace
              "!" 'cider-eval-current-sexp-and-replace
              "c" 'cider-eval-last-sexp-and-replace)
        "y" 'cider-copy-last-result))

(nmap :keymaps 'sly-mode-map
  "c" (general-key-dispatch 'evil-change
        "p" (general-key-dispatch 'sly-eval
              ;; "p" 'eval-sexp-at-point
              "c" 'sly-eval-last-expression
              "d" 'sly-eval-defun
              ;; "r" 'cider-test-run-test
              )
        ;; "x" (general-key-dispatch 'fireplace-eval-context
        ;;       "x" 'cider-eval-sexp-at-point-in-context
        ;;       "c" 'cider-eval-last-sexp-in-context
        ;;       )
        ;; "!" (general-key-dispatch 'fireplace-replace
        ;;       "!" 'cider-eval-current-sexp-and-replace
        ;;       "c" 'cider-eval-last-sexp-and-replace)
        ;; "y" 'cider-copy-last-result
        ))


;; >) ; slurp forward
;; <) ; barf forward
;; <( ; slurp backward
;; >( ; slurp backward

;; (require 'doom-themes)
(defun grfn/haskell-test-file-p ()
  (string-match-p (rx (and "Spec.hs" eol))
                  (buffer-file-name)))

(require 'haskell)

(defun grfn/intero-run-main ()
  (interactive)
  (intero-repl-load)
  (intero-with-repl-buffer nil
    (comint-simple-send
     (get-buffer-process (current-buffer))
     "main")))

(defun grfn/run-clj-or-cljs-test ()
  (interactive)
  (message "Running tests...")
  (cl-case (cider-repl-type-for-buffer)
    ('cljs
     (cider-interactive-eval
      "(with-out-str (cljs.test/run-tests))"
      (nrepl-make-response-handler
       (current-buffer)
       (lambda (_ value)
         (with-output-to-temp-buffer "*cljs-test-results*"
           (print
            (->> value
                 (s-replace "\"" "")
                 (s-replace "\\n" "\n")))))
       nil nil nil)))
    (('clj 'multi)
     (funcall-interactively
      #'cider-test-run-ns-tests
      nil))))

(defun cider-copy-last-result ()
  (interactive)
  (cider-interactive-eval
   "*1"
   (nrepl-make-response-handler
    (current-buffer)
    (lambda (_ value)
      (kill-new value)
      (message "Copied last result (%s) to clipboard"
               (if (= (length value) 1) "1 char"
                 (format "%d chars" (length value)))))
    nil nil nil)))


(defun grfn/insert-new-src-block ()
  (interactive)
  (let* ((current-src-block (org-element-at-point))
         (src-block-head (save-excursion
                           (goto-char (org-element-property
                                       :begin current-src-block))
                           (let ((line (thing-at-point 'line t)))
                             (if (not (s-starts-with? "#+NAME:" (s-trim line)))
                                 line
                               (forward-line)
                               (thing-at-point 'line t)))))
         (point-to-insert
          (if-let (results-loc (org-babel-where-is-src-block-result))
              (save-excursion
                (goto-char results-loc)
                (org-element-property
                 :end
                 (org-element-at-point)))
            (org-element-property :end (org-element-at-point)))))
    (goto-char point-to-insert)
    (insert "\n")
    (insert src-block-head)
    (let ((contents (point-marker)))
      (insert "\n#+END_SRC\n")
      (goto-char contents))))

(defun grfn/+org-insert-item (orig direction)
  (interactive)
  (if (and (org-in-src-block-p)
           (equal direction 'below))
    (grfn/insert-new-src-block)
    (funcall orig direction)))

(advice-add #'+org--insert-item :around #'grfn/+org-insert-item)
;; (advice-add #'+org/insert-item-below :around
;;             (lambda (orig) (grfn/+org-insert-item orig 'below)))

(defun set-pdb-trace ()
  (interactive)
  (end-of-line)
  (insert (format "\n%simport pdb;pdb.set_trace()"
                  (make-string (python-indent-calculate-indentation)
                               ?\s)))
  (evil-indent (line-beginning-position)
               (line-end-position)))

(map!

 (:map magit-mode-map
   :n "#" 'forge-dispatch)

 (:map haskell-mode-map
   :n "K"     'lsp-info-under-point
   :n "g d"   'lsp-ui-peek-find-definitions
   :n "g r"   'lsp-ui-peek-find-references
   :n "g \\"  '+haskell/repl
   ;; :n "K"     'intero-info
   ;; :n "g d"   'intero-goto-definition
   ;; :n "g SPC" 'intero-repl-load
   ;; :n "g \\"  'intero-repl
   ;; :n "g y"   'intero-type-at
   ;; :n "g RET" 'grfn/run-sputnik-test-for-file

   (:localleader
     :desc "Apply action"  :n "e" 'intero-repl-eval-region
     :desc "Rename symbol" :n "r" 'intero-apply-suggestions))

 (:map python-mode-map
   :n "K" #'anaconda-mode-show-doc
   :n "g SPC" #'+eval/buffer
   :n "g RET" #'python-pytest-file
   :n "g \\" #'+python/open-ipython-repl
   [remap evil-commentary-yank] #'set-pdb-trace)

 (:after agda2-mode
   (:map agda2-mode-map
     :n "g SPC" 'agda2-load
     :n "g d"   'agda2-goto-definition-keyboard
     :n "] g"   'agda2-next-goal
     :n "[ g"   'agda2-previous-goal

     (:localleader
       :desc "Give"                               :n "SPC" 'agda2-give
       :desc "Case Split"                         :n "c"   'agda2-make-case
       :desc "Make Helper"                        :n "h"   'agda2-helper-function-type
       :desc "Refine"                             :n "r"   'agda2-refine
       :desc "Auto"                               :n "a"   'agda2-auto-maybe-all
       :desc "Goal type and context"              :n "t"   'agda2-goal-and-context
       :desc "Goal type and context and inferred" :n ";"   'agda2-goal-and-context-and-inferred)))

 (:after clojure-mode
   (:map clojure-mode-map
     :n "] f" 'forward-sexp
     :n "[ f" 'backward-sexp))

 (:after cider-mode
   (:map cider-mode-map
     :n "g SPC" 'cider-eval-buffer
     :n "g \\"  'cider-switch-to-repl-buffer
     :n "K"     'cider-doc
     :n "g K"   'cider-apropos
     :n "g d"   'cider-find-dwim
     :n "C-w ]" 'cider-find-dwim-other-window
     ;; :n "g RET" 'cider-test-run-ns-tests
     :n "g RET" 'grfn/run-clj-or-cljs-test
     :n "g r" #'cljr-rename-symbol

     "C-c C-r r" 'cljr-add-require-to-ns
     "C-c C-r i" 'cljr-add-import-to-ns

     (:localleader
       ;; :desc "Inspect last result" :n "i" 'cider-inspect-last-result
       ;; :desc "Search for documentation" :n "h s" 'cider-apropos-doc
       :desc "Add require to ns" :n "n r" 'cljr-add-require-to-ns
       :desc "Add import to ns" :n "n i" 'cljr-add-import-to-ns))
   (:map cider-repl-mode-map
     :n "g \\" 'cider-switch-to-last-clojure-buffer))

 (:after w3m
   (:map w3m-mode-map
     "/" #'evil-search-forward
     "?" #'evil-search-backward
     "r" #'w3m-reload-this-page))

 (:after slack
   (:map slack-message-buffer-mode-map
     :i "<up>" #'slack-message-edit))

 (:after org
   :n "C-c C-x C-o" #'org-clock-out
   (:map org-mode-map
     [remap counsel-imenu] #'counsel-org-goto
     "M-k" #'org-move-subtree-up
     "M-j" #'org-move-subtree-down
     (:localleader
       :n "g" #'counsel-org-goto))

   (:map org-capture-mode-map
     :n "g RET" #'org-capture-finalize
     :n "g \\"  #'org-captue-refile))

 (:map lsp-mode-map
   :n "K"   #'lsp-describe-thing-at-point
   :n "g r" #'lsp-rename
   (:localleader
    :n "a" #'lsp-execute-code-action))

 (:map prolog-mode-map
  :n "g SPC" #'prolog-compile-buffer
  :n "g \\" #'run-prolog)

 (:map tuareg-mode-map
  :n "g RET" (λ! () (compile "dune build @@runtest"))
  :n "g SPC" #'dune-promote
  :n "g \\" #'utop
  :n "g y" #'merlin-locate-type
  "C-c C-f" (λ! () (compile "dune fmt"))))
