;;;;
;;;;;; Devel (project/langs/tags/build/debug)
;;;;


;;
;; SECTION: project
;; global project management - src and build directories
;;

(defvar eaw-src-home (concat (getenv "HOME") "/src"))

(defvar eaw-build-map nil)

(defun eaw-expand-src-dir (dir)
  (expand-file-name (concat eaw-src-home "/" dir "/")))

(defun eaw-find-likely-proj-root ()
  (or
   (eaw-find-file-down-heirarchy ".gitignore" eaw-src-home)
   (eaw-find-file-up-heirarchy "GTAGS" eaw-src-home)
   (concat eaw-src-home "/")))

(defvar eaw-proj-map (make-sparse-keymap))
(define-key global-map (kbd "C-x g") eaw-proj-map)
(defvar eaw-proj-rootdir "")
(defvar eaw-proj-change-hook nil)
(defun eaw-proj-go (proj)
  (interactive
   (list (read-directory-name "proj root: "
                              (eaw-find-likely-proj-root)
                              (eaw-find-likely-proj-root)
                              t)))
  (if (not (unless (string= (expand-file-name proj) eaw-proj-rootdir)))
      (let* ((projdir (expand-file-name (concat proj "/")))
             (nonsrc (substring projdir (length (expand-file-name (concat eaw-src-home "/"))) -1))
             dirname)
        (message "switching to %s" projdir)
        (setq eaw-proj-rootdir projdir)
        (run-hooks 'eaw-proj-change-hook))))
(define-key eaw-proj-map "b" 'eaw-proj-go)

(defun eaw-find-dir-in-build-map (curdir)
  (let (found)
    (dolist (mapval eaw-build-map)
      (unless found
        (let ((mapdir (eaw-expand-src-dir (car mapval))))
          (if (or (string= curdir mapdir)
                  (and (> (length curdir) (length mapdir))
                   (string= (substring curdir (length mapdir) -1) mapdir)))
              (setq found (cdr mapval))))))
    found))

(defun eaw-find-likely-build-list ()
  (let ((found nil))
    (setq found (eaw-find-dir-in-build-map eaw-proj-rootdir))
    (unless found (setq found (eaw-find-dir-in-build-map (eaw-find-likely-proj-root))))
    (cond
     ((stringp found) (eaw-expand-src-dir found))
     ((listp found) (mapcar 'eaw-expand-src-dir found))
     (t default-directory))))

(defun eaw-find-likely-build-dir ()
  "Return the likely build directory for the given project. First
consult `eaw-proj-rootdir', then `eaw-find-likely-proj-root'.
Consult each against `eaw-build-map'. If the build map is nil, or
they aren't in the build map, just return `default-directory'. If
the build map is non-nil, then car is a source directory and cdr
is either a build directory (string) or a list of build
directories."
  (let ((fulldirs (eaw-find-likely-build-list)))
    (if fulldirs
        (if (stringp fulldirs)
            fulldirs
          (reduce '(lambda (a b)
                     (if (string-prefix-p (file-truename b) (file-truename default-directory)) b a))
                  fulldirs))
      eaw-proj-rootdir)))

(defun eaw-find-next-likely-build-dir ()
  (let ((fulldirs (eaw-find-likely-build-list)))
    (if (stringp fulldirs)
        fulldirs
      (let* ((curdir (expand-file-name (concat default-directory "/")))
             (tail (member curdir fulldirs)))
        (if (> (length tail) 1)
            (cadr tail)
          (car fulldirs))))))

(defvar eaw-compile-list
  '(("build.ninja" . "ninja -j 4 %s")
    ("Makefile" . "make -j 4 %s")))

(require 'cl)
(defun eaw-compile-likely-dir (&optional tgt)
  (interactive)
  (let ((default-directory (eaw-find-likely-build-dir))
        (target (or tgt "")))
    (dolist (elt eaw-compile-list)
      (when (file-exists-p (car elt))
        (compile
         (cond
          ((stringp (cdr elt)) (format (cdr elt) target))
          ((functionp (cdr elt)) (funcall (cdr elt) target))
          (t (error))))
        (return)))))
(define-key eaw-proj-map "n" 'eaw-compile-likely-dir)

(defun eaw-clean-likely-dir ()
  (interactive)
  (eaw-compile-likely-dir "clean"))
(define-key eaw-proj-map "c" 'eaw-clean-likely-dir)

;;
;; SECTION: langs
;; c-like, c++, obj-c, as, js, xml, groovy
;;

;; Make a non-standard key binding.  We can put this in
;; c-mode-base-map because c-mode-map, c++-mode-map, and so on,
;; inherit from it.
(defun my-c-initialization-hook ()
  (define-key c-mode-base-map "\C-m" 'c-context-line-break)
  (define-key c-mode-base-map (kbd "C-c C-c") 'comment-or-uncomment-region))
(add-hook 'c-initialization-hook 'my-c-initialization-hook)

;; Create my personal style.
(defconst my-c-style
  '((c-block-comment-prefix     . "* ")
    (c-cleanup-list             . (brace-else-brace
                                   brace-elseif-brace
                                   brace-catch-brace
                                   defun-close-semi
                                   list-close-comma
                                   scope-operator
                                   comment-close-slash))
    (c-basic-offset             . 4)
    (c-offsets-alist            . ((inline-open . 0)
                                   (statement . 0)
                                   (statement-cont . +)
                                   (statement-block-intro . +)
                                   (statement-case-intro  . +)
                                   (statement-case-open  . 0)
                                   (substatement . +)
                                   (substatement-open . 0)
                                   (member-init-intro . *)
                                   (member-init-cont . c-lineup-multi-inher)
                                   (access-label . -)
                                   (case-label . 0)
                                   (label . /)
                                   (friend . 0)
                                   (extern-lang-open . 0)
                                   (inextern-lang . 0)
                                   (extern-lang-close . 0)
                                   (namespace-open . 0)
                                   (innamespace . 0)
                                   (namespace-close . 0)
                                   (knr-argdecl . 0)
                                   (knr-argdecl-intro . +)))
    (c-hanging-braces-alist     . ((brace-list-open)
                                   (brace-list-close)
                                   (brace-entry-open)
                                   (statement-cont)
                                   (substatement-open after)
                                   (block-close)
                                   (defun-close)
                                   (extern-lang-open after)
                                   (namespace-open after)
                                   (module-open after)
                                   (composition-open after)
                                   (inexpr-class-open after)
                                   (inexpr-class-close before)))
    (fill-column . 80)
    (c-comment-only-line-offset . 0))
  "My C Programming Style")
(c-add-style "sane" my-c-style)

;; opening a .h file that's actually c++ should use c++ mode.
(defun eaw-cpp-header ()
  (let ((name (or buffer-file-name (buffer-name))))
    (and name
         (string-match "\\.h\\'" name)
         (re-search-forward "\\W\\(class\\|template\\|namespace\\)\\W" nil t))))
(add-to-list 'magic-mode-alist '(eaw-cpp-header . c++-mode))
(defun eaw-objc-header ()
  (let ((name (or buffer-file-name (buffer-name))))
    (and name
         (string-match "\\.h\\'" name)
         (re-search-forward "\\W@\\(interface\\|property\\|end\\)\\W" nil t))))
(add-to-list 'magic-mode-alist '(eaw-objc-header . objc-mode))
(add-to-list 'auto-mode-alist '("\\.mm\\'" . objc-mode))
(if (require 'grep nil t)
    (progn
      (add-to-list 'grep-files-aliases '("mm" . "*.m *.mm *.cc *.cxx *.cpp *.C *.CC *.c++"))
      (add-to-list 'grep-files-aliases '("mmhh" . "*.m *.mm *.cc *.cxx *.cpp *.C *.CC *.c++ *.hxx *.hpp *.[Hh] *.HH *.h++"))))

(defvar eaw-anything-c-source-objc-headline
  '((name . "Objective-C Headline")
    (headline  "^[-+@]\\|^#pragma mark")))
(if (require 'anything nil t)
    (progn
      (require 'anything-config)
      (defun eaw-objc-headline ()
        (interactive)
        ;; Set to 500 so it is displayed even if all methods are not narrowed down.
        (let ((anything-candidate-number-limit 500))
          (anything-other-buffer '(eaw-anything-c-source-objc-headline)
                                 "*ObjC Headline*")))
      (global-set-key (kbd "C-c o") 'eaw-objc-headline)))

(setenv "CTAGS" "----langmap=ObjectiveC:.m.h")

;; Customizations for all modes in CC Mode.
(defun my-c-mode-common-hook ()
  ;; set my personal style for the current buffer
  (c-set-style "sane")
  ;; preferred minor modes
  (c-toggle-electric-state 1)
  (c-toggle-auto-newline 1)
  (c-toggle-hungry-state -1)
  (if (fboundp 'c-subword-mode)
      (c-subword-mode 1)
    (subword-mode 1))
  (if (fboundp 'hs-minor-mode)
      (hs-minor-mode 1))
  (c-toggle-syntactic-indentation 1)
  ;; tab should format, not insert tab (counter-intuitively perhaps)
  (setq c-tab-always-indent t)
  (setq c-electric-pound-behavior '(alignleft))
;;  (if (not buffer-read-only)
;;      (flymake-mode 1))
  )
(add-hook 'c-mode-common-hook 'my-c-mode-common-hook)

;; actionscript
(require 'actionscript-mode nil t)

;; javascript
(if (require 'js2-mode nil t)
    (progn
      (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode))
      (add-hook 'js2-mode-hook '(lambda () (whitespace-mode 1)))
      (setq js2-highlight-level 3
            js2-bounce-indent-p t)
      (define-key js2-mode-map (kbd "C-m") 'newline-and-indent)

;; After js2 has parsed a js file, we look for jslint globals decl comment ("/* global Fred, _, Harry */") and
;; add any symbols to a buffer-local var of acceptable global vars
;; Note that we also support the "symbol: true" way of specifying names via a hack (remove any ":true"
;; to make it look like a plain decl, and any ':false' are left behind so they'll effectively be ignored as
;; you can;t have a symbol called "someName:false"
      (defun eaw-setup-js2-externs ()
        (when (> (buffer-size) 0)
          (let ((btext (replace-regexp-in-string
                        ": *true" " "
                        (replace-regexp-in-string "[\n\t ]+" " " (buffer-substring-no-properties 1 (buffer-size)) t t))))
            (mapc (apply-partially 'add-to-list 'js2-additional-externs)
                  (split-string
                   (if (string-match "/\\* *global *\\(.*?\\) *\\*/" btext) (match-string-no-properties 1 btext) "")
                   " *, *" t))
            )))
      (add-hook 'js2-post-parse-callbacks 'eaw-setup-js2-externs)
      (setq js2-global-externs '("require" "module"))))

(defun eaw-json-cleanup (start end)
  "Pretty-print JSON in region"
  (interactive (list (region-beginning) (region-end)))
  (shell-command-on-region start end "python -m json.tool" nil t))

(defun eaw-js-cleanup (start end)
  "Pretty-print JS in region"
  (interactive (list (region-beginning) (region-end)))
  (shell-command-on-region start end
                           (concat "java -jar " (getenv "HOME") "/var/compiler.jar "
                                   "--formatting PRETTY_PRINT --compilation_level WHITESPACE_ONLY --language_in ECMASCRIPT5") nil t))

;; nxml
(setq nxml-slash-auto-complete-flag t)

;; groovy
(autoload 'groovy-mode "groovy-mode" "Major mode for editing Groovy code." t)
(add-to-list 'auto-mode-alist '("\.groovy$" . groovy-mode))
(add-to-list 'interpreter-mode-alist '("groovy" . groovy-mode))
(add-hook 'groovy-mode-hook
          '(lambda ()
             (require 'groovy-electric)
             (groovy-electric-mode)))

;;
;; SECTION: tags
;; emacs (flymake, compile), ant, cmake
;;

;; better searching of tags
(require 'etags-select nil t)

(setq gtags-suggested-key-mapping t)
(if (require 'gtags nil t)
    (progn
      (define-key gtags-select-mode-map "\C-b" 'backward-char)
      (define-key gtags-select-mode-map "\C-f" 'forward-char)
      (define-key gtags-mode-map "\C-t" nil)
      (define-key eaw-proj-map "f" 'gtags-find-file)
      (define-key eaw-proj-map "r" 'gtags-find-rtag)
      (define-key eaw-proj-map "s" 'gtags-find-symbol)
      (define-key eaw-proj-map "p" 'gtags-find-pattern)
      (define-key eaw-proj-map "g" 'gtags-find-with-grep)

      (setenv "GTAGSCONF" (expand-file-name "~/etc/global"))

      ;; get the path of gtags root directory.
      (defun gtags-update ()
        (interactive)
        (with-current-buffer (generate-new-buffer (generate-new-buffer-name "*rootdir*"))
          (set-process-sentinel (start-process gtags-rootdir (current-buffer) "global" "-u")
                                (lambda (proc evt)
                                  (if (not (eq (process-status proc) 'exit))
                                      (message "sentinel event for %s: `%s'" (process-name proc) evt)
                                    (kill-buffer (process-buffer proc))
                                    (message "global update of `%s' complete" (process-name proc)))))))
      (define-key eaw-proj-map "u" 'gtags-update)

      (defun eaw-gtags-reroot (projdir)
        (setq gtags-rootdir projdir)
        (setenv "GTAGSROOT" projdir)
        (if (and (not (file-exists-p (expand-file-name "GTAGS" gtags-rootdir)))
                 (y-or-n-p (concat "GTAGS does not exist in " gtags-rootdir ", create it? ")))
            (let ((default-directory gtags-rootdir))
              (with-current-buffer (generate-new-buffer (generate-new-buffer-name "*gtags*"))
                (set-process-sentinel (start-process gtags-rootdir (current-buffer) "gtags")
                                      (lambda (proc evt)
                                        (if (not (eq (process-status proc) 'exit))
                                            (message "sentinel event for %s: `%s'" (process-name proc) evt)
                                          (kill-buffer (process-buffer proc))
                                          (message "gtags creation in %s complete" (process-name proc)))))))))

      (add-hook 'eaw-proj-change-hook '(lambda () (eaw-gtags-reroot projdir)))

      (defadvice gtags-decode-pathname (after eaw-gtags-pathname)
        (setq ad-return-value (file-truename ad-return-value)))
      (ad-activate 'gtags-decode-pathname)

      (defadvice gtags-push-tramp-environment (around eaw-gtags-tramp)
        "Don't ad-do-it"
        )
      (ad-activate 'gtags-push-tramp-environment)))

(when (require 'rtags nil t)
  (setq rtags-path
        (expand-file-name
         (concat (file-name-directory (find-lisp-object-file-name 'rtags-path 'defvar)) "../bin")))
  (define-key eaw-proj-map "f" 'rtags-find-file)
  (define-key eaw-proj-map "r" 'rtags-find-references)
  (define-key eaw-proj-map "s" 'rtags-find-symbol)
  (define-key eaw-proj-map "d" 'rtags-restart-process))

;; Use GNU global instead of normal find-tag, fall back to etags-select
(defun eaw-find-tag ()
  (interactive)
  (call-interactively (cond
   ((fboundp 'rtags-find-symbol) 'rtags-find-symbol)
   ((fboundp 'gtags-find-tag) 'gtags-find-tag)
   (t 'etags-select-find-tag))))
(global-set-key (kbd "M-.") 'eaw-find-tag)

(add-hook 'etags-select-mode-hook
          '(lambda ()
             (define-key etags-select-mode-map "\C-m" 'etags-select-goto-tag)))

;; cscope
(require 'xcscope nil t)

(add-hook 'cscope-list-entry-hook
          '(lambda ()
             (define-key cscope-list-entry-keymap "\C-m" 'cscope-select-entry-other-window)))

;;
;; SECTION: build
;; emacs (flymake, compile), ant, cmake
;;

;; turn off flymake-mode immediately if a header can't find its master
(defadvice flymake-master-make-header-init (after eaw-flymake-after-master)
  (if (string= flymake-mode-line-e-w "!")
      (flymake-report-fatal-status "NOMASTER" "no master file for header")))
(ad-activate 'flymake-master-make-header-init)

;; clean up flymake timers
(defun eaw-cleanup-flymake-timers ()
  (dolist (tmr timer-list)
    (if (and (timer--args tmr)
             (nth 0 (timer--args tmr))
             (bufferp (nth 0 (timer--args tmr)))
             (or (not (buffer-live-p (nth 0 (timer--args tmr))))
                 (with-current-buffer (nth 0 (timer--args tmr)) buffer-read-only)))
        (cancel-timer tmr))))
(run-with-timer (* 30 60) (* 30 60) 'eaw-cleanup-flymake-timers)

;; when skipping to errors, show a few lines above
(setq compilation-context-lines 1)

;; scroll compilation buffer
(setq compilation-scroll-output t)

;; make sure ant's output is in a format emacs likes
(setenv "ANT_ARGS" "-emacs")

;; cmake
(autoload 'cmake-mode "cmake-mode")
(add-to-list 'auto-mode-alist '("CMakeLists\\.txt\\'" . cmake-mode))
(add-to-list 'auto-mode-alist '("\\.cmake\\'" . cmake-mode))

;;
;; SECTION: debug
;; emacs (flymake, compile), ant, cmake
;;

;; gdb should use many windows, to make it look like an IDE
(setq gdb-many-windows t
      gdb-max-frames 120)

;; lldb works better for some things than gdb
(require 'lldb nil t)

(provide 'ew-devel)