@@ -9,35 +9,111 @@ import AppKit
9
9
import CodeEditTextView
10
10
11
11
extension TextViewController {
12
- /// Handles indentation and unindentation
12
+ /// Handles indentation and unindentation for selected lines in the text view.
13
13
///
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.
15
18
///
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.
19
21
///
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
+
21
63
public func handleIndent( inwards: Bool = false ) {
22
- let indentationChars : String = indentOption. stringValue
23
64
guard !cursorPositions. isEmpty else { return }
24
65
25
66
textView. undoManager? . beginUndoGrouping ( )
26
- for cursorPosition in self . cursorPositions. reversed ( ) {
67
+ var selectionIndex = 0
68
+ textView. editSelections { textView, selection in
27
69
// 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
37
83
}
38
84
textView. undoManager? . endUndoGrouping ( )
39
85
}
40
86
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
+
41
117
/// This method is used to handle tabs appropriately when multiple lines are selected,
42
118
/// allowing normal use of tabs.
43
119
///
@@ -66,6 +142,17 @@ extension TextViewController {
66
142
return startLineInfo. index... endLineInfo. index
67
143
}
68
144
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
+
69
156
private func adjustIndentation( lineIndex: Int , indentationChars: String , inwards: Bool ) {
70
157
guard let lineInfo = textView. layoutManager. textLineForIndex ( lineIndex) else { return }
71
158
@@ -86,7 +173,8 @@ extension TextViewController {
86
173
) {
87
174
textView. replaceCharacters (
88
175
in: NSRange ( location: lineInfo. range. lowerBound, length: 0 ) ,
89
- with: indentationChars
176
+ with: indentationChars,
177
+ skipUpdateSelection: true
90
178
)
91
179
}
92
180
@@ -102,7 +190,8 @@ extension TextViewController {
102
190
103
191
textView. replaceCharacters (
104
192
in: NSRange ( location: lineInfo. range. lowerBound, length: removeSpacesCount) ,
105
- with: " "
193
+ with: " " ,
194
+ skipUpdateSelection: true
106
195
)
107
196
}
108
197
@@ -114,7 +203,8 @@ extension TextViewController {
114
203
if lineContent. first == " \t " {
115
204
textView. replaceCharacters (
116
205
in: NSRange ( location: lineInfo. range. lowerBound, length: 1 ) ,
117
- with: " "
206
+ with: " " ,
207
+ skipUpdateSelection: true
118
208
)
119
209
}
120
210
}
0 commit comments