EditorSelection.cursor() ignores assoc parameter on initial view creation #1656

Closed
opened 2026-01-02 21:29:51 +01:00 by rubywwwilde · 7 comments
rubywwwilde commented 2026-01-02 21:29:51 +01:00 (Migrated from github.com)

Describe the issue

Summary

When creating an EditorView with an initial selection that has a non-zero assoc value, the cursor association is not visually respected at soft-wrap boundaries. The cursor appears on the wrong visual line until the selection is programmatically updated after view creation.

Reproduction

https://github.com/rubywwwilde/codemirror-assoc-bug

  const selection = EditorSelection.create([EditorSelection.cursor(48, -1)]);

  const state = EditorState.create({
    doc: text,
    extensions: [EditorView.lineWrapping],
    selection,
  });

  const view = new EditorView({ state, parent: container });
view.focus();

Expected Behavior

With assoc=-1 at a soft-wrap boundary, the cursor should appear at the end of the first visual line.

Actual Behavior

The cursor appears at the start of the second visual line, as if assoc=1.

Root Cause

In the enforceCursorAssoc condition:

  if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
      update.state.selection.main.empty && update.state.selection.main.assoc &&
      !update.state.facet(nativeSelectionHidden))

This only triggers when update.selectionSet is true. On initial view creation, the selection comes from the initial state rather than from a transaction that "sets" it, so selectionSet is false and the assoc enforcement is skipped.

Proposed Fix

Include update.focusChanged in the condition so that cursor association is enforced when the view first receives focus:

if (!this.mustEnforceCursorAssoc && (update.selectionSet || update.focusChanged) && update.view.lineWrapping &&
      update.state.selection.main.empty && update.state.selection.main.assoc &&
      !update.state.facet(nativeSelectionHidden))

This ensures that when a user focuses a freshly-created view (or refocuses after blur), the cursor association is correctly applied.

Environment

  • @codemirror/view: 6.39.8
  • @codemirror/state: 6.5.2

Browser and platform

Mac, Chrome

https://github.com/rubywwwilde/codemirror-assoc-bug

### Describe the issue ## Summary When creating an `EditorView` with an initial selection that has a non-zero `assoc` value, the cursor association is not visually respected at soft-wrap boundaries. The cursor appears on the wrong visual line until the selection is programmatically updated after view creation. ## Reproduction https://github.com/rubywwwilde/codemirror-assoc-bug ```js const selection = EditorSelection.create([EditorSelection.cursor(48, -1)]); const state = EditorState.create({ doc: text, extensions: [EditorView.lineWrapping], selection, }); const view = new EditorView({ state, parent: container }); view.focus(); ``` ### Expected Behavior With assoc=-1 at a soft-wrap boundary, the cursor should appear at the end of the first visual line. ### Actual Behavior The cursor appears at the start of the second visual line, as if assoc=1. ### Root Cause In the enforceCursorAssoc condition: ```js if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping && update.state.selection.main.empty && update.state.selection.main.assoc && !update.state.facet(nativeSelectionHidden)) ``` This only triggers when update.selectionSet is true. On initial view creation, the selection comes from the initial state rather than from a transaction that "sets" it, so selectionSet is false and the assoc enforcement is skipped. ### Proposed Fix Include update.focusChanged in the condition so that cursor association is enforced when the view first receives focus: ```js if (!this.mustEnforceCursorAssoc && (update.selectionSet || update.focusChanged) && update.view.lineWrapping && update.state.selection.main.empty && update.state.selection.main.assoc && !update.state.facet(nativeSelectionHidden)) ``` This ensures that when a user focuses a freshly-created view (or refocuses after blur), the cursor association is correctly applied. ## Environment - @codemirror/view: 6.39.8 - @codemirror/state: 6.5.2 ### Browser and platform Mac, Chrome ### Reproduction link https://github.com/rubywwwilde/codemirror-assoc-bug
rubywwwilde commented 2026-01-02 21:50:11 +01:00 (Migrated from github.com)

I hope I did everything right in pull request! There are some failing tests, but as it appears, they are not caused by my change.

Figuring out this bug took me almost the whole day, though I liked exploring CodeMirror in this way.

This issue is important for me, because my app restores selection on page reload and it's important for me to restore the selection correctly.

I hope I did everything right in pull request! There are some failing tests, but as it appears, they are not caused by my change. Figuring out this bug took me almost the whole day, though I liked exploring CodeMirror in this way. This issue is important for me, because my app restores selection on page reload and it's important for me to restore the selection correctly.
marijnh commented 2026-01-03 09:57:42 +01:00 (Migrated from github.com)

That looks like precisely the correct way to fix this. Thanks for figuring that out.

How are you running the tests, that you see failures? They are all passing on my machine on both Chrome and Firefox, but yeah, browsers and platforms may influence that, so I'd be interested to know what's going on.

That looks like precisely the correct way to fix this. Thanks for figuring that out. How are you running the tests, that you see failures? They are all passing on my machine on both Chrome and Firefox, but yeah, browsers and platforms may influence that, so I'd be interested to know what's going on.
rubywwwilde commented 2026-01-03 12:11:41 +01:00 (Migrated from github.com)

I ran tests with npm run test inside view repo and got 6 failing ones.

370 passing (65ms)
6 failing

