;;; This file contains user defined
;;     functions
;;     vars
;;     kbd macros
;;     faces
;; which can be used independently.

;; it's mean to be able to share between other users
;; don't put your customizations here, put them in ~/.emacs
;; or ~/.emacs.d/startup-scripts/

;;ADD-new-content-here

;;======================================================================
(defun goto-line-lisp (line)
  "`goto-line' is not supposed to be used in lisp code. This function is."
  (interactive)
  (goto-char (point-min))
  (forward-line (1- line)))

;;======================================================================
(defun format-dr-downing-c++-code ()
  "Dr Downing's code has `}' at the same line of `;', such as `;}}}'.
This function format it so that `}' will be on a separate line."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (search-forward ";}" nil t)
      (backward-char 1)
      (while (looking-at-p "}")
	(insert ?\n)
	(funcall indent-line-function)
	(forward-char 1)))))

;;======================================================================
;; start a simple new frame, binded to C-x 5 s (simple)
(defun make-simple-frame ()
  "make a new frame without modeline, minibuffer. and possible always on top.
somewhat like the little ediff control window."
  (interactive)
  ;; see default-frame-alist doc
  ;; see ediff
  ;; TODO how to add/overwrite only given options. keep original properties.
  (let ((simple-frame-alist
	  '(;; size
	    (width . 40)
	    (height . 10)
	    ;; (user-size . t)
	    ;; pos
	    (top . 0)
	    (left . 0)
	    ;; (user-position . t)
	    ;; layout
	    ;; (vertical-scroll-bars . nil)
	    ;; (menu-bar-lines . nil)
	    ;; (tool-bar-lines . nil)
	    ;; buffer
	    (minibuffer . nil)
	    (unsplittable . t)
	    )))
    (make-frame simple-frame-alist)))

;;======================================================================
;; paste to pastebin.com, binded to f11
(defvar pastebinit-major-mode-format-alist
  ;;this is only the init value, customize it in .emacs
  '((c++-mode . "cpp")
    (c-mode . "c")
    (sh-mode . "bash")
    (html-mode . "html4strict")
    (java-mode . "java")
    (php-mode . "php")
    (python-mode . "python")
    (scheme-mode . "scheme")
    (emacs-lisp-mode . "lisp"))
  "map emacs major mode to pastebin.com syntax format.")

;; now use my simple version: ~/bin/pastebinit-my
;; the original pastebinit seems not working sometimes
;; TODO sometimes doesn't post, and return:
;;      http://sylecn.pastebin.com/pastebin.php
(defun pastebinit-current-buffer ()
  "run pastebinit on current buffer, put the resulting url in default kill-ring. in other words, type C-y to paste the link after this command."
  (interactive)
  ;; I use a private pastebin address here
  (let ((textformat (or (cdr (assq major-mode
				   pastebinit-major-mode-format-alist))
			"text")))
    (shell-command-on-region (point-min)
    			     (point-max)
			     (concat "pastebinit-my " textformat)))
  (set-buffer "*Shell Command Output*")
  (copy-region-as-kill (point-min) (point-max)))

;;======================================================================
(defun unbalance-two-window ()
  "when there are two window. make the current one height +10."
  (interactive)
  (enlarge-window 10))

;;======================================================================
(defun cut-for-learnlinux ()
  "common pattern is `something is a some other thing.' this will remove the \"is\" and add two slashes."
  (interactive)
  (move-beginning-of-line nil)
  (forward-word nil)
  (kill-word 1)
  (forward-char nil)
  (insert " //"))

;;======================================================================


;;======================================================================
;;open manpage in current window
;;TODO should use man minibuffer input history.
(defun m (&optional word)
  "replace default man command. open in one window."
  (interactive
   (let ((v (word-at-point))
	 (enable-recursive-minibuffers t)
	 (completion-ignore-case t)
	 val)
     (setq val
	   (completing-read
	    (if (stringp v)
		(format "man (default %s): " v)
	      "man: ")
	    obarray))
     (list (if (equal val "")
	       v val))))
  (man word)
  (sleep-for 1)
  (other-window 1)
  (delete-other-windows))

;;======================================================================
;;ref: http://www.emacswiki.org/emacs/TrampMode
;;can be used as a ref on how to implement my "svim" function.
(defun find-alternate-file-with-sudo ()
  "open the current file using /sudo::/your-file"
  (interactive)
  (when buffer-file-name
    (find-alternate-file
     (concat "/sudo:root@localhost:"
	     buffer-file-name))))
(defalias 'sudo 'find-alternate-file-with-sudo)

;;======================================================================
(defun shrink-other-window-if-larger-than-buffer ()
  "shrink-window-if-larger-than-buffer for other window."
  (interactive)
  (other-window 1)
  (shrink-window-if-larger-than-buffer)
  (other-window -1))

