Skip to content

Conversation

@jbdyn
Copy link
Contributor

@jbdyn jbdyn commented Jun 10, 2025

Please review the following checklist.

  • Docstrings on all new or modified functions / classes
  • Updated documentation
  • Updated CHANGELOG.md (where appropriate)

Hey 👋

I am combining realtime syncing provided by pycrdt with the TextArea widget and I am pretty impressed by how little code it takes to adapt the widget to my needs, given the complexity of src/textual/document/*.py and src/textual/widgets/_text_area.py.
Good job, @darrenburns!

Background Story

The edit flow in the released TextArea widget is:

TextArea.replace/insert/delete/clear  # <-- `maintain_selection_offset` set
  - TextArea.edit
    - Edit.do  # <-- `maintain_selection_offset` evaluated
      - TextArea.document.replace_range

with maintain_selection_offset deciding in Edit.do whether the current selection gets shifted/moved (True) or set as cursor to the end of the edit (False).

However, this edit flow is not compatible to CRDTs in pycrdt.
Changes to those CRDTs are announced via callbacks with event objects passed holding the information about a particular change.
For the Text-CRDT, there is the TextEvent event object holding info about which text range has been deleted and where a particular piece of text has been inserted.

This mechanism requires text manipulation on the TextArea widget - from the user via keyboard, the API or as update message from remote - to be done with the information given in the callback.
It does not provide a way of transporting arbitrary/app-specific metadata, especially not allowing to pass maintain_selection_offset flag:

TextArea.replace/insert/delete/clear
  - TextCRDT.insert/delete  # <-- `maintain_selection_offset` shall not pass
    - TextArea.on_ytext_event_callback
      - TextArea.edit
        - Edit.do
          - TextArea.document.replace_range

I thereby started experimenting whether I could get decent functionality without maintain_selection_offset.

What I Changed

  • I removed maintain_selection_offset from the API.

  • Edits via the API (insert, delete) do not automatically set the TextArea selection as a cursor to the end of the edit anymore (maintain_selection_offset = False before), but keep the current selection.
    See also tests for ctrl + u (delete to line start)

    comparison ctrl + u

    before:
    ctrl_u_old
    now:
    ctrl_u_new

    and ctrl + k (delete to line end).

    comparison ctrl + k

    before:
    ctrl_k_old
    now:
    ctrl_k_new

    On clear, maintain_selection_offset had no effect.

  • Edits deleting a range covering the current TextArea selection place the selection as a cursor at the end of the edit.

    comparison

    before:
    selection_within_edit_old

    now:
    selection_within_edit_new

  • Selection now supports the operators in, <, <=, > and >= for Locations (tuples).

  • I updated the tests.

What are your thoughts, especially about the new API behavior?

@jbdyn jbdyn marked this pull request as ready for review June 10, 2025 19:32
@willmcgugan
Copy link
Member

I can't support changes to help with pycrdt, since I don't know much about that.

If you want these changes, I suggest explaining the benefits from the POV of Textual. What use case does it solve. Why this is better. You may also want to break it in to several PRs, as this appear to be making multiple changes.

@jbdyn
Copy link
Contributor Author

jbdyn commented Jun 11, 2025

Textual benefits from these changes as they are meant to improve the TextArea widget by:

  • slimming down its API by removing the maintain_selection_offset flag without compromising functionality and
  • reworking cursor movement, thus
  • improving compatibility with other projects.

These points are somewhat interconnected and - as a side-effect - fixed an "issue" where the cursor won't move if the selection is within the deletion range of an edit (see point 3 under "What I changed" above).

From the TextArea blog post by @darrenburns, this widget is intended to support remote text manipulation, hence the more involved cursor movement logic.
Darren also mentions collaboration with @davidbrochart, the maintainer of pycrdt.

I thereby see this PR in line with the intended use of TextArea, namely supporting text manipulation from different sources, and I would be very happy to see it re-opened.

Side note: CRDTs and pycrdt

CRDTs are conflict-free replicating data types. Essentially, you have the data types Text (like str), Map (like dict), Array (like list) etc. which change by incremental updates.
These updates can be send over some network connection from A to B.
On B, the updates are applied to the local instance of the same CRDT as well, thus replicating the content of the CRDT on A on B.

There are different projects implementing CRDTs, and y-crdt/yrs is one of the most advanced library out there at the time of writing.
pycrdt provides the Python bindings for yrs.

Using pycrdt in combination with TextArea allows for a slim real-time syncing text editor.

@jbdyn
Copy link
Contributor Author

jbdyn commented Jul 11, 2025

Pinging @willmcgugan, @darrenburns and @davidbrochart for re-opening this PR so it does not get lost.

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.

2 participants