;;; go-rename.el --- Integration of the 'gorename' tool into Emacs.

;; Copyright 2014 The Go Authors. All rights reserved.
;; Use of this source code is governed by a BSD-style
;; license that can be found in the LICENSE file.

;; Version: 0.1
;; Package-Requires: ((go-mode "1.3.1"))
;; Keywords: tools

;;; Commentary:

;; To install:

;; % go get golang.org/x/tools/cmd/gorename
;; % go build golang.org/x/tools/cmd/gorename
;; % mv gorename $HOME/bin/         # or elsewhere on $PATH

;; The go-rename-command variable can be customized to specify an
;; alternative location for the installed command.

;;; Code:

(require 'cl-lib)
(require 'compile)
(require 'go-mode)
(require 'thingatpt)

(defgroup go-rename nil
  "Options specific to the Go rename."
  :group 'go)

(defcustom go-rename-command "gorename"
  "The `gorename' command; by the default, $PATH is searched."
  :type 'string
  :group 'go-rename)

;;;###autoload
(defun go-rename (new-name &optional force)
  "Rename the entity denoted by the identifier at point, using
the `gorename' tool. With FORCE, call `gorename' with the
`-force' flag."
  (interactive (list
                (if (and buffer-file-name (not (buffer-modified-p)))
                  (read-string "New name: " (thing-at-point 'symbol)))
                current-prefix-arg))
  (if (not buffer-file-name)
      (error "Cannot use go-rename on a buffer without a file name"))
  ;; It's not sufficient to save the current buffer if modified,
  ;; since if gofmt-before-save is on the before-save-hook,
  ;; saving will disturb the selected region.
  (if (buffer-modified-p)
      (error "Please save the current buffer before invoking go-rename"))
  ;; Prompt-save all other modified Go buffers, since they might get written.
  (save-some-buffers nil #'(lambda ()
              (and (buffer-file-name)
                   (string= (file-name-extension (buffer-file-name)) ".go"))))
  (let* ((posflag (format "-offset=%s:#%d"
                          buffer-file-name
                          (1- (position-bytes (point)))))
         (env-vars (go-root-and-paths))
         (goroot-env (concat "GOROOT=" (car env-vars)))
         (gopath-env (concat "GOPATH=" (mapconcat #'identity (cdr env-vars) ":")))
         success)
    (with-current-buffer (get-buffer-create "*go-rename*")
      (setq buffer-read-only nil)
      (erase-buffer)
      (let ((args (append (list go-rename-command nil t nil posflag "-to" new-name) (if force '("-force")))))
        ;; Log the command to *Messages*, for debugging.
        (message "Command: %s:" args)
        (message "Running gorename...")
        ;; Use dynamic binding to modify/restore the environment
        (setq success (zerop (let ((process-environment (cl-list* goroot-env gopath-env process-environment)))
          (apply #'call-process args))))
      (insert "\n")
      (compilation-mode)
      (setq compilation-error-screen-columns nil)

      ;; On success, print the one-line result in the message bar,
      ;; and hide the *go-rename* buffer.
      (if success
          (progn
            (message "%s" (go--buffer-string-no-trailing-space))
            (gofmt--kill-error-buffer (current-buffer)))
        ;; failure
        (let ((w (display-buffer (current-buffer))))
          (message "gorename exited")
          (set-window-point w (point-min)))))))

  ;; Reload the modified files, saving line/col.
  ;; (Don't restore the point since the text has changed.)
  ;;
  ;; TODO(adonovan): should we also do this for all other files
  ;; that were updated (the tool can print them)?
  (let ((line (line-number-at-pos))
        (col (current-column)))
    (revert-buffer t t t) ; safe, because we just saved it
    (goto-char (point-min))
    (forward-line (1- line))
    (forward-char col)))


(defun go--buffer-string-no-trailing-space ()
  (replace-regexp-in-string "[\t\n ]*\\'"
                            ""
                            (buffer-substring (point-min) (point-max))))

(provide 'go-rename)

;;; go-rename.el ends here
