From c6556eb6b86d4267bb35b7a27c34a0046668e3b9 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Fri, 11 Mar 2022 20:27:15 -0500 Subject: [PATCH] Clasp directives to the following span as required by XEP-0393 --- telegram/formatter/formatter.go | 51 +++++++++++++++++- telegram/formatter/formatter_test.go | 80 ++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/telegram/formatter/formatter.go b/telegram/formatter/formatter.go index eca514a..d6541cb 100644 --- a/telegram/formatter/formatter.go +++ b/telegram/formatter/formatter.go @@ -2,6 +2,7 @@ package formatter import ( "sort" + "unicode" log "github.com/sirupsen/logrus" "github.com/zelenin/go-tdlib/client" @@ -118,6 +119,54 @@ func MergeAdjacentEntities(entities []*client.TextEntity) []*client.TextEntity { return mergedEntities } +// ClaspDirectives to the following span as required by XEP-0393 +func ClaspDirectives(text string, entities []*client.TextEntity) []*client.TextEntity { + alignedEntities := make([]*client.TextEntity, len(entities)) + copy(alignedEntities, entities) + + // transform the source text into a form with uniform runes and code points, + // by duplicating the Basic Multilingual Plane + doubledRunes := make([]rune, 0, len(text)*2) + + for _, cp := range text { + if cp > 0x0000ffff { + doubledRunes = append(doubledRunes, cp, cp) + } else { + doubledRunes = append(doubledRunes, cp) + } + } + for i, entity := range alignedEntities { + var dirty bool + endOffset := entity.Offset + entity.Length + + if unicode.IsSpace(doubledRunes[entity.Offset]) { + for j, r := range doubledRunes[entity.Offset+1:endOffset] { + if !unicode.IsSpace(r) { + dirty = true + entity.Offset += int32(j+1) + entity.Length -= int32(j+1) + break + } + } + } + if unicode.IsSpace(doubledRunes[endOffset-1]) { + for j := endOffset-2; j >= entity.Offset; j-- { + if !unicode.IsSpace(doubledRunes[j]) { + dirty = true + entity.Length = j+1-entity.Offset + break + } + } + } + + if dirty { + alignedEntities[i] = entity + } + } + + return alignedEntities +} + func markupBraces(entity *client.TextEntity, lbrace, rbrace []rune) (*Insertion, *Insertion) { return &Insertion{ Offset: entity.Offset, @@ -191,7 +240,7 @@ func Format( return sourceText } - mergedEntities := SortEntities(MergeAdjacentEntities(SortEntities(entities))) + mergedEntities := SortEntities(ClaspDirectives(sourceText, MergeAdjacentEntities(SortEntities(entities)))) startStack := make(InsertionStack, 0, len(sourceText)) endStack := make(InsertionStack, 0, len(sourceText)) diff --git a/telegram/formatter/formatter_test.go b/telegram/formatter/formatter_test.go index c988b10..e4bdd23 100644 --- a/telegram/formatter/formatter_test.go +++ b/telegram/formatter/formatter_test.go @@ -392,3 +392,83 @@ func TestFormattingXEP0393Strikethrough(t *testing.T) { t.Errorf("Wrong strikethrough formatting: %v", markup) } } + +func TestClaspLeft(t *testing.T) { + text := "a b c" + entities := []*client.TextEntity{ + &client.TextEntity{ + Offset: 1, + Length: 2, + }, + } + entities = ClaspDirectives(text, entities) + if !(len(entities) == 1 && + entities[0].Offset == 2 && entities[0].Length == 1) { + t.Errorf("Wrong claspleft: %#v", entities) + } +} + +func TestClaspBoth(t *testing.T) { + text := "a b c" + entities := []*client.TextEntity{ + &client.TextEntity{ + Offset: 1, + Length: 3, + }, + } + entities = ClaspDirectives(text, entities) + if !(len(entities) == 1 && + entities[0].Offset == 2 && entities[0].Length == 1) { + t.Errorf("Wrong claspboth: %#v", entities) + } +} + +func TestClaspNotNeeded(t *testing.T) { + text := " abc " + entities := []*client.TextEntity{ + &client.TextEntity{ + Offset: 1, + Length: 3, + }, + } + entities = ClaspDirectives(text, entities) + if !(len(entities) == 1 && + entities[0].Offset == 1 && entities[0].Length == 3) { + t.Errorf("Wrong claspnotneeded: %#v", entities) + } +} + +func TestClaspNested(t *testing.T) { + text := "a b c" + entities := []*client.TextEntity{ + &client.TextEntity{ + Offset: 1, + Length: 3, + }, + &client.TextEntity{ + Offset: 2, + Length: 2, + }, + } + entities = ClaspDirectives(text, entities) + if !(len(entities) == 2 && + entities[0].Offset == 2 && entities[0].Length == 1 && + entities[1].Offset == 2 && entities[1].Length == 1) { + t.Errorf("Wrong claspnested: %#v", entities) + } +} + +func TestClaspEmoji(t *testing.T) { + text := "a 🐖 c" + entities := []*client.TextEntity{ + &client.TextEntity{ + Offset: 1, + Length: 4, + }, + } + entities = ClaspDirectives(text, entities) + if !(len(entities) == 1 && + entities[0].Offset == 2 && entities[0].Length == 2) { + t.Errorf("Wrong claspemoji: %#v", entities) + } +}