Skip to content

Commit a985cb1

Browse files
Fix multi-cursor updates in ui.codemirror (#4702)
This PR fixes issue #4701 which has been introduced in PR #4676. When one change caused the content to shift, subsequent changes have been applied at the wrong position. This is fixed by creating a new string from scratch instead of manipulating the old string "in place". While at it, this PR replaces the many instantiations of large strings with a single `''.join()` operation to connect individual parts.
1 parent 13ee793 commit a985cb1

File tree

2 files changed

+11
-10
lines changed

2 files changed

+11
-10
lines changed

nicegui/elements/codemirror.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from itertools import zip_longest
1+
from itertools import accumulate, zip_longest
22
from pathlib import Path
3-
from typing import List, Literal, Optional, Tuple, cast, get_args
3+
from typing import List, Literal, Optional, get_args
44

55
from nicegui.elements.mixins.disableable_element import DisableableElement
66
from nicegui.elements.mixins.value_element import ValueElement
@@ -338,12 +338,12 @@ def _event_args_to_value(self, e: GenericEventArguments) -> str:
338338

339339
def _apply_change_set(doc, sections: List[int], inserted: List[List[str]]) -> str:
340340
# based on https://github.com/codemirror/state/blob/main/src/change.ts
341-
assert sum(sections[::2]) == len(doc), 'Cannot apply change set to document due to length mismatch'
342-
pos = 0
341+
old_lengths = sections[::2]
342+
new_lengths = sections[1::2]
343+
end_positions = accumulate(old_lengths)
343344
joined_inserts = ('\n'.join(ins) for ins in inserted)
344-
for section in zip_longest(sections[::2], sections[1::2], joined_inserts, fillvalue=''):
345-
old_len, new_len, ins = cast(Tuple[int, int, str], section)
346-
if new_len >= 0:
347-
doc = doc[:pos] + ins + doc[pos + old_len:]
348-
pos += old_len
349-
return doc
345+
assert sum(old_lengths) == len(doc), 'Cannot apply change set to document due to length mismatch'
346+
return ''.join(
347+
doc[pos-old_len:pos] if new_len == -1 else ins # type: ignore
348+
for pos, old_len, new_len, ins in zip_longest(end_positions, old_lengths, new_lengths, joined_inserts, fillvalue='')
349+
)

tests/test_codemirror.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ def test_change_set():
4242
assert _apply_change_set('X', [1, -1, 0, 1], [[], ['Y']]) == 'XY'
4343
assert _apply_change_set('Hello', [5, -1, 0, 8], [[], [', world!']]) == 'Hello, world!'
4444
assert _apply_change_set('Hello, world!', [5, -1, 7, 0, 1, -1], []) == 'Hello!'
45+
assert _apply_change_set('Hello, hello!', [2, -1, 3, 1, 4, -1, 3, 1, 1, -1], [[], ['y'], [], ['y']]) == 'Hey, hey!'

0 commit comments

Comments
 (0)