From 9657183b6f79dceb468bf70ce0980788dc3f0da7 Mon Sep 17 00:00:00 2001 From: Alan Mackenzie Date: Wed, 18 Jan 2012 13:19:31 +0000 Subject: [PATCH] Eliminate sluggishness and hangs in fontification of "semicolon deserts". cc-engine.el (c-state-nonlit-pos-interval): change value 10000 -> 3000. (c-state-safe-place): Reformulate so it doesn't stack up an infinite number of wrong entries in c-state-nonlit-pos-cache. (c-determine-limit-get-base, c-determine-limit): New functions to determine backward search limits disregarding literals. (c-find-decl-spots): Amend commenting. (c-cheap-inside-bracelist-p): New function which detects "={". cc-fonts.el (c-make-font-lock-BO-decl-search-function): Give a limit to a backward search. (c-font-lock-declarations): Fix an occurrence of point being undefined. Check additionally for point being in a bracelist or near a macro invocation without a semicolon so as to avoid a fruitless time consuming search for a declarator. Give a more precise search limit for declarators using the new c-determine-limit. --- lisp/progmodes/cc-engine.el | 143 +++++++++++++++++++++++++++++++----- lisp/progmodes/cc-fonts.el | 48 ++++++++++-- 2 files changed, 168 insertions(+), 23 deletions(-) diff --git a/lisp/progmodes/cc-engine.el b/lisp/progmodes/cc-engine.el index 2e0294341da..25344fe96a7 100644 --- a/lisp/progmodes/cc-engine.el +++ b/lisp/progmodes/cc-engine.el @@ -2074,7 +2074,7 @@ comment at the start of cc-engine.el for more info." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; We maintain a simple cache of positions which aren't in a literal, so as to ;; speed up testing for non-literality. -(defconst c-state-nonlit-pos-interval 10000) +(defconst c-state-nonlit-pos-interval 3000) ;; The approximate interval between entries in `c-state-nonlit-pos-cache'. (defvar c-state-nonlit-pos-cache nil) @@ -2129,7 +2129,7 @@ comment at the start of cc-engine.el for more info." (widen) (save-excursion (let ((c c-state-nonlit-pos-cache) - pos npos lit) + pos npos lit macro-beg) ;; Trim the cache to take account of buffer changes. (while (and c (> (car c) c-state-nonlit-pos-cache-limit)) (setq c (cdr c))) @@ -2139,16 +2139,32 @@ comment at the start of cc-engine.el for more info." (setq c (cdr c))) (setq pos (or (car c) (point-min))) - (while (<= (setq npos (+ pos c-state-nonlit-pos-interval)) - here) - (setq lit (car (cddr (c-state-pp-to-literal pos npos)))) - (setq pos (or (cdr lit) npos)) ; end of literal containing npos. + (while + ;; Add an element to `c-state-nonlit-pos-cache' each iteration. + (and + (<= (setq npos (+ pos c-state-nonlit-pos-interval)) here) + (progn + (setq lit (car (cddr (c-state-pp-to-literal pos npos)))) + (cond + ((null lit) + (setq pos npos) + t) + ((<= (cdr lit) here) + (setq pos (cdr lit)) + t) + (t + (setq pos (car lit)) + nil)))) + (goto-char pos) (when (and (c-beginning-of-macro) (/= (point) pos)) - (c-syntactic-end-of-macro) - (or (eobp) (forward-char)) - (setq pos (point))) - (setq c-state-nonlit-pos-cache (cons pos c-state-nonlit-pos-cache))) + (setq macro-beg (point)) + (c-syntactic-end-of-macro) + (or (eobp) (forward-char)) + (setq pos (if (<= (point) here) + (point) + macro-beg))) + (setq c-state-nonlit-pos-cache (cons pos c-state-nonlit-pos-cache))) (if (> pos c-state-nonlit-pos-cache-limit) (setq c-state-nonlit-pos-cache-limit pos)) @@ -4351,6 +4367,78 @@ comment at the start of cc-engine.el for more info." (t 'c))) ; Assuming the range is valid. range)) +(defsubst c-determine-limit-get-base (start try-size) + ;; Get a "safe place" approximately TRY-SIZE characters before START. + ;; This doesn't preserve point. + (let* ((pos (max (- start try-size) (point-min))) + (base (c-state-safe-place pos)) + (s (parse-partial-sexp base pos))) + (if (or (nth 4 s) (nth 3 s)) ; comment or string + (nth 8 s) + (point)))) + +(defun c-determine-limit (how-far-back &optional start try-size) + ;; Return a buffer position HOW-FAR-BACK non-literal characters from START + ;; (default point). This is done by going back further in the buffer then + ;; searching forward for literals. The position found won't be in a + ;; literal. We start searching for the sought position TRY-SIZE (default + ;; twice HOW-FAR-BACK) bytes back from START. This function must be fast. + ;; :-) + (save-excursion + (let* ((start (or start (point))) + (try-size (or try-size (* 2 how-far-back))) + (base (c-determine-limit-get-base start try-size)) + (pos base) + + (s (parse-partial-sexp pos pos)) ; null state. + stack elt size + (count 0)) + (while (< pos start) + ;; Move forward one literal each time round this loop. + ;; Move forward to the start of a comment or string. + (setq s (parse-partial-sexp + pos + start + nil ; target-depth + nil ; stop-before + s ; state + 'syntax-table)) ; stop-comment + + ;; Gather details of the non-literal-bit - starting pos and size. + (setq size (- (if (or (nth 4 s) (nth 3 s)) + (nth 8 s) + (point)) + pos)) + (if (> size 0) + (setq stack (cons (cons pos size) stack))) + + ;; Move forward to the end of the comment/string. + (if (or (nth 4 s) (nth 3 s)) + (setq s (parse-partial-sexp + (point) + start + nil ; target-depth + nil ; stop-before + s ; state + 'syntax-table))) ; stop-comment + (setq pos (point))) + + ;; Now try and find enough non-literal characters recorded on the stack. + ;; Go back one recorded literal each time round this loop. + (while (and (< count how-far-back) + stack) + (setq elt (car stack) + stack (cdr stack)) + (setq count (+ count (cdr elt)))) + + ;; Have we found enough yet? + (cond + ((>= count how-far-back) + (+ (car elt) (- count how-far-back))) + ((eq base (point-min)) + (point-min)) + (t + (c-determine-limit (- how-far-back count) base try-size)))))) ;; `c-find-decl-spots' and accompanying stuff. @@ -4487,13 +4575,14 @@ comment at the start of cc-engine.el for more info." ;; Call CFD-FUN for each possible spot for a declaration, cast or ;; label from the point to CFD-LIMIT. ;; - ;; CFD-FUN is called with point at the start of the spot. It's - ;; passed two arguments: The first is the end position of the token - ;; preceding the spot, or 0 for the implicit match at bob. The - ;; second is a flag that is t when the match is inside a macro. If - ;; CFD-FUN adds `c-decl-end' properties somewhere below the current - ;; spot, it should return non-nil to ensure that the next search - ;; will find them. + ;; CFD-FUN is called with point at the start of the spot. It's passed two + ;; arguments: The first is the end position of the token preceding the spot, + ;; or 0 for the implicit match at bob. The second is a flag that is t when + ;; the match is inside a macro. Point should be moved forward by at least + ;; one token. + ;; + ;; If CFD-FUN adds `c-decl-end' properties somewhere below the current spot, + ;; it should return non-nil to ensure that the next search will find them. ;; ;; Such a spot is: ;; o The first token after bob. @@ -4867,7 +4956,8 @@ comment at the start of cc-engine.el for more info." (goto-char cfd-continue-pos) (if (= cfd-continue-pos cfd-limit) (setq cfd-match-pos cfd-limit) - (c-find-decl-prefix-search))))) + (c-find-decl-prefix-search))))) ; Moves point, sets cfd-continue-pos, + ; cfd-match-pos, etc. ;; A cache for found types. @@ -8047,6 +8137,23 @@ comment at the start of cc-engine.el for more info." next-open-brace (c-pull-open-brace paren-state))) open-brace)) +(defun c-cheap-inside-bracelist-p (paren-state) + ;; Return the position of the L-brace if point is inside a brace list + ;; initialization of an array, etc. This is an approximate function, + ;; designed for speed over accuracy. It will not find every bracelist, but + ;; a non-nil result is reliable. We simply search for "= {" (naturally with + ;; syntactic whitespace allowed). PAREN-STATE is the normal thing that it + ;; is everywhere else. + (let (b-pos) + (save-excursion + (while + (and (setq b-pos (c-pull-open-brace paren-state)) + (progn (goto-char b-pos) + (c-backward-sws) + (c-backward-token-2) + (not (looking-at "="))))) + b-pos))) + (defun c-inside-bracelist-p (containing-sexp paren-state) ;; return the buffer position of the beginning of the brace list ;; statement if we're inside a brace list, otherwise return nil. diff --git a/lisp/progmodes/cc-fonts.el b/lisp/progmodes/cc-fonts.el index e7d00815708..2d116e1ecdc 100644 --- a/lisp/progmodes/cc-fonts.el +++ b/lisp/progmodes/cc-fonts.el @@ -446,10 +446,12 @@ ;; `parse-sexp-lookup-properties' (when it exists). (parse-sexp-lookup-properties (cc-eval-when-compile - (boundp 'parse-sexp-lookup-properties)))) + (boundp 'parse-sexp-lookup-properties))) + (BOD-limit + (c-determine-limit 1000))) (goto-char (let ((here (point))) - (if (eq (car (c-beginning-of-decl-1)) 'same) + (if (eq (car (c-beginning-of-decl-1 BOD-limit)) 'same) (point) here))) ,(c-make-font-lock-search-form regexp highlights)) @@ -1240,6 +1242,7 @@ casts and declarations are fontified. Used on level 2 and higher." ;; it finds any. That's necessary so that we later will ;; stop inside them to fontify types there. (c-parse-and-markup-<>-arglists t) + lbrace ; position of some {. ;; The font-lock package in Emacs is known to clobber ;; `parse-sexp-lookup-properties' (when it exists). (parse-sexp-lookup-properties @@ -1351,7 +1354,6 @@ casts and declarations are fontified. Used on level 2 and higher." (or (looking-at c-typedef-key) (goto-char start-pos))) - ;; Now analyze the construct. ;; In QT, "more" is an irritating keyword that expands to nothing. ;; We skip over it to prevent recognition of "more slots: " ;; as a bitfield declaration. @@ -1360,6 +1362,8 @@ casts and declarations are fontified. Used on level 2 and higher." (concat "\\(more\\)\\([^" c-symbol-chars "]\\|$\\)"))) (goto-char (match-end 1)) (c-forward-syntactic-ws)) + + ;; Now analyze the construct. (setq decl-or-cast (c-forward-decl-or-cast-1 match-pos context last-cast-end)) @@ -1428,6 +1432,39 @@ casts and declarations are fontified. Used on level 2 and higher." (c-fontify-recorded-types-and-refs) nil) + ;; Restore point, since at this point in the code it has been + ;; left undefined by c-forward-decl-or-cast-1 above. + ((progn (goto-char start-pos) nil)) + + ;; If point is inside a bracelist, there's no point checking it + ;; being at a declarator. + ((let ((paren-state (c-parse-state))) + (setq lbrace (c-cheap-inside-bracelist-p paren-state))) + ;; Move past this bracelist to prevent an endless loop. + (goto-char lbrace) + (unless (c-safe (progn (forward-list) t)) + (goto-char start-pos) + (c-forward-token-2)) + nil) + + ;; If point is just after a ")" which is followed by an + ;; identifier which isn't a label, or at the matching "(", we're + ;; at either a macro invocation, a cast, or a + ;; for/while/etc. statement. The cast case is handled above. + ;; None of these cases can contain a declarator. + ((or (and (eq (char-before match-pos) ?\)) + (c-on-identifier) + (save-excursion (not (c-forward-label)))) + (and (eq (char-after) ?\() + (save-excursion + (and + (progn (c-backward-token-2) (c-on-identifier)) + (save-excursion (not (c-forward-label))) + (progn (c-backward-token-2) + (eq (char-after) ?\()))))) + (c-forward-token-2) ; Must prevent looping. + nil) + ((and (not c-enums-contain-decls) ;; An optimization quickly to eliminate scans of long enum ;; declarations in the next cond arm. @@ -1441,13 +1478,14 @@ casts and declarations are fontified. Used on level 2 and higher." (progn (c-backward-token-2) (looking-at c-brace-list-key))))))) - t) + (c-forward-token-2) + nil) (t ;; Are we at a declarator? Try to go back to the declaration ;; to check this. If we get there, check whether a "typedef" ;; is there, then fontify the declarators accordingly. - (let ((decl-search-lim (max (- (point) 50000) (point-min))) + (let ((decl-search-lim (c-determine-limit 1000)) paren-state bod-res encl-pos is-typedef c-recognize-knr-p) ; Strictly speaking, bogus, but it ; speeds up lisp.h tremendously. -- 2.39.2