@@ -37,7 +37,12 @@ func highlightedCodeBlock(
3737 return formatted
3838}
3939
40- func highlighted( code: String , language: String , brightMode: Bool ) -> [ NSAttributedString ] {
40+ func highlighted(
41+ code: String ,
42+ language: String ,
43+ brightMode: Bool ,
44+ droppingLeadingSpaces: Bool
45+ ) -> ( code: [ NSAttributedString ] , commonLeadingSpaceCount: Int ) {
4146 let formatted = highlightedCodeBlock (
4247 code: code,
4348 language: language,
@@ -47,24 +52,70 @@ func highlighted(code: String, language: String, brightMode: Bool) -> [NSAttribu
4752 let middleDotColor = brightMode
4853 ? NSColor . black. withAlphaComponent ( 0.1 )
4954 : NSColor . white. withAlphaComponent ( 0.1 )
50- return convertToCodeLines ( formatted, middleDotColor: middleDotColor)
55+ return convertToCodeLines (
56+ formatted,
57+ middleDotColor: middleDotColor,
58+ droppingLeadingSpaces: droppingLeadingSpaces
59+ )
5160}
5261
53- private func convertToCodeLines(
62+ func convertToCodeLines(
5463 _ formattedCode: NSAttributedString ,
55- middleDotColor: NSColor
56- ) -> [ NSAttributedString ] {
64+ middleDotColor: NSColor ,
65+ droppingLeadingSpaces: Bool
66+ ) -> ( code: [ NSAttributedString ] , commonLeadingSpaceCount: Int ) {
5767 let input = formattedCode. string
68+ func isEmptyLine( _ line: String ) -> Bool {
69+ guard let regex = try ? NSRegularExpression ( pattern: #"^\s*\n?$"# ) else { return false }
70+ if regex. firstMatch (
71+ in: line,
72+ options: [ ] ,
73+ range: NSMakeRange ( 0 , line. utf16. count)
74+ ) != nil {
75+ return true
76+ }
77+ return false
78+ }
79+
5880 let separatedInput = input. components ( separatedBy: " \n " )
81+ let commonLeadingSpaceCount = {
82+ if !droppingLeadingSpaces { return 0 }
83+ let splitted = separatedInput
84+ var result = 0
85+ outerLoop: for i in [ 4 , 8 , 12 , 16 , 20 ] {
86+ for line in splitted {
87+ if isEmptyLine ( line) { continue }
88+ if i >= line. count { break outerLoop }
89+ let targetIndex = line. index ( line. startIndex, offsetBy: i - 1 )
90+ if line [ targetIndex] != " " { break outerLoop }
91+ }
92+ result = i
93+ }
94+ return result
95+ } ( )
5996 var output = [ NSAttributedString] ( )
6097 var start = 0
6198 for sub in separatedInput {
6299 let range = NSMakeRange ( start, sub. utf16. count)
63100 let attributedString = formattedCode. attributedSubstring ( from: range)
64101 let mutable = NSMutableAttributedString ( attributedString: attributedString)
102+
103+ // remove leading spaces
104+ if commonLeadingSpaceCount > 0 {
105+ let leadingSpaces = String ( repeating: " " , count: commonLeadingSpaceCount)
106+ if isEmptyLine ( mutable. string) {
107+ mutable. mutableString. setString ( " " )
108+ } else if mutable. string. hasPrefix ( leadingSpaces) {
109+ mutable. replaceCharacters (
110+ in: NSRange ( location: 0 , length: commonLeadingSpaceCount) ,
111+ with: " "
112+ )
113+ }
114+ }
115+
65116 // use regex to replace all spaces to a middle dot
66117 do {
67- let regex = try NSRegularExpression ( pattern: " [ ]* " , options: [ ] )
118+ let regex = try NSRegularExpression ( pattern: #"\s*"# , options: [ ] )
68119 let result = regex. matches (
69120 in: mutable. string,
70121 range: NSRange ( location: 0 , length: mutable. mutableString. length)
@@ -83,5 +134,5 @@ private func convertToCodeLines(
83134 output. append ( mutable)
84135 start += range. length + 1
85136 }
86- return output
137+ return ( output, commonLeadingSpaceCount )
87138}
0 commit comments