1) Browser tests (chrome)

     bidi
       LTR context
         كودالمرآة:
 Error: 8-7-6-5-4-3-2-1-0 != 8-7-6-4-5-3-2-1-0 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17)
  at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32)
  at process.processImmediate (node:internal/timers:485:21)

2) Browser tests (chrome)

     bidi
       LTR context
         كود1234المرآة:
 Error: 12-11-10-9-8-7-3-4-5-6-2-1-0 != 12-11-10-8-9-7-3-4-5-6-2-1-0 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17)
  at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32)
  at process.processImmediate (node:internal/timers:485:21)

3) Browser tests (chrome)

     bidi
       LTR context
         كودabcالمرآة:
 Error: 2-1-0-3-4-5-11-10-9-8-7-6 != 2-1-0-3-4-5-11-10-9-7-8-6 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17)
  at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32)
  at process.processImmediate (node:internal/timers:485:21)

4) Browser tests (chrome)

     bidi
       RTL context
         كودالمرآة:
 Error: 8-7-6-5-4-3-2-1-0 != 8-7-6-4-5-3-2-1-0 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17)
  at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32)
  at process.processImmediate (node:internal/timers:485:21)

5) Browser tests (chrome)

     bidi
       RTL context
         كود1234المرآة:
 Error: 12-11-10-9-8-7-3-4-5-6-2-1-0 != 12-11-10-8-9-7-3-4-5-6-2-1-0 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17)
  at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32)
  at process.processImmediate (node:internal/timers:485:21)

6) Browser tests (chrome)

     bidi
       RTL context
         كودabcالمرآة:
 Error: 11-10-9-8-7-6-3-4-5-2-1-0 != 11-10-9-7-8-6-3-4-5-2-1-0 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17)
  at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32)
  at process.processImmediate (node:internal/timers:485:21)
I ran tests with `npm run test` inside `view` repo and got 6 failing ones. ```bash 370 passing (65ms) 6 failing 1) Browser tests (chrome) bidi LTR context كودالمرآة: Error: 8-7-6-5-4-3-2-1-0 != 8-7-6-4-5-3-2-1-0 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17) at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32) at process.processImmediate (node:internal/timers:485:21) 2) Browser tests (chrome) bidi LTR context كود1234المرآة: Error: 12-11-10-9-8-7-3-4-5-6-2-1-0 != 12-11-10-8-9-7-3-4-5-6-2-1-0 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17) at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32) at process.processImmediate (node:internal/timers:485:21) 3) Browser tests (chrome) bidi LTR context كودabcالمرآة: Error: 2-1-0-3-4-5-11-10-9-8-7-6 != 2-1-0-3-4-5-11-10-9-7-8-6 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17) at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32) at process.processImmediate (node:internal/timers:485:21) 4) Browser tests (chrome) bidi RTL context كودالمرآة: Error: 8-7-6-5-4-3-2-1-0 != 8-7-6-4-5-3-2-1-0 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17) at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32) at process.processImmediate (node:internal/timers:485:21) 5) Browser tests (chrome) bidi RTL context كود1234المرآة: Error: 12-11-10-9-8-7-3-4-5-6-2-1-0 != 12-11-10-8-9-7-3-4-5-6-2-1-0 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17) at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32) at process.processImmediate (node:internal/timers:485:21) 6) Browser tests (chrome) bidi RTL context كودabcالمرآة: Error: 11-10-9-8-7-6-3-4-5-2-1-0 != 11-10-9-7-8-6-3-4-5-2-1-0 at Context.<anonymous> (http://localhost:55042/_m/test/webtest-bidi.js:74:17) at Context.<anonymous> (node_modules/@marijn/testtool/src/testtool.js:50:32) at process.processImmediate (node:internal/timers:485:21) ```
marijnh commented 2026-01-03 12:17:28 +01:00 (Migrated from github.com)

I ran tests with npm run test inside view repo and got 6 failing ones.

Which platform are you on?

> I ran tests with npm run test inside view repo and got 6 failing ones. Which platform are you on?
rubywwwilde commented 2026-01-03 12:18:35 +01:00 (Migrated from github.com)

I'm running on mac m4 pro with Sequoia 15.5.

I'm running on mac m4 pro with Sequoia 15.5.
rubywwwilde commented 2026-01-03 12:19:10 +01:00 (Migrated from github.com)

That looks like precisely the correct way to fix this. Thanks for figuring that out.

@marijnh thank you for such a fantastic editor! It's very extensible and allows me to build a block-based rich text editor easily, focusing fully on the higher-level features I care about. I initialize CodeMirror when the user clicks or focuses a block in a different way, and this approach has been very performant for me.

> That looks like precisely the correct way to fix this. Thanks for figuring that out. @marijnh thank you for such a fantastic editor! It's very extensible and allows me to build a block-based rich text editor easily, focusing fully on the higher-level features I care about. I initialize CodeMirror when the user clicks or focuses a block in a different way, and this approach has been very performant for me.
marijnh commented 2026-01-05 12:57:05 +01:00 (Migrated from github.com)

Thanks! I was able to reproduce the MacOS Chrome issues with the tests (all related to obscure font handling differences), and work around them.

Thanks! I was able to reproduce the MacOS Chrome issues with the tests (all related to obscure font handling differences), and work around them.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
codemirror/dev#1656
No description provided.