;;======================================================================
;;my customized hippie-expand functions

;;complete file name
;;don't show . and .. dir
;;TODO
(defun hippie-expand-filename ()
  (interactive)
  nil)

;;complete line
(defun hippie-expand-line ()
  (interactive)
  nil)

;;======================================================================
;;view current file in browser
(defun view-current-file-in-browser ()
  (interactive)
  (browse-url (format "file://%s%s" (pwd) (buffer-name))))

;;======================================================================
;;font setting helper functions for Chinese users
;;the font size of Chinese font should be 5/4 as large as english font

(defvar default-font-size 12
  "This is the default font size. It is used in `my-set-default-font' and `set-unicode-font'.")

(defun my-set-default-font (fn &optional size)
  "Set default font to fn. fn is a string containing xft font name.
This function is made to easy set font just by font name."
  (interactive "sSet default font name to: ")
  (unless size
    (setq size default-font-size))
  (set-frame-font (format "%s-%d" fn size)))

(defun set-unicode-font-mono (fn &optional size)
  "Set default unicode font to fn. fn is a string containing xft font name.
This function is made to easy set font just by font name. font size will be set so that a Chinese character is twice as wide as a English letter."
  ;;TODO add (was xxx) in promtp
  (interactive "sSet default unicode font name to: ")
  (unless size
    (setq size default-font-size))
  (let ((font-size-unicode (* 5 (/ size 4.0))))
    (set-fontset-font t 'unicode (format "%s-%d" fn font-size-unicode))))

(defun set-unicode-font (fn &optional size)
  "Set default unicode font to fn. fn is a string containing xft font name.
This function is made to easy set font just by font name. Note that use this function will *not* set Chinese char wide to twice the English char. If you want to do that, use `set-unicode-font-mono'"
  ;;TODO add (was xxx) in promtp
  (interactive "sSet default unicode font name to: ")
  (unless size
    (setq size default-font-size))
  (set-fontset-font t 'unicode (format "%s-%d" fn size)))

(defun change-font-size (size)
  "Change font size. This function will take care of both ascii font and unicode font."
  (interactive
   ;;FIXME how to get currently using default font's size?
   ;; (format "nSet default font size to (was %d): " default-font-size))
   "nSet (ascii) font size to: ")
  ;;FIXME how to get currently using default font and unicode font?
  (my-set-default-font "Bitstream Vera Sans Mono" size)
  (set-unicode-font "NSimSun" size))


;;======================================================================
;; search word using dict.cn or google
;;
;; let default word be the word under/around point
;;   code borrowed from C-h v
(defun w3m-dict-cn-search (&optional word)
  "search word on dict.cn, create w3m buffer if necessary."
  (interactive
   (let ((v (word-at-point))
	 (enable-recursive-minibuffers t)
	 (completion-ignore-case t)
	 val)
     (setq val
	   (completing-read
	    (if (stringp v)
		(format "Search word on dict.cn (default %s): " v)
	      "Search word on dict.cn: ")
	    ;;TODO set collection to english words
	    ;;TODO should allow space in input. for phrases
	    ;;     space is bind to complete word, you can type C-q SPC to
	    ;;     input a raw space.
	    obarray))
     (list (if (equal val "")
	       v val))))
  (w3m-goto-url (concat "http://dict.cn/" word)))

(defun w3m-google-dict-search (&optional word)
  "search word on google dict cn, create w3m buffer if necessary."
  (interactive
   (let ((v (word-at-point))
	 (enable-recursive-minibuffers t)
	 (completion-ignore-case t)
	 val)
     (setq val
	   (completing-read
	    (if (stringp v)
		(format "Search word on google dict cn (default %s): " v)
	      "Search word on google dict cn: ")
	    ;;TODO set collection to english words
	    obarray))
     (list (if (equal val "")
	       v val))))
  (w3m-goto-url (format "http://www.google.cn/dictionary?langpair=en|zh-CN&q=%s&hl=zh-CN&aq=f" word)))

;;======================================================================
;;google-search using emacs default browser

;;function is not enough, must use macro
;; (defun run-function-on-word-under-point (&optional function msg word)
;;   "General function to help you define a function that use word under point as default argument. function should accept one string argument. msg is the prompt message at minibuffer. word will defaults to the word under point."
;;   (interactive
;;    (let ((v (word-at-point))
;; 	 val)
;;      (setq val
;; 	    (if (stringp v)
;; 		(format "%s (default %s): " msg v)
;; 	      (format "%s: " msg v)))
;;      (list (if (equal val "")
;; 	       v val))))
;;   (w3m-goto-url (concat "http://dict.cn/" word)))

;;TODO if thers is a region, search the region instead
;;     need to quote space and special chars?
(defun google-search (word)
  "Mostly copied from `w3m-dict-cn-search'."
  (interactive
   (let ((v (word-at-point))
	 val)
     (setq val
	   (read-from-minibuffer
	    (if (stringp v)
		(format "Search on google (default %s): " v)
	      "Search on google: ")))
     (list (if (equal val "")
	       v
	     val))))
  (let ((url (format
	      "http://www.google.com/#hl=en&q=%s" word)))
    ;;TODO process started in this way will be terminated when emacs exit
    ;;     I want to start a process like M-! foo &
    ;;     don't start as sub-process, but a independent process from emacs.
    (start-process "google-search" nil "firefox" url)))

;;======================================================================
;; abbrev hook functions
;;do make abbrev for them. see `define-abbrev' or
;;info node: Skeletons as Abbrev Expansions

;;insert date and time
(defun insert-time ()
  "Insert current time like '07:31:36 PM'. see format-time-string function."
  (interactive)
  (insert (format-time-string "%r")))

(defun insert-date ()
  "Insert today's date like '2009-04-28'."
  (interactive)
  (insert (format-time-string "%F")))

(defun insert-datetime ()
  "Insert date at point.
With prefix argument, insert date and time."
  (interactive "P")
  (insert (format-time-string "%F %r")))

(defalias 'today 'insert-date)
(defalias 'now 'insert-datetime)

;;insert email
;;  Yuanle Song <sylecn@gmail.com>
(defun insert-email-formal ()
  "insert email address. If you need anti-spam style, use `insert-email-antispam'."
  (interactive)
  (insert (format "%s <%s>" user-full-name user-mail-address)))

(defun insert-email-antispam ()
  "insert email address. If you need formal style, use `insert-email-formal'."
  (interactive)
  ;;TODO form an antispam address
  ;;replace @ by " AT "
  ;;replace . by " DOT "
  (insert "sylecn AT gmail DOT com"))

(defalias 'insert-email 'insert-email-formal)

;;======================================================================
;; copy/kill things under point
;; vim y/d function simulation

(load "~/.emacs.d/sylecn-scripts/copy-kill-ex-uni.el")

;;copy-line is special, I put it here.
;; TODO rewrite in lispy style
;;      how to get identation point of current line?
;; better than yy function in vim
(defun copy-line ()
  "copy line. without indentation spaces."
  (interactive)
  (save-excursion
    (back-to-indentation)
    ;;prefer don't use set-mark-command, I don't like the "Mark set" message
    (set-mark-command nil)
    (copy-region-as-kill
     (region-beginning) (line-end-position))))

;;======================================================================
;; my-comment-dwim

(defun comment-line-toggle ()
  "Comment or uncomment current line."
  (interactive)
  (comment-or-uncomment-region
   (line-beginning-position) (line-end-position)))

(defun my-comment-dwim ()
  "Comment or uncomment current line. If there is a highlighted region, comment or uncomment that region instead.

When one of the following is true, add new comment (`comment-indent') rather than comment or uncomment current line:
* at an empty line.
* point is at end of line, and there is a space before point.
"
  (interactive)
  (comment-normalize-vars)
  (if (not (region-active-p))
      (if (looking-at-p "$")
	  ;; We are at the end of line. It's probable the user want to
	  ;; add new comment. Add new comment when:
	  ;; * at empty line, or
	  ;; * there is a space before end-of-line
	  (let ((prev-char (char-before)))
	    (cond
	     ((null prev-char)
	      (insert comment-start))
	     ((= prev-char ?\s)
	      (let ((prev-prev-char (char-before (1- (point)))))
		(if (or (= prev-prev-char ?\s)
			(= prev-prev-char ?\t)
			(= prev-prev-char ?\n))
		    (insert comment-start)
		  (progn
		    (backward-delete-char 1)
		    (comment-indent)))))
	     ((or (= prev-char ?\n)
		  (= prev-char ?\t))
	      (insert comment-start))
	     (t (comment-line-toggle))))
	(comment-line-toggle))
    (comment-or-uncomment-region (region-beginning) (region-end))))

;;======================================================================
(defun swap-recent-buffer ()
  "do C-x b RET"
  (interactive)
  (switch-to-buffer nil))

;;======================================================================
(defun current-address ()
  "Run findphone command. Find address or phone numbers in ~/texts/phone"
  (interactive)
  (shell-command "findphone current" t))

;;======================================================================
;; for myfiles

;; test case for these two functions:
;; (mapcar 'myfiles-full-file-name myfiles)
;; (mapcar 'myfiles-buffer-name myfiles)

(defun myfiles-file-name (x)
  "Get full file name with path for a file in `myfiles'."
  (if (file-name-absolute-p x)
      x
    (concat "~/" x)))

(defun myfiles-file-name-nodirectory (x)
  "Get file name without path for a file in `myfiles'."
  ;; call functions in std lib
  (file-name-nondirectory (myfiles-file-name x)))

(defalias 'myfiles-buffer-name 'myfiles-file-name-nodirectory)

;;======================================================================
(defun wc ()
  "Run wc command on file in current buffer."
  (interactive)
  (shell-command (format "wc %s" (buffer-file-name))))

;;======================================================================
;;TODO should show summary of the two byte-recompile-directory, not
;;     the last one in minibuffer.
(defun byte-recompile-my-emacs-dir ()
  "handy function for easier update my el files"
  (interactive)
  (byte-compile-file "~/.emacs-custom.el")
  (byte-recompile-directory "~/.emacs.d/sylecn-scripts/" 0)
  (byte-recompile-directory "~/.emacs.d/startup-scripts/" 0)
  (byte-recompile-directory "~/fromsource/yasnippet/" 0)
  )

;;======================================================================
(defun x-version-running ()
  "return t if current frame is running on a x window. nil if running on terminal."
  (interactive)
  window-system)

;;======================================================================
(defun exit-emacs-client ()
  "consistent exit emacsclient.
   if not in emacs client, echo a message in minibuffer, don't exit emacs.
   if in server mode
      and editing file, do C-x # server-edit
      else do C-x 5 0 delete-frame"
  (interactive)
  (if server-buffer-clients
      (server-edit)
    (delete-frame)))

;;======================================================================
;;TODO scroll up half a screen
(defun scroll-up-half-screen ()
  (interactive)
  (scroll-up 10))

;;======================================================================
;; auto wrap search, like in vim
;; this could be default
;; ref: http://stackoverflow.com/questions/285660/automatically-wrapping-i-search/286099
(defadvice isearch-search (after isearch-no-fail activate)
  (unless isearch-success
    (ad-disable-advice 'isearch-search 'after 'isearch-no-fail)
    (ad-activate 'isearch-search)
    (isearch-repeat (if isearch-forward 'forward))
    (ad-enable-advice 'isearch-search 'after 'isearch-no-fail)
    (ad-activate 'isearch-search)))

;;======================================================================
;;add the % jump function in vim
;;ref: http://docs.huihoo.com/homepage/shredderyin/emacs_elisp.html
;;ref: emacs FAQ info doc "Matching parentheses"
(defun match-paren (arg)
  "Go to the matching paren if on a paren; otherwise insert %."
  (interactive "p")
  (cond ((looking-at "\\s\(") (forward-list 1) (backward-char 1))
        ((looking-at "\\s\)") (forward-char 1) (backward-list 1))
        (t (self-insert-command (or arg 1)))))

;;======================================================================
;;add the f function in vim
(defun go-to-char (n char)
  "Move forward to Nth occurence of CHAR.
Typing `go-to-char-key' or the same char again will move forwad to the next Nth
occurence of CHAR."
  (interactive "p\ncGo to char: ")
  (search-forward (string char) nil nil n)
  (while (char-equal (read-char)
		     char)
    (search-forward (string char) nil nil n))
  (setq unread-command-events (list last-input-event)))

;;======================================================================
(defun cmd ()
  "start a shell in buffer \"*cmd*\""
  (interactive)
  (shell "*cmd*"))

(defun switch-to-shell-buffer ()
  "switch to buffer \"*cmd*\""
  (interactive)
  (switch-to-buffer "*cmd*"))

;;======================================================================
(defun lword ()
  "open the lword file to add new word"
  (interactive)
  (find-file "~/texts/lwords"))

(defun dict-cn-trim-word ()
  "format/trim pasted dict.cn word"
  (interactive)
  (forward-line -1)
  ;;Note, don't move the newline char at the end. That's part of the search str.
  (search-backward-regexp "^
")
  (set-mark-command nil)
  (search-forward-regexp "剑\n?桥\n?辞\n?典")
  (kill-region (region-beginning) (region-end))
  (kill-line 1)
  (forward-line -1)
  (search-forward "[")
  (backward-char)
  (just-one-space)
  (search-forward "]")
  (kill-line)
  (goto-char (point-max)))

(defun w3m-dict-cn-add-word-to-lword ()
  "add current word on dict.cn page to lword file, auto format, save the file."
  (interactive)
  (goto-char (point-min))
  (forward-line (1- 12))
  (set-mark-command nil)
  (forward-line 3)
  (search-forward-regexp "^
")
  (copy-region-as-kill (region-beginning) (region-end))
  ;; hide *w3m* buffer 
  ;; (w3m-close-window)
  ;;make it more portable if someday I removed lword from myfiles var.
  ;;(switch-to-buffer "lwords")
  (lword)
  (goto-char (point-max))
  (yank)
  (dict-cn-trim-word)
  (save-buffer))

(defun search-lword ()
  "search word in lword, and jump to it in lword buffer."
  ;TODO
  nil)

