From 2c578850246f76ada633e8af447a1c12958f267a Mon Sep 17 00:00:00 2001 From: Luke Lee Date: Mon, 7 Jul 2014 16:59:32 +0800 Subject: [PATCH] HideIfDef mode bug fixes and enhancements. This is #3 of 3 patches based on the completed work posted on http://www.emacswiki.org/emacs/HideIfDef. - Add macro evaluation function and key binding for a marked region. - Merge continuous "..." lines into one. - Fix old hideif bugs that fail to hide the correct #elif regions - Support hide/show commands in a marked region. - Expand top level for .h files to prevent re-inclusion protection. - Change maintainer. * lisp/progmodes/hideif.el (hide-ifdef-env): Change to global. (hide-ifdef-env-backup): New variable. (hide-ifdef-expand-reinclusion-protection, hide-ifdef-header-regexp): New customizable variables. (hif-clear-all-ifdef-defined): New defun. (hif-merge-ifdef-region, hide-ifdef-region-internal, hide-ifdef-region) (hif-show-ifdef-region): Merge hidden regions to prevent continuous "...". (hif-tokenize): Fix for MS-DOS/Win EOL style. (hif-endif-to-ifdef, hif-make-range, hif-find-range, hif-possibly-hide): Fix bug to hide the correct #elif region(s). (hif-range-elif): New defun. (hif-recurse-level): New var. (hif-evaluate-region, hif-evaluate-macro): New defun. (hide-ifdef-guts): Prevent reinclusion protected C/C++ headers from fully hidden. (hide-ifdef-define, hide-ifdefs, hide-ifdef-block, show-ifdef-block): Better interaction. --- etc/NEWS | 10 + lisp/ChangeLog | 20 + lisp/progmodes/hideif.el | 772 ++++++++++++++++++++++++++------------- 3 files changed, 553 insertions(+), 249 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index f46a8aa499f..e8440d1ba4d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -119,6 +119,16 @@ protocols as well as for "telnet" and "ftp" are passed to Tramp. fitting for use in money calculations; factorial works with non-integer inputs. +** HideIfDef mode now support full C/C++ expressions, argumented macro expansions +, interactive macro evaluation and automatic scanning of #defined symbols. + +*** New custom variable `hide-ifdef-header-regexp' to define C/C++ header file +name patterns. Default case-insensitive .h, .hh, .hpp and .hxx. +*** New custom variable `hide-ifdef-expand-reinclusion-protection' to prevent +reinclusion protected header files from being fully hidden. +*** New custom variable `hide-ifdef-exclude-define-regexp' to define symbol +name patterns (e.x. all "FOR_DOXYGEN_ONLY_*") to be excluded. + ** Obsolete packages --- diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 5c91db50037..6bda0e122db 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,23 @@ +2014-07-07 Luke Lee + + * lisp/progmodes/hideif.el (hide-ifdef-env): Change to global. + (hide-ifdef-env-backup): New variable. + (hide-ifdef-expand-reinclusion-protection, hide-ifdef-header-regexp): + New customizable variables. + (hif-clear-all-ifdef-defined): New defun. + (hif-merge-ifdef-region, hide-ifdef-region-internal, hide-ifdef-region) + (hif-show-ifdef-region): Merge hidden regions to prevent continuous "...". + (hif-tokenize): Fix for MS-DOS/Win EOL style. + (hif-endif-to-ifdef, hif-make-range, hif-find-range, hif-possibly-hide): + Fix bug to hide the correct #elif region(s). + (hif-range-elif): New defun. + (hif-recurse-level): New var. + (hif-evaluate-region, hif-evaluate-macro): New defun. + (hide-ifdef-guts): Prevent reinclusion protected C/C++ headers from + fully hidden. + (hide-ifdef-define, hide-ifdefs, hide-ifdef-block, show-ifdef-block): + Better interaction. + 2014-07-04 Michael Albinus * net/dbus.el (dbus-peer-handler): New defun. diff --git a/lisp/progmodes/hideif.el b/lisp/progmodes/hideif.el index f6562f32e94..d60c009e66d 100644 --- a/lisp/progmodes/hideif.el +++ b/lisp/progmodes/hideif.el @@ -4,7 +4,7 @@ ;; Author: Brian Marick ;; Daniel LaLiberte -;; Maintainer: emacs-devel@gnu.org +;; Maintainer: Luke Lee ;; Keywords: c, outlines ;; This file is part of GNU Emacs. @@ -141,6 +141,36 @@ :type 'string :version "24.5") +(defcustom hide-ifdef-expand-reinclusion-protection t + "Non-nil means don't hide an entire header file enclosed by #ifndef...#endif. +Most C/C++ headers are usually wrapped with ifdefs to prevent re-inclusion: + + ----- beginning of file ----- + #ifndef _XXX_HEADER_FILE_INCLUDED_ + #define _XXX_HEADER_FILE_INCLUDED_ + xxx + xxx + xxx... + #endif + ----- end of file ----- + +The first time we visit such a file, _XXX_HEADER_FILE_INCLUDED_ is +undefined, and so nothing is hidden. The next time we visit it, everything will +be hidden. + +This behavior is generally undesirable. If this option is non-nil, the outermost +#if is always visible." + :type 'boolean + :version "24.5") + +(defcustom hide-ifdef-header-regexp + "\\.h\\(h\\|xx\\|pp\\)?\\'" + "C/C++ header file name patterns to determine if current buffer is a header. +Effective only if `hide-ifdef-expand-reinclusion-protection' is t." + :type 'string + :group 'hide-ifdef + :version "24.5") + (defvar hide-ifdef-mode-submap ;; Set up the submap that goes after the prefix key. (let ((map (make-sparse-keymap))) @@ -153,6 +183,8 @@ (define-key map "s" 'show-ifdefs) (define-key map "\C-d" 'hide-ifdef-block) (define-key map "\C-s" 'show-ifdef-block) + (define-key map "e" 'hif-evaluate-macro) + (define-key map "C" 'hif-clear-all-ifdef-defined) (define-key map "\C-q" 'hide-ifdef-toggle-read-only) (define-key map "\C-w" 'hide-ifdef-toggle-shadowing) @@ -217,6 +249,11 @@ (defvar hide-ifdef-env nil "An alist of defined symbols and their values.") +(defvar hide-ifdef-env-backup nil + "This variable is a backup of the previously cleared `hide-ifdef-env'. +This backup prevents any accidental clearance of `hide-fidef-env' by +`hif-clear-all-ifdef-defined'.") + (defvar hif-outside-read-only nil "Internal variable. Saves the value of `buffer-read-only' while hiding.") @@ -233,53 +270,75 @@ that the C preprocessor would eliminate may be hidden from view. Several variables affect how the hiding is done: `hide-ifdef-env' - An association list of defined and undefined symbols for the - current buffer. Initially, the global value of `hide-ifdef-env' - is used. + An association list of defined and undefined symbols for the + current project. Initially, the global value of `hide-ifdef-env' + is used. This variable was a buffer-local variable, which limits + hideif to parse only one C/C++ file at a time. We've extended + hideif to support parsing a C/C++ project containing multiple C/C++ + source files opened simultaneously in different buffers. Therefore + `hide-ifdef-env' can no longer be buffer local but must be global. `hide-ifdef-define-alist' - An association list of defined symbol lists. + An association list of defined symbol lists. Use `hide-ifdef-set-define-alist' to save the current `hide-ifdef-env' and `hide-ifdef-use-define-alist' to set the current `hide-ifdef-env' from one of the lists in `hide-ifdef-define-alist'. `hide-ifdef-lines' - Set to non-nil to not show #if, #ifdef, #ifndef, #else, and - #endif lines when hiding. + Set to non-nil to not show #if, #ifdef, #ifndef, #else, and + #endif lines when hiding. `hide-ifdef-initially' - Indicates whether `hide-ifdefs' should be called when Hide-Ifdef mode - is activated. + Indicates whether `hide-ifdefs' should be called when Hide-Ifdef mode + is activated. `hide-ifdef-read-only' - Set to non-nil if you want to make buffers read only while hiding. - After `show-ifdefs', read-only status is restored to previous value. + Set to non-nil if you want to make buffers read only while hiding. + After `show-ifdefs', read-only status is restored to previous value. \\{hide-ifdef-mode-map}" :group 'hide-ifdef :lighter " Ifdef" (if hide-ifdef-mode (progn - ;; inherit global values - (set (make-local-variable 'hide-ifdef-env) - (default-value 'hide-ifdef-env)) - (set (make-local-variable 'hide-ifdef-hiding) - (default-value 'hide-ifdef-hiding)) - (set (make-local-variable 'hif-outside-read-only) buffer-read-only) - (set (make-local-variable 'line-move-ignore-invisible) t) - (add-hook 'change-major-mode-hook - (lambda () (hide-ifdef-mode -1)) nil t) - - (add-to-invisibility-spec '(hide-ifdef . t)) - - (if hide-ifdef-initially - (hide-ifdefs) - (show-ifdefs))) + ;; inherit global values + + ;; `hide-ifdef-env' is now a global variable. + ;; We can still simulate the behavior of older hideif versions (i.e. + ;; `hide-ifdef-env' being buffer local) by clearing this variable + ;; (C-c @ C) everytime before hiding current buffer. +;; (set (make-local-variable 'hide-ifdef-env) +;; (default-value 'hide-ifdef-env)) + (set 'hide-ifdef-env (default-value 'hide-ifdef-env)) + ;; Some C/C++ headers might have other ways to prevent reinclusion and + ;; thus would like `hide-ifdef-expand-reinclusion-protection' to be nil. + (set (make-local-variable 'hide-ifdef-expand-reinclusion-protection) + (default-value 'hide-ifdef-expand-reinclusion-protection)) + (set (make-local-variable 'hide-ifdef-hiding) + (default-value 'hide-ifdef-hiding)) + (set (make-local-variable 'hif-outside-read-only) buffer-read-only) + (set (make-local-variable 'line-move-ignore-invisible) t) + (add-hook 'change-major-mode-hook + (lambda () (hide-ifdef-mode -1)) nil t) + + (add-to-invisibility-spec '(hide-ifdef . t)) + + (if hide-ifdef-initially + (hide-ifdefs) + (show-ifdefs))) ;; else end hide-ifdef-mode (kill-local-variable 'line-move-ignore-invisible) (remove-from-invisibility-spec '(hide-ifdef . t)) (when hide-ifdef-hiding (show-ifdefs)))) +(defun hif-clear-all-ifdef-defined () + "Clears all symbols defined in `hide-ifdef-env'. +It will backup this variable to `hide-ifdef-env-backup' before clearing to +prevent accidental clearance." + (interactive) + (when (y-or-n-p "Clear all #defined symbols? ") + (setq hide-ifdef-env-backup hide-ifdef-env) + (setq hide-ifdef-env nil))) (defun hif-show-all () "Show all of the text in the current buffer." @@ -299,16 +358,64 @@ Several variables affect how the hiding is done: (while (= (logand 1 (skip-chars-backward "\\\\")) 1) (end-of-line 2))) +(defun hif-merge-ifdef-region (start end) + "This function merges nearby ifdef regions to form a bigger overlay. +The region is defined by START and END. This will decrease the number of +overlays created." + ;; Generally there is no need to call itself recursively since there should + ;; originally exists no un-merged regions; however, if a part of the file is + ;; hidden with `hide-ifdef-lines' equals to nil while another part with 't, + ;; this case happens. + ;; TODO: Should we merge? or just create a container overlay? -- this can + ;; prevent `hideif-show-ifdef' expanding too many hidden contents since there + ;; is only a big overlay exists there without any smaller overlays. + (save-restriction + (widen) ; Otherwise `point-min' and `point-max' will be restricted and thus + ; fail to find neighbor overlays + (let ((begovrs (overlays-in + (max (- start 2) (point-min)) + (max (- start 1) (point-min)))) + (endovrs (overlays-in + (min (+ end 1) (point-max)) + (min (+ end 2) (point-max)))) + (ob nil) + (oe nil) + b e) + ;; Merge overlays before START + (dolist (o begovrs) + (when (overlay-get o 'hide-ifdef) + (setq b (min start (overlay-start o)) + e (max end (overlay-end o))) + (move-overlay o b e) + (hif-merge-ifdef-region b e) + (setq ob o))) + ;; Merge overlays after END + (dolist (o endovrs) + (when (overlay-get o 'hide-ifdef) + (setq b (min start (overlay-start o)) + e (max end (overlay-end o))) + (move-overlay o b e) + (hif-merge-ifdef-region b e) + (setf oe o))) + ;; If both START and END merging happens, merge into bigger one + (when (and ob oe) + (let ((b (min (overlay-start ob) (overlay-start oe))) + (e (max (overlay-end ob) (overlay-end oe)))) + (delete-overlay oe) + (move-overlay ob b e) + (hif-merge-ifdef-region b e))) + (or ob oe)))) + (defun hide-ifdef-region-internal (start end) - (remove-overlays start end 'hide-ifdef t) + (unless (hif-merge-ifdef-region start end) (let ((o (make-overlay start end))) (overlay-put o 'hide-ifdef t) (if hide-ifdef-shadow - (overlay-put o 'face 'hide-ifdef-shadow) - (overlay-put o 'invisible 'hide-ifdef)))) + (overlay-put o 'face 'hide-ifdef-shadow) + (overlay-put o 'invisible 'hide-ifdef))))) (defun hide-ifdef-region (start end) - "START is the start of a #if or #else form. END is the ending part. + "START is the start of a #if, #elif, or #else form. END is the ending part. Everything including these lines is made invisible." (save-excursion (goto-char start) (hif-end-of-line) (setq start (point)) @@ -317,7 +424,9 @@ Everything including these lines is made invisible." (defun hif-show-ifdef-region (start end) "Everything between START and END is made visible." - (remove-overlays start end 'hide-ifdef t)) + (let ((onum (length (overlays-in start end)))) + (remove-overlays start end 'hide-ifdef t) + (/= onum (length (overlays-in start end))))) ;;===%%SF%% evaluation (Start) === @@ -342,11 +451,11 @@ that form should be displayed.") (defun hif-lookup (var) (or (when (bound-and-true-p semantic-c-takeover-hideif) - (semantic-c-hideif-lookup var)) + (semantic-c-hideif-lookup var)) (let ((val (assoc var hide-ifdef-env))) - (if val - (cdr val) - hif-undefined-symbol)))) + (if val + (cdr val) + hif-undefined-symbol)))) (defun hif-defined (var) (cond @@ -362,24 +471,22 @@ that form should be displayed.") ;;===%%SF%% parsing (Start) === ;;; The code that understands what ifs and ifdef in files look like. -(defconst hif-cpp-prefix "\\(^\\|\r\\)[ \t]*#[ \t]*") -(defconst hif-ifxdef-regexp (concat hif-cpp-prefix "if\\(n\\)?def")) -(defconst hif-ifndef-regexp (concat hif-cpp-prefix "ifndef")) -(defconst hif-ifx-regexp (concat hif-cpp-prefix "if\\(n?def\\)?[ \t]+")) -(defconst hif-elif-regexp (concat hif-cpp-prefix "elif")) -(defconst hif-else-regexp (concat hif-cpp-prefix "else")) -(defconst hif-endif-regexp (concat hif-cpp-prefix "endif")) +(defconst hif-cpp-prefix "\\(^\\|\r\\)[ \t]*#[ \t]*") +(defconst hif-ifxdef-regexp (concat hif-cpp-prefix "if\\(n\\)?def")) +(defconst hif-ifndef-regexp (concat hif-cpp-prefix "ifndef")) +(defconst hif-ifx-regexp (concat hif-cpp-prefix "if\\(n?def\\)?[ \t]+")) +(defconst hif-elif-regexp (concat hif-cpp-prefix "elif")) +(defconst hif-else-regexp (concat hif-cpp-prefix "else")) +(defconst hif-endif-regexp (concat hif-cpp-prefix "endif")) (defconst hif-ifx-else-endif-regexp (concat hif-ifx-regexp "\\|" hif-elif-regexp "\\|" hif-else-regexp "\\|" hif-endif-regexp)) (defconst hif-macro-expr-prefix-regexp (concat hif-cpp-prefix "\\(if\\(n?def\\)?\\|elif\\|define\\)[ \t]+")) -(defconst hif-white-regexp "[ \t]*") -(defconst hif-define-regexp - (concat hif-cpp-prefix "\\(define\\|undef\\)")) -(defconst hif-id-regexp - (concat "[[:alpha:]_][[:alnum:]_]*")) +(defconst hif-white-regexp "[ \t]*") +(defconst hif-define-regexp (concat hif-cpp-prefix "\\(define\\|undef\\)")) +(defconst hif-id-regexp (concat "[[:alpha:]_][[:alnum:]_]*")) (defconst hif-macroref-regexp (concat hif-white-regexp "\\(" hif-id-regexp "\\)" hif-white-regexp "\\(" @@ -398,9 +505,9 @@ that form should be displayed.") (defconst hif-token-alist '(("||" . hif-or) ("&&" . hif-and) - ("|" . hif-logior) + ("|" . hif-logior) ("^" . hif-logxor) - ("&" . hif-logand) + ("&" . hif-logand) ("<<" . hif-shiftleft) (">>" . hif-shiftright) ("==" . hif-equal) @@ -408,22 +515,22 @@ that form should be displayed.") ;; expression syntax, because they are still relevant for the tokenizer, ;; especially in conjunction with ##. ("=" . hif-assign) - ("!=" . hif-notequal) + ("!=" . hif-notequal) ("##" . hif-token-concat) ("!" . hif-not) ("~" . hif-lognot) ("(" . hif-lparen) (")" . hif-rparen) - (">" . hif-greater) - ("<" . hif-less) - (">=" . hif-greater-equal) - ("<=" . hif-less-equal) - ("+" . hif-plus) - ("-" . hif-minus) + (">" . hif-greater) + ("<" . hif-less) + (">=" . hif-greater-equal) + ("<=" . hif-less-equal) + ("+" . hif-plus) + ("-" . hif-minus) ("*" . hif-multiply) ("/" . hif-divide) ("%" . hif-modulo) - ("?" . hif-conditional) + ("?" . hif-conditional) (":" . hif-colon) ("," . hif-comma) ("#" . hif-stringify) @@ -465,23 +572,23 @@ that form should be displayed.") (setq hif-simple-token-only t) (with-syntax-table hide-ifdef-syntax-table (save-excursion - (goto-char start) - (while (progn (forward-comment (point-max)) (< (point) end)) - ;; (message "expr-start = %d" expr-start) (sit-for 1) - (cond - ((looking-at "\\\\\n") - (forward-char 2)) + (goto-char start) + (while (progn (forward-comment (point-max)) (< (point) end)) + ;; (message "expr-start = %d" expr-start) (sit-for 1) + (cond + ((looking-at "\\\\\n") + (forward-char 2)) ((looking-at hif-string-literal-regexp) (push (substring-no-properties (match-string 1)) token-list) (goto-char (match-end 0))) - ((looking-at hif-token-regexp) + ((looking-at hif-token-regexp) (let ((token (buffer-substring-no-properties (point) (match-end 0)))) - (goto-char (match-end 0)) - ;; (message "token: %s" token) (sit-for 1) - (push + (goto-char (match-end 0)) + ;; (message "token: %s" token) (sit-for 1) + (push (or (cdr (assoc token hif-token-alist)) (if (string-equal token "defined") 'hif-defined) ;; TODO: @@ -500,7 +607,9 @@ that form should be displayed.") (setq hif-simple-token-only nil))) token-list))) - (t (error "Bad #if expression: %s" (buffer-string))))))) + ((looking-at "\r") ; Sometimes MS-Windows user will leave CR in + (forward-char 1)) ; the source code. Let's not get stuck here. + (t (error "Bad #if expression: %s" (buffer-string))))))) (nreverse token-list))) @@ -573,7 +682,7 @@ that form should be displayed.") (defun hif-expand-token-list (tokens &optional macroname expand_list) "Perform expansion on TOKENS till everything expanded. Self-reference (directly or indirectly) tokens are not expanded. -EXPAND_LIST is the list of macro names currently being expanded, use for +EXPAND_LIST is the list of macro names currently being expanded, used for detecting self-reference." (catch 'self-referencing (let ((expanded nil) @@ -657,28 +766,28 @@ macros to prevent self-reference." "Parse an exprlist: expr { ',' expr}." (let ((result (hif-expr))) (if (eq hif-token 'hif-comma) - (let ((temp (list result))) - (while - (progn - (hif-nexttoken) - (push (hif-expr) temp) - (eq hif-token 'hif-comma))) - (cons 'hif-comma (nreverse temp))) + (let ((temp (list result))) + (while + (progn + (hif-nexttoken) + (push (hif-expr) temp) + (eq hif-token 'hif-comma))) + (cons 'hif-comma (nreverse temp))) result))) (defun hif-expr () "Parse an expression as found in #if. - expr : or-expr | or-expr '?' expr ':' expr." +expr : or-expr | or-expr '?' expr ':' expr." (let ((result (hif-or-expr)) - middle) + middle) (while (eq hif-token 'hif-conditional) (hif-nexttoken) (setq middle (hif-expr)) (if (eq hif-token 'hif-colon) - (progn - (hif-nexttoken) - (setq result (list 'hif-conditional result middle (hif-expr)))) - (error "Error: unexpected token: %s" hif-token))) + (progn + (hif-nexttoken) + (setq result (list 'hif-conditional result middle (hif-expr)))) + (error "Error: unexpected token: %s" hif-token))) result)) (defun hif-or-expr () @@ -767,7 +876,7 @@ macros to prevent self-reference." "Parse an expression with *,/,%. muldiv : factor | muldiv '*|/|%' factor." (let ((result (hif-factor)) - (math-op nil)) + (math-op nil)) (while (memq hif-token '(hif-multiply hif-divide hif-modulo)) (setq math-op hif-token) (hif-nexttoken) @@ -791,20 +900,20 @@ factor : '!' factor | '~' factor | '(' expr ')' | 'defined(' id ')' | (hif-nexttoken) (let ((result (hif-exprlist))) (if (not (eq hif-token 'hif-rparen)) - (error "Bad token in parenthesized expression: %s" hif-token) - (hif-nexttoken) - result))) + (error "Bad token in parenthesized expression: %s" hif-token) + (hif-nexttoken) + result))) ((eq hif-token 'hif-defined) (hif-nexttoken) (let ((paren (when (eq hif-token 'hif-lparen) (hif-nexttoken) t)) - (ident hif-token)) + (ident hif-token)) (if (memq hif-token '(or and not hif-defined hif-lparen hif-rparen)) - (error "Error: unexpected token: %s" hif-token)) + (error "Error: unexpected token: %s" hif-token)) (when paren - (hif-nexttoken) + (hif-nexttoken) (unless (eq hif-token 'hif-rparen) - (error "Error: expected \")\" after identifier"))) + (error "Error: expected \")\" after identifier"))) (hif-nexttoken) `(hif-defined (quote ,ident)))) @@ -817,7 +926,7 @@ factor : '!' factor | '~' factor | '(' expr ')' | 'defined(' id ')' | ((memq hif-token '(hif-minus hif-plus)) (list (prog1 hif-token (hif-nexttoken)) 0 (hif-factor))) - (t ; identifier + (t ; identifier (let ((ident hif-token)) (hif-nexttoken) (if (eq hif-token 'hif-lparen) @@ -994,7 +1103,7 @@ preprocessing token" (setq prev (hif-token-concat prev (cadr l))) (setq l (cddr l))) (if prev - (setq result (append result (list prev)))) + (setq result (append result (list prev)))) (setq prev (car l) l (cdr l))) (if prev @@ -1068,11 +1177,11 @@ preprocessing token" (defun hif-invoke (macro-name actual-parms) "Invoke a macro by expanding it, reparse macro-body and finally invoke it." - ;; Reparse the macro body and evaluate it - (funcall hide-ifdef-evaluator - (hif-parse-exp - (hif-macro-supply-arguments macro-name actual-parms) - macro-name))) + ;; Reparse the macro body and evaluate it + (funcall hide-ifdef-evaluator + (hif-parse-exp + (hif-macro-supply-arguments macro-name actual-parms) + macro-name))) ;;;----------- end of parser ----------------------- @@ -1158,12 +1267,12 @@ Do this when cursor is at the beginning of `regexp' (i.e. #ifX)." (hif-ifdef-to-endif)) ; Find outer endif or else ((hif-looking-at-elif) (hif-ifdef-to-endif)) - ((hif-looking-at-else) + ((hif-looking-at-else) (hif-ifdef-to-endif)) ; Find endif following else - ((hif-looking-at-endif) - 'done) - (t - (error "Mismatched #ifdef #endif pair")))) + ((hif-looking-at-endif) + 'done) + (t + (error "Mismatched #ifdef #endif pair")))) (defun hif-endif-to-ifdef () @@ -1172,15 +1281,18 @@ Do this when cursor is at the beginning of `regexp' (i.e. #ifX)." (let ((start (point))) (hif-find-previous-relevant) (if (= start (point)) - (error "Mismatched #ifdef #endif pair"))) + (error "Mismatched #ifdef #endif pair"))) (cond ((hif-looking-at-endif) - (hif-endif-to-ifdef) ; find beginning of nested if - (hif-endif-to-ifdef)) ; find beginning of outer if or else - ((hif-looking-at-else) - (hif-endif-to-ifdef)) - ((hif-looking-at-ifX) - 'done) - (t))) ; never gets here + (hif-endif-to-ifdef) ; Find beginning of nested if + (hif-endif-to-ifdef)) ; Find beginning of outer if or else + ((hif-looking-at-elif) + (hif-endif-to-ifdef)) + ((hif-looking-at-else) + (hif-endif-to-ifdef)) + ((hif-looking-at-ifX) + 'done) + (t + (error "Mismatched #endif")))) ; never gets here (defun forward-ifdef (&optional arg) @@ -1274,26 +1386,25 @@ With argument, do this that many times." ;;===%%SF%% hide-ifdef-hiding (Start) === -;;; A range is a structure with four components: -;;; ELSE-P True if there was an else clause for the ifdef. -;;; START The start of the range. (beginning of line) -;;; ELSE The else marker (beginning of line) -;;; Only valid if ELSE-P is true. -;;; END The end of the range. (beginning of line) +;; A range is a structure with four components: +;; START The start of the range. (beginning of line) +;; ELSE The else marker (beginning of line) +;; END The end of the range. (beginning of line) +;; ELIF A sequence of #elif markers (beginning of line) -(defsubst hif-make-range (start end &optional else) - (list start else end)) +(defsubst hif-make-range (start end &optional else elif) + (list start else end elif)) (defsubst hif-range-start (range) (elt range 0)) (defsubst hif-range-else (range) (elt range 1)) (defsubst hif-range-end (range) (elt range 2)) +(defsubst hif-range-elif (range) (elt range 3)) - -;;; Find-Range -;;; The workhorse, it delimits the #if region. Reasonably simple: -;;; Skip until an #else or #endif is found, remembering positions. If -;;; an #else was found, skip some more, looking for the true #endif. +;; Find-Range +;; The workhorse, it delimits the #if region. Reasonably simple: +;; Skip until an #else or #endif is found, remembering positions. If +;; an #else was found, skip some more, looking for the true #endif. (defun hif-find-range () "Return a Range structure describing the current #if region. @@ -1302,29 +1413,33 @@ Point is left unchanged." (save-excursion (beginning-of-line) (let ((start (point)) - (else nil) - (end nil)) - ;; Part one. Look for either #endif or #else. + (elif nil) + (else nil) + (end nil)) + ;; Part one. Look for either #elif, #else or #endif. ;; This loop-and-a-half dedicated to E. Dijkstra. - (while (progn - (hif-find-next-relevant) - (hif-looking-at-ifX)) ; Skip nested ifdef - (hif-ifdef-to-endif)) - ;; Found either a #else or an #endif. - (cond ((hif-looking-at-else) - (setq else (point))) - (t - (setq end (point)))) ; (line-end-position) + (while (and (not else) (not end)) + (while (progn + (hif-find-next-relevant) + (hif-looking-at-ifX)) ; Skip nested ifdef + (hif-ifdef-to-endif)) + ;; Found either a #else, #elif, or an #endif. + (cond ((hif-looking-at-elif) + (setq elif (nconc elif (list (point))))) + ((hif-looking-at-else) + (setq else (point))) + (t + (setq end (point))))) ;; If found #else, look for #endif. (when else - (while (progn - (hif-find-next-relevant) - (hif-looking-at-ifX)) ; Skip nested ifdef - (hif-ifdef-to-endif)) - (if (hif-looking-at-else) - (error "Found two elses in a row? Broken!")) - (setq end (point))) ; (line-end-position) - (hif-make-range start end else)))) + (while (progn + (hif-find-next-relevant) + (hif-looking-at-ifX)) ; Skip nested ifdef + (hif-ifdef-to-endif)) + (if (hif-looking-at-else) + (error "Found two elses in a row? Broken!")) + (setq end (point))) ; (line-end-position) + (hif-make-range start end else elif)))) ;; A bit slimy. @@ -1339,70 +1454,180 @@ Does nothing if `hide-ifdef-lines' is nil." (line-beginning-position) (progn (hif-end-of-line) (point)))))) -;;; Hif-Possibly-Hide -;;; There are four cases. The #ifX expression is "taken" if it -;;; the hide-ifdef-evaluator returns T. Presumably, this means the code -;;; inside the #ifdef would be included when the program was -;;; compiled. -;;; -;;; Case 1: #ifX taken, and there's an #else. -;;; The #else part must be hidden. The #if (then) part must be -;;; processed for nested #ifX's. -;;; Case 2: #ifX taken, and there's no #else. -;;; The #if part must be processed for nested #ifX's. -;;; Case 3: #ifX not taken, and there's an #else. -;;; The #if part must be hidden. The #else part must be processed -;;; for nested #ifs. -;;; Case 4: #ifX not taken, and there's no #else. -;;; The #ifX part must be hidden. -;;; -;;; Further processing is done by narrowing to the relevant region -;;; and just recursively calling hide-ifdef-guts. -;;; -;;; When hif-possibly-hide returns, point is at the end of the -;;; possibly-hidden range. - -(defun hif-recurse-on (start end) +;; Hif-Possibly-Hide +;; There are four cases. The #ifX expression is "taken" if it +;; the hide-ifdef-evaluator returns T. Presumably, this means the code +;; inside the #ifdef would be included when the program was +;; compiled. +;; +;; Case 1: #ifX taken, and there's an #else. +;; The #else part must be hidden. The #if (then) part must be +;; processed for nested #ifX's. +;; Case 2: #ifX taken, and there's no #else. +;; The #if part must be processed for nested #ifX's. +;; Case 3: #ifX not taken, and there's an #elif +;; The #if part must be hidden, and then evaluate +;; the #elif condition like a new #ifX. +;; Case 4: #ifX not taken, and there's just an #else. +;; The #if part must be hidden. The #else part must be processed +;; for nested #ifs. +;; Case 5: #ifX not taken, and there's no #else. +;; The #ifX part must be hidden. +;; +;; Further processing is done by narrowing to the relevant region +;; and just recursively calling hide-ifdef-guts. +;; +;; When hif-possibly-hide returns, point is at the end of the +;; possibly-hidden range. + +(defvar hif-recurse-level 0) + +(defun hif-recurse-on (start end &optional dont-go-eol) "Call `hide-ifdef-guts' after narrowing to end of START line and END line." (save-excursion (save-restriction (goto-char start) - (end-of-line) + (unless dont-go-eol + (end-of-line)) (narrow-to-region (point) end) - (hide-ifdef-guts)))) + (let ((hif-recurse-level (1+ hif-recurse-level))) + (hide-ifdef-guts))))) -(defun hif-possibly-hide () +(defun hif-possibly-hide (expand-reinclusion) "Called at #ifX expression, this hides those parts that should be hidden. -It uses the judgment of `hide-ifdef-evaluator'." +It uses the judgment of `hide-ifdef-evaluator'. EXPAND-REINCLUSION is a flag +indicating that we should expand the #ifdef even if it should be hidden. +Refer to `hide-ifdef-expand-reinclusion-protection' for more details." ;; (message "hif-possibly-hide") (sit-for 1) - (let ((test (hif-canonicalize hif-ifx-regexp)) - (range (hif-find-range))) + (let* ((case-fold-search nil) + (test (hif-canonicalize hif-ifx-regexp)) + (range (hif-find-range)) + (elifs (hif-range-elif range)) + (if-part t) ; Everytime we start from if-part + (complete nil)) ;; (message "test = %s" test) (sit-for 1) (hif-hide-line (hif-range-end range)) - (if (not (hif-not (funcall hide-ifdef-evaluator test))) - (cond ((hif-range-else range) ; case 1 - (hif-hide-line (hif-range-else range)) - (hide-ifdef-region (hif-range-else range) - (1- (hif-range-end range))) - (hif-recurse-on (hif-range-start range) - (hif-range-else range))) - (t ; case 2 - (hif-recurse-on (hif-range-start range) - (hif-range-end range)))) - (cond ((hif-range-else range) ; case 3 - (hif-hide-line (hif-range-else range)) - (hide-ifdef-region (hif-range-start range) - (1- (hif-range-else range))) - (hif-recurse-on (hif-range-else range) - (hif-range-end range))) - (t ; case 4 - (hide-ifdef-region (point) - (1- (hif-range-end range)))))) + (while (not complete) + (if (and (not (and expand-reinclusion if-part)) + (hif-not (funcall hide-ifdef-evaluator test))) + ;; ifX/elif is FALSE + (if elifs + ;; Case 3 - Hide the #ifX and eval #elif + (let ((newstart (car elifs))) + (hif-hide-line (hif-range-start range)) + (hide-ifdef-region (hif-range-start range) + (1- newstart)) + (setcar range newstart) + (goto-char newstart) + (setq elifs (cdr elifs)) + (setq test (hif-canonicalize hif-elif-regexp))) + + ;; Check for #else + (cond ((hif-range-else range) + ;; Case 4 - #else block visible + (hif-hide-line (hif-range-else range)) + (hide-ifdef-region (hif-range-start range) + (1- (hif-range-else range))) + (hif-recurse-on (hif-range-else range) + (hif-range-end range))) + (t + ;; Case 5 - No #else block, hide #ifX + (hide-ifdef-region (point) + (1- (hif-range-end range))))) + (setq complete t)) + + ;; ifX/elif is TRUE + (cond (elifs + ;; Luke fix: distinguish from #elif..#elif to #elif..#else + (let ((elif (car elifs))) + ;; hide all elifs + (hif-hide-line elif) + (hide-ifdef-region elif (1- (hif-range-end range))) + (hif-recurse-on (hif-range-start range) + elif))) + ((hif-range-else range) + ;; Case 1 - Hide #elif and #else blocks, recurse #ifX + (hif-hide-line (hif-range-else range)) + (hide-ifdef-region (hif-range-else range) + (1- (hif-range-end range))) + (hif-recurse-on (hif-range-start range) + (hif-range-else range))) + (t + ;; Case 2 - No #else, just recurse #ifX + (hif-recurse-on (hif-range-start range) + (hif-range-end range)))) + (setq complete t)) + (setq if-part nil)) + + ;; complete = t (hif-hide-line (hif-range-start range)) ; Always hide start. (goto-char (hif-range-end range)) (end-of-line))) +(defun hif-evaluate-region (start end) + (let* ((tokens (ignore-errors ; Prevent C statement things like + ; 'do { ... } while (0)' + (hif-tokenize start end))) + (expr (and tokens + (condition-case nil + (hif-parse-exp tokens) + (error + tokens)))) + (result (funcall hide-ifdef-evaluator expr))) + result)) + +(defun hif-evaluate-macro (rstart rend) + "Evaluate the macro expansion result for a region. +If no region active, find the current #ifdefs and evaluate the result. +Currently it supports only math calculations, strings or argumented macros can +not be expanded." + (interactive "r") + (let ((case-fold-search nil)) + (save-excursion + (unless mark-active + (setq rstart nil rend nil) + (beginning-of-line) + (when (and (re-search-forward hif-macro-expr-prefix-regexp nil t) + (string= "define" (match-string 2))) + (re-search-forward hif-macroref-regexp nil t))) + (let* ((start (or rstart (point))) + (end (or rend (progn (hif-end-of-line) (point)))) + (defined nil) + (simple 't) + (tokens (ignore-errors ; Prevent C statement things like + ; 'do { ... } while (0)' + (hif-tokenize start end))) + (expr (or (and (<= (length tokens) 1) ; Simple token + (setq defined (assoc (car tokens) hide-ifdef-env)) + (setq simple (atom (hif-lookup (car tokens)))) + (hif-lookup (car tokens))) + (and tokens + (condition-case nil + (hif-parse-exp tokens) + (error + nil))))) + (result (funcall hide-ifdef-evaluator expr)) + (exprstring (replace-regexp-in-string + ;; Trim off leading/trailing whites + "^[ \t]*\\([^ \t]+\\)[ \t]*" "\\1" + (replace-regexp-in-string + "\\(//.*\\)" "" ; Trim off end-of-line comments + (buffer-substring-no-properties start end))))) + (cond + ((and (<= (length tokens) 1) simple) ; Simple token + (if defined + (message "%S <= `%s'" result exprstring) + (message "`%s' is not defined" exprstring))) + ((integerp result) + (if (or (= 0 result) (= 1 result)) + (message "%S <= `%s'" result exprstring) + (message "%S (0x%x) <= `%s'" result result exprstring))) + ((null result) (message "%S <= `%s'" 'false exprstring)) + ((eq t result) (message "%S <= `%s'" 'true exprstring)) + (t (message "%S <= `%s'" result exprstring))) + result)))) + (defun hif-parse-macro-arglist (str) "Parse argument list formatted as '( arg1 [ , argn] [...] )'. The '...' is also included. Return a list of the arguments, if '...' exists the @@ -1410,7 +1635,7 @@ first arg will be `hif-etc'." (let* ((hif-simple-token-only nil) ; Dynamic binding var for `hif-tokenize' (tokenlist (cdr (hif-tokenize - (- (point) (length str)) (point)))) ; Remove `hif-lparen' + (- (point) (length str)) (point)))) ; Remove `hif-lparen' etc result token) (while (not (eq (setq token (pop tokenlist)) 'hif-rparen)) (cond @@ -1530,15 +1755,20 @@ first arg will be `hif-etc'." It does not do the work that's pointless to redo on a recursive entry." ;; (message "hide-ifdef-guts") (save-excursion - (let ((case-fold-search nil) - min max) + (let* ((case-fold-search t) ; Ignore case for `hide-ifdef-header-regexp' + (expand-header (and hide-ifdef-expand-reinclusion-protection + (string-match hide-ifdef-header-regexp + (buffer-file-name)) + (zerop hif-recurse-level))) + (case-fold-search nil) + min max) (goto-char (point-min)) (setf min (point)) (cl-loop do (setf max (hif-find-any-ifX)) (hif-add-new-defines min max) (if max - (hif-possibly-hide)) + (hif-possibly-hide expand-header)) (setf min (point)) while max)))) @@ -1585,17 +1815,30 @@ It does not do the work that's pointless to redo on a recursive entry." (overlay-put overlay 'face nil) (overlay-put overlay 'invisible 'hide-ifdef)))))) -(defun hide-ifdef-define (var) - "Define a VAR so that #ifdef VAR would be included." - (interactive "SDefine what? ") - (hif-set-var var 1) +(defun hide-ifdef-define (var &optional val) + "Define a VAR to VAL (default 1) in `hide-ifdef-env'. +This allows #ifdef VAR to be hidden." + (interactive + (let* ((default (save-excursion + (beginning-of-line) + (cond ((looking-at hif-ifx-else-endif-regexp) + (forward-word 2) + (current-word 'strict)) + (t + nil)))) + (var (read-minibuffer "Define what? " default)) + (val (read-from-minibuffer (format "Set %s to? (default 1): " var) + nil nil t nil "1"))) + (list var val))) + (hif-set-var var (or val 1)) + (message "%s set to %s" var (or val 1)) + (sleep-for 1) (if hide-ifdef-hiding (hide-ifdefs))) (defun hif-undefine-symbol (var) (setq hide-ifdef-env (delete (assoc var hide-ifdef-env) hide-ifdef-env))) - (defun hide-ifdef-undef (start end) "Undefine a VAR so that #ifdef VAR would not be included." (interactive "r") @@ -1616,20 +1859,23 @@ It does not do the work that's pointless to redo on a recursive entry." Assume that defined symbols have been added to `hide-ifdef-env'. The text hidden is the text that would not be included by the C preprocessor if it were given the file with those symbols defined. +With prefix command presents it will also hide the #ifdefs themselves. Turn off hiding by calling `show-ifdefs'." (interactive) - (message "Hiding...") - (setq hif-outside-read-only buffer-read-only) - (unless hide-ifdef-mode (hide-ifdef-mode 1)) ; turn on hide-ifdef-mode - (if hide-ifdef-hiding - (show-ifdefs)) ; Otherwise, deep confusion. - (setq hide-ifdef-hiding t) - (hide-ifdef-guts) - (setq buffer-read-only (or hide-ifdef-read-only hif-outside-read-only)) - (or nomsg - (message "Hiding done"))) + (let ((hide-ifdef-lines current-prefix-arg)) + (or nomsg + (message "Hiding...")) + (setq hif-outside-read-only buffer-read-only) + (unless hide-ifdef-mode (hide-ifdef-mode 1)) ; Turn on hide-ifdef-mode + (if hide-ifdef-hiding + (show-ifdefs)) ; Otherwise, deep confusion. + (setq hide-ifdef-hiding t) + (hide-ifdef-guts) + (setq buffer-read-only (or hide-ifdef-read-only hif-outside-read-only)) + (or nomsg + (message "Hiding done")))) (defun show-ifdefs () @@ -1645,46 +1891,74 @@ Turn off hiding by calling `show-ifdefs'." Return as (TOP . BOTTOM) the extent of ifdef block." (let (max-bottom) (cons (save-excursion - (beginning-of-line) - (unless (or (hif-looking-at-else) (hif-looking-at-ifX)) - (up-ifdef)) - (prog1 (point) - (hif-ifdef-to-endif) - (setq max-bottom (1- (point))))) - (save-excursion - (beginning-of-line) - (unless (hif-looking-at-endif) - (hif-find-next-relevant)) - (while (hif-looking-at-ifX) - (hif-ifdef-to-endif) - (hif-find-next-relevant)) - (min max-bottom (1- (point))))))) - - -(defun hide-ifdef-block () - "Hide the ifdef block (true or false part) enclosing or before the cursor." - (interactive) - (unless hide-ifdef-mode (hide-ifdef-mode 1)) - (let ((top-bottom (hif-find-ifdef-block))) - (hide-ifdef-region (car top-bottom) (cdr top-bottom)) - (when hide-ifdef-lines - (hif-hide-line (car top-bottom)) - (hif-hide-line (1+ (cdr top-bottom)))) - (setq hide-ifdef-hiding t)) - (setq buffer-read-only (or hide-ifdef-read-only hif-outside-read-only))) - -(defun show-ifdef-block () + (beginning-of-line) + (unless (or (hif-looking-at-else) (hif-looking-at-ifX)) + (up-ifdef)) + (prog1 (point) + (hif-ifdef-to-endif) + (setq max-bottom (1- (point))))) + (save-excursion + (beginning-of-line) + (unless (hif-looking-at-endif) + (hif-find-next-relevant)) + (while (hif-looking-at-ifX) + (hif-ifdef-to-endif) + (hif-find-next-relevant)) + (min max-bottom (1- (point))))))) + + +(defun hide-ifdef-block (&optional arg start end) + "Hide the ifdef block (true or false part) enclosing or before the cursor. +With optional prefix agument ARG, also hide the #ifdefs themselves." + (interactive "P\nr") + (let ((hide-ifdef-lines arg)) + (if mark-active + (let ((hif-recurse-level (1+ hif-recurse-level))) + (hif-recurse-on start end t) + (setq mark-active nil)) + (unless hide-ifdef-mode (hide-ifdef-mode 1)) + (let ((top-bottom (hif-find-ifdef-block))) + (hide-ifdef-region (car top-bottom) (cdr top-bottom)) + (when hide-ifdef-lines + (hif-hide-line (car top-bottom)) + (hif-hide-line (1+ (cdr top-bottom)))) + (setq hide-ifdef-hiding t)) + (setq buffer-read-only + (or hide-ifdef-read-only hif-outside-read-only))))) + +(defun show-ifdef-block (&optional start end) "Show the ifdef block (true or false part) enclosing or before the cursor." - (interactive) - (let ((top-bottom (hif-find-ifdef-block))) + (interactive "r") + (if mark-active + (progn + (dolist (o (overlays-in start end)) + (if (overlay-get o 'hide-ifdef) + (delete-overlay o))) + (setq mark-active nil)) + (let ((top-bottom (condition-case nil + (hif-find-ifdef-block) + (error + nil))) + (ovrs (overlays-in (max (point-min) (1- (point))) + (min (point-max) (1+ (point))))) + (del nil)) + (if top-bottom (if hide-ifdef-lines - (hif-show-ifdef-region - (save-excursion - (goto-char (car top-bottom)) (line-beginning-position)) - (save-excursion - (goto-char (1+ (cdr top-bottom))) - (hif-end-of-line) (point))) - (hif-show-ifdef-region (1- (car top-bottom)) (cdr top-bottom))))) + (hif-show-ifdef-region + (save-excursion + (goto-char (car top-bottom)) (line-beginning-position)) + (save-excursion + (goto-char (1+ (cdr top-bottom))) + (hif-end-of-line) (point))) + (setf del (hif-show-ifdef-region + (1- (car top-bottom)) (cdr top-bottom))))) + (if (not (and top-bottom + del)) + (dolist (o ovrs) + ;;(dolist (o (overlays-in (1- (point)) (1+ (point)))) + ;; (if (overlay-get o 'hide-ifdef) (message "%S" o))) + (if (overlay-get o 'hide-ifdef) + (delete-overlay o))))))) ;;; definition alist support -- 2.39.2