;;; clang-rename.el --- Renames every occurrence of a symbol found at <offset>.  -*- lexical-binding: t; -*-

;; Keywords: tools, c

;;; Commentary:

;; To install clang-rename.el make sure the directory of this file is in your
;; `load-path' and add
;;
;;   (require 'clang-rename)
;;
;; to your .emacs configuration.

;;; Code:

(defgroup clang-rename nil
  "Integration with clang-rename"
  :group 'c)

(defcustom clang-rename-binary "clang-rename"
  "Path to clang-rename executable."
  :type '(file :must-match t)
  :group 'clang-rename)

;;;###autoload
(defun clang-rename (new-name)
  "Rename all instances of the symbol at point to NEW-NAME using clang-rename."
  (interactive "sEnter a new name: ")
  (save-some-buffers :all)
  ;; clang-rename should not be combined with other operations when undoing.
  (undo-boundary)
  (let ((output-buffer (get-buffer-create "*clang-rename*")))
    (with-current-buffer output-buffer (erase-buffer))
    (let ((exit-code (call-process
                      clang-rename-binary nil output-buffer nil
                      (format "-offset=%d"
                              ;; clang-rename wants file (byte) offsets, not
                              ;; buffer (character) positions.
                              (clang-rename--bufferpos-to-filepos
                               ;; Emacs treats one character after a symbol as
                               ;; part of the symbol, but clang-rename doesn’t.
                               ;; Use the beginning of the current symbol, if
                               ;; available, to resolve the inconsistency.
                               (or (car (bounds-of-thing-at-point 'symbol))
                                   (point))
                               'exact))
                      (format "-new-name=%s" new-name)
                      "-i" (buffer-file-name))))
      (if (and (integerp exit-code) (zerop exit-code))
          ;; Success; revert current buffer so it gets the modifications.
          (progn
            (kill-buffer output-buffer)
            (revert-buffer :ignore-auto :noconfirm :preserve-modes))
        ;; Failure; append exit code to output buffer and display it.
        (let ((message (clang-rename--format-message
                        "clang-rename failed with %s %s"
                        (if (integerp exit-code) "exit status" "signal")
                        exit-code)))
          (with-current-buffer output-buffer
            (insert ?\n message ?\n))
          (message "%s" message)
          (display-buffer output-buffer))))))

(defalias 'clang-rename--bufferpos-to-filepos
  (if (fboundp 'bufferpos-to-filepos)
      'bufferpos-to-filepos
    ;; Emacs 24 doesn’t have ‘bufferpos-to-filepos’, simulate it using
    ;; ‘position-bytes’.
    (lambda (position &optional _quality _coding-system)
      (1- (position-bytes position)))))

;; ‘format-message’ is new in Emacs 25.1.  Provide a fallback for older
;; versions.
(defalias 'clang-rename--format-message
  (if (fboundp 'format-message) 'format-message 'format))

(provide 'clang-rename)

;;; clang-rename.el ends here
