From 050073ab3c7f27b54ddb0459f880fda11a93102b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Thu, 9 Apr 2026 13:39:39 +0400 Subject: [PATCH] Fix TextLength to count characters instead of bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use utf8.RuneCountInString so multi-byte characters like é count as one character. Add a unicode test case to cover this. Signed-off-by: Émile Ré --- pkg/prosemirror/node.go | 8 ++++---- pkg/prosemirror/node_test.go | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pkg/prosemirror/node.go b/pkg/prosemirror/node.go index 3f84501a5..932221dff 100644 --- a/pkg/prosemirror/node.go +++ b/pkg/prosemirror/node.go @@ -17,6 +17,7 @@ package prosemirror import ( "encoding/json" "fmt" + "unicode/utf8" ) type ( @@ -168,13 +169,12 @@ func (n Node) TableCellAttrs() (TableCellAttrs, error) { return a, nil } -// TextLength returns the total length of all text content in the node tree, -// measured in bytes (consistent with Go's len on strings). Only text carried -// by leaf text nodes is counted; structural markup is excluded. +// TextLength returns the total number of characters (Unicode code points) +// across all text nodes in the tree. Structural markup is excluded. func (n Node) TextLength() int { length := 0 if n.Text != nil { - length += len(*n.Text) + length += utf8.RuneCountInString(*n.Text) } for _, child := range n.Content { length += child.TextLength() diff --git a/pkg/prosemirror/node_test.go b/pkg/prosemirror/node_test.go index ff3676430..d1a605334 100644 --- a/pkg/prosemirror/node_test.go +++ b/pkg/prosemirror/node_test.go @@ -487,6 +487,17 @@ func TestTextLength(t *testing.T) { }, ) + t.Run( + "multi-byte unicode characters", + func(t *testing.T) { + t.Parallel() + raw := `{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"café résumé"}]}]}` + doc, err := Parse(raw) + require.NoError(t, err) + assert.Equal(t, 11, doc.TextLength()) + }, + ) + t.Run( "testdata document", func(t *testing.T) {