1 "a line editor for src/ui/textbox.slate"
3 prototypes ensureNamespace: #Editor &delegate: True.
6 Editor define: #Cursor &parents: {Cloneable} &slots: {#line -> 0. #column -> 0}.
7 Editor define: #Line &parents: {Cloneable} &slots: {#contents -> ''}.
8 Editor define: #EditorAction &parents: {Cloneable} &slots: {#redo -> Nil. #undo -> Nil}.
10 Editor define: #LineEditor &parents: {Cloneable}
11 &slots: {#lines -> ExtensibleArray new.
12 #cursors -> Stack new.
13 "when the cursor moves these may be updated... they help decide what needs to be painted"
14 #lineDelimeter -> '\n'.
15 #firstVisibleLine -> 0.
17 #undoHistory -> ExtensibleArray new.
18 #undoHistoryPosition -> 0.
22 x@(LineEditor traits) new
29 le@(LineEditor traits) reset
31 le lines: (ExtensibleArray newWith: Line new).
32 le cursors: (Stack newWith: (Cursor line: 0 column: 0)).
33 le undoHistory: ExtensibleArray new.
37 le@(LineEditor traits) cursor
38 ["return the current cursor point"
42 c@(Cursor traits) line: line column: column
44 c clone `setting: #{#line. #column} to: {line. column}
47 c1@(Cursor traits) min: c2@(Cursor traits)
49 c1 line < c2 line ifTrue: [^ c1].
50 c1 line > c2 line ifTrue: [^ c2].
51 c1 column < c2 column ifTrue: [^ c1].
55 c1@(Cursor traits) max: c2@(Cursor traits)
57 c1 line < c2 line ifTrue: [^ c2].
58 c1 line > c2 line ifTrue: [^ c1].
59 c1 column < c2 column ifTrue: [^ c2].
63 c1@(Cursor traits) = c2@(Cursor traits)
65 c1 line = c2 line /\ [c1 column = c2 column]
68 ea@(EditorAction traits) redo: redo undo: undo
70 ea clone `setting: #{#redo. #undo} to: {redo. undo}
73 "------------------------------------------------------------"
74 "functions that you can replace if you change the underlying data types. all functions on the buffer
75 should use these functions instead of accessing the lines directly. these should NOT modify the cursor point"
76 "------------------------------------------------------------"
78 l@(Line traits) size [l contents size].
79 l@(Line traits) first: n [l contents first: n].
80 l@(Line traits) allButFirst: n [l contents allButFirst: n].
81 l@(Line traits) merge: l2@(Line traits) [l contents: l contents ; l2 contents].
82 l@(Line traits) as: _@(String traits) [l contents].
84 l@(Line traits) deleteAt: n
86 l contents: (l first: n) ; (l allButFirst: n + 1).
90 l@(Line traits) insert: c at: n
92 l contents: (l first: n) ; (c as: String) ; (l allButFirst: n).
96 le@(LineEditor traits) lineCount [le lines size].
98 le@(LineEditor traits) lineAt: line
103 le@(LineEditor traits) textOnLineAfter: c@(Cursor traits)
104 ["this includes the point"
105 (le lineAt: c line) allButFirst: c column
108 le@(LineEditor traits) textOnLineBefore: c@(Cursor traits)
110 (le lineAt: c line) first: c column
113 le@(LineEditor traits) deleteFrom: c@(Cursor traits)
115 (le lineAt: c line) deleteAt: c column.
118 le@(LineEditor traits) characterAt: c@(Cursor traits)
120 line := (le lineAt: c line).
121 line contents size <= c column ifTrue: [line contents size = 0
122 ifTrue: [^ (line contents at: line contents size - 1)]
124 line contents at: c column
128 le@(LineEditor traits) insertCharacter: c@(ASCIICharacter traits) at: point@(Cursor traits)
130 (le lineAt: point line) insert: c at: point column.
134 le@(LineEditor traits) setTextOnLine: line to: s
136 (le lineAt: line) contents: s
139 le@(LineEditor traits) insertLineAt: line withText: s
141 le lines at: line insert: (Line new `>> [contents: s. ]).
144 le@(LineEditor traits) breakLine: line at: column
146 p := (Cursor line: line column: column).
147 restOfLine := (le textOnLineAfter: p).
148 le insertLineAt: line withText: (le textOnLineBefore: p).
149 le setTextOnLine: line + 1 to: restOfLine.
152 le@(LineEditor traits) mergeLine: line with: line2
154 (le lineAt: line) merge: (le lineAt: line2).
155 le deleteLine: line2.
158 le@(LineEditor traits) deleteLine: line
160 le lines at: line remove: 1
163 p@(Cursor traits) forwardOn: le@(LineEditor traits)
166 (le lineAt: point line) size > point column ifTrue: [point column: point column + 1. ^ point].
167 le lineCount <= (point line + 1) ifTrue: [inform: 'End of buffer'. ^ point].
168 point line: point line + 1.
173 p@(Cursor traits) forwardOnSameLine: le@(LineEditor traits)
176 (le lineAt: point line) size > point column ifTrue: [point column: point column + 1. ^ point].
180 p@(Cursor traits) backwardOn: le@(LineEditor traits)
183 point column > 0 ifTrue: [point column: point column - 1. ^ point].
184 point line < 1 ifTrue: [inform: 'Beginning of buffer'. ^ point].
185 point line: point line - 1.
186 point column: (le lineAt: point line) size.
190 p@(Cursor traits) nextLineOn: le@(LineEditor traits)
193 point line: (point line + 1 min: le lineCount - 1).
197 p@(Cursor traits) previousLineOn: le@(LineEditor traits)
200 point line: (point line - 1 max: 0).
206 p@(Cursor traits) setTo: point@(Cursor traits)
209 p column: point column.
213 le@(LineEditor traits) do: block undo: undoBlock record: record
215 record `defaultsTo: True.
216 record ifFalse: [^ block do].
217 undoFromEnd := le undoHistory size - le undoHistoryPosition.
218 undoFromEnd > 0 ifTrue: [le undoHistory removeLast: undoFromEnd].
220 "fixme: perhaps catch errors from doing the block and altering undo history?"
221 le undoHistoryPosition: le undoHistoryPosition + 1.
222 le undoHistory addLast: (EditorAction redo: block undo: undoBlock).
227 le@(LineEditor traits) undo
229 le undoHistoryPosition < 1 ifTrue: [^ Nil].
230 le undoHistoryPosition: le undoHistoryPosition - 1.
231 (le undoHistory at: le undoHistoryPosition) undo do.
234 le@(LineEditor traits) redo
236 le undoHistoryPosition >= le undoHistory size ifTrue: [^ Nil].
237 (le undoHistory at: le undoHistoryPosition) redo do.
238 le undoHistoryPosition: le undoHistoryPosition + 1.
242 "------------------------------------------------------------"
243 "functions here use the functions above to modify the buffer.
244 these functions will create undo history and have side effects
247 record -> should this be recorded in the undo history?"
248 "------------------------------------------------------------"
250 "fixme use forward on same line and stuff"
252 le@(LineEditor traits) insert: str@(ASCIIString traits) at: point@(Cursor traits) &record
253 [ |s| "fixme this is slow"
254 s := (str copyReplaceAll: le lineDelimeter with: '\r').
255 le do: [s do: [|:c| le insert: c at: point &record: False]]
256 undo: [s size timesRepeat: [le deleteBackwardAt: point &record: False]]
260 le@(LineEditor traits) insert: c@(ASCIICharacter traits) at: point@(Cursor traits) &record
262 p := point copy. "make a copy so the undo history doesn't get one with side effects"
264 le do: [le breakLine: p line at: p column.
265 point setTo: (p forwardOn: le)]
266 undo: [le deleteFrom: p.
269 ifFalse: [le do: [le insertCharacter: c at: p.
270 point setTo: (p forwardOnSameLine: le)]
271 undo: [le deleteFrom: p.
278 le@(LineEditor traits) deleteBackwardAt: point@(Cursor traits) &record
279 [ | deletedCharacter p prevLineSize|
280 p := point copy. "make a copy so the undo history doesn't get one with side effects"
281 p column < 1 ifTrue: [p line = 0 ifTrue: [inform: 'Cannot delete beginning of buffer'. ^ Nil].
282 prevLineSize := (le lineAt: point line - 1) size.
283 le do: [point setTo: (p backwardOn: le).
284 le mergeLine: p line - 1 with: p line]
285 undo: [le breakLine: p line - 1 at: prevLineSize.
288 ifFalse: [deletedCharacter: (le characterAt: (p backwardOn: le)).
289 inform: 'deleted: ' ; deletedCharacter printString.
290 le do: [le deleteFrom: (p backwardOn: le).
291 point setTo: (p backwardOn: le)]
292 undo: [le insertCharacter: deletedCharacter at: (p backwardOn: le).
298 "fixme: deleteFrom name might be confused with this"
299 le@(LineEditor traits) deleteAt: point@(Cursor traits) &record
300 [ | deletedCharacter p lineSize|
301 p := point copy. "make a copy so the undo history doesn't get one with side effects"
302 p column >= (le lineAt: p line) size
303 ifTrue: [p line + 1 >= le lineCount ifTrue: [inform: 'Cannot delete end of buffer'. ^ Nil].
304 lineSize := (le lineAt: point line) size.
305 le do: [le mergeLine: p line with: p line + 1.
307 undo: [le breakLine: p line at: lineSize.
310 ifFalse: [deletedCharacter: (le characterAt: p).
311 inform: 'deleted: ' ; deletedCharacter printString.
312 le do: [le deleteFrom: p.
314 undo: [le insertCharacter: deletedCharacter at: p.
320 le@(LineEditor traits) textFrom: start@(Cursor traits) to: end@(Cursor traits)
321 [ |startLineText endLineText |
322 (start min: end) == end ifTrue: [error: 'Cannot get text because start point is >= end' ; start printString ; end printString].
323 start line = end line ifTrue: [^ ((le lineAt: start line) contents copyFrom: start column to: end column - 1)].
324 startLineText := ((le lineAt: start line) contents allButFirst: start column).
325 endLineText := ((le lineAt: end line) contents first: end column).
326 startLineText concatenateAll:
327 ((start line + 1 to: end line - 1) collect: [|:i| (le lineAt: i) contents]) ; {endLineText}
328 &separator: le lineDelimeter
332 le@(LineEditor traits) wipeFrom: start@(Cursor traits) to: end@(Cursor traits) &record
333 [ | text startSaved |
334 text := (le textFrom: start to: end).
335 inform: 'text: ' ; text.
337 le do: [text size timesRepeat: [le deleteBackwardAt: end &record: False]]
340 text do: [|:c| le insert: c at: point &record: False]]
346 le@(LineEditor traits) setMark &record
348 le do: [le cursors push: le cursor copy] undo: [le cursors pop] record: record
351 le@(LineEditor traits) deleteMark &record
353 le cursors size > 1 ifFalse: [inform: 'Cannot delete last mark'. ^ Nil].
355 le do: [le cursors pop] undo: [le cursors push: mark copy] record: record
358 le@(LineEditor traits) cutTo: window &record
359 [ |minPoint maxPoint text|
360 le cursors size < 2 ifTrue: [inform: 'cannot cut... no marked region'. ^ Nil].
361 minPoint := (le cursors top min: (le cursors fromTop: 1)).
362 maxPoint := (le cursors top max: (le cursors fromTop: 1)).
363 text := (le wipeFrom: minPoint to: maxPoint &record: record).
364 inform: 'text: ' ; text.
365 window clipboardCopy: text.
369 le@(LineEditor traits) copyTo: window &record
370 [ |text maxPoint minPoint|
371 le cursors size < 2 ifTrue: [inform: 'cannot copy... no marked region'. ^ Nil].
372 minPoint := (le cursors top min: (le cursors fromTop: 1)).
373 maxPoint := (le cursors top max: (le cursors fromTop: 1)).
374 text := (le textFrom: minPoint to: maxPoint).
375 inform: 'text: ' ; text.
376 window clipboardCopy: text.
380 le@(LineEditor traits) pasteFrom: window &record
382 str := (window clipboardPasteAs: String).
383 le insert: (str before: $\0 ifAbsent: [str]) at: le cursor.