1     let edit_keys : Set<Key> = array@
2         Key/Z Key/X Key/V Key/Tab Key/Enter Key/NumPadEnter Key/Delete Key/Backspace
3     
4     let remove_range (text_box : MultilineTextBox
5                       start : TextPos
6                       end : TextPos
7                       cursor_pos : TextPos) =
8         text_box.is_changed = true
9         let display_text = text_box.display_text
10        let item = HistoryItem/RemoveStr
11            position = start
12            =cursor_pos
13            s = display_text.range start end
14    
15        text_box.history.add item
16        display_text.remove_range start end
17        text_box.update_colors start.line
18        if start.line <> end.line && display_text.lines.size > start.line + 1 then
19            display_text.lines[start.line + 1].commented_in =
20                display_text.lines[start.line].commented_out
21    
22            text_box.update_colors (start.line + 1)
23    
24    let get_first_character_position (text : Text) (position : TextPos) =
25        let line = text.lines[position.line]
26        for i = 0 until line.length do
27            let c = line[i].char
28            if c <> ' ' && c <> '\n' then
29                return TextPos position.line i
30    
31        TextPos position.line 0
32    
33    let get_line_end_position (text : Text) (position : TextPos) =
34        let line = text.lines[position.line]
35        TextPos position.line (line.length - 1)
36    
37    let insert (text_box : MultilineTextBox) (s : String) =
38        assert s.is_not_empty
39        text_box.is_changed = true
40    
41        let display_text = text_box.display_text
42        let cursor_pos = text_box.cursor_pos
43        let last_pos : TextPos @late
44        case text_box.selection of
45            None ->
46                last_pos = display_text.insert cursor_pos s
47                let item = HistoryItem/AddStr
48                    from = cursor_pos
49                    to = last_pos
50    
51                text_box.history.add item
52                         cursor_pos = last_pos
53    
54            Some (start, end) ->
55                let remove_item = HistoryItem/RemoveStr
56                    position = start
57                    =cursor_pos
58                    s = display_text.range start end
59    
60                text_box.history.add remove_item
61                display_text.remove_range start end
62                last_pos = display_text.insert start s
63                let add_item = HistoryItem/AddStr
64                    from = start
65                    to = last_pos
66    
67                text_box.history.add add_item
68                         selection = None
69                         cursor_pos = last_pos
70    
71        text_box.update_colors cursor_pos.line (last_pos.line + 1)
72    
73    type MultilineTextBox
74        def TextEditControl.process_key (self
75                                         key : Key
76                                         action : KeyAction
77                                         mods : KeyModifiers) =
78            assert self.is_focused
79            if self.is_readonly && edit_keys.contains key then return
80    
81            let display_text = self.display_text
82            let scroller = self.scroller
83            let scroll_to (y : u32) =
84                scroller/move_to scroller None -y.as<i32>
85    
86            let remove_at (position : TextPos) (cursor_pos : TextPos) =
87                self.is_changed = true
88                let item = HistoryItem/Remove
89                    =position
90                    =cursor_pos
91                    c = display_text[position]
92    
93                self.history.add item
94                display_text.remove position
95                self.update_colors position.line
96    
97            if action <> KeyAction/Press && action <> KeyAction/Repeat then
98                return
99    
100           case key of
101               Key/Backspace ->
102                   let selection = self.selection
103                   if self.cursor_pos <> TextPos@zero || selection.is_some
104                   then
105                       case selection of
106                           None ->
107                               let cursor_pos = self.cursor_pos
108                               let remove_pos = display_text.prev cursor_pos
109                               if cursor_pos.char == 0 then
110                                   assert display_text[remove_pos] == '\n'
111   
112                               remove_at remove_pos cursor_pos
113                               self.cursor_pos = remove_pos
114   
115                           Some (l, r) ->
116                               remove_range self l r self.cursor_pos
117                               self.selection = None
118                                    cursor_pos = l
119   
120                       self.update_offset
121   
122               Key/Left
123               | Key/Right
124               | Key/Up
125               | Key/Down
126               | Key/Home
127               | Key/End
128               | Key/PageUp
129               | Key/PageDown ->
130                   let prev_cursor_pos = self.cursor_pos
131                   let mut cursor_pos = prev_cursor_pos
132                   case key of
133                       Key/Left ->
134                           if self.cursor_pos <> TextPos@zero then
135                               cursor_pos = display_text.prev self.cursor_pos
136                               self.show cursor_pos
137   
138                       Key/Right ->
139                           if self.cursor_pos < display_text.max then
140                               cursor_pos = display_text.next self.cursor_pos
141                               self.show cursor_pos
142   
143                       Key/Up | Key/Down ->
144                           let is_up = key == Key/Up
145                           if not is_up || cursor_pos.line > 0 then
146                               let position = if is_up
147                                              then cursor_pos.up
148                                              else cursor_pos.down
149   
150                               cursor_pos = self.coerce_pos position
151   
152                           if cursor_pos <> prev_cursor_pos then
153                               let (up, down) = text_position_to_y self.text_display
154                                                                   cursor_pos
155                               let line_height = self.text_display.line_height
156                               if scroller.visible_y.down - line_height < down then
157                                   let y = down + line_height
158                                           + scroller.visible_y.up
159                                           - scroller.visible_y.down
160   
161                                   scroll_to y
162   
163                               else if scroller.visible_y.up + line_height > up then
164                                   let y = if up > line_height
165                                           then up - line_height
166                                           else 0
167   
168                                   scroll_to y
169   
170                       Key/Home ->
171                           cursor_pos = get_first_character_position display_text
172                                                                     prev_cursor_pos
173                       Key/End ->
174                           cursor_pos = get_line_end_position display_text
175                                                              prev_cursor_pos
176                       Key/PageUp ->
177                           let text_display = self.text_display
178                           let (visible_up, visible_down) = scroller.visible_y
179                           let n = visible_down - visible_up |> as<f32>
180                                   / text_display.line_height as f32
181                                   |> round as u32
182   
183                           let line = if cursor_pos.line > n
184                                      then cursor_pos.line - n
185                                      else 0
186   
187                           cursor_pos = self.coerce_pos (TextPos line cursor_pos.char)
188   
189                           let y = if visible_up > visible_down - visible_up then
190                               let y = visible_up + visible_up - visible_down
191   
192                               (y - text_display.margin.up) / text_display.line_height
193                               * text_display.line_height
194                           else
195                               0
196   
197                           scroll_to y
198   
199                       Key/PageDown ->
200                           let text_display = self.text_display
201                           let (visible_up, visible_down) = scroller.visible_y
202                           let n = visible_down - visible_up
203                                   |> as<f32> / text_display.line_height as f32
204                                   |> round as u32
205   
206                           let position = TextPos
207                               line = cursor_pos.line + n
208                               char = cursor_pos.char
209   
210                           cursor_pos = self.coerce_pos position
211                           let y = (visible_down - text_display.margin.up)
212                                   / text_display.line_height * text_display.line_height
213   
214                           scroll_to y
215   
216                       else -> fail
217   
218                   set_cursor_position self prev_cursor_pos cursor_pos
219                                       is_mouse_move = false
220                                       is_move = true
221   
222               Key/Delete ->
223                   let selection = self.selection
224                   if display_text.is_not_empty
225                      && (self.cursor_pos < display_text.max || selection.is_some)
226                   then
227                       let cursor_pos = self.cursor_pos
228                       case selection of
229                           None -> remove_at cursor_pos cursor_pos
230                           Some (l, r) ->
231                               remove_range self l r cursor_pos
232                               self.selection = None
233                                    cursor_pos = l
234   
235                       self.update_offset
236   
237               Key/Enter | Key/NumPadEnter ->
238                   let cursor_pos = self.cursor_pos
239                   let first_character = get_first_character_position display_text
240                                                                      cursor_pos
241                   let indent =
242                       if first_character.char == 0
243                          && display_text.lines[cursor_pos.line].is_blank
244                       then cursor_pos.char
245                       else first_character.char.min cursor_pos.char
246   
247                   let sb = StringBuilder.new
248                   sb.append '\n'
249                   for _ = 0 until indent do
250                       sb.append ' '
251   
252                   insert self sb.as_string
253                   self.show self.cursor_pos
254   
255               Key/Tab -> insert self "    "
256               Key/C if mods.contains KeyModifiers/Control ->
257                   let window = self.os_window
258                   if self.selection ? Some (start, end) then
259                       let selected_text = display_text.range start end
260                       window.set_clipboard selected_text
261   
262               Key/X if mods.contains KeyModifiers/Control ->
263                   let window = self.os_window
264                   if self.selection ? Some (start, end) then
265                       let selected_text = display_text.range start end
266                       window.set_clipboard selected_text
267                       remove_range self start end self.cursor_pos
268                       self.selection = None
269                            cursor_pos = start
270   
271               Key/V if mods.contains KeyModifiers/Control ->
272                   let window = self.os_window
273                   let s = window.get_clipboard
274                   if s.is_not_empty then
275                       insert self s
276                       self.show self.cursor_pos
277   
278               Key/Z if mods.contains KeyModifiers/Control ->
279                   let history = self.history
280                   if history.is_not_empty then
281                       if self.selection.is_some then
282                           self.clear_selection
283   
284                       let item = history.last
285                       case item of
286                           is HistoryItem/Add ->
287                               display_text.remove item.position
288                               self.cursor_pos = item.position
289                                    update_colors item.position.line
290   
291                           is HistoryItem/AddStr ->
292                               display_text.remove_range item.from item.to
293                               self.cursor_pos = item.from
294                                    update_colors item.from.line
295   
296                               if item.from.line <> item.to.line then
297                                   self.update_colors (item.from.line + 1)
298   
299                           is HistoryItem/Remove ->
300                               display_text.insert item.position item.c
301                               self.cursor_pos = item.cursor_pos
302                                    update_colors item.position.line
303   
304                           is HistoryItem/RemoveStr ->
305                               let last_pos = display_text.insert item.position item.s
306                               self.cursor_pos = item.cursor_pos
307                                    update_colors item.position.line (last_pos.line + 1)
308                                    selection = (item.position, last_pos)
309   
310                       history.remove_at (history.size - 1)
311                       self.is_changed = true
312   
313               else -> ()
314   
315       def TextEditControl.process_char (c : Char) =
316           assert is_focused
317           if self.is_readonly then return
318   
319           is_changed = true
320           if selection ? Some (l, r) then
321               remove_range self l r cursor_pos
322               cursor_pos = l
323   
324           let item = HistoryItem/Add
325               position = cursor_pos
326   
327           history.add item
328           display_text.insert cursor_pos c
329           set_cursor_position self cursor_pos (display_text.next cursor_pos)
330                               is_mouse_move = false
331                               is_move = false
332   
333           update_colors cursor_pos.line
334