@@ -42,7 +42,7 @@ class BlockFormatter(editor: AztecText, val listStyle: ListStyle, val quoteStyle
4242 }
4343
4444 fun toggleQuote () {
45- if (! containQuote ()) {
45+ if (! containsQuote ()) {
4646 applyBlockStyle(AztecTextFormat .FORMAT_QUOTE )
4747 } else {
4848 removeBlockStyle(AztecTextFormat .FORMAT_QUOTE )
@@ -134,7 +134,7 @@ class BlockFormatter(editor: AztecText, val listStyle: ListStyle, val quoteStyle
134134 val boundsOfSelectedText = if (ignoreLineBounds) {
135135 IntRange (start, end)
136136 } else {
137- getSelectedTextBounds (editableText, start, end)
137+ getBoundsOfText (editableText, start, end)
138138 }
139139
140140 var startOfBounds = boundsOfSelectedText.start
@@ -296,34 +296,59 @@ class BlockFormatter(editor: AztecText, val listStyle: ListStyle, val quoteStyle
296296 }
297297 }
298298
299- fun getSelectedTextBounds (editable : Editable , selectionStart : Int , selectionEnd : Int ): IntRange {
300- val startOfLine: Int
301- val endOfLine: Int
299+ /* *
300+ * Returns paragraph bounds (\n) to the left and to the right of selection.
301+ */
302+ fun getBoundsOfText (editable : Editable , selectionStart : Int , selectionEnd : Int ): IntRange {
303+ val startOfBlock: Int
304+ val endOfBlock: Int
305+
306+ val selectionStartIsOnTheNewLine = selectionStart != selectionEnd && selectionStart > 0
307+ && selectionStart < editableText.length
308+ && editable[selectionStart] == ' \n '
309+
310+ val selectionStartIsBetweenNewlines = selectionStartIsOnTheNewLine
311+ && selectionStart > 0
312+ && selectionStart < editableText.length
313+ && editable[selectionStart - 1 ] == ' \n '
314+
315+ val isTrailingNewlineAtTheEndOfSelection = selectionStart != selectionEnd
316+ && selectionEnd > 0
317+ && editableText.length > selectionEnd
318+ && editableText[selectionEnd] != Constants .END_OF_BUFFER_MARKER
319+ && editableText[selectionEnd] != ' \n '
320+ && editableText[selectionEnd - 1 ] == ' \n '
302321
303322 val indexOfFirstLineBreak: Int
304- val indexOfLastLineBreak = editable.indexOf(" \n " , selectionEnd)
323+ var indexOfLastLineBreak = editable.indexOf(" \n " , selectionEnd)
305324
306- if (indexOfLastLineBreak > 0 ) {
307- val characterBeforeLastLineBreak = editable[indexOfLastLineBreak - 1 ]
308- if (characterBeforeLastLineBreak != ' \n ' ) {
309- indexOfFirstLineBreak = editable.lastIndexOf(" \n " , selectionStart - 1 ) + 1
310- } else {
311- indexOfFirstLineBreak = editable.lastIndexOf(" \n " , selectionStart)
325+
326+ if (selectionStartIsBetweenNewlines) {
327+ indexOfFirstLineBreak = selectionStart
328+ } else if (selectionStartIsOnTheNewLine) {
329+ val isSingleCharacterLine = (selectionStart > 1 && editableText[selectionStart - 1 ] != ' \n ' && editableText[selectionStart - 2 ] == ' \n ' ) || selectionStart == 1
330+ indexOfFirstLineBreak = editable.lastIndexOf(" \n " , selectionStart - if (isSingleCharacterLine) 0 else 1 ) - if (isSingleCharacterLine) 1 else 0
331+
332+ if (isTrailingNewlineAtTheEndOfSelection) {
333+ indexOfLastLineBreak = editable.indexOf(" \n " , selectionEnd - 1 )
312334 }
313- } else {
314- if (indexOfLastLineBreak == - 1 ) {
315- indexOfFirstLineBreak = if (selectionStart == 0 ) 0 else {
316- editable.lastIndexOf(" \n " , selectionStart) + 1
317- }
318- } else {
319- indexOfFirstLineBreak = editable.lastIndexOf(" \n " , selectionStart)
335+ } else if (isTrailingNewlineAtTheEndOfSelection) {
336+ indexOfFirstLineBreak = editable.lastIndexOf(" \n " , selectionStart - 1 ) + 1
337+ indexOfLastLineBreak = editable.indexOf(" \n " , selectionEnd - 1 )
338+ } else if (indexOfLastLineBreak > 0 ) {
339+ indexOfFirstLineBreak = editable.lastIndexOf(" \n " , selectionStart - 1 ) + 1
340+ } else if (indexOfLastLineBreak == - 1 ) {
341+ indexOfFirstLineBreak = if (selectionStart == 0 ) 0 else {
342+ editable.lastIndexOf(" \n " , selectionStart) + 1
320343 }
344+ } else {
345+ indexOfFirstLineBreak = editable.lastIndexOf(" \n " , selectionStart)
321346 }
322347
323- startOfLine = if (indexOfFirstLineBreak != - 1 ) indexOfFirstLineBreak else 0
324- endOfLine = if (indexOfLastLineBreak != - 1 ) (indexOfLastLineBreak + 1 ) else editable.length
348+ startOfBlock = if (indexOfFirstLineBreak != - 1 ) indexOfFirstLineBreak else 0
349+ endOfBlock = if (indexOfLastLineBreak != - 1 ) (indexOfLastLineBreak + 1 ) else editable.length
325350
326- return IntRange (startOfLine, endOfLine )
351+ return IntRange (startOfBlock, endOfBlock )
327352 }
328353
329354 fun applyBlockStyle (blockElementType : ITextFormat , start : Int = selectionStart, end : Int = selectionEnd) {
@@ -333,42 +358,27 @@ class BlockFormatter(editor: AztecText, val listStyle: ListStyle, val quoteStyle
333358 }
334359
335360 if (start != end) {
336- val nestingLevel = IAztecNestable .getNestingLevelAt(editableText, start)
361+ val nestingLevelAtTheStartOfSelection = IAztecNestable .getNestingLevelAt(editableText, start)
362+ val nestingLevelAtTheEndOfSelection = IAztecNestable .getNestingLevelAt(editableText, end)
337363
338- if (IAztecNestable .getNestingLevelAt(editableText, end) != nestingLevel) {
339- // TODO: styling across multiple nesting levels not support yet
340- return
364+ // TODO: styling across multiple nesting levels not support yet
365+ if (nestingLevelAtTheStartOfSelection != nestingLevelAtTheEndOfSelection) {
366+ if (nestingLevelAtTheStartOfSelection == 0 && nestingLevelAtTheEndOfSelection == 1 ) {
367+ // 0/1 is ok!
368+ } else {
369+ return
370+ }
341371 }
342372
343- val indexOfFirstLineBreak = editableText.indexOf(" \n " , end)
344-
345- val endOfBlock = if (indexOfFirstLineBreak != - 1 ) indexOfFirstLineBreak else editableText.length
346- val startOfBlock = editableText.lastIndexOf(" \n " , start)
347-
348- val selectedLines = editableText.subSequence(startOfBlock + 1 .. endOfBlock - 1 ) as Editable
349-
350- var numberOfLinesWithSpanApplied = 0
351- var numberOfLines = 0
352-
353- val lines = TextUtils .split(selectedLines.toString(), " \n " )
373+ val boundsOfSelectedText = getBoundsOfText(editableText, start, end)
354374
355- for (i in lines.indices) {
356- numberOfLines++
357- if (containsList(blockElementType, i, selectedLines, nestingLevel)) {
358- numberOfLinesWithSpanApplied++
359- }
360- }
375+ val startOfBlock = boundsOfSelectedText.start
376+ val endOfBlock = boundsOfSelectedText.endInclusive
361377
362- if (numberOfLines == numberOfLinesWithSpanApplied) {
363- removeBlockStyle(blockElementType)
364- } else {
365- // if block starts with newline do not move index to the right
366- val startOfBlockModifier = if (startOfBlock >= 0 && editableText[startOfBlock] == ' \n ' ) 0 else 1
367- applyBlock(makeBlockSpan(blockElementType, nestingLevel), startOfBlock + startOfBlockModifier,
368- (if (endOfBlock == editableText.length) endOfBlock else endOfBlock + 1 ))
369- }
378+ applyBlock(makeBlockSpan(blockElementType, nestingLevelAtTheStartOfSelection), startOfBlock,
379+ (if (endOfBlock == editableText.length) endOfBlock else endOfBlock))
370380 } else {
371- val boundsOfSelectedText = getSelectedTextBounds (editableText, start, end)
381+ val boundsOfSelectedText = getBoundsOfText (editableText, start, end)
372382
373383 val startOfLine = boundsOfSelectedText.start
374384 val endOfLine = boundsOfSelectedText.endInclusive
@@ -517,10 +527,10 @@ class BlockFormatter(editor: AztecText, val listStyle: ListStyle, val quoteStyle
517527
518528 if (list.isEmpty()) return false
519529
520- return list.any { containsList (textFormat, it, editableText, nestingLevel) }
530+ return list.any { containsBlockElement (textFormat, it, editableText, nestingLevel) }
521531 }
522532
523- fun containsList (textFormat : ITextFormat , index : Int , text : Editable , nestingLevel : Int ): Boolean {
533+ fun containsBlockElement (textFormat : ITextFormat , index : Int , text : Editable , nestingLevel : Int ): Boolean {
524534 val lines = TextUtils .split(text.toString(), " \n " )
525535 if (index < 0 || index >= lines.size) {
526536 return false
@@ -537,53 +547,26 @@ class BlockFormatter(editor: AztecText, val listStyle: ListStyle, val quoteStyle
537547 return spans.isNotEmpty()
538548 }
539549
540- fun containQuote (selStart : Int = selectionStart, selEnd : Int = selectionEnd): Boolean {
541- val lines = TextUtils .split(editableText.toString(), " \n " )
542- val list = ArrayList <Int >()
543-
544- for (i in lines.indices) {
545- val lineStart = (0 .. i - 1 ).sumBy { lines[it].length + 1 }
546- val lineEnd = lineStart + lines[i].length
547-
548- if (lineStart >= lineEnd) {
549- continue
550- }
551-
552- /* *
553- * lineStart >= selStart && selEnd >= lineEnd // single line, current entirely selected OR
554- * multiple lines (before and/or after), current entirely selected
555- * lineStart <= selEnd && selEnd <= lineEnd // single line, current partially or entirely selected OR
556- * multiple lines (after), current partially or entirely selected
557- * lineStart <= selStart && selStart <= lineEnd // single line, current partially or entirely selected OR
558- * multiple lines (before), current partially or entirely selected
559- */
560- if ((lineStart >= selStart && selEnd >= lineEnd)
561- || (lineStart <= selEnd && selEnd <= lineEnd)
562- || (lineStart <= selStart && selStart <= lineEnd)) {
563- list.add(i)
564- }
565- }
566-
567- if (list.isEmpty()) return false
568-
569- return list.any { containQuote(it) }
570- }
571-
572- fun containQuote (index : Int ): Boolean {
573- val lines = TextUtils .split(editableText.toString(), " \n " )
574- if (index < 0 || index >= lines.size) {
575- return false
576- }
550+ fun containsQuote (selStart : Int = selectionStart, selEnd : Int = selectionEnd): Boolean {
551+ if (selStart < 0 || selEnd < 0 ) return false
577552
578- val start = (0 .. index - 1 ).sumBy { lines[it].length + 1 }
579- val end = start + lines[index].length
553+ return editableText.getSpans(selStart, selEnd, AztecQuoteSpan ::class .java)
554+ .any {
555+ val spanStart = editableText.getSpanStart(it)
556+ val spanEnd = editableText.getSpanEnd(it)
580557
581- if (start >= end) {
582- return false
583- }
558+ if (selStart == selEnd) {
559+ if (editableText.length == selStart) {
560+ selStart in spanStart.. spanEnd
561+ } else {
562+ (spanEnd != selStart) && selStart in spanStart.. spanEnd
563+ }
584564
585- val spans = editableText.getSpans(start, end, AztecQuoteSpan ::class .java)
586- return spans.isNotEmpty()
565+ } else {
566+ (selStart in spanStart.. spanEnd || selEnd in spanStart.. spanEnd) ||
567+ (spanStart in selStart.. selEnd || spanEnd in spanStart.. spanEnd)
568+ }
569+ }
587570 }
588571
589572 fun containsHeading (textFormat : ITextFormat , selStart : Int = selectionStart, selEnd : Int = selectionEnd): Boolean {
0 commit comments