From fabb558250fca14a2684357a95225241253cdfda Mon Sep 17 00:00:00 2001 From: Alan Mackenzie Date: Thu, 9 Jun 2016 12:24:27 +0000 Subject: [PATCH] Handle C++ raw strings. * lisp/progmodes/cc-engine.el (c-raw-string-pos, c-depropertize-raw-string) (c-depropertize-raw-strings-in-region, c-before-change-check-raw-strings) (c-propertize-raw-string-opener, c-after-change-re-mark-raw-strings): New functions. * lisp/progmodes/cc-fonts.el (c-basic-matchers-before): Insert a clause for c-font-lock-raw-strings. (c-font-lock-raw-strings): New function. * lisp/progmodes/cc-langs.el (c-get-state-before-change-functions): Insert c-before-change-check-raw-strings into the C++ value, and c-depropertize-CPP into the values for C, C++, and Objective C. (c-before-font-lock-functions): Insert c-after-change-re-mark-raw-strings into the C++ value. * lisp/progmodes/cc-mode.el (c-old-BEG, c-old-END): New variables. (c-depropertize-CPP): New function, extracted from c-neutralize-syntax-in-and-mark-CPP. (c-neutralize-syntax-in-and-mark-CPP): Remove the call to c-clear-char-property-with-value for 'syntax-table value '(1) at the beginning of the function. (c-after-change): Set c-old-BEG and c-old-END to the current values of c-new-BEG and c-new-END. --- lisp/progmodes/cc-engine.el | 338 +++++++++++++++++++++++++++++++++++- lisp/progmodes/cc-fonts.el | 41 +++++ lisp/progmodes/cc-langs.el | 4 + lisp/progmodes/cc-mode.el | 39 ++++- 4 files changed, 416 insertions(+), 6 deletions(-) diff --git a/lisp/progmodes/cc-engine.el b/lisp/progmodes/cc-engine.el index 4d6a1203c2..7c77b70de5 100644 --- a/lisp/progmodes/cc-engine.el +++ b/lisp/progmodes/cc-engine.el @@ -83,8 +83,9 @@ ;; ;; 'syntax-table ;; Used to modify the syntax of some characters. It is used to -;; mark the "<" and ">" of angle bracket parens with paren syntax, and -;; to "hide" obtrusive characters in preprocessor lines. +;; mark the "<" and ">" of angle bracket parens with paren syntax, to +;; "hide" obtrusive characters in preprocessor lines, and to mark C++ +;; raw strings to enable their fontification. ;; ;; This property is used on single characters and is therefore ;; always treated as front and rear nonsticky (or start and end open @@ -2293,7 +2294,8 @@ comment at the start of cc-engine.el for more info." ;; (STATE TYPE (BEG . END)) if TO is in a literal; or ;; (STATE) otherwise, ;; where STATE is the parsing state at TO, TYPE is the type of the literal - ;; (one of 'c, 'c++, 'string) and (BEG . END) is the boundaries of the literal. + ;; (one of 'c, 'c++, 'string) and (BEG . END) is the boundaries of the literal, + ;; including the delimiters. ;; ;; Unless NOT-IN-DELIMITER is non-nil, when TO is inside a two-character ;; comment opener, this is recognized as being in a comment literal. @@ -5657,6 +5659,9 @@ comment at the start of cc-engine.el for more info." ;; Set by c-common-init in cc-mode.el. (defvar c-new-BEG) (defvar c-new-END) +;; Set by c-after-change in cc-mode.el. +(defvar c-old-BEG) +(defvar c-old-END) (defun c-before-change-check-<>-operators (beg end) ;; Unmark certain pairs of "< .... >" which are currently marked as @@ -5777,6 +5782,333 @@ comment at the start of cc-engine.el for more info." 'c-decl-arg-start))))))) (or (c-forward-<>-arglist nil) (forward-char))))) + + +;; Functions to handle C++ raw strings. +;; +;; A valid C++ raw string looks like +;; R"()" +;; , where is an identifier from 0 to 16 characters long, not containing +;; spaces, control characters, double quote or left/right paren. +;; can include anything which isn't the terminating )", including new +;; lines, "s, parentheses, etc. +;; +;; CC Mode handles C++ raw strings by the use of `syntax-table' text +;; properties as follows: +;; +;; (i) On a validly terminated raw string, no `syntax-table' text properties +;; are applied to the opening and closing delimiters, but any " in the +;; contents is given the property value "punctuation" (`(1)') to prevent it +;; interacting with the "s in the delimiters. +;; +;; The font locking routine `c-font-lock-c++-raw-strings' (in cc-fonts.el) +;; recognizes valid raw strings, and fontifies the delimiters (apart from +;; the parentheses) with the default face and the parentheses and the +;; with font-lock-string-face. +;; +;; (ii) A valid, but unterminated, raw string opening delimiter gets the +;; "punctuation" value (`(1)') of the `syntax-table' text property, and the +;; open parenthesis gets the "string fence" value (`(15)'). +;; +;; `c-font-lock-c++-raw-strings' puts c-font-lock-warning-face on the entire +;; unmatched opening delimiter (from the R up to the open paren), and allows +;; the rest of the buffer to get font-lock-string-face, caused by the +;; unmatched "string fence" `syntax-table' text property value. +;; +;; (iii) Inside a macro, a valid raw string is handled as in (i). An +;; unmatched opening delimiter is handled slightly differently. In addition +;; to the "punctuation" and "string fence" properties on the delimiter, +;; another "string fence" `syntax-table' property is applied to the last +;; possible character of the macro before the terminating linefeed (if there +;; is such a character after the "("). This "last possible" character is +;; never a backslash escaping the end of line. If the character preceding +;; this "last possible" character is itself a backslash, this preceding +;; character gets a "punctuation" `syntax-table' value. If the "(" is +;; already at the end of the macro, it gets the "punctuaion" value, and no +;; "string fence"s are used. +;; +;; The effect on the fontification of either of these tactics is that rest of +;; the macro (if any) after the "(" gets font-lock-string-face, but the rest +;; of the file is fontified normally. + + +(defun c-raw-string-pos () + ;; Get POINT's relationship to any containing raw string. + ;; If point isn't in a raw string, return nil. + ;; Otherwise, return the following list: + ;; + ;; (POS B\" B\( E\) E\") + ;; + ;; , where POS is the symbol `open-delim' if point is in the opening + ;; delimiter, the symbol `close-delim' if it's in the closing delimiter, and + ;; nil if it's in the string body. B\", B\(, E\), E\" are the positions of + ;; the opening and closing quotes and parentheses of a correctly terminated + ;; raw string. (N.B.: E\) and E\" are NOT on the "outside" of these + ;; characters.) If the raw string is not terminated, E\) and E\" are set to + ;; nil. + ;; + ;; Note: this routine is dependant upon the correct syntax-table text + ;; properties being set. + (let* ((safe (c-state-semi-safe-place (point))) + (state (c-state-pp-to-literal safe (point))) + open-quote-pos open-paren-pos close-paren-pos close-quote-pos id) + (save-excursion + (when + (and + (cond + ((null (cadr state)) + (or (eq (char-after) ?\") + (search-backward "\"" (max (- (point) 17) (point-min)) t))) + ((and (eq (cadr state) 'string) + (goto-char (car (nth 2 state))) + (or (eq (char-after) ?\") + (search-backward "\"" (max (- (point) 17) (point-min)) t)) + (not (bobp))))) + (eq (char-before) ?R) + (looking-at "\"\\([^ ()\\\n\r\t]\\{,16\\}\\)(")) + (setq open-quote-pos (point) + open-paren-pos (match-end 1) + id (match-string-no-properties 1)) + (goto-char (1+ open-paren-pos)) + (when (and (not (c-get-char-property open-paren-pos 'syntax-table)) + (search-forward (concat ")" id "\"") nil t)) + (setq close-paren-pos (match-beginning 0) + close-quote-pos (1- (point)))))) + (and open-quote-pos + (list + (cond + ((<= (point) open-paren-pos) + 'open-delim) + ((and close-paren-pos + (> (point) close-paren-pos)) + 'close-delim) + (t nil)) + open-quote-pos open-paren-pos close-paren-pos close-quote-pos)))) + +(defun c-depropertize-raw-string (id open-quote open-paren bound) + ;; Point is immediately after a raw string opening delimiter. Remove any + ;; `syntax-table' text properties associated with the delimiter (if it's + ;; unmatched) or the raw string. + ;; + ;; ID, a string, is the delimiter's identifier. OPEN-QUOTE and OPEN-PAREN + ;; are the buffer positions of the delimiter's components. BOUND is the + ;; bound for searching for a matching closing delimiter; it is usually nil, + ;; but if we're inside a macro, it's the end of the macro. + ;; + ;; Point is moved to after the (terminated) raw string, or left after the + ;; unmatched opening delimiter, as the case may be. The return value is of + ;; no significance. + (let ((open-paren-prop (c-get-char-property open-paren 'syntax-table))) + (cond + ((null open-paren-prop) + ;; A terminated raw string + (if (search-forward (concat ")" id "\"") nil t) + (c-clear-char-property-with-value + (1+ open-paren) (match-beginning 0) 'syntax-table '(1)))) + ((or (and (equal open-paren-prop '(15)) (null bound)) + (equal open-paren-prop '(1))) + ;; An unterminated raw string either not in a macro, or in a macro with + ;; the open parenthesis right up against the end of macro + (c-clear-char-property open-quote 'syntax-table) + (c-clear-char-property open-paren 'syntax-table)) + (t + ;; An unterminated string in a macro, with at least one char after the + ;; open paren + (c-clear-char-property open-quote 'syntax-table) + (c-clear-char-property open-paren 'syntax-table) + (let ((after-string-fence-pos + (save-excursion + (goto-char (1+ open-paren)) + (c-search-forward-char-property 'syntax-table '(15) bound)))) + (when after-string-fence-pos + (c-clear-char-property (1- after-string-fence-pos) 'syntax-table))) + )))) + +(defun c-depropertize-raw-strings-in-region (start finish) + ;; Remove any `syntax-table' text properties associated with C++ raw strings + ;; contained in the region (START FINISH). Point is undefined at entry and + ;; exit, and the return value has no significance. + (goto-char start) + (while (and (< (point) finish) + (re-search-forward + (concat "\\(" ; 1 + c-anchored-cpp-prefix ; 2 + "\\)\\|\\(" ; 3 + "R\"\\([^ ()\\\n\r\t]\\{,16\\}\\)(" ; 4 + "\\)") + finish t)) + (when (save-excursion + (goto-char (match-beginning 0)) (not (c-in-literal))) + (if (match-beginning 4) ; the id + ;; We've found a raw string + (c-depropertize-raw-string + (match-string-no-properties 4) ; id + (1+ (match-beginning 3)) ; open quote + (match-end 4) ; open paren + nil) ; bound + ;; We've found a CPP construct. Search for raw strings within it. + (goto-char (match-beginning 2)) ; the "#" + (c-end-of-macro) + (let ((eom (point))) + (goto-char (match-end 2)) ; after the "#". + (while (and (< (point) eom) + (c-syntactic-re-search-forward + "R\"\\([^ ()\\\n\r\t]\\{,16\\}\\)(" eom t)) + (c-depropertize-raw-string + (match-string-no-properties 1) ; id + (1+ (match-beginning 0)) ; open quote + (match-end 1) ; open paren + eom))))))) ; bound. + +(defun c-before-change-check-raw-strings (beg end) + ;; This function clears `syntax-table' text properties from C++ raw strings + ;; in the region (c-new-BEG c-new-END). BEG and END are the standard + ;; arguments supplied to any before-change function. + ;; + ;; Point is undefined on both entry and exit, and the return value has no + ;; significance. + ;; + ;; This function is called as a before-change function solely due to its + ;; membership of the C++ value of `c-get-state-before-change-functions'. + (c-save-buffer-state + ((beg-rs (progn (goto-char beg) (c-raw-string-pos))) + (beg-plus (if (null beg-rs) + beg + (max beg + (1+ (or (nth 4 beg-rs) (nth 2 beg-rs)))))) + (end-rs (progn (goto-char end) (c-raw-string-pos))) ; FIXME!!! + ; Optimize this so that we don't call + ; `c-raw-string-pos' twice when once + ; will do. (2016-06-02). + (end-minus (if (null end-rs) + end + (min end (cadr end-rs)))) + ) + (when beg-rs + (setq c-new-BEG (min c-new-BEG (1- (cadr beg-rs))))) + (c-depropertize-raw-strings-in-region c-new-BEG beg-plus) + + (when end-rs + (setq c-new-END (max c-new-END + (1+ (or (nth 4 end-rs) + (nth 2 end-rs)))))) + (c-depropertize-raw-strings-in-region end-minus c-new-END))) + +(defun c-propertize-raw-string-opener (id open-quote open-paren bound) + ;; Point is immediately after a raw string opening delimiter. Apply any + ;; pertinent `syntax-table' text properties to the delimiter and also the + ;; raw string, should there be a valid matching closing delimiter. + ;; + ;; ID, a string, is the delimiter's identifier. OPEN-QUOTE and OPEN-PAREN + ;; are the buffer positions of the delimiter's components. BOUND is the + ;; bound for searching for a matching closing delimiter; it is usually nil, + ;; but if we're inside a macro, it's the end of the macro. + ;; + ;; Point is moved to after the (terminated) raw string, or left after the + ;; unmatched opening delimiter, as the case may be. The return value is of + ;; no significance. + (if (search-forward (concat ")" id "\"") bound t) + (let ((end-string (match-beginning 0)) + (after-quote (match-end 0))) + (goto-char open-paren) + (while (progn (skip-syntax-forward "^\"" end-string) + (< (point) end-string)) + (c-put-char-property (point) 'syntax-table '(1)) ; punctuation + (forward-char)) + (goto-char after-quote)) + (c-put-char-property open-quote 'syntax-table '(1)) ; punctuation + (c-put-char-property open-paren 'syntax-table '(15)) ; generic string + (when bound + ;; In a CPP construct, we try to apply a generic-string `syntax-table' + ;; text property to the last possible character in the string, so that + ;; only characters within the macro get "stringed out". + (goto-char bound) + (if (save-restriction + (narrow-to-region (1+ open-paren) (point-max)) + (re-search-backward + (eval-when-compile + ;; This regular expression matches either an escape pair (which + ;; isn't an escaped NL) (submatch 5) or a non-escaped character + ;; (which isn't itself a backslash) (submatch 10). The long + ;; preambles to these (respectively submatches 2-4 and 6-9) + ;; ensure that we have the correct parity for sequences of + ;; backslashes, etc.. + (concat "\\(" ; 1 + "\\(\\`[^\\]?\\|[^\\][^\\]\\)\\(\\\\\\(.\\|\n\\)\\)*" ; 2-4 + "\\(\\\\.\\)" ; 5 + "\\|" + "\\(\\`\\|[^\\]\\|\\(\\`[^\\]?\\|[^\\][^\\]\\)\\(\\\\\\(.\\|\n\\)\\)+\\)" ; 6-9 + "\\([^\\]\\)" ; 10 + "\\)" + "\\(\\\\\n\\)*\\=")) ; 11 + (1+ open-paren) t)) + (if (match-beginning 10) + (c-put-char-property (match-beginning 10) 'syntax-table '(15)) + (c-put-char-property (match-beginning 5) 'syntax-table '(1)) + (c-put-char-property (1+ (match-beginning 5)) 'syntax-table '(15))) + (c-put-char-property open-paren 'syntax-table '(1))) + (goto-char bound)))) + +(defun c-after-change-re-mark-raw-strings (beg end old-len) + ;; This function applies `syntax-table' text properties to C++ raw strings + ;; beginning in the region (c-new-BEG c-new-END). BEG, END, and OLD-LEN are + ;; the standard arguments supplied to any after-change function. + ;; + ;; Point is undefined on both entry and exit, and the return value has no + ;; significance. + ;; + ;; This function is called as an after-change function solely due to its + ;; membership of the C++ value of `c-before-font-lock-functions'. + (c-save-buffer-state () + ;; If the region (c-new-BEG c-new-END) has expanded, remove + ;; `syntax-table' text-properties from the new piece(s). + (when (< c-new-BEG c-old-BEG) + (let ((beg-rs (progn (goto-char c-old-BEG) (c-raw-string-pos)))) + (c-depropertize-raw-strings-in-region + c-new-BEG + (if beg-rs + (1+ (or (nth 4 beg-rs) (nth 2 beg-rs))) + c-old-BEG)))) + (when (> c-new-END c-old-END) + (let ((end-rs (progn (goto-char c-old-END) (c-raw-string-pos)))) + (c-depropertize-raw-strings-in-region + (if end-rs + (cadr end-rs) + c-old-END) + c-new-END))) + + (goto-char c-new-BEG) + (while (and (< (point) c-new-END) + (re-search-forward + (concat "\\(" ; 1 + c-anchored-cpp-prefix ; 2 + "\\)\\|\\(" ; 3 + "R\"\\([^ ()\\\n\r\t]\\{,16\\}\\)(" ; 4 + "\\)") + c-new-END t)) + (when (save-excursion + (goto-char (match-beginning 0)) (not (c-in-literal))) + (if (match-beginning 4) ; the id + ;; We've found a raw string. + (c-propertize-raw-string-opener + (match-string-no-properties 4) ; id + (1+ (match-beginning 3)) ; open quote + (match-end 4) ; open paren + nil) ; bound + ;; We've found a CPP construct. Search for raw strings within it. + (goto-char (match-beginning 2)) ; the "#" + (c-end-of-macro) + (let ((eom (point))) + (goto-char (match-end 2)) ; after the "#". + (while (and (< (point) eom) + (c-syntactic-re-search-forward + "R\"\\([^ ()\\\n\r\t]\\{,16\\}\\)(" eom t)) + (c-propertize-raw-string-opener + (match-string-no-properties 1) ; id + (1+ (match-beginning 0)) ; open quote + (match-end 1) ; open paren + eom)))))))) ; bound + ;; Handling of small scale constructs like types and names. diff --git a/lisp/progmodes/cc-fonts.el b/lisp/progmodes/cc-fonts.el index 4e83d6df62..f3f369f5f8 100644 --- a/lisp/progmodes/cc-fonts.el +++ b/lisp/progmodes/cc-fonts.el @@ -723,6 +723,10 @@ casts and declarations are fontified. Used on level 2 and higher." (concat ".\\(" c-string-limit-regexp "\\)") '((c-font-lock-invalid-string))) + ;; Fontify C++ raw strings. + ,@(when (c-major-mode-is 'c++-mode) + '(c-font-lock-raw-strings)) + ;; Fontify keyword constants. ,@(when (c-lang-const c-constant-kwds) (let ((re (c-make-keywords-re nil (c-lang-const c-constant-kwds)))) @@ -1571,6 +1575,43 @@ casts and declarations are fontified. Used on level 2 and higher." (c-forward-syntactic-ws) (c-font-lock-declarators limit t in-typedef))))))) +(defun c-font-lock-raw-strings (limit) + ;; Fontify C++ raw strings. + ;; + ;; This function will be called from font-lock for a region bounded by POINT + ;; and LIMIT, as though it were to identify a keyword for + ;; font-lock-keyword-face. It always returns NIL to inhibit this and + ;; prevent a repeat invocation. See elisp/lispref page "Search-based + ;; Fontification". + (while (search-forward-regexp + "R\\(\"\\)\\([^ ()\\\n\r\t]\\{,16\\}\\)(" limit t) + (when + (or (and (eobp) + (eq (c-get-char-property (1- (point)) 'face) + 'font-lock-warning-face)) + (eq (c-get-char-property (point) 'face) 'font-lock-string-face) + (and (equal (c-get-char-property (match-end 2) 'syntax-table) '(1)) + (equal (c-get-char-property (match-beginning 1) 'syntax-table) + '(1)))) + (let ((paren-prop (c-get-char-property (1- (point)) 'syntax-table))) + (if paren-prop + (progn + (c-put-font-lock-face (match-beginning 0) (match-end 0) + 'font-lock-warning-face) + (when + (and + (equal paren-prop '(15)) + (not (c-search-forward-char-property 'syntax-table '(15) limit))) + (goto-char limit))) + (c-put-font-lock-face (match-beginning 1) (match-end 2) 'default) + (when (search-forward-regexp + (concat ")\\(" (regexp-quote (match-string-no-properties 2)) + "\\)\"") + limit t) + (c-put-font-lock-face (match-beginning 1) (point) + 'default)))))) + nil) + (c-lang-defconst c-simple-decl-matchers "Simple font lock matchers for types and declarations. These are used on level 2 only and so aren't combined with `c-complex-decl-matchers'." diff --git a/lisp/progmodes/cc-langs.el b/lisp/progmodes/cc-langs.el index 18f1cc4374..ba05e81aba 100644 --- a/lisp/progmodes/cc-langs.el +++ b/lisp/progmodes/cc-langs.el @@ -474,9 +474,12 @@ so that all identifiers are recognized as words.") ;; The value here may be a list of functions or a single function. t nil c++ '(c-extend-region-for-CPP + c-before-change-check-raw-strings c-before-change-check-<>-operators + c-depropertize-CPP c-invalidate-macro-cache) (c objc) '(c-extend-region-for-CPP + c-depropertize-CPP c-invalidate-macro-cache) ;; java 'c-before-change-check-<>-operators awk 'c-awk-record-region-clear-NL) @@ -510,6 +513,7 @@ parameters \(point-min) and \(point-max).") c-neutralize-syntax-in-and-mark-CPP c-change-expand-fl-region) c++ '(c-extend-font-lock-region-for-macros + c-after-change-re-mark-raw-strings c-neutralize-syntax-in-and-mark-CPP c-restore-<>-properties c-change-expand-fl-region) diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el index 9ab04808af..6f32613367 100644 --- a/lisp/progmodes/cc-mode.el +++ b/lisp/progmodes/cc-mode.el @@ -665,6 +665,14 @@ that requires a literal mode spec at compile time." (make-variable-buffer-local 'c-new-BEG) (defvar c-new-END 0) (make-variable-buffer-local 'c-new-END) +;; The following two variables record the values of `c-new-BEG' and +;; `c-new-END' just after `c-new-END' has been adjusted for the length of text +;; inserted or removed. They may be read by any after-change function (but +;; should not be altered by one). +(defvar c-old-BEG 0) +(make-variable-buffer-local 'c-old-BEG) +(defvar c-old-END 0) +(make-variable-buffer-local 'c-old-END) (defun c-common-init (&optional mode) "Common initialization for all CC Mode modes. @@ -877,6 +885,31 @@ Note that the style variables are always made local to the buffer." (memq (cadr (backtrace-frame 3)) '(put-text-property remove-list-of-text-properties))) +(defun c-depropertize-CPP (beg end) + ;; Remove the punctuation syntax-table text property from the CPP parts of + ;; (c-new-BEG c-new-END). + ;; + ;; This function is in the C/C++/ObjC values of + ;; `c-get-state-before-change-functions' and is called exclusively as a + ;; before change function. + (goto-char c-new-BEG) + (while (and (< (point) beg) + (search-forward-regexp c-anchored-cpp-prefix beg t)) + (goto-char (match-beginning 1)) + (let ((m-beg (point))) + (c-end-of-macro) + (c-clear-char-property-with-value + m-beg (min (point) beg) 'syntax-table '(1)))) + + (goto-char end) + (while (and (< (point) c-new-END) + (search-forward-regexp c-anchored-cpp-prefix c-new-END t)) + (goto-char (match-beginning 1)) + (let ((m-beg (point))) + (c-end-of-macro) + (c-clear-char-property-with-value + m-beg (min (point) c-new-END) 'syntax-table '(1))))) + (defun c-extend-region-for-CPP (beg end) ;; Adjust `c-new-BEG', `c-new-END' respectively to the beginning and end of ;; any preprocessor construct they may be in. @@ -967,9 +1000,9 @@ Note that the style variables are always made local to the buffer." ;; Note: SPEED _MATTERS_ IN THIS FUNCTION!!! ;; ;; This function might make hidden buffer changes. - (c-save-buffer-state (limits ) + (c-save-buffer-state (limits) ;; Clear 'syntax-table properties "punctuation": - (c-clear-char-property-with-value c-new-BEG c-new-END 'syntax-table '(1)) + ;; (c-clear-char-property-with-value c-new-BEG c-new-END 'syntax-table '(1)) ;; CPP "comment" markers: (if (eval-when-compile (memq 'category-properties c-emacs-features));Emacs. @@ -1125,8 +1158,8 @@ Note that the style variables are always made local to the buffer." ;; (c-new-BEG c-new-END) will be the region to fontify. It may become ;; larger than (beg end). - ;; (setq c-new-BEG beg c-new-END end) (setq c-new-END (- (+ c-new-END (- end beg)) old-len)) + (setq c-old-BEG c-new-BEG c-old-END c-new-END) (unless (c-called-from-text-property-change-p) (setq c-just-done-before-change nil) -- 2.39.2