@@ -9,35 +9,111 @@ import AppKit
99import CodeEditTextView
1010
1111extension TextViewController {
12- /// Handles indentation and unindentation
12+ /// Handles indentation and unindentation for selected lines in the text view.
1313 ///
14- /// Handles the indentation of lines in the text view based on the current indentation option.
14+ /// This function modifies the indentation of the selected lines based on the current `indentOption`.
15+ /// It handles both indenting (moving text to the right) and unindenting (moving text to the left), with the
16+ /// behavior depending on whether `inwards` is `true` or `false`. It processes the `indentOption` to apply the
17+ /// correct number of spaces or tabs.
1518 ///
16- /// This function assumes that the document is formatted according to the current selected indentation option.
17- /// It will not indent a tab character if spaces are selected, and vice versa. Ensure that the document is
18- /// properly formatted before invoking this function.
19+ /// The function operates on **one-to-many selections**, where each selection can affect **one-to-many lines**.
20+ /// Each of those lines will be modified accordingly.
1921 ///
20- /// - Parameter inwards: A Boolean flag indicating whether to outdent (default is `false`).
22+ /// ```
23+ /// +----------------------------+
24+ /// | Selection 1 |
25+ /// | |
26+ /// | +------------------------+ |
27+ /// | | Line 1 (Modified) | |
28+ /// | +------------------------+ |
29+ /// | +------------------------+ |
30+ /// | | Line 2 (Modified) | |
31+ /// | +------------------------+ |
32+ /// +----------------------------+
33+ ///
34+ /// +----------------------------+
35+ /// | Selection 2 |
36+ /// | |
37+ /// | +------------------------+ |
38+ /// | | Line 1 (Modified) | |
39+ /// | +------------------------+ |
40+ /// | +------------------------+ |
41+ /// | | Line 2 (Modified) | |
42+ /// | +------------------------+ |
43+ /// +----------------------------+
44+ /// ```
45+ ///
46+ /// **Selection Updates**:
47+ /// The method will not update the selection (and its highlighting) until all lines for the given selection
48+ /// have been processed. This ensures that the selection updates are only applied after all indentations
49+ /// are completed, preventing issues where the selection might be updated incrementally during the processing
50+ /// of multiple lines.
51+ ///
52+ /// - Parameter inwards: A `Bool` flag indicating whether to outdent (default is `false`).
53+ /// - If `inwards` is `true`, the text will be unindented.
54+ /// - If `inwards` is `false`, the text will be indented.
55+ ///
56+ /// - Note: This function assumes that the document is formatted according to the selected indentation option.
57+ /// It will not indent a tab character if spaces are selected, and vice versa. Ensure that the document is
58+ /// properly formatted before invoking this function.
59+ ///
60+ /// - Important: This method operates on the current selections in the `textView`. It performs a reverse iteration
61+ /// over the text selections, ensuring that edits do not affect the later selections.
62+
2163 public func handleIndent( inwards: Bool = false ) {
22- let indentationChars : String = indentOption. stringValue
2364 guard !cursorPositions. isEmpty else { return }
2465
2566 textView. undoManager? . beginUndoGrouping ( )
26- for cursorPosition in self . cursorPositions. reversed ( ) {
67+ var selectionIndex = 0
68+ textView. editSelections { textView, selection in
2769 // get lineindex, i.e line-numbers+1
28- guard let lineIndexes = getHighlightedLines ( for: cursorPosition. range) else { continue }
29-
30- for lineIndex in lineIndexes {
31- adjustIndentation (
32- lineIndex: lineIndex,
33- indentationChars: indentationChars,
34- inwards: inwards
35- )
36- }
70+ guard let lineIndexes = getHighlightedLines ( for: selection. range) else { return }
71+
72+ adjustIndentation ( lineIndexes: lineIndexes, inwards: inwards)
73+
74+ updateSelection (
75+ selection: selection,
76+ textSelectionCount: textView. selectionManager. textSelections. count,
77+ inwards: inwards,
78+ lineCount: lineIndexes. count,
79+ selectionIndex: selectionIndex
80+ )
81+
82+ selectionIndex += 1
3783 }
3884 textView. undoManager? . endUndoGrouping ( )
3985 }
4086
87+ private func updateSelection(
88+ selection: TextSelectionManager . TextSelection ,
89+ textSelectionCount: Int ,
90+ inwards: Bool ,
91+ lineCount: Int ,
92+ selectionIndex: Int
93+ ) {
94+ let sectionModifier = calculateSelectionIndentationAdjustment (
95+ textSelectionCount: textSelectionCount,
96+ selectionIndex: selectionIndex,
97+ lineCount: lineCount
98+ )
99+
100+ let charCount = indentOption. charCount
101+
102+ selection. range. location += inwards ? - charCount * sectionModifier : charCount * sectionModifier
103+ if lineCount > 1 {
104+ let ammount = charCount * ( lineCount - 1 )
105+ selection. range. length += inwards ? - ammount : ammount
106+ }
107+ }
108+
109+ private func calculateSelectionIndentationAdjustment(
110+ textSelectionCount: Int ,
111+ selectionIndex: Int ,
112+ lineCount: Int
113+ ) -> Int {
114+ return 1 + ( ( textSelectionCount - selectionIndex) - 1 ) * lineCount
115+ }
116+
41117 /// This method is used to handle tabs appropriately when multiple lines are selected,
42118 /// allowing normal use of tabs.
43119 ///
@@ -66,6 +142,17 @@ extension TextViewController {
66142 return startLineInfo. index... endLineInfo. index
67143 }
68144
145+ private func adjustIndentation( lineIndexes: ClosedRange < Int > , inwards: Bool ) {
146+ let indentationChars : String = indentOption. stringValue
147+ for lineIndex in lineIndexes {
148+ adjustIndentation (
149+ lineIndex: lineIndex,
150+ indentationChars: indentationChars,
151+ inwards: inwards
152+ )
153+ }
154+ }
155+
69156 private func adjustIndentation( lineIndex: Int , indentationChars: String , inwards: Bool ) {
70157 guard let lineInfo = textView. layoutManager. textLineForIndex ( lineIndex) else { return }
71158
@@ -86,7 +173,8 @@ extension TextViewController {
86173 ) {
87174 textView. replaceCharacters (
88175 in: NSRange ( location: lineInfo. range. lowerBound, length: 0 ) ,
89- with: indentationChars
176+ with: indentationChars,
177+ skipUpdateSelection: true
90178 )
91179 }
92180
@@ -102,7 +190,8 @@ extension TextViewController {
102190
103191 textView. replaceCharacters (
104192 in: NSRange ( location: lineInfo. range. lowerBound, length: removeSpacesCount) ,
105- with: " "
193+ with: " " ,
194+ skipUpdateSelection: true
106195 )
107196 }
108197
@@ -114,7 +203,8 @@ extension TextViewController {
114203 if lineContent. first == " \t " {
115204 textView. replaceCharacters (
116205 in: NSRange ( location: lineInfo. range. lowerBound, length: 1 ) ,
117- with: " "
206+ with: " " ,
207+ skipUpdateSelection: true
118208 )
119209 }
120210 }
0 commit comments