@@ -34,25 +34,31 @@ class ListFormatter(editor: AztecText) : AztecFormatter(editor) {
3434 // If the following list item is missing or it's on the same level as the current list item, create a new span
3535 if ((listItemSpanAfterSelection == null || listItemSpanAfterSelection.nestingLevel <= nestingLevel)) {
3636 selectedListItems.indentAll()
37- val wrapper = directParent.copyList(increaseNestingLevel = true ) ? : return @apply
38- editableText.setSpan(wrapper, firstSelectedItemStart, lastSelectedItemEnd, directParentFlags)
37+ val wrapper = directParentList.copyList(increaseNestingLevel = true ) ? : return @apply
38+ editableText.setSpan(wrapper, firstSelectedItemStart, selectionEnd, directParentFlags)
39+
3940 } else if (listSpanAfterSelection != null && listSpanAfterSelection.nestingLevel > nestingLevel) {
4041 selectedListItems.indentAll()
4142 listSpanAfterSelection.changeSpanStart(firstSelectedItemStart)
43+ selectedListItems.last().trimEnd(editableText.getSpanStart(listItemSpanAfterSelection))
4244 }
4345 } else if (deeperListSpanBeforeSelection?.nestingLevel == nestingLevel + 1 ) {
4446 // In this case the previous list span is indented by one level, we can indent current span on the same level
4547 if ((listItemSpanAfterSelection == null || listItemSpanAfterSelection.nestingLevel <= nestingLevel)) {
4648 selectedListItems.indentAll()
47- deeperListSpanBeforeSelection.changeSpanEnd(lastSelectedItemEnd )
49+ deeperListSpanBeforeSelection.changeSpanEnd(selectionEnd )
4850 } else if (listItemSpanAfterSelection.nestingLevel == nextItemLevel) {
4951 // Merge previous and following list before and after the selection
5052 selectedListItems.indentAll()
5153 val followingSpanEnd = editableText.getSpanEnd(listSpanAfterSelection)
5254 editableText.removeSpan(listSpanAfterSelection)
5355 deeperListSpanBeforeSelection.changeSpanEnd(followingSpanEnd)
56+ selectedListItems.last().trimEnd(editableText.getSpanStart(listItemSpanAfterSelection))
5457 }
5558 }
59+ listItemSpansBeforeSelection.filter { it.nestingLevel < nextItemLevel }.forEach {
60+ it.stretchEnd(selectionEnd)
61+ }
5662 }
5763 return true
5864 }
@@ -82,64 +88,87 @@ class ListFormatter(editor: AztecText) : AztecFormatter(editor) {
8288 when {
8389 listItemSpanBeforeSelection == null && listItemSpanAfterSelection == null -> {
8490 // In case of the selected list spam doesn't have any predecessor or successor, remove the list container
85- editableText.removeSpan(directParent )
91+ editableText.removeSpan(directParentList )
8692 selectedListItems.forEach { editableText.removeSpan(it) }
8793 }
8894 listItemSpanBeforeSelection == null && listItemSpanAfterSelection != null -> {
8995 if (listItemSpanAfterSelection.nestingLevel == nestingLevel) {
9096 // In case there is no predecessor and the successor has the same nesting level, move the list wrapper
9197 // to the end of the current selection and remove the selection from the list
9298 selectedListItems.outdentAll()
93- directParent .changeSpanStart(lastSelectedItemEnd )
99+ directParentList .changeSpanStart(selectionEnd )
94100 }
95101 }
96102 listItemSpanBeforeSelection != null && listItemSpanAfterSelection == null -> {
97103 if (listItemSpanBeforeSelection.nestingLevel >= nestingLevel) {
98104 // In case there is no successor and the predecessor has the same nesting level, move the list wrapper
99105 // to the start of the current selection and remove the selection from the list
100106 selectedListItems.outdentAll()
101- directParent .changeSpanEnd(firstSelectedItemStart)
107+ directParentList .changeSpanEnd(firstSelectedItemStart)
102108 } else {
103109 // Predecessor has a lower nesting level and there is no successor, this means that the currently
104110 // selected items can be all moved to lower nesting level and their wrapper can be removed
105111 selectedListItems.outdentAll()
106- editableText.removeSpan(directParent )
112+ editableText.removeSpan(directParentList )
107113 }
114+ directParentListItem?.trimEnd(firstSelectedItemStart)
108115 }
109116 listItemSpanBeforeSelection != null && listItemSpanAfterSelection != null -> {
110117 if (listItemSpanBeforeSelection.nestingLevel >= nestingLevel) {
111118 if (listItemSpanAfterSelection.nestingLevel == nestingLevel) {
112119 // Predecessor and successor are on the same level as selected items, this means we have to split
113120 // the current list wrapper in half and move the selected items to the lower nesting level
114121 selectedListItems.outdentAll()
115- val spanStart = editableText.getSpanStart(directParent)
116- val spanFlags = editableText.getSpanFlags(directParent)
117- editableText.setSpan(directParent.copyList(), spanStart, firstSelectedItemStart, spanFlags)
118- directParent.changeSpanStart(lastSelectedItemEnd)
122+ val spanStart = editableText.getSpanStart(directParentList)
123+ val spanFlags = editableText.getSpanFlags(directParentList)
124+ editableText.setSpan(directParentList.copyList(), spanStart, firstSelectedItemStart, spanFlags)
125+ directParentListItem?.apply {
126+ val listItemEnd = editableText.getSpanEnd(this )
127+ selectedListItems.last().changeSpanEnd(listItemEnd)
128+ }
129+ directParentList.changeSpanStart(selectionEnd)
130+ directParentListItem?.changeSpanEnd(firstSelectedItemStart)
119131 } else if (listItemSpanAfterSelection.nestingLevel < nestingLevel) {
120132 // In case the span after selection has lower nesting level, we don't have to worry about it
121133 selectedListItems.outdentAll()
122- directParent.changeSpanEnd(firstSelectedItemStart)
134+ directParentList.changeSpanEnd(firstSelectedItemStart)
135+ directParentListItem?.changeSpanEnd(firstSelectedItemStart)
123136 }
124137 } else if (listItemSpanBeforeSelection.nestingLevel < nestingLevel && listItemSpanAfterSelection.nestingLevel == nestingLevel) {
125138 // Predecessor is on lower level and successor is on the same level, this means we can move all the
126139 // selected items to lower level and leave the successor on the current level
127140 selectedListItems.outdentAll()
128- directParent.changeSpanStart(lastSelectedItemEnd)
141+ directParentList.changeSpanStart(selectionEnd)
142+ directParentListItem?.changeSpanEnd(firstSelectedItemStart)
143+ selectedListItems.last().changeSpanEnd(editableText.getSpanEnd(directParentList))
129144 } else if (listItemSpanBeforeSelection.nestingLevel < nestingLevel && listItemSpanAfterSelection.nestingLevel < nestingLevel) {
130145 // In this case the selected items are the only items on the current level. Both the successor and
131146 // the predecessor are on a lower level. This means we can remove the wrapping span and move all
132147 // the selected items to the lower level.
133148 selectedListItems.outdentAll()
134- editableText.removeSpan(directParent)
149+ editableText.removeSpan(directParentList)
150+ directParentListItem?.changeSpanEnd(firstSelectedItemStart)
135151 }
136152 }
137153 }
138154 }
139155 return true
140156 }
141157
142- private data class ListState (val nestingLevel : Int , val directParent : AztecListSpan , val directParentFlags : Int , val selectedListItems : List <AztecListItemSpan >, val deeperListSpanBeforeSelection : AztecListSpan ? , val listSpanAfterSelection : AztecListSpan ? , val listItemSpanBeforeSelection : AztecListItemSpan ? , val listItemSpanAfterSelection : AztecListItemSpan ? , val firstSelectedItemStart : Int , val lastSelectedItemEnd : Int )
158+ private data class ListState (
159+ val nestingLevel : Int ,
160+ val directParentList : AztecListSpan ,
161+ val directParentListItem : AztecListItemSpan ? ,
162+ val directParentFlags : Int ,
163+ val selectedListItems : List <AztecListItemSpan >,
164+ val deeperListSpanBeforeSelection : AztecListSpan ? ,
165+ val listSpanAfterSelection : AztecListSpan ? ,
166+ val listItemSpanBeforeSelection : AztecListItemSpan ? ,
167+ val listItemSpansBeforeSelection : List <AztecListItemSpan >,
168+ val listItemSpanAfterSelection : AztecListItemSpan ? ,
169+ val firstSelectedItemStart : Int ,
170+ val selectionEnd : Int
171+ )
143172
144173 private fun buildListState (listSpans : List <AztecListSpan >, selStart : Int , selEnd : Int ): ListState ? {
145174 val directParent = listSpans.maxByOrNull { it.nestingLevel } ? : return null
@@ -148,42 +177,57 @@ class ListFormatter(editor: AztecText) : AztecFormatter(editor) {
148177 val fullListEnd = editableText.getSpanEnd(topLevelParent)
149178 val directParentFlags = editableText.getSpanFlags(directParent)
150179
180+ var startIndex = selStart
151181 val selectedItems = editableText.getSpans(selStart, selEnd, AztecListItemSpan ::class .java).filterCorrectSpans(selectionStart = selStart, selectionEnd = selEnd)
152- if (! validateSelection(selectedItems, directParent)) return null
153182 val selectedListItems = selectedItems.filter {
154183 it.nestingLevel > directParent.nestingLevel
155184 }
185+ var countdown = selectedListItems.size
186+ while (countdown > 0 ) {
187+ selectedListItems.find { startIndex in editableText.getSpanStart(it) until editableText.getSpanEnd(it) }?.let {
188+ startIndex = editableText.getSpanEnd(it)
189+ }
190+ countdown - = 1
191+ }
192+ if (startIndex < selEnd) return null
193+
194+ val directParentListItem = selectedItems.filter { it.nestingLevel < directParent.nestingLevel }.maxByOrNull { it.nestingLevel }
156195 if (selectedListItems.isEmpty()) return null
157196 val nestingLevel = selectedListItems.first().nestingLevel
158197 if (selectedListItems.any { it.nestingLevel != nestingLevel }) return null
159198 val firstSelectedItemStart = editableText.getSpanStart(selectedListItems.first())
160- val lastSelectedItemEnd = editableText.getSpanEnd(selectedListItems.last())
199+ val selectionEnd = editableText.getSpanEnd(selectedListItems.last())
161200
162201 val allLists = editableText.getSpans(fullListStart, fullListEnd, AztecListSpan ::class .java)
163202 val allListItems = editableText.getSpans(fullListStart, fullListEnd, AztecListItemSpan ::class .java)
164203 val deeperListSpanBeforeSelection: AztecListSpan ? = allLists.find {
165204 it.nestingLevel == nestingLevel + 1 && editableText.getSpanEnd(it) == firstSelectedItemStart
166205 }
167206 val listSpanAfterSelection: AztecListSpan ? = allLists.find {
168- editableText.getSpanStart(it) == lastSelectedItemEnd
207+ val spanStart = editableText.getSpanStart(it)
208+ val spanEnd = editableText.getSpanEnd(it)
209+ spanStart in (firstSelectedItemStart + 1 ).. selectionEnd && spanEnd >= selectionEnd
169210 }
170- val listItemSpanBeforeSelection : AztecListItemSpan ? = allListItems.find {
171- editableText.getSpanEnd(it) = = firstSelectedItemStart
211+ val listItemSpansBeforeSelection : List < AztecListItemSpan > = allListItems.filter {
212+ editableText.getSpanStart(it) < firstSelectedItemStart && editableText. getSpanEnd(it) > = firstSelectedItemStart
172213 }
173214 val listItemSpanAfterSelection: AztecListItemSpan ? = allListItems.find {
174- editableText.getSpanStart(it) == lastSelectedItemEnd
215+ val spanStart = editableText.getSpanStart(it)
216+ spanStart in (firstSelectedItemStart + 1 ).. selectionEnd
175217 }
176218 return ListState (
177219 nestingLevel = nestingLevel,
178- directParent = directParent,
220+ directParentList = directParent,
221+ directParentListItem = directParentListItem,
179222 directParentFlags = directParentFlags,
180223 selectedListItems = selectedListItems,
181224 deeperListSpanBeforeSelection = deeperListSpanBeforeSelection,
182225 listSpanAfterSelection = listSpanAfterSelection,
183- listItemSpanBeforeSelection = listItemSpanBeforeSelection,
226+ listItemSpanBeforeSelection = listItemSpansBeforeSelection.maxByOrNull { it.nestingLevel },
227+ listItemSpansBeforeSelection = listItemSpansBeforeSelection,
184228 listItemSpanAfterSelection = listItemSpanAfterSelection,
185229 firstSelectedItemStart = firstSelectedItemStart,
186- lastSelectedItemEnd = lastSelectedItemEnd
230+ selectionEnd = selectionEnd
187231 )
188232 }
189233
@@ -239,5 +283,27 @@ class ListFormatter(editor: AztecText) : AztecFormatter(editor) {
239283 it.nestingLevel = nestingLevel + 2
240284 }
241285 }
286+
287+ private fun IAztecBlockSpan.stretchEnd (newEnd : Int ) {
288+ val end = editableText.getSpanEnd(this )
289+ if (end >= newEnd) {
290+ return
291+ }
292+ val start = editableText.getSpanStart(this )
293+ val flags = editableText.getSpanFlags(this )
294+ editableText.removeSpan(this )
295+ editableText.setSpan(this , start, newEnd, flags)
296+ }
297+
298+ private fun IAztecBlockSpan.trimEnd (newEnd : Int ) {
299+ val end = editableText.getSpanEnd(this )
300+ if (end <= newEnd) {
301+ return
302+ }
303+ val start = editableText.getSpanStart(this )
304+ val flags = editableText.getSpanFlags(this )
305+ editableText.removeSpan(this )
306+ editableText.setSpan(this , start, newEnd, flags)
307+ }
242308}
243309
0 commit comments