Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include guard refactors #63

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
251 changes: 246 additions & 5 deletions emr-c.el
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,214 @@ Library and project includes are kept separate."
(s-append "\n")
(insert)))))

; ------------------
;;; include guards

(defun emr-cc-basename-include-guard ()
"Derive an include guard from the buffer's basename.
All non-identifier characters of either the buffer's filename if
available or the buffer's name are replaced by underscores, and
the result is upcased.

E.g. foo.h -> FOO_H."
(let ((name (or (-some-> (buffer-file-name) file-name-nondirectory)
(buffer-name))))
(upcase (replace-regexp-in-string
(rx (or (not (any alpha "_")) (: bos digit)))
"_" name t t))))

(defun emr-cc-basename-included-guard ()
"Like `emr-cc-basename-include-guard', but ends in _INCLUDED."
(concat (emr-cc-basename-include-guard) "_INCLUDED"))

(defcustom emr-cc-include-guard-style #'emr-cc-include-guard
"Function used to derive the include guard symbol.
It will be called with no arguments in the context of the buffer
where `emr-cc-add-include-guard' is called and must return a
string to be used as include guard."
:type '(choice
(const emr-cc-basename-include-guard)
(const emr-cc-basename-included-guard)
(function :tag "custom function"))
:group 'emr-cc)

(defcustom emr-cc-include-guard-value nil
"What include guards are #defined to, or nil.
If this is non-nil, `emr-cc-add-include-guard' will insert this
string after \"#define <symbol> \" (note the space)."
:type '(choice string (const nil))
:group 'emr-cc)

(defcustom emr-cc-include-guard-space nil
"Whether there should be a space after #.
If this is t, `emr-cc-add-include-guard' will insert a space
after the # of #define, #ifndef and #endif. This variable may
also be a string, in which case that is inserted instead."
:type '(choice
(const :tag "Insert a space after #" t)
(const :tag "Don't insert space after #" nil)
(string :tag "Insert after #:"))
:group 'emr-cc)

(defun emr-cc-include-guard-suffix-c89 (guard)
"Insert the include GUARD wrapped in a c89-style comment."
(format "/* %s */" guard))

(defun emr-cc-include-guard-suffix-comment (guard)
"Insert the include GUARD wrapped in a // comment."
(format "// %s" guard))

(defcustom emr-cc-include-guard-suffix nil
"Function to determine the text after #endif.
If this is non-nil, `emr-cc-add-include-guard' will insert the
result of calling this function with the include guard (see
`emr-cc-include-guard-style') as only argument and insert its
result, if non-nil, after \"#endif \" (note the space). If this
variable is a string, it will be `format'ted with the include
guard as second argument and inserted the same way as if it were
a function."
:type '(choice (const :tag "/* GUARD */" emr-cc-include-guard-suffix-c89)
(const :tag "// GUARD" emr-cc-include-guard-suffix-comment)
(const :tag "No #endif suffix" nil)
(function :tag "custom function")
(string :tag "Format string (one argument)")))

(defun emr-cc--include-guard-space (variable)
"Resolve a # spacing VARIABLE.
VARIABLE is the value of the # spacing variable, for example the
value of `emr-cc-include-guard-space'."
(pcase variable
((pred stringp) variable)
(`nil "")
(_ " ")))

(defun emr-cc--beginning-of-header ()
"Go to the start of the source file, skipping comments."
(goto-char (point-min))
;; Skip comment(s) and whitespace (e.g. license header)
(let ((parse-sexp-ignore-comments t))
(with-syntax-table (copy-syntax-table (syntax-table))
(modify-syntax-entry ?\\ " ")
(forward-sexp)))
(beginning-of-line))

(defun emr-cc--end-of-header ()
"Go to bol at the end of the source file, skipping comments."
(goto-char (point-max))
(let ((parse-sexp-ignore-comments t))
(backward-sexp)
(forward-sexp))
(beginning-of-line))

(defun emr-cc-add-include-guard ()
"Add an include guard to the current buffer."
(interactive)
(let ((guard (funcall emr-cc-include-guard-style)))
(save-excursion
(emr-cc--beginning-of-header)
(insert
(format
"#%3$sifndef %1$s
#%3$sdefine %1$s%2$s

"
guard (or emr-cc-include-guard-value "")
(emr-cc--include-guard-space emr-cc-include-guard-space)))
(emr-cc--end-of-header)
(forward-line)
(unless (= (char-before) ?\n)
(insert ?\n))
(insert
(format
"\n#%sendif%s"
(emr-cc--include-guard-space emr-cc-include-guard-space)
(or (-some->>
(-some-> emr-cc-include-guard-suffix
(cl-etypecase
(string (format emr-cc-include-guard-suffix guard))
(function (funcall emr-cc-include-guard-suffix guard))))
(concat " "))
""))))))

(defun emr-cc--looking-at-include-guard ()
"`looking-at' the include guard of the buffer, if it has one."
(save-excursion
(emr-cc--beginning-of-header)
(looking-at
(rx bol (* space) "#" (* space) "ifndef" (* space) (group (+ any) symbol-end) (* any) "\n"
bol (* space) "#" (* space) "define" (* space) (backref 1) symbol-end (* any) "\n"
(? "\n")))))

(defun emr-cc-delete-include-guard ()
"Remove the current buffer's include guard.
Return non-nil if an include guard was actually removed."
(interactive)
(save-match-data
(when (emr-cc--looking-at-include-guard)
(replace-match "")

(save-excursion
(emr-cc--end-of-header)
(when (looking-at (rx bol "#" (* space) "endif" symbol-end (* any) eol))
(replace-match "")
;; We can't know if the file should end in a newline, so don't delete
;; yet another newline (even if it was inserted by
;; `emr-cc-add-include-guard'). User's own whitespace management
;; solutions (e.g. `ws-butler') can fix this.
(when (eq (char-before) ?\n)
(delete-char -1))))
;; Success, even if there was no #endif.
t)))

(defcustom emr-cc-pragma-once-space nil
"`emr-cc-include-guard-space', but for #pragma once."
:type '(choice
(const :tag "Insert a space after #" t)
(const :tag "Don't insert space after #" nil)
(string :tag "Insert after #:"))
:group 'emr-cc)

(defun emr-cc-add-pragma-once ()
"Add #pragma once."
(interactive)
(save-excursion
(emr-cc--beginning-of-header)
(insert (format "#%spragma once\n\n"
(emr-cc--include-guard-space emr-cc-pragma-once-space)))))

(defun emr-cc--looking-at-pragma-once ()
(save-excursion
(emr-cc--beginning-of-header)
(looking-at (rx bol (* space) "#" (* space) "pragma" (* space) "once" (* any) (** 0 2 ?\n)))))

(defun emr-cc-delete-pragma-once ()
"Remove #pragma once.
Return non-nil if a #pragma once was removed."
(interactive)
(save-match-data
(when (emr-cc--looking-at-pragma-once)
(replace-match "")
t)))

(defun emr-cc-toggle-include-guard ()
"Toggle between #pragma once and include guards."
(interactive)
(if (emr-cc-delete-pragma-once)
(emr-cc-add-include-guard)
(unless (emr-cc-delete-include-guard)
(user-error "Current buffer contains neither an include guard nor #pragma once"))
(emr-cc-add-pragma-once)))


(defun emr-c:headers-in-project ()
"Return a list of available C header files.

Find header files in the current project. If this is not a valid
project, return all header files in the current directory."
(->> (-if-let (proj (projectile-project-p))
(--map (concat proj it) (projectile-dir-files proj))
(--map (concat proj it) (projectile-dir-files proj))
(-> (buffer-file-name) (file-name-directory) (directory-files t)))
(--filter (-contains? '("h" "hpp") (file-name-extension it)))
(-map 'file-relative-name)))
(--filter (-contains? '("h" "hpp") (file-name-extension it)))
(-map 'file-relative-name)))

;;;###autoload
(defun emr-c-insert-include (header)
Expand Down Expand Up @@ -235,7 +431,18 @@ Uses either clang-format, if available, or `emr-c-format-fallback-func.'"
"Return nil if a valid region is active."
(not (emr-region-active?)))

; ------------------

(defun emr-cc--has-include-guard? ()
"Check if there is an include guard or #pragma once."
(save-match-data
(or (emr-cc--looking-at-include-guard)
(emr-cc--looking-at-pragma-once))))

(defun emr-cc--need-include-guard? ()
"Check if the buffer has no include guard or #pragma once."
(not (emr-cc--has-include-guard?)))

; ------------------

;;; EMR Declarations

Expand Down Expand Up @@ -280,6 +487,40 @@ Uses either clang-format, if available, or `emr-c-format-fallback-func.'"
:modes '(c-mode)
:predicate (lambda () t))

(emr-declare-command 'emr-cc-add-include-guard
:title "add include guard"
:description "#ifndef X #define X... #endif"
:modes '(c-mode c++-mode)
:predicate #'emr-cc--need-include-guard?)

(emr-declare-command 'emr-cc-delete-include-guard
:title "remove include guard"
:description "remove #ifndef X #define X... #endif"
:modes '(c-mode c++-mode)
:predicate (lambda ()
(save-match-data
(emr-cc--looking-at-include-guard))))

(emr-declare-command 'emr-cc-add-pragma-once
:title "add #pragma once"
:description "#pragma once"
:modes '(c-mode c++-mode)
:predicate #'emr-cc--need-include-guard?)

(emr-declare-command 'emr-cc-delete-pragma-once
:title "remove #pragma once"
:description "remove #pragma once"
:modes '(c-mode c++-mode)
:predicate (lambda ()
(save-match-data
(emr-cc--looking-at-pragma-once))))

(emr-declare-command 'emr-cc-toggle-include-guard
:title "toggle include guard"
:description "toggle between #pragma once and an include guard"
:modes '(c-mode c++-mode)
:predicate #'emr-cc--has-include-guard?)

;; ------------------

(defun emr-c:show-menu ()
Expand Down