1     type Text =
2         val lines =
3             let line = TextLine.new
4             line.insert 0 '\n'
5             mut_list_of line
6     
7         fun get (i : u32) = lines[i]
8         fun get (position : TextPos) = lines[position.line][position.char].char
9     
10        def clear =
11            lines.clear
12            let line = TextLine.new
13            line.insert 0 '\n'
14            lines.add line
15    
16        def set_color (line : u32) (color : u8) =
17            let l = lines[line]
18            for i = 0 until l.length do
19                l.set_color i color
20    
21        def is_not_empty =
22            lines.size > 1 || lines.size > 0 && lines[0].length > 0
23    
24        def max = TextPos (lines.size - 1) (lines.last.length - 1)
25    
26        def line_max_index (line : u32) =
27            TextPos line (lines[line].length - 1)
28    
29        def assert_end_of_line_chars =
30            for line in lines do
31                assert line.last == '\n'
32    
33        def insert (position : TextPos) (c : Char) =
34            let line = lines[position.line]
35            line.insert position.char c
36            if c == '\n' then
37                let s = line.as_string
38                assert s.length > position.char + 1
39                let next_line = TextLine.new
40                next_line.insert 0 (s.drop (position.char + 1))
41                line.remove_from (position.char + 1)
42                lines.add (position.line + 1) next_line
43                assert self[line_max_index position.line] == '\n'
44                assert self[line_max_index (position.line + 1)] == '\n'
45    
46            assert_end_of_line_chars
47    
48        def insert (position : TextPos) (s : String) =
49            let mut next = position
50            for c in s.chars.filter { _.is_ascii } do
51                lines[next.line].insert next.char c
52                if c == '\n' then
53                    let line = lines[next.line]
54                    let next_line = TextLine.new
55                    for i = next.char + 1 until line.length do
56                        next_line.insert (i - next.char - 1) line[i]
57    
58                    line.remove_from (next.char + 1)
59                    lines.add (next.line + 1) next_line
60                    next = TextPos (next.line + 1) 0
61                else
62                    next = next.right
63    
64            assert_end_of_line_chars
65    
66            next
67    
68        def append (s : String) =
69            let index = line_max_index (lines.size - 1)
70            insert index s |> ignore
71    
72        def append (c : Char) =
73            let index = line_max_index (lines.size - 1)
74            insert index c
75    
76        def remove (position : TextPos) =
77            let c = self[position]
78            lines[position.line].remove position.char
79            if c == '\n' then
80                let line = lines[position.line + 1]
81                lines.remove_at (position.line + 1)
82                lines[position.line].insert position.char line.as_string
83    
84            assert_end_of_line_chars
85    
86        def remove_range (from : TextPos) (to : TextPos) =
87            assert from < to
88            if from.line == to.line then
89                let line = lines[from.line]
90                line.remove_range from.char to.char
91                return
92    
93            let first_line = lines[from.line]
94            first_line.remove_from from.char
95            let last_line = lines[to.line]
96            first_line.insert from.char (last_line.as_string.drop to.char)
97            lines.remove_at to.line
98            for _ = 0 until to.line - from.line - 1 do
99                lines.remove_at (from.line + 1)
100   
101           assert_end_of_line_chars
102   
103       def last_pos =
104           let line = lines.size - 1
105           TextPos line (lines[line].length - 1)
106   
107       def prev (position : TextPos) = when
108           position.char > 0 -> position.left
109           position.line > 0 -> line_max_index (position.line - 1)
110           else -> fail position
111   
112       def next (position : TextPos) =
113           let line = lines[position.line]
114           when
115               position.char < line.length - 1 -> position.right
116               position.line < lines.size - 1 -> TextPos (position.line + 1) 0
117               else -> fail position
118   
119       def range (from : TextPos) (to : TextPos) =
120           assert from < to
121           let sb = StringBuilder.new
122           if from.line == to.line then
123               let line = lines[from.line]
124               for i = from.char until to.char do
125                   sb.append line[i].char
126   
127               return sb.create
128   
129           let first_line = lines[from.line]
130           for i = from.char until first_line.length do
131               sb.append first_line[i].char
132   
133           for i = from.line + 1 until to.line do
134               let line = lines[i]
135               sb.append line.as_string
136   
137           let last_line = lines[to.line]
138           for i = 0 until to.char do
139               sb.append last_line[i].char
140   
141           sb.create
142   
143       def trim_end =
144           let mut index = lines.size - 1
145           while index > 0
146                 && lines[index].is_blank
147                 && lines[index - 1].is_blank
148           do
149               index -= 1
150   
151           let count = lines.size - 1 - index
152           for _ = 0 until count do
153               lines.remove_at (lines.size - 1)
154   
155           let last_line = lines[lines.size - 1]
156           if last_line.is_blank && last_line.as_string <> "\n" then
157               last_line.trim_end
158   
159       def ends_with_blank_line = lines[lines.size - 1].is_blank
160   
161       def ends_with_two_blank_lines =
162           lines.size > 1 && ends_with_blank_line && lines[lines.size - 2].is_blank
163   
164       def add_empty_line =
165           let line = TextLine.new
166           line.insert 0 '\n'
167           lines.add line
168