;;; cc-mode-expansions.el --- C-specific expansions for expand-region  -*- lexical-binding: t; -*-

;; Copyright (C) 2012-2023  Free Software Foundation, Inc

;; Author: François Févotte
;; Based on js-mode-expansions by: Magnar Sveen <magnars@gmail.com>
;; Keywords: marking region

;; 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 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; Extra expansions for C-like modes that I've found useful so far:
;;
;; er/c-mark-statement
;;     Captures simple and more complex statements.
;;
;; er/c-mark-fully-qualified-name
;;     Captures identifiers composed of several '::'-separated parts.
;;
;; er/c-mark-function-call[-1|-2]
;;     Captures an identifier followed by a '()'-enclosed block.
;;
;; er/c-mark-statement-block[-1|-2]
;;     Captures a statement followed by a '{}'-enclosed block.
;;     This matches function definitions and if/for/... constructs.
;;
;; er/c-mark-vector-access[-1|-2]
;;     Captures an identifier followed by a '[]'-enclosed block.
;;
;; Feel free to contribute any other expansions for C at
;;
;;     https://github.com/magnars/expand-region.el

;;; Code:

(require 'expand-region-core)
(require 'er-basic-expansions)
(require 'cc-cmds)

(defun er/c-mark-statement ()
  "Mark the current C statement.

This function tries to ensure that pair-delimited substring are
either fully inside or fully outside the statement."
  (interactive)
  (unless (use-region-p)
    (set-mark (point)))

  (if (< (point) (mark))
      (exchange-point-and-mark))

  ;; Contract the region a bit to make the
  ;; er/c-mark-statement function idempotent
  (when (>= (- (point) (mark)) 2)
    (exchange-point-and-mark)
    (forward-char)
    (exchange-point-and-mark)
    (backward-char))

  (let (beg end)
    ;; Determine boundaries of the outside-pairs region
    (save-mark-and-excursion
      (c-end-of-statement)
      (er/mark-outside-pairs)
      (setq beg (point)
            end (mark)))

    ;; Determine boundaries of the statement as given
    ;; by c-beginning-of-statement/c-end-of-statement
    (c-end-of-statement)
    (exchange-point-and-mark)
    (c-end-of-statement)(c-beginning-of-statement 1)

    ;; If the two regions overlap, expand the region
    (cond ((and (<= (point) beg)
                (<  (mark)  end))
           (set-mark end))
          ((and (>  (point) beg)
                (>= (mark)  end))
           (goto-char beg)
           (c-end-of-statement)
           (c-beginning-of-statement 1)))))

(defun er/c-mark-fully-qualified-name ()
  "Mark the current C++ fully qualified identifier.

This function captures identifiers composed of multiple
'::'-separated parts."
  (interactive)
  (er/mark-symbol)
  (when (use-region-p)
    (when (> (point) (mark))
      (exchange-point-and-mark))
    (while (er/looking-back-exact "::")
      (backward-char 2)
      (skip-syntax-backward "_w"))
    (exchange-point-and-mark)
    (while (looking-at "::")
      (forward-char 2)
      (skip-syntax-forward "_w"))
    (exchange-point-and-mark)))

(defmacro er/c-define-construct (name mark-first-part open-brace doc)
  (let ((docstring (make-symbol "docstring-tmp")))
    (setq docstring
          (concat
           doc "\n\n"
           "This function tries to mark a region consisting of two parts:\n"
           (format " - the first part is marked using `%s'\n" (symbol-name mark-first-part))
           (format " - the second part is a block beginning with %S\n\n" open-brace)))
    `(progn
       (defun ,(intern (concat (symbol-name name) "-1")) ()
         ,(concat docstring
                  "This function assumes that point is in the first part and the\n"
                  "region is active.\n\n"
                  (format "See also `%s'." (concat (symbol-name name) "-2")))
         (interactive)
         (when (use-region-p)
           (,mark-first-part)
           (exchange-point-and-mark)
           (let ((oldpos (point)))
             (skip-syntax-forward " ")
             (if (looking-at ,open-brace)
                 (progn (forward-sexp)
                        (exchange-point-and-mark))
               (goto-char oldpos)))))
       (defun ,(intern (concat (symbol-name name) "-2")) ()
         ,(concat docstring
                  "This function assumes that the block constituting the second part\n"
                  "is already marked and active.\n\n"
                  (format "See also `%s'." (concat (symbol-name name) "-1")))
         (interactive)
         (when (use-region-p)
           (when (> (point) (mark))
             (exchange-point-and-mark))
           (when (looking-at ,open-brace)
             (let ((beg (point))
                   (end (progn (forward-sexp 1)
                               (point))))
               (goto-char beg)
               (skip-syntax-backward " ")
               (backward-char)
               (deactivate-mark)
               (,mark-first-part)
               (set-mark end))))))))

(er/c-define-construct er/c-mark-function-call er/c-mark-fully-qualified-name "("
                       "Mark the current function call.")
(er/c-define-construct er/c-mark-statement-block er/c-mark-statement "{"
                       "Mark the current block construct (like if, for, etc.)")
(er/c-define-construct er/c-mark-vector-access er/c-mark-fully-qualified-name "\\["
                       "Mark the current vector access.")

(defun er/add-cc-mode-expansions ()
  "Adds expansions for buffers in c-mode."
  (set (make-local-variable 'er/try-expand-list)
       (append er/try-expand-list
               '(er/c-mark-statement
                 er/c-mark-fully-qualified-name
                 er/c-mark-function-call-1   er/c-mark-function-call-2
                 er/c-mark-statement-block-1 er/c-mark-statement-block-2
                 er/c-mark-vector-access-1   er/c-mark-vector-access-2))))

(er/enable-mode-expansions 'c-mode #'er/add-cc-mode-expansions)
(er/enable-mode-expansions 'c++-mode #'er/add-cc-mode-expansions)
(er/enable-mode-expansions 'objc-mode #'er/add-cc-mode-expansions)
(er/enable-mode-expansions 'java-mode #'er/add-cc-mode-expansions)
(er/enable-mode-expansions 'idl-mode #'er/add-cc-mode-expansions)
(er/enable-mode-expansions 'pike-mode #'er/add-cc-mode-expansions)
(er/enable-mode-expansions 'awk-mode #'er/add-cc-mode-expansions)

(provide 'cc-mode-expansions)

;; cc-mode-expansions.el ends here
