Skip to content

Typing in the prompt of a long chat rescans the whole history for code fences #333

Open

Description

@brdloush

Typing in the prompt of a long chat rescans the whole history for code fences

In a long chat (217 KB buffer, ~670 tool calls), typing in the prompt becomes noticeably sluggish. After I cleaned up the unrelated noise (see the end), about half of the CPU time per keystroke was in one place:

markdown-find-previous-prop
  markdown-find-previous-block
    markdown-syntax-propertize-fenced-block-constructs
      markdown-syntax-propertize

markdown-syntax-propertize-fenced-block-constructs goes to start and calls markdown-find-previous-block. That runs previous-single-property-change for each fence-begin property, with no limit other than point-min. When a fence type doesn't occur in the buffer at all (tilde fences, YAML metadata), the search walks back to the very start of the chat. So the cost of every keystroke in the prompt grows with the size of the history.

Two paths trigger it on each keystroke:

  1. jit-lock-after-change → eca-chat--font-lock-extend-region-function → markdown-font-lock-extend-region-function → markdown-syntax-propertize (~35%)
  2. syntax-ppss → syntax-propertize → eca-chat--syntax-propertize → markdown-syntax-propertize (~20%)

Path 2 is triggered by whatever calls syntax-ppss on insert. For me that was smartparens first. When I turned smartparens off in the chat, the same cost simply moved to Doom's electric-indent comment continuation. So it's not something a user can fix by disabling one minor mode.

Workaround I'm using

When the change starts inside the prompt, narrow to the prompt while the fence scan runs. The history above is already propertized and doesn't change while I type.

One thing that tripped me up: the eca-chat-prompt-area overlay is zero-length. It only marks where the prompt starts, so it has to be looked up across the buffer, not at start.

(defvar-local my/eca-chat--prompt-ov nil)

(defun my/eca-chat--prompt-start ()
(unless (and my/eca-chat--prompt-ov (overlay-buffer my/eca-chat--prompt-ov))
(setq my/eca-chat--prompt-ov
(save-restriction
(widen)
(cl-find-if (lambda (o) (overlay-get o 'eca-chat-prompt-area))
(overlays-in (point-min) (point-max))))))
(and my/eca-chat--prompt-ov (overlay-start my/eca-chat--prompt-ov)))

(define-advice markdown-syntax-propertize-fenced-block-constructs
(:around (fn start end) eca-prompt-narrow)
(let ((ps (and (derived-mode-p 'eca-chat-mode) (my/eca-chat--prompt-start))))
(if (and ps (>= start ps))
(save-restriction
(narrow-to-region ps (point-max))
(funcall fn start end))
(funcall fn start end))))

Before and after, same chat, profiling while typing in the prompt:

  before after
markdown-find-previous-prop 48% 0%
all Lisp under self-insert-command 38% 11%

What's left is mostly redisplay and GC, which is what I'd expect.

Suggestion

Advising markdown-mode from user config is obviously not the right place for this. ECA knows where the prompt starts, so eca-chat--syntax-propertize and eca-chat--font-lock-extend-region-function could narrow to the prompt (or clamp the region) when the change is inside it. That fixes both paths at once, and also anything else that calls syntax-ppss later.

Related: checkbox button leak

Before the above, the bigger problem was a markdown-mode bug: every change on a line with [ ] / [x] adds one more duplicate button overlay. Because ECA updates the task list all the time, my buffer collected ~34.7k duplicate overlays, and redisplay alone took 73% of CPU. I reported that to markdown-mode (https://github.com/jrblevin/markdown-mode/issues/953). Until it's fixed there, ECA could set markdown-make-gfm-checkboxes-buttons to nil in eca-chat-mode before the markdown setup runs. Clickable checkboxes in chat output don't do anything useful anyway, since a click only toggles the rendered text.

Activity

  1. brdloush commented on Oct 10, 2026

    @brdloush
    Author

    FYI jrblevin/markdown-mode#953 has already been fixed. Although that revision is not yet a part of doom emacs.

  2. ericdallo commented on Oct 14, 2026

    @ericdallo
    Member

    I believe we should wait for this to be bumped in doom so? sounds better than do any workaround in eca-emacs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions