;;; ess-smart-underscore.el --- Ess Smart Underscore
;;
;; Filename: ess-smart-underscore.el
;; Description: ess-smart-underscore
;; Author: Matthew L. Fidler
;; Maintainer: Matthew Fidler
;; Created: Thu Jul 14 11:04:42 2011 (-0500)
;; Version: 0.79
;; Last-Updated: Mon Apr  9 15:27:09 2012 (-0500)
;;           By: Matthew L. Fidler
;;     Update #: 137
;; URL: http://github.com/mlf176f2/ess-smart-underscore.el
;; Package-Requires: ((ess "0"))
;; Keywords: ESS, underscore
;; Compatibility:
;; 
;; Features that might be required by this library:
;;
;;   `custom', `easymenu', `ess', `ess-compat', `ess-custom',
;;   `font-lock', `syntax', `widget'.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Commentary:
;; 
;; * Installation
;; 
;; To use without using a package manager:
;; 
;;  - Put the library in a directory in the emacs load path, like ~/.emacs.d
;;  - Add (require 'ess-smart-underscore) in your ~/.emacs file
;; 
;; This is in emacswiki, so this package can also be installed using el-get.
;; 
;; After installing el-get, Type M-x el-get-install ess-smart-underscore.
;; * Ess-Smart Underscore Package Information
;; Smart "_" key: insert `ess-S-assign', unless:
;; 
;;   1. in string/comment
;;   2. after a $ (like d$one_two) (toggle with `ess-S-underscore-after-$')
;;   3. when the underscore is part of a variable definition previously defined.
;;      (toggle with `ess-S-underscore-after-defined')
;;   4. when the underscore is after a "=" or "<-" on the same line.
;;   5. inside a parenthetical statement () or [].
;;      (toggle with `ess-S-underscore-when-inside-paren')
;;   6. At the beginning of a line.
;;   7. In a variable that contains underscores already (for example foo_a)
;;      (toggle with `ess-S-underscore-when-variable-contains-underscores')
;;   8. The preceding character is not a tab/space (toggle with
;;      `ess-S-underscore-when-last-character-is-a-space'.  Not enabled
;;      by default.)
;; 
;; An exception to 
;; 
;; 
;; a <- b |
;; 
;; 
;; Pressing an underscore here would produce
;; 
;; 
;; 
;; a <- b <-
;; 
;; 
;; However when in the following situation
;; 
;; 
;; a <- b|
;; 
;; 
;; Pressing an underscore would produce
;; 
;; 
;; a <- b_
;; 
;; 
;; This behavior can be toggled by `ess-S-space-underscore-is-assignment'
;; 
;; If the underscore key is pressed a second time, the assignment
;; operator is removed and replaced by the underscore.  `ess-S-assign',
;; typically " <- ", can be customized.  In ESS modes other than R/S,
;; an underscore is always inserted.
;; 
;; In addition the ess-smart-underscore attempts to work with noweb-mode
;; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Change Log:
;; 22-May-2013    Matthew L. Fidler  
;;    Last-Updated: Mon Apr  9 15:27:09 2012 (-0500) #137 (Matthew L. Fidler)
;;    Added more ggplot operators. 
;; 21-May-2013    Matthew L. Fidler  
;;    Last-Updated: Mon Apr  9 15:27:09 2012 (-0500) #137 (Matthew L. Fidler)
;;    Added math to ggplot's functions...
;; 21-May-2013    Matthew L. Fidler  
;;    Last-Updated: Mon Apr  9 15:27:09 2012 (-0500) #137 (Matthew L. Fidler)
;;    Added hook to R-mode to make it behave a little better.
;; 15-May-2013    Matthew L. Fidler  
;;    Last-Updated: Mon Apr  9 15:27:09 2012 (-0500) #137 (Matthew L. Fidler)
;;    Fixed ess-smart-underscore to work in an inferior R process.
;; 15-May-2013    Matthew L. Fidler  
;;    Last-Updated: Mon Apr  9 15:27:09 2012 (-0500) #137 (Matthew L. Fidler)
;;    Added ggplot function prefixes.  
;; 05-Nov-2012    Matthew L. Fidler  
;;    Last-Updated: Mon Apr  9 15:27:09 2012 (-0500) #137 (Matthew L. Fidler)
;;    Better handling of noweb.  I think it Came from Denis Haine and
;;    Martin Maechler.
;; 22-Feb-2012    Matthew L. Fidler  
;;    Last-Updated: Wed Feb 22 20:27:04 2012 (-0600) #120 (Matthew L. Fidler)
;;    Support unbalanced sexps.
;; 02-Feb-2012    Matthew L. Fidler  
;;    Last-Updated: Thu Feb  2 21:06:52 2012 (-0600) #117 (Matthew L. Fidler)
;;    Took out auto-installing.  Most package managers don't want you
;;    to do this.
;; 03-Aug-2011    Matthew L. Fidler  
;;    Last-Updated: Wed Aug  3 15:05:15 2011 (-0500) #112 (Matthew L. Fidler)
;;    Bug fix for parenthetical statement
;; 20-Jul-2011    Matthew L. Fidler  
;;    Last-Updated: Wed Jul 20 15:20:10 2011 (-0500) #101 (Matthew L. Fidler)

;;    Changed to allow underscore instead of assign when inside a
;;    parenthetical statement.

;; 15-Jul-2011    Matthew L. Fidler
;;    Last-Updated: Fri Jul 15 11:34:52 2011 (-0500) #90 (Matthew L. Fidler)
;;    Bug fix for d[d$CMT == 2,"DV"] _ to produce d[d$CMT == 2,"DV"] <-
;; 
;; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 3, or
;; (at your option) any later version.
;; 
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.
;; 
;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
;; Floor, Boston, MA 02110-1301, USA.
;; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;;; Code:
(require 'ess)

(defcustom ess-S-underscore-after-$ t
  "Should underscore produce an underscore if it is an element of a list/data structure?

 Used by \\[ess-smart-underscore]."
  :group 'ess-S
  :type 'boolean)

(defcustom ess-S-underscore-after-defined t
  "Should underscore produce an underscore if it is after a variable has been defined?

 Used by \\[ess-smart-underscore]."
  :group 'ess-S
  :type 'boolean)

(defcustom ess-S-underscore-after-<-or-= t
  "Should underscore produce an underscore if it is after a \"<-\" or \"=\"?

 Used by \\[ess-smart-underscore]."
  :group 'ess-S
  :type 'boolean)

(defcustom ess-S-space-underscore-is-assignment t
  "Should underscore produce `ess-S-assign' when a space is right before the cursor.

Used by \\[ess-smart-underscore]."
  :group 'ess-S
  :type 'boolean)

(defcustom ess-S-underscore-when-inside-paren t
  "Should an underscore be produced instead of `ess-S-assign' when inside a parenthetical expression?"
  :group 'ess-S
  :type 'boolean)

(defcustom ess-S-underscore-when-inside-unbalanced-parenthesis t
  "Should an underscore be produced instead of `ess-S-assign' when inside an unbalanced parenthetical expression such as:

  foo(bar_

This requires `ess-S-underscore-when-inside-paren' to be true.
"
  :group 'ess-S
  :type 'boolean)

(defcustom ess-S-underscore-when-preceeding-words
  '(
    "add"
    "aes"
    "annotation"
    "calc"
    "continuous"
    "coord"
    "coord"
    "cut"
    "discrete"
    "element"
    "expand"
    "facet"
    "geom"
    "gg"
    "group"
    "guide"
    "label"
    "last"
    "math"
    "mean"
    "mutate"
    "position"
    "scale"
    "scale_color"
    "scale_colour"
    "scale_x"
    "scale_y"
    "stat"
    "theme"
    "trans"
    "translate"
    "translate_qplot"
    "update"
    "update_"
    "update_geom"
    )
  "Things that should have underscores after them. "
  :group 'ess-S
  :type '(repeat
          (string :tag "Word")))


(defcustom ess-S-underscore-when-variable-contains-underscores t
  "Should an underscore be produced instead of `ess-S-assign' when variable already contains an underscore?"
  :group 'ess-S
  :type 'boolean)

(defcustom ess-S-underscore-when-last-character-is-a-space nil
  "ESS produces an underscore only when the last character is not a space or a tab."
  :group 'ess-S
  :type 'boolean)

;;;###autoload
(defun ess-smarter-underscore ()
  "Smart \"_\" key: insert `ess-S-assign', unless:
  1. in string/comment
  2. after a $ (like d$one_two) (toggle with `ess-S-underscore-after-$')
  3. when the underscore is part of a variable definition previously defined.
     (toggle with `ess-S-underscore-after-defined')
  4. when the underscore is after a \"=\" or \"<-\" on the same line.
     (toggle with `ess-S-underscore-after-<-or-=')
  5. inside a parenthetical statement () or [].
     (toggle with `ess-S-underscore-when-inside-paren')
  6. At the beginning of a line.
  7. In a variable that contains underscores already (for example foo_a)
     (toggle with `ess-S-underscore-when-variable-contains-underscores')
  8. The preceding character is not a tab/space
     (toggle with `ess-S-underscore-when-last-character-is-a-space'.  Not enabled by default.)
  9. The preceding words/characters are in `ess-S-underscore-when-preceeding-words'


An exception to #4 is in the following situation:

a <- b |

pressing an underscore here would produce

a <- b <-

However when in the following situation

a <- b|

pressing an underscore would produce

a <- b_

This behavior can be toggled by `ess-S-space-underscore-is-assignment'

If the underscore key is pressed a second time, the assignment
operator is removed and replaced by the underscore.  `ess-S-assign',
typically \" <- \", can be customized.  In ESS modes other than R/S,
an underscore is always inserted. "
  (interactive)
  ;;(insert (if (ess-inside-string-or-comment-p (point)) "_"
  ;;ess-S-assign))
  ;;(message "%s" (looking-back "_[^ \t\n]*?\\="))
  (save-restriction
    (ignore-errors
      (when (and (eq major-mode 'inferior-ess-mode)
                 (> (point) (process-mark (get-buffer-process
                                           (current-buffer)))))
        (narrow-to-region (process-mark (get-process ess-local-process-name)) (point-max)))
      (and ess-noweb-mode
           (noweb-in-code-chunk)
           (noweb-narrow-to-chunk)))
    (if (or
         (not (equal ess-language "S"))
         (looking-back "^[ \t\n]*\\=")
         (looking-back (regexp-opt ess-S-underscore-when-preceeding-words t))
         (and ess-S-underscore-when-variable-contains-underscores
              (looking-back "_[^ \t\n]*?\\="))
         (and ess-S-underscore-when-last-character-is-a-space
              (looking-back "[^ \t]\\="))
         (ess-inside-string-or-comment-p (point))
         ;; Data
         (and ess-S-underscore-after-$ (save-match-data (save-excursion (re-search-backward "\\([$]\\)[A-Za-z0-9.]+\\=" nil t))))
         (and ess-S-underscore-after-<-or-=
              (let ((ret (save-match-data (and (not (looking-back ess-S-assign))
                                               (looking-back "\\(<-\\|\\<=\\>\\).*")))))
                (if (and ret ess-S-space-underscore-is-assignment
                         (looking-back "[ \t]"))
                    (setq ret nil))
                (symbol-value 'ret)))
         ;; Look for variable
         (and ess-S-underscore-after-defined
              (not (looking-back ess-S-assign)) ; Hack to fix bug
              (save-match-data
                (save-excursion
                  (let (word)
                    (when (looking-back "\\<[A-Za-z0-9.]+[ \t]*")
                      (setq word (match-string 0))
                      (setq ret
                            (or (re-search-backward (format "^[ \t]*%s_[A-Za-z0-9.]*[ \t]*\\(<-\\|=\\)" word) nil t)
                                (re-search-backward (format "->[ \t]*%s_[A-Za-z0-9.]*[ \t]*$" word) nil t)))
                      (symbol-value 'ret))))))
         (and ess-S-underscore-when-inside-paren
              (save-match-data
                (save-excursion
                  (let ((pt (point))
                        ret)
                    (when (re-search-backward "\\((\\|\\[\\).*\\=" nil t)
                      (condition-case err
                          (progn
                            (forward-sexp)
                            (when (> (point) pt)
                              (setq ret t)))
                        (error
                         (when ess-S-underscore-when-inside-unbalanced-parenthesis
                           (setq ret 't)))))
                    (symbol-value 'ret))))))
        (insert "_")
      ;; Else one keypress produces ess-S-assign; a second keypress will delete
      ;; ess-S-assign and instead insert _
      ;; Rather than trying to count a second _ keypress, just check whether
      ;; the current point is preceded by ess-S-assign.
      (let ((assign-len (length ess-S-assign)))
        (if (and
             (>= (point) (+ assign-len (point-min))) ;check that we can move back
             (looking-back ess-S-assign))
            ;; If we are currently looking at ess-S-assign, replace it with _
            (progn
              (replace-match "")
              (insert "_"))
          (delete-horizontal-space)
          (insert ess-S-assign))))))
(define-key ess-mode-map (kbd "_") 'ess-smarter-underscore)

(defun ess-smart-underscore-add-inf ()
  "Add to inferior mode."
  (local-set-key (kbd "_") 'ess-smarter-underscore))

(add-hook 'ess-R-post-run-hook 'ess-smart-underscore-add-inf)
(add-hook 'R-mode-hook 'ess-smart-underscore-add-inf)
(provide 'ess-smart-underscore)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; ess-smart-underscore.el ends here
