Support blockquotes in formatter
This commit is contained in:
parent
576acba0d1
commit
6bd8379114
|
@ -8,15 +8,29 @@ import (
|
|||
"github.com/zelenin/go-tdlib/client"
|
||||
)
|
||||
|
||||
// Insertion is a piece of text in given position
|
||||
type Insertion struct {
|
||||
type insertionType int
|
||||
const (
|
||||
insertionOpening insertionType = iota
|
||||
insertionClosing
|
||||
insertionUnpaired
|
||||
)
|
||||
|
||||
type MarkupModeType int
|
||||
const (
|
||||
MarkupModeXEP0393 MarkupModeType = iota
|
||||
MarkupModeMarkdown
|
||||
)
|
||||
|
||||
// insertion is a piece of text in given position
|
||||
type insertion struct {
|
||||
Offset int32
|
||||
Runes []rune
|
||||
Type insertionType
|
||||
}
|
||||
|
||||
// InsertionStack contains the sequence of insertions
|
||||
// insertionStack contains the sequence of insertions
|
||||
// from the start or from the end
|
||||
type InsertionStack []*Insertion
|
||||
type insertionStack []*insertion
|
||||
|
||||
var boldRunesMarkdown = []rune("**")
|
||||
var boldRunesXEP0393 = []rune("*")
|
||||
|
@ -26,11 +40,16 @@ var strikeRunesXEP0393 = []rune("~")
|
|||
var codeRunes = []rune("`")
|
||||
var preRuneStart = []rune("```\n")
|
||||
var preRuneEnd = []rune("\n```")
|
||||
var quoteRunes = []rune("> ")
|
||||
var newlineRunes = []rune("\n")
|
||||
var doubleNewlineRunes = []rune("\n\n")
|
||||
var newlineCode = rune(0x0000000a)
|
||||
var bmpCeil = rune(0x0000ffff)
|
||||
|
||||
// rebalance pumps all the values until the given offset to current stack (growing
|
||||
// from start) from given stack (growing from end); should be called
|
||||
// before any insertions to the current stack at the given offset
|
||||
func (s InsertionStack) rebalance(s2 InsertionStack, offset int32) (InsertionStack, InsertionStack) {
|
||||
func (s insertionStack) rebalance(s2 insertionStack, offset int32) (insertionStack, insertionStack) {
|
||||
for len(s2) > 0 && s2[len(s2)-1].Offset <= offset {
|
||||
s = append(s, s2[len(s2)-1])
|
||||
s2 = s2[:len(s2)-1]
|
||||
|
@ -41,10 +60,10 @@ func (s InsertionStack) rebalance(s2 InsertionStack, offset int32) (InsertionSta
|
|||
|
||||
// NewIterator is a second order function that sequentially scans and returns
|
||||
// stack elements; starts returning nil when elements are ended
|
||||
func (s InsertionStack) NewIterator() func() *Insertion {
|
||||
func (s insertionStack) NewIterator() func() *insertion {
|
||||
i := -1
|
||||
|
||||
return func() *Insertion {
|
||||
return func() *insertion {
|
||||
i++
|
||||
if i < len(s) {
|
||||
return s[i]
|
||||
|
@ -120,21 +139,10 @@ func MergeAdjacentEntities(entities []*client.TextEntity) []*client.TextEntity {
|
|||
}
|
||||
|
||||
// ClaspDirectives to the following span as required by XEP-0393
|
||||
func ClaspDirectives(text string, entities []*client.TextEntity) []*client.TextEntity {
|
||||
func ClaspDirectives(doubledRunes []rune, 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
|
||||
|
@ -167,18 +175,89 @@ func ClaspDirectives(text string, entities []*client.TextEntity) []*client.TextE
|
|||
return alignedEntities
|
||||
}
|
||||
|
||||
func markupBraces(entity *client.TextEntity, lbrace, rbrace []rune) (*Insertion, *Insertion) {
|
||||
return &Insertion{
|
||||
func markupBraces(entity *client.TextEntity, lbrace, rbrace []rune) []*insertion {
|
||||
return []*insertion{
|
||||
&insertion{
|
||||
Offset: entity.Offset,
|
||||
Runes: lbrace,
|
||||
}, &Insertion{
|
||||
Type: insertionOpening,
|
||||
},
|
||||
&insertion{
|
||||
Offset: entity.Offset + entity.Length,
|
||||
Runes: rbrace,
|
||||
Type: insertionClosing,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// EntityToMarkdown generates the wrapping Markdown tags
|
||||
func EntityToMarkdown(entity *client.TextEntity) (*Insertion, *Insertion) {
|
||||
func quotePrependNewlines(entity *client.TextEntity, doubledRunes []rune, markupMode MarkupModeType) []*insertion {
|
||||
if len(doubledRunes) == 0 {
|
||||
return []*insertion{}
|
||||
}
|
||||
|
||||
startRunes := []rune("\n> ")
|
||||
if entity.Offset == 0 || doubledRunes[entity.Offset-1] == newlineCode {
|
||||
startRunes = quoteRunes
|
||||
}
|
||||
insertions := []*insertion{
|
||||
&insertion{
|
||||
Offset: entity.Offset,
|
||||
Runes: startRunes,
|
||||
Type: insertionUnpaired,
|
||||
},
|
||||
}
|
||||
|
||||
entityEnd := entity.Offset + entity.Length
|
||||
entityEndInt := int(entityEnd)
|
||||
|
||||
var wasNewline bool
|
||||
// last newline is omitted, there's no need to put quote mark after the quote
|
||||
for i := entity.Offset; i < entityEnd-1; i++ {
|
||||
isNewline := doubledRunes[i] == newlineCode
|
||||
if (isNewline && markupMode == MarkupModeXEP0393) || (wasNewline && isNewline && markupMode == MarkupModeMarkdown) {
|
||||
insertions = append(insertions, &insertion{
|
||||
Offset: i+1,
|
||||
Runes: quoteRunes,
|
||||
Type: insertionUnpaired,
|
||||
})
|
||||
}
|
||||
|
||||
if isNewline {
|
||||
wasNewline = true
|
||||
} else {
|
||||
wasNewline = false
|
||||
}
|
||||
}
|
||||
|
||||
var rbrace []rune
|
||||
if len(doubledRunes) > entityEndInt {
|
||||
if doubledRunes[entityEnd] == newlineCode {
|
||||
if markupMode == MarkupModeMarkdown && len(doubledRunes) > entityEndInt+1 && doubledRunes[entityEndInt+1] != newlineCode {
|
||||
rbrace = newlineRunes
|
||||
}
|
||||
} else {
|
||||
if markupMode == MarkupModeMarkdown {
|
||||
rbrace = doubleNewlineRunes
|
||||
} else {
|
||||
rbrace = newlineRunes
|
||||
}
|
||||
}
|
||||
}
|
||||
insertions = append(insertions, &insertion{
|
||||
Offset: entityEnd,
|
||||
Runes: rbrace,
|
||||
Type: insertionClosing,
|
||||
})
|
||||
|
||||
return insertions
|
||||
}
|
||||
|
||||
// entityToMarkdown generates the wrapping Markdown tags
|
||||
func entityToMarkdown(entity *client.TextEntity, doubledRunes []rune, markupMode MarkupModeType) []*insertion {
|
||||
if entity == nil || entity.Type == nil {
|
||||
return []*insertion{}
|
||||
}
|
||||
|
||||
switch entity.Type.TextEntityTypeType() {
|
||||
case client.TypeTextEntityTypeBold:
|
||||
return markupBraces(entity, boldRunesMarkdown, boldRunesMarkdown)
|
||||
|
@ -193,18 +272,20 @@ func EntityToMarkdown(entity *client.TextEntity) (*Insertion, *Insertion) {
|
|||
case client.TypeTextEntityTypePreCode:
|
||||
preCode, _ := entity.Type.(*client.TextEntityTypePreCode)
|
||||
return markupBraces(entity, []rune("\n```"+preCode.Language+"\n"), codeRunes)
|
||||
case client.TypeTextEntityTypeBlockQuote:
|
||||
return quotePrependNewlines(entity, doubledRunes, MarkupModeMarkdown)
|
||||
case client.TypeTextEntityTypeTextUrl:
|
||||
textURL, _ := entity.Type.(*client.TextEntityTypeTextUrl)
|
||||
return markupBraces(entity, []rune("["), []rune("]("+textURL.Url+")"))
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return []*insertion{}
|
||||
}
|
||||
|
||||
// EntityToXEP0393 generates the wrapping XEP-0393 tags
|
||||
func EntityToXEP0393(entity *client.TextEntity) (*Insertion, *Insertion) {
|
||||
// entityToXEP0393 generates the wrapping XEP-0393 tags
|
||||
func entityToXEP0393(entity *client.TextEntity, doubledRunes []rune, markupMode MarkupModeType) []*insertion {
|
||||
if entity == nil || entity.Type == nil {
|
||||
return nil, nil
|
||||
return []*insertion{}
|
||||
}
|
||||
|
||||
switch entity.Type.TextEntityTypeType() {
|
||||
|
@ -221,29 +302,55 @@ func EntityToXEP0393(entity *client.TextEntity) (*Insertion, *Insertion) {
|
|||
case client.TypeTextEntityTypePreCode:
|
||||
preCode, _ := entity.Type.(*client.TextEntityTypePreCode)
|
||||
return markupBraces(entity, []rune("\n```"+preCode.Language+"\n"), codeRunes)
|
||||
case client.TypeTextEntityTypeBlockQuote:
|
||||
return quotePrependNewlines(entity, doubledRunes, MarkupModeXEP0393)
|
||||
case client.TypeTextEntityTypeTextUrl:
|
||||
textURL, _ := entity.Type.(*client.TextEntityTypeTextUrl)
|
||||
// non-standard, Pidgin-specific
|
||||
return markupBraces(entity, []rune{}, []rune(" <"+textURL.Url+">"))
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return []*insertion{}
|
||||
}
|
||||
|
||||
// transform the source text into a form with uniform runes and code points,
|
||||
// by duplicating anything beyond the Basic Multilingual Plane
|
||||
func textToDoubledRunes(text string) []rune {
|
||||
doubledRunes := make([]rune, 0, len(text)*2)
|
||||
for _, cp := range text {
|
||||
if cp > bmpCeil {
|
||||
doubledRunes = append(doubledRunes, cp, cp)
|
||||
} else {
|
||||
doubledRunes = append(doubledRunes, cp)
|
||||
}
|
||||
}
|
||||
|
||||
return doubledRunes
|
||||
}
|
||||
|
||||
// Format traverses an already sorted list of entities and wraps the text in a markup
|
||||
func Format(
|
||||
sourceText string,
|
||||
entities []*client.TextEntity,
|
||||
entityToMarkup func(*client.TextEntity) (*Insertion, *Insertion),
|
||||
markupMode MarkupModeType,
|
||||
) string {
|
||||
if len(entities) == 0 {
|
||||
return sourceText
|
||||
}
|
||||
|
||||
mergedEntities := SortEntities(ClaspDirectives(sourceText, MergeAdjacentEntities(SortEntities(entities))))
|
||||
var entityToMarkup func(*client.TextEntity, []rune, MarkupModeType) []*insertion
|
||||
if markupMode == MarkupModeXEP0393 {
|
||||
entityToMarkup = entityToXEP0393
|
||||
} else {
|
||||
entityToMarkup = entityToMarkdown
|
||||
}
|
||||
|
||||
startStack := make(InsertionStack, 0, len(sourceText))
|
||||
endStack := make(InsertionStack, 0, len(sourceText))
|
||||
doubledRunes := textToDoubledRunes(sourceText)
|
||||
|
||||
mergedEntities := SortEntities(ClaspDirectives(doubledRunes, MergeAdjacentEntities(SortEntities(entities))))
|
||||
|
||||
startStack := make(insertionStack, 0, len(sourceText))
|
||||
endStack := make(insertionStack, 0, len(sourceText))
|
||||
|
||||
// convert entities to a stack of brackets
|
||||
var maxEndOffset int32
|
||||
|
@ -260,36 +367,70 @@ func Format(
|
|||
|
||||
startStack, endStack = startStack.rebalance(endStack, entity.Offset)
|
||||
|
||||
startInsertion, endInsertion := entityToMarkup(entity)
|
||||
if startInsertion != nil {
|
||||
startStack = append(startStack, startInsertion)
|
||||
insertions := entityToMarkup(entity, doubledRunes, markupMode)
|
||||
if len(insertions) > 1 {
|
||||
startStack = append(startStack, insertions[0:len(insertions)-1]...)
|
||||
}
|
||||
if endInsertion != nil {
|
||||
endStack = append(endStack, endInsertion)
|
||||
if len(insertions) > 0 {
|
||||
endStack = append(endStack, insertions[len(insertions)-1])
|
||||
}
|
||||
}
|
||||
// flush the closing brackets that still remain in endStack
|
||||
startStack, endStack = startStack.rebalance(endStack, maxEndOffset)
|
||||
// sort unpaired insertions
|
||||
sort.SliceStable(startStack, func(i int, j int) bool {
|
||||
ins1 := startStack[i]
|
||||
ins2 := startStack[j]
|
||||
if ins1.Type == insertionUnpaired && ins2.Type == insertionUnpaired {
|
||||
return ins1.Offset < ins2.Offset
|
||||
}
|
||||
if ins1.Type == insertionUnpaired {
|
||||
if ins1.Offset == ins2.Offset {
|
||||
if ins2.Type == insertionOpening { // > **
|
||||
return true
|
||||
} else if ins2.Type == insertionClosing { // **>
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return ins1.Offset < ins2.Offset
|
||||
}
|
||||
}
|
||||
if ins2.Type == insertionUnpaired {
|
||||
if ins1.Offset == ins2.Offset {
|
||||
if ins1.Type == insertionOpening { // > **
|
||||
return false
|
||||
} else if ins1.Type == insertionClosing { // **>
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return ins1.Offset < ins2.Offset
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// merge brackets into text
|
||||
markupRunes := make([]rune, 0, len(sourceText))
|
||||
|
||||
nextInsertion := startStack.NewIterator()
|
||||
insertion := nextInsertion()
|
||||
var runeI int32
|
||||
var skipNext bool
|
||||
|
||||
for _, cp := range sourceText {
|
||||
for insertion != nil && insertion.Offset <= runeI {
|
||||
for i, cp := range doubledRunes {
|
||||
if skipNext {
|
||||
skipNext = false
|
||||
continue
|
||||
}
|
||||
|
||||
for insertion != nil && int(insertion.Offset) <= i {
|
||||
markupRunes = append(markupRunes, insertion.Runes...)
|
||||
insertion = nextInsertion()
|
||||
}
|
||||
|
||||
markupRunes = append(markupRunes, cp)
|
||||
// skip two UTF-16 code units (not points actually!) if needed
|
||||
if cp > 0x0000ffff {
|
||||
runeI += 2
|
||||
} else {
|
||||
runeI++
|
||||
if cp > bmpCeil {
|
||||
skipNext = true
|
||||
}
|
||||
}
|
||||
for insertion != nil {
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
func TestNoFormatting(t *testing.T) {
|
||||
markup := Format("abc\ndef", []*client.TextEntity{}, EntityToMarkdown)
|
||||
markup := Format("abc\ndef", []*client.TextEntity{}, MarkupModeMarkdown)
|
||||
if markup != "abc\ndef" {
|
||||
t.Errorf("No formatting expected, but: %v", markup)
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func TestFormattingSimple(t *testing.T) {
|
|||
Length: 4,
|
||||
Type: &client.TextEntityTypeBold{},
|
||||
},
|
||||
}, EntityToMarkdown)
|
||||
}, MarkupModeMarkdown)
|
||||
if markup != "👙**🐧🐖**" {
|
||||
t.Errorf("Wrong simple formatting: %v", markup)
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ func TestFormattingAdjacent(t *testing.T) {
|
|||
Url: "https://narayana.im/",
|
||||
},
|
||||
},
|
||||
}, EntityToMarkdown)
|
||||
}, MarkupModeMarkdown)
|
||||
if markup != "a👙_🐧_[🐖](https://narayana.im/)" {
|
||||
t.Errorf("Wrong adjacent formatting: %v", markup)
|
||||
}
|
||||
|
@ -63,18 +63,18 @@ func TestFormattingAdjacentAndNested(t *testing.T) {
|
|||
Length: 2,
|
||||
Type: &client.TextEntityTypeItalic{},
|
||||
},
|
||||
}, EntityToMarkdown)
|
||||
}, MarkupModeMarkdown)
|
||||
if markup != "```\n**👙**🐧\n```_🐖_" {
|
||||
t.Errorf("Wrong adjacent&nested formatting: %v", markup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRebalanceTwoZero(t *testing.T) {
|
||||
s1 := InsertionStack{
|
||||
&Insertion{Offset: 7},
|
||||
&Insertion{Offset: 8},
|
||||
s1 := insertionStack{
|
||||
&insertion{Offset: 7},
|
||||
&insertion{Offset: 8},
|
||||
}
|
||||
s2 := InsertionStack{}
|
||||
s2 := insertionStack{}
|
||||
s1, s2 = s1.rebalance(s2, 7)
|
||||
if !(len(s1) == 2 && len(s2) == 0 && s1[0].Offset == 7 && s1[1].Offset == 8) {
|
||||
t.Errorf("Wrong rebalance 2–0: %#v %#v", s1, s2)
|
||||
|
@ -82,13 +82,13 @@ func TestRebalanceTwoZero(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRebalanceNeeded(t *testing.T) {
|
||||
s1 := InsertionStack{
|
||||
&Insertion{Offset: 7},
|
||||
&Insertion{Offset: 8},
|
||||
s1 := insertionStack{
|
||||
&insertion{Offset: 7},
|
||||
&insertion{Offset: 8},
|
||||
}
|
||||
s2 := InsertionStack{
|
||||
&Insertion{Offset: 10},
|
||||
&Insertion{Offset: 9},
|
||||
s2 := insertionStack{
|
||||
&insertion{Offset: 10},
|
||||
&insertion{Offset: 9},
|
||||
}
|
||||
s1, s2 = s1.rebalance(s2, 9)
|
||||
if !(len(s1) == 3 && len(s2) == 1 &&
|
||||
|
@ -99,13 +99,13 @@ func TestRebalanceNeeded(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRebalanceNotNeeded(t *testing.T) {
|
||||
s1 := InsertionStack{
|
||||
&Insertion{Offset: 7},
|
||||
&Insertion{Offset: 8},
|
||||
s1 := insertionStack{
|
||||
&insertion{Offset: 7},
|
||||
&insertion{Offset: 8},
|
||||
}
|
||||
s2 := InsertionStack{
|
||||
&Insertion{Offset: 10},
|
||||
&Insertion{Offset: 9},
|
||||
s2 := insertionStack{
|
||||
&insertion{Offset: 10},
|
||||
&insertion{Offset: 9},
|
||||
}
|
||||
s1, s2 = s1.rebalance(s2, 8)
|
||||
if !(len(s1) == 2 && len(s2) == 2 &&
|
||||
|
@ -116,13 +116,13 @@ func TestRebalanceNotNeeded(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRebalanceLate(t *testing.T) {
|
||||
s1 := InsertionStack{
|
||||
&Insertion{Offset: 7},
|
||||
&Insertion{Offset: 8},
|
||||
s1 := insertionStack{
|
||||
&insertion{Offset: 7},
|
||||
&insertion{Offset: 8},
|
||||
}
|
||||
s2 := InsertionStack{
|
||||
&Insertion{Offset: 10},
|
||||
&Insertion{Offset: 9},
|
||||
s2 := insertionStack{
|
||||
&insertion{Offset: 10},
|
||||
&insertion{Offset: 9},
|
||||
}
|
||||
s1, s2 = s1.rebalance(s2, 10)
|
||||
if !(len(s1) == 4 && len(s2) == 0 &&
|
||||
|
@ -133,7 +133,7 @@ func TestRebalanceLate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIteratorEmpty(t *testing.T) {
|
||||
s := InsertionStack{}
|
||||
s := insertionStack{}
|
||||
g := s.NewIterator()
|
||||
v := g()
|
||||
if v != nil {
|
||||
|
@ -142,9 +142,9 @@ func TestIteratorEmpty(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIterator(t *testing.T) {
|
||||
s := InsertionStack{
|
||||
&Insertion{Offset: 7},
|
||||
&Insertion{Offset: 8},
|
||||
s := insertionStack{
|
||||
&insertion{Offset: 7},
|
||||
&insertion{Offset: 8},
|
||||
}
|
||||
g := s.NewIterator()
|
||||
v := g()
|
||||
|
@ -208,7 +208,7 @@ func TestSortEmpty(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNoFormattingXEP0393(t *testing.T) {
|
||||
markup := Format("abc\ndef", []*client.TextEntity{}, EntityToXEP0393)
|
||||
markup := Format("abc\ndef", []*client.TextEntity{}, MarkupModeXEP0393)
|
||||
if markup != "abc\ndef" {
|
||||
t.Errorf("No formatting expected, but: %v", markup)
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ func TestFormattingXEP0393Simple(t *testing.T) {
|
|||
Length: 4,
|
||||
Type: &client.TextEntityTypeBold{},
|
||||
},
|
||||
}, EntityToXEP0393)
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "👙*🐧🐖*" {
|
||||
t.Errorf("Wrong simple formatting: %v", markup)
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ func TestFormattingXEP0393Adjacent(t *testing.T) {
|
|||
Url: "https://narayana.im/",
|
||||
},
|
||||
},
|
||||
}, EntityToXEP0393)
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "a👙_🐧_🐖 <https://narayana.im/>" {
|
||||
t.Errorf("Wrong adjacent formatting: %v", markup)
|
||||
}
|
||||
|
@ -264,7 +264,7 @@ func TestFormattingXEP0393AdjacentAndNested(t *testing.T) {
|
|||
Length: 2,
|
||||
Type: &client.TextEntityTypeItalic{},
|
||||
},
|
||||
}, EntityToXEP0393)
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "```\n*👙*🐧\n```_🐖_" {
|
||||
t.Errorf("Wrong adjacent&nested formatting: %v", markup)
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ func TestFormattingXEP0393AdjacentItalicBoldItalic(t *testing.T) {
|
|||
Length: 69,
|
||||
Type: &client.TextEntityTypeItalic{},
|
||||
},
|
||||
}, EntityToXEP0393)
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "_раса двуногих крысолюдей, *которую так редко замечают, что многие отрицают само их существование*_" {
|
||||
t.Errorf("Wrong adjacent italic/bold-italic formatting: %v", markup)
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ func TestFormattingXEP0393MultipleAdjacent(t *testing.T) {
|
|||
Length: 1,
|
||||
Type: &client.TextEntityTypeItalic{},
|
||||
},
|
||||
}, EntityToXEP0393)
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "a*bcd*_e_" {
|
||||
t.Errorf("Wrong multiple adjacent formatting: %v", markup)
|
||||
}
|
||||
|
@ -343,7 +343,7 @@ func TestFormattingXEP0393Intersecting(t *testing.T) {
|
|||
Length: 1,
|
||||
Type: &client.TextEntityTypeBold{},
|
||||
},
|
||||
}, EntityToXEP0393)
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "a*b*_*cd*e_" {
|
||||
t.Errorf("Wrong intersecting formatting: %v", markup)
|
||||
}
|
||||
|
@ -361,7 +361,7 @@ func TestFormattingXEP0393InlineCode(t *testing.T) {
|
|||
Length: 25,
|
||||
Type: &client.TextEntityTypePre{},
|
||||
},
|
||||
}, EntityToXEP0393)
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "Is `Gajim` a thing?\n\n```\necho 'Hello'\necho 'world'\n```\n\nhruck(" {
|
||||
t.Errorf("Wrong intersecting formatting: %v", markup)
|
||||
}
|
||||
|
@ -374,7 +374,7 @@ func TestFormattingMarkdownStrikethrough(t *testing.T) {
|
|||
Length: 3,
|
||||
Type: &client.TextEntityTypeStrikethrough{},
|
||||
},
|
||||
}, EntityToMarkdown)
|
||||
}, MarkupModeMarkdown)
|
||||
if markup != "Everyone ~~dis~~likes cake." {
|
||||
t.Errorf("Wrong strikethrough formatting: %v", markup)
|
||||
}
|
||||
|
@ -387,14 +387,14 @@ func TestFormattingXEP0393Strikethrough(t *testing.T) {
|
|||
Length: 3,
|
||||
Type: &client.TextEntityTypeStrikethrough{},
|
||||
},
|
||||
}, EntityToXEP0393)
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "Everyone ~dis~likes cake." {
|
||||
t.Errorf("Wrong strikethrough formatting: %v", markup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaspLeft(t *testing.T) {
|
||||
text := "a b c"
|
||||
text := textToDoubledRunes("a b c")
|
||||
entities := []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 1,
|
||||
|
@ -409,7 +409,7 @@ func TestClaspLeft(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClaspBoth(t *testing.T) {
|
||||
text := "a b c"
|
||||
text := textToDoubledRunes("a b c")
|
||||
entities := []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 1,
|
||||
|
@ -424,7 +424,7 @@ func TestClaspBoth(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClaspNotNeeded(t *testing.T) {
|
||||
text := " abc "
|
||||
text := textToDoubledRunes(" abc ")
|
||||
entities := []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 1,
|
||||
|
@ -439,7 +439,7 @@ func TestClaspNotNeeded(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClaspNested(t *testing.T) {
|
||||
text := "a b c"
|
||||
text := textToDoubledRunes("a b c")
|
||||
entities := []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 1,
|
||||
|
@ -459,7 +459,7 @@ func TestClaspNested(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClaspEmoji(t *testing.T) {
|
||||
text := "a 🐖 c"
|
||||
text := textToDoubledRunes("a 🐖 c")
|
||||
entities := []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 1,
|
||||
|
@ -472,3 +472,111 @@ func TestClaspEmoji(t *testing.T) {
|
|||
t.Errorf("Wrong claspemoji: %#v", entities)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoNewlineBlockquoteXEP0393(t *testing.T) {
|
||||
markup := Format("yes it can i think", []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 4,
|
||||
Length: 6,
|
||||
Type: &client.TextEntityTypeBlockQuote{},
|
||||
},
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "yes \n> it can\n i think" {
|
||||
t.Errorf("Wrong blockquote formatting: %v", markup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoNewlineBlockquoteMarkdown(t *testing.T) {
|
||||
markup := Format("yes it can i think", []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 4,
|
||||
Length: 6,
|
||||
Type: &client.TextEntityTypeBlockQuote{},
|
||||
},
|
||||
}, MarkupModeMarkdown)
|
||||
if markup != "yes \n> it can\n\n i think" {
|
||||
t.Errorf("Wrong blockquote formatting: %v", markup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultilineBlockquoteXEP0393(t *testing.T) {
|
||||
markup := Format("hruck\npuck\n\nshuck\ntext", []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 0,
|
||||
Length: 17,
|
||||
Type: &client.TextEntityTypeBlockQuote{},
|
||||
},
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "> hruck\n> puck\n> \n> shuck\ntext" {
|
||||
t.Errorf("Wrong blockquote formatting: %v", markup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultilineBlockquoteMarkdown(t *testing.T) {
|
||||
markup := Format("hruck\npuck\n\nshuck\ntext", []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 0,
|
||||
Length: 17,
|
||||
Type: &client.TextEntityTypeBlockQuote{},
|
||||
},
|
||||
}, MarkupModeMarkdown)
|
||||
if markup != "> hruck\npuck\n\n> shuck\n\ntext" {
|
||||
t.Errorf("Wrong blockquote formatting: %v", markup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMixedBlockquoteXEP0393(t *testing.T) {
|
||||
markup := Format("hruck\npuck\nshuck\ntext", []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 0,
|
||||
Length: 16,
|
||||
Type: &client.TextEntityTypeBlockQuote{},
|
||||
},
|
||||
&client.TextEntity{
|
||||
Offset: 0,
|
||||
Length: 16,
|
||||
Type: &client.TextEntityTypeBold{},
|
||||
},
|
||||
&client.TextEntity{
|
||||
Offset: 0,
|
||||
Length: 10,
|
||||
Type: &client.TextEntityTypeItalic{},
|
||||
},
|
||||
&client.TextEntity{
|
||||
Offset: 7,
|
||||
Length: 2,
|
||||
Type: &client.TextEntityTypeStrikethrough{},
|
||||
},
|
||||
}, MarkupModeXEP0393)
|
||||
if markup != "> *_hruck\n> p~uc~k_\n> shuck*\ntext" {
|
||||
t.Errorf("Wrong blockquote formatting: %v", markup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMixedBlockquoteMarkdown(t *testing.T) {
|
||||
markup := Format("hruck\npuck\nshuck\ntext", []*client.TextEntity{
|
||||
&client.TextEntity{
|
||||
Offset: 0,
|
||||
Length: 16,
|
||||
Type: &client.TextEntityTypeBlockQuote{},
|
||||
},
|
||||
&client.TextEntity{
|
||||
Offset: 0,
|
||||
Length: 16,
|
||||
Type: &client.TextEntityTypeBold{},
|
||||
},
|
||||
&client.TextEntity{
|
||||
Offset: 0,
|
||||
Length: 10,
|
||||
Type: &client.TextEntityTypeItalic{},
|
||||
},
|
||||
&client.TextEntity{
|
||||
Offset: 7,
|
||||
Length: 2,
|
||||
Type: &client.TextEntityTypeStrikethrough{},
|
||||
},
|
||||
}, MarkupModeMarkdown)
|
||||
if markup != "> **_hruck\np~~uc~~k_\nshuck**\n\ntext" {
|
||||
t.Errorf("Wrong blockquote formatting: %v", markup)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -593,7 +593,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|||
return "<empty message>"
|
||||
}
|
||||
|
||||
markupFunction := c.getFormatter()
|
||||
markupMode := c.getFormatter()
|
||||
switch message.Content.MessageContentType() {
|
||||
case client.TypeMessageSticker:
|
||||
sticker, _ := message.Content.(*client.MessageSticker)
|
||||
|
@ -646,7 +646,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|||
return formatter.Format(
|
||||
photo.Caption.Text,
|
||||
photo.Caption.Entities,
|
||||
markupFunction,
|
||||
markupMode,
|
||||
)
|
||||
}
|
||||
case client.TypeMessageAudio:
|
||||
|
@ -657,7 +657,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|||
return formatter.Format(
|
||||
audio.Caption.Text,
|
||||
audio.Caption.Entities,
|
||||
markupFunction,
|
||||
markupMode,
|
||||
)
|
||||
}
|
||||
case client.TypeMessageVideo:
|
||||
|
@ -668,7 +668,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|||
return formatter.Format(
|
||||
video.Caption.Text,
|
||||
video.Caption.Entities,
|
||||
markupFunction,
|
||||
markupMode,
|
||||
)
|
||||
}
|
||||
case client.TypeMessageDocument:
|
||||
|
@ -679,7 +679,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|||
return formatter.Format(
|
||||
document.Caption.Text,
|
||||
document.Caption.Entities,
|
||||
markupFunction,
|
||||
markupMode,
|
||||
)
|
||||
}
|
||||
case client.TypeMessageText:
|
||||
|
@ -690,7 +690,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|||
return formatter.Format(
|
||||
text.Text.Text,
|
||||
text.Text.Entities,
|
||||
markupFunction,
|
||||
markupMode,
|
||||
)
|
||||
}
|
||||
case client.TypeMessageVoiceNote:
|
||||
|
@ -701,7 +701,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|||
return formatter.Format(
|
||||
voice.Caption.Text,
|
||||
voice.Caption.Entities,
|
||||
markupFunction,
|
||||
markupMode,
|
||||
)
|
||||
}
|
||||
case client.TypeMessageVideoNote:
|
||||
|
@ -714,7 +714,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|||
return formatter.Format(
|
||||
animation.Caption.Text,
|
||||
animation.Caption.Entities,
|
||||
markupFunction,
|
||||
markupMode,
|
||||
)
|
||||
}
|
||||
case client.TypeMessageContact:
|
||||
|
@ -1500,8 +1500,8 @@ func (c *Client) hasLastMessageHashChanged(chatId, messageId int64, content clie
|
|||
return !ok || oldHash != newHash
|
||||
}
|
||||
|
||||
func (c *Client) getFormatter() func(*client.TextEntity) (*formatter.Insertion, *formatter.Insertion) {
|
||||
return formatter.EntityToXEP0393
|
||||
func (c *Client) getFormatter() formatter.MarkupModeType {
|
||||
return formatter.MarkupModeXEP0393
|
||||
}
|
||||
|
||||
func (c *Client) usernamesToString(usernames []string) string {
|
||||
|
|
Loading…
Reference in a new issue