Skip to content

Make find-and-replace search from the caret's position when opened (Fixes #156) - #232

Open
PeterKnego wants to merge 1 commit into
WebCoder49:mainfrom
PeterKnego:find-from-caret
Open

Make find-and-replace search from the caret's position when opened (Fixes #156)#232
PeterKnego wants to merge 1 commit into
WebCoder49:mainfrom
PeterKnego:find-from-caret

Conversation

@PeterKnego

Copy link
Copy Markdown

Fixes #156.

The cause

FindMatchState stores focusedMatchStartIndex, the position focusMatch() searches forward from, and its constructor initialises it from the caret. But it is constructed in only one place — inside the branch of showPrompt that runs when the dialog does not yet exist:

https://github.com/WebCoder49/code-input/blob/v2.8.3/plugins/find-and-replace.js#L313

Every subsequent Ctrl+F reuses the cached dialog and its findMatchState, so focusedMatchStartIndex still holds the position of the match the previous search left focused. The first search after a page load starts from the caret correctly; every one after it carries on from where the last one stopped.

The change

One line, next to where showPrompt already saves the selection for both the create and the reopen paths, so it applies either way and runs before the closing updateFindMatches recomputes the matches:

dialog.findMatchState.focusedMatchStartIndex = dialog.selectionStart;

Existing behaviour this deliberately preserves:

  • Closing the dialog selects the focused match (cancelPrompt), so reopening without moving the caret stays on that match rather than jumping forward — the same as VS Code.
  • Opening with text selected still copies it into the find field, and since the caret is that selection's start, the selection itself is the first match.
  • With no match at or after the caret, it still wraps round to the first match in the code.

Behaviour change

Flagging this as the breaking change the issue was labelled: reopening the dialog now moves the focused match where previously it did not. Anything driving showPrompt programmatically and relying on the search resuming from the last match will see a different match focused.

Tests

Four assertions added to the FindAndReplace group in tests/tester.js. They are automated assertEquals — no confirm() judgement needed — and slot in after the existing find-and-replace block, leaving the sequential state the later GoToLine tests depend on untouched.

I checked they actually fail without the fix rather than assuming it, by reverting the one-line change and re-running:

Test Without the fix With the fix
Finds First Match After Caret when Reopened fails (3, expected 29) passes
Finds Match Starting Exactly at Caret fails (3, expected 16) passes
Wraps to First Match when Caret is After Last Match passes passes
Keeps Focused Match when Reopened without Moving Caret passes passes

The first two are ordered so the match the previous one leaves focused differs from the correct answer, which is what makes them fail when the caret is ignored. The last two cannot discriminate in a chained sequence — once the first case fails, the stale position is the first match — so they are commented as locking in the surrounding behaviour rather than guarding this fix.

That table comes from a standalone page in Chromium using a minimal custom template, driving the real Ctrl/Cmd+F key events rather than calling showPrompt directly. The change is in showPrompt, above any highlighting, so it is not template-specific — but I have not yet run the full tests/prism.html / tests/hljs.html pages end to end, since they need a person to answer the confirm() prompts. Happy to do that and report back if you'd like it before merging.

Not included

  • *.min.js — left to the Auto-minify workflow.
  • esm/ — generated at prepack; no marker comments affected.
  • code-input.d.ts — no public interface change (showPrompt's docstring is updated in the plugin itself).

…ixes WebCoder49#156)

The findMatchState of a code-input element is only constructed when its
find-and-replace dialog is first created, and it is what stores the position
matches are searched from. Reopening the dialog reuses the cached one, so
every search after the first carried on from wherever the previous search
left off rather than from the caret, unlike the IDE behaviour users expect.

Set that position from the caret each time the dialog is opened, next to
where the selection is already saved so it applies whether the dialog is
being created or reopened.

Two of the added tests are ordered so the match the previous one left focused
differs from the correct answer, so they fail without this change; the other
two lock in the surrounding wrapping and reopening behaviour.
@WebCoder49

WebCoder49 commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Sorry, since this is a breaking change, it needs to wait for the next major version (v3.0.0). I expect v3.0.0 to be quite a lot of refactoring, so this specific PR might not be useful after that.

Do you use code-input.js yourself and want the fix soon? If so, I'd recommend using your fork with this fix for the meantime, but it also helps me set priorities.

@PeterKnego

Copy link
Copy Markdown
Author

Don't worry, release it at your own pace.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make find-and-replace matches start at the position of the caret before Ctrl+F was pressed

2 participants