# # $Id: editabletextlist.icn,v 1.14 2004/11/11 20:00:24 rparlett Exp $ # # This file is in the public domain. # # Author: Robert Parlett (parlett@dial.pipex.com) # package gui link graphics import undo import lang $include "guih.icn" # # A scrollable editable text area. # An CONTENT_CHANGED_EVENT is generated whenever the contents # are changed by the user, a CURSOR_MOVED_EVENT when the cursor moves, # and a SELECTION_CHANGED_EVENT whenver the selection changes. # class EditableTextList : LineBasedScrollArea( contents, printable, # The printable characters cursor_x, # cursor_y, # mark_x, # mark_y, # direction, # is_held, long_line, # undo_manager, old_contents_size, old_mw, old_cursor_x, old_cursor_y, old_has_region, changed ) method get_view_x_padding() return DEFAULT_TEXT_X_SURROUND end method get_view_y_padding() return DEFAULT_TEXT_Y_SURROUND end method get_line_count() return *self.contents end method get_contents() return self.contents end # # Set the contents of the component. # # @param x the contents, as a list of strings # method set_contents(x) self.contents := x contents_changed() end # # Call this method if the contents list, previously # set with {set_contents()}, has changed. # method contents_changed() if *self.contents = 0 then # # Must have somewhere for the cursor to go. # self.contents := [""] clear_mark() if \ (\self.parent_dialog).is_open then { long_line := &null self.cursor_x := self.cursor_y := 1 compute_and_invalidate() } undo_manager.clear() end # # Move cursor y to line n, and constrain x within range of that line. # # @p method set_cursor_y(n) local d, i, dest # Search for the nearest x position on the new line corresponding to # the current x position. d := TextWidthEx(self.cwin, self.contents[cursor_y], 1, self.cursor_x) dest := self.contents[n] i := 1 while (i <= *dest) & (TextWidthEx(self.cwin, dest, 1, i) < d) do i +:= 1 self.cursor_x := i self.cursor_y := n return n end # # Move cursor so that it is in the text area, if possible. May not be possible # if cursor at end of line to the left of the text area. # # @p method constrain_cursor() if self.cursor_y < self.get_first_line() then self.set_cursor_y(self.get_first_line()) else if self.cursor_y >= self.get_first_line() + self.get_max_lines() then self.set_cursor_y(self.get_first_line() + self.get_max_lines() - 1) s := self.contents[self.cursor_y] || " " i := TextWidthEx(self.cwin, s, 1, self.cursor_x) j := i + TextWidthEx(self.cwin, s, self.cursor_x) l := self.get_left_pos() if self.view.x - l > i then { while (self.cursor_x < *s) & (TextWidthEx(self.cwin, s, 1, self.cursor_x) < self.view.x - l) do self.cursor_x +:= 1 } else if self.view.x - l + self.view.w < j then { while (self.cursor_x > 1) & TextWidthEx(self.cwin, s, 1, self.cursor_x + 1) > self.view.x - l + self.view.w do self.cursor_x -:= 1 } end # # Move the text area displayed so that the cursor is on the screen. # # @p method constrain_line() if self.cursor_y < self.get_first_line() then self.vsb.set_value(self.line_height * (self.cursor_y - 1)) else if self.cursor_y > self.get_last_line() then self.vsb.set_value(self.line_height * (self.cursor_y - self.get_max_lines())) s := self.contents[self.cursor_y] || " " i := TextWidthEx(self.cwin, s, 1, self.cursor_x) j := i + TextWidthEx(self.cwin, s, self.cursor_x) l := self.get_left_pos() if self.view.x - l > i then self.hsb.set_value(i) else if self.view.x - l + self.view.w < j then self.hsb.set_value(j - self.view.w) end method handle_cut(e) start_handle(e) if has_region() then { get_clipboard().set_content(get_region()) delete_region(e) } end_handle(e) end method handle_copy(e) start_handle(e) if has_region() then { get_clipboard().set_content(get_region()) } end_handle(e) end method can_undo() return undo_manager.can_undo() end method can_redo() return undo_manager.can_redo() end method get_pasteable_clipboard() local x, t, s, c x := get_clipboard().get_content() t := string(x) | fail # Apply the filter to the string to paste s := "" every c := !t do { if member(printable, c) then s ||:= c } if *s = 0 then fail return s end method handle_paste(e) local s, ce, ed start_handle(e) if s := get_pasteable_clipboard() then { ce := CompoundEdit() if has_region() then { ed := EditableTextListDeleteRegionEdit(self) ed.redo() ce.add_edit(ed) } ed := EditableTextListPasteEdit(self, s) ed.redo() ce.add_edit(ed) undo_manager.add_edit(ce) changed := 1 } end_handle(e) end method on_vsb(ev) start_handle() self.constrain_cursor() self.refresh() end_handle(ev) end method on_hsb(ev) start_handle() self.constrain_cursor() self.refresh() end_handle(ev) end method start_handle() old_contents_size := *contents old_mw := TextWidthEx(cwin, contents[long_line]) old_cursor_x := cursor_x old_cursor_y := cursor_y old_has_region := has_region() | &null changed := &null end method end_handle(e) local hr, moved if \changed then { if (*contents ~= old_contents_size) | /long_line | (old_mw ~= TextWidthEx(cwin, contents[long_line])) then { # # Contents changed. Re-compute all internal fields, ensure on # screen and re-display whole object. # self.set_internal_fields() self.constrain_line() self.invalidate() } else { self.constrain_line() self.refresh(1) } fire(CONTENT_CHANGED_EVENT, e) } if (cursor_x ~= old_cursor_x) | (cursor_y ~= old_cursor_y) then { moved := 1 if /changed then { self.constrain_line() self.refresh(1) } fire(CURSOR_MOVED_EVENT, e) } # # Deduce a region change from looking for a change in whether there # was/is a region; or if there is and was a region and if the cursor # has moved, or the content changed. # hr := has_region() if (/old_has_region & \hr) | (\old_has_region & /hr) | (\hr & (\moved | \changed)) then { self.invalidate() fire(SELECTION_CHANGED_EVENT, e) } end method handle_event(e) (\self.vsb).handle_event(e) (\self.hsb).handle_event(e) if e === (&lpress | &rpress | &mpress) then handle_press(e) else if e === (&ldrag | &rdrag | &mdrag) then handle_drag(e) else if e === (&lrelease | &rrelease | &mrelease) then handle_release(e) else if \self.has_focus then { case e of { Key_Home : handle_key_home(e) Key_End : handle_key_end(e) Key_PgUp : handle_key_page_up(e) Key_PgDn : handle_key_page_down(e) Key_Up : handle_key_up(e) Key_Down : handle_key_down(e) Key_Left : handle_key_left(e) Key_Right : handle_key_right(e) "\b" : handle_delete_left(e) "\r" | "\l": handle_return(e) "\^k" : handle_delete_line(e) "\^a" : handle_select_all(e) "\^e" : handle_end_of_line(e) "\d" | "\^d" : handle_delete_right(e) "\^x" : handle_cut(e) "\^c" : handle_copy(e) "\^v" : handle_paste(e) "\^z" : handle_undo(e) "\^y" : handle_redo(e) default : handle_default(e) } } end # # Set cursor from the current &x, &y # # @p method set_cursor_from_pos() local l, nlines, s, i l := (&y - self.view.y) / self.line_height nlines := self.get_curr_lines() l <:= 0 l >:= nlines - 1 self.cursor_y := l + self.get_first_line() s := self.contents[self.cursor_y] || " " i := 1 l := self.get_left_pos() while (i < *s) & (TextWidthEx(self.cwin, s, 1, i + 1) < &x - l) do i+:= 1 self.cursor_x := i end method clear_mark() mark_x := mark_y := &null end method has_region() return \mark_x & (mark_x ~= cursor_x | mark_y ~= cursor_y) end method handle_press(e) start_handle(e) if ((self.x <= &x < self.x + self.view.w + 2 * DEFAULT_TEXT_X_SURROUND) & (self.y <= &y < self.y + self.view.h + 2 * DEFAULT_TEXT_Y_SURROUND)) then { # # Button down in region - move to cursor position. # set_cursor_from_pos() self.mark_y := self.cursor_y self.mark_x := self.cursor_x self.is_held := 1 } end_handle(e) end method handle_undo(e) start_handle(e) if undo_manager.can_undo() then { undo_manager.undo() changed := 1 } end_handle(e) end method handle_redo(e) start_handle(e) if undo_manager.can_redo() then { undo_manager.redo() changed := 1 } end_handle(e) end method handle_drag(e) start_handle(e) if \self.is_held then { if &y < self.y then direction := "up" else if &y >= self.y + self.view.h + 2 * DEFAULT_TEXT_Y_SURROUND then direction := "down" else if &x < self.x then direction := "left" else if &x >= self.x + self.view.w + 2 * DEFAULT_TEXT_X_SURROUND then direction := "right" else direction := &null if /direction then { stop_ticker() set_cursor_from_pos() } else { is_ticking() | set_ticker(30) } } end_handle(e) end method tick() local l, s start_handle(e) case self.direction of { "up" : { l := self.get_first_line() - 1 if l > 0 then { self.set_cursor_y(l) } } "down" : { l := self.get_last_line() + 1 if l <= *self.contents then { self.set_cursor_y(l) } } "left" : { l := self.get_left_pos() s := self.contents[self.cursor_y] || " " while (self.cursor_x > 1) & TextWidthEx(self.cwin, s, 1, self.cursor_x) >= self.view.x - l do { self.cursor_x -:= 1 } } "right" : { l := self.get_left_pos() s := self.contents[self.cursor_y] || " " while (self.cursor_x < *s) & TextWidthEx(self.cwin, s, 1, self.cursor_x) < self.view.x - l + self.view.w do { self.cursor_x +:= 1 } } } end_handle(e) end method handle_release(e) start_handle(e) if \self.is_held then { # # Mouse released after being held down. Clear flag. If there # is no region (mouse released where it was pressed), then clear # the mark. This prevents selecting when using the scrollbars # after release. # self.is_held := &null has_region() | clear_mark() stop_ticker() } end_handle(e) end method keyboard_mark() if &shift then { /mark_x := cursor_x /mark_y := cursor_y } else clear_mark() end method handle_start_of_line(e) start_handle(e) keyboard_mark() cursor_x := 1 end_handle(e) end method handle_end_of_line(e) start_handle(e) keyboard_mark() cursor_x := *contents[cursor_y] + 1 end_handle(e) end method handle_key_up(e) start_handle(e) keyboard_mark() self.set_cursor_y(0 < self.cursor_y - 1) end_handle(e) end method handle_key_home(e) start_handle(e) keyboard_mark() cursor_y := cursor_x := 1 end_handle(e) end method handle_key_end(e) start_handle(e) keyboard_mark() cursor_y := *contents cursor_x := *contents[cursor_y] + 1 end_handle(e) end method handle_select_all(e) start_handle(e) mark_x := mark_y := 1 cursor_y := *contents cursor_x := *contents[cursor_y] + 1 end_handle(e) end method handle_key_down(e) start_handle(e) keyboard_mark() self.set_cursor_y(*self.contents >= self.cursor_y + 1) end_handle(e) end method handle_key_left(e) start_handle(e) keyboard_mark() if self.cursor_x = 1 then { if self.cursor_y > 1 then { self.cursor_y -:= 1 self.cursor_x := *self.contents[self.cursor_y] + 1 } } else self.cursor_x -:= 1 end_handle(e) end method handle_key_right(e) start_handle(e) keyboard_mark() if self.cursor_x = *self.contents[self.cursor_y] + 1 then { if self.cursor_y < *self.contents then { self.cursor_x := 1 self.cursor_y +:= 1 } } else self.cursor_x +:= 1 end_handle(e) end method handle_key_page_up(e) start_handle(e) keyboard_mark() if i := (\self.vsb).get_value() then { self.vsb.set_value(i - self.vsb.page_size) self.constrain_cursor() self.refresh() } end_handle(e) end method handle_key_page_down(e) start_handle(e) keyboard_mark() if i := (\self.vsb).get_value() then { self.vsb.set_value(i + self.vsb.page_size) self.constrain_cursor() self.refresh() } end_handle(e) end method handle_delete_line(e) local ed start_handle(e) if (cursor_y < *self.contents) | (*self.contents[cursor_y] > 0) then { ed := EditableTextListDeleteLineEdit(self) undo_manager.add_edit(ed) ed.redo() changed := 1 } end_handle(e) end method get_region() local r r := "" if self.mark_y < self.cursor_y then { r := self.contents[self.mark_y][self.mark_x:0] || "\n" every r ||:= self.contents[self.mark_y + 1 to self.cursor_y - 1] || "\n" r ||:= self.contents[self.cursor_y][1:self.cursor_x] } else if self.mark_y > self.cursor_y then { r := self.contents[self.cursor_y][self.cursor_x:0] || "\n" every r ||:= self.contents[self.cursor_y + 1 to self.mark_y - 1] || "\n" r ||:= self.contents[self.mark_y][1:self.mark_x] } else { # mark_y = cursor_y if self.mark_x < self.cursor_x then { r := self.contents[self.cursor_y][self.mark_x:self.cursor_x] } else { r := self.contents[self.cursor_y][self.cursor_x:self.mark_x] } } return r end method delete_region(e) local ed ed := EditableTextListDeleteRegionEdit(self) undo_manager.add_edit(ed) ed.redo() changed := 1 end method handle_delete_left(e) local ed start_handle(e) if has_region() then { delete_region(e) } else { if (self.cursor_x > 1) | (self.cursor_y > 1) then { ed := EditableTextListDeleteLeftEdit(self) undo_manager.add_edit(ed) ed.redo() changed := 1 } } end_handle(e) end method handle_delete_right(e) local ed start_handle(e) if has_region() then { delete_region(e) } else { if (self.cursor_x <= *contents[cursor_y]) | (self.cursor_y < *contents) then { ed := EditableTextListDeleteRightEdit(self) undo_manager.add_edit(ed) ed.redo() changed := 1 } } end_handle(e) end method handle_return(e) local ed start_handle(e) ed := EditableTextListReturnEdit(self) undo_manager.add_edit(ed) ed.redo() changed := 1 end_handle(e) end method handle_default(e) local ce, ed start_handle(e) # # Add any printable character at cursor position # if type(e) == "string" & not(&control | &meta) & any(printable, e) then { if has_region() then { ce := CompoundEdit() ed := EditableTextListDeleteRegionEdit(self) ed.redo() ce.add_edit(ed) ed := EditableTextListDefaultEdit(self, e) ed.redo() ce.add_edit(ed) undo_manager.add_edit(ce) } else { ed := EditableTextListDefaultEdit(self, e) ed.redo() undo_manager.add_edit(ed) } changed := 1 } end_handle(e) end method resize() self.ScrollArea.resize() self.constrain_line() end method draw(subject_x, subject_y, vx, vy, vw, vh) local rev, first_line, last_line, xp, yp, i # # Which lines to draw # first_line := get_first_line() last_line := get_last_line() last_line >:= get_line_count() # # Where to draw them # yp := vy + self.line_height / 2 # # Left offset # xp := vx - subject_x rev := Clone(self.cbwin, "drawop=reverse") # # Write the lines # every i := first_line to last_line do { draw_line(xp, yp, i, rev) yp +:= self.line_height } Uncouple(rev) rev := &null return end method draw_line(xp, yp, i, rev) local s, off1, off2 s := self.contents[i] left_string(self.cbwin, xp, yp, detab(s)) if i = \self.cursor_y then { s ||:= " " if \self.has_focus then { cw := Clone(self.cbwin, "bg=red", "fg=white") off := TextWidthEx(self.cbwin, s, 1, self.cursor_x) EraseRectangle(cw, xp + off, 1 + yp - self.line_height / 2, CharWidth(self.cbwin, s[self.cursor_x]), self.line_height) if s[self.cursor_x] ~== "\t" then left_string(cw, xp + off, yp, s[self.cursor_x]) Uncouple(cw) } else { cw := Clone(self.cbwin, "fg=red") Rectangle(cw, xp + TextWidthEx(self.cbwin, s, 1, self.cursor_x), 1 + yp - self.line_height / 2, CharWidth(self.cbwin, s[self.cursor_x]), self.line_height) Uncouple(cw) } } if \self.mark_y then { if (self.mark_y < i < self.cursor_y) | (self.mark_y > i > self.cursor_y) then { # Whole line selected off1 := 0 off2 := TextWidthEx(self.cbwin, s) } else if i = self.mark_y = self.cursor_y then { # Part of line if self.mark_x < self.cursor_x then { off1 := TextWidthEx(self.cbwin, s, 1, self.mark_x) off2 := TextWidthEx(self.cbwin, s, 1, self.cursor_x) } else if self.mark_x > self.cursor_x then { off1 := TextWidthEx(self.cbwin, s, 1, self.cursor_x + 1) off2 := TextWidthEx(self.cbwin, s, 1, self.mark_x) } } else if i = self.mark_y then { if self.mark_y < self.cursor_y then { off1 := TextWidthEx(self.cbwin, s, 1, self.mark_x) off2 := TextWidthEx(self.cbwin, s) } else { off1 := 0 off2 := TextWidthEx(self.cbwin, s, 1, self.mark_x) } } else if i = self.cursor_y then { if self.mark_y > self.cursor_y then { off1 := TextWidthEx(self.cbwin, s, 1, self.cursor_x + 1) off2 := TextWidthEx(self.cbwin, s) } else { off1 := 0 off2 := TextWidthEx(self.cbwin, s, 1, self.cursor_x) } } if \off1 then FillRectangle(rev, xp + off1, 1 + yp - self.line_height / 2, off2 - off1, self.line_height) } end method lost_focus(e) clear_mark() self.Component.lost_focus(e) end method get_line_height() return WAttrib(self.cwin, "fheight") end method keeps(e) # This component keeps all events. return end method get_subject_width() if /long_line then { mw := TextWidthEx(cwin, contents[1]) long_line := 1 every i := 2 to *self.contents do if mw <:= TextWidthEx(cwin, contents[i]) then long_line := i } else mw := TextWidthEx(self.cwin, contents[long_line]) return mw + TextWidthEx(self.cwin, " ") end method set_one(attr, val) case attr of { "contents" : set_contents(val) default: self.LineBasedScrollArea.set_one(attr, val) } end initially(a[]) self.LineBasedScrollArea.initially() self.set_accepts_focus() undo_manager := UndoManager() printable := cset(&cset[33:0]) ++ '\t\n' self.cursor_x := self.cursor_y := 1 set_fields(a) end class EditableTextListEdit:UndoableEdit(parent, cursor_x, cursor_y, mark_x, mark_y, long_line ) method redo() restore() self.redo_impl() end method undo() self.undo_impl() restore() end abstract method redo_impl() abstract method undo_impl() method save() self.cursor_x := parent.cursor_x self.cursor_y := parent.cursor_y self.mark_x := parent.mark_x self.mark_y := parent.mark_y self.long_line := parent.long_line end method restore() parent.cursor_x := self.cursor_x parent.cursor_y := self.cursor_y parent.mark_x := self.mark_x parent.mark_y := self.mark_y parent.long_line := self.long_line end initially(parent) self.parent := parent save() end class EditableTextListDefaultEdit:EditableTextListEdit(s) method add_edit(other) if is_instance(other, "gui__EditableTextListDefaultEdit") & (other.cursor_y = self.cursor_y) & (other.cursor_x = self.cursor_x + *s) then { s ||:= other.s return } end method redo_impl() if parent.cursor_x = 1 then parent.contents[parent.cursor_y] := s || parent.contents[parent.cursor_y] else parent.contents[parent.cursor_y][parent.cursor_x - 1] ||:= s parent.cursor_x +:= *s if TextWidthEx(parent.cwin, parent.contents[parent.cursor_y]) > TextWidthEx(parent.cwin, parent.contents[\parent.long_line]) then parent.long_line := parent.cursor_y parent.clear_mark() end method undo_impl() parent.contents[self.cursor_y][self.cursor_x +: *s] := "" end initially(parent, e) self.EditableTextListEdit.initially(parent) self.s := e end class EditableTextListReturnEdit:EditableTextListEdit() method redo_impl() local s s := parent.contents[parent.cursor_y] parent.contents[parent.cursor_y] := s[1:parent.cursor_x] parent.contents := parent.contents[1:parent.cursor_y + 1] ||| [s[parent.cursor_x:0]] ||| parent.contents[parent.cursor_y + 1:0] if parent.long_line = parent.cursor_y then parent.long_line := &null else if parent.long_line > parent.cursor_y then parent.long_line +:= 1 parent.cursor_y +:= 1 parent.cursor_x := 1 end method undo_impl() parent.contents[self.cursor_y] ||:= parent.contents[self.cursor_y + 1] delete(parent.contents, self.cursor_y + 1) end initially(parent) self.EditableTextListEdit.initially(parent) end class EditableTextListDeleteRightEdit:EditableTextListEdit(ch) method redo_impl() if parent.cursor_x = *parent.contents[parent.cursor_y] + 1 then { # We know cursor_y < *contents from the handle method above. parent.contents[parent.cursor_y] ||:= parent.contents[parent.cursor_y + 1] parent.contents := parent.contents[1:parent.cursor_y + 1] ||| parent.contents[parent.cursor_y + 2 : 0] parent.long_line := &null } else { # Cursor not at end of line ch := parent.contents[parent.cursor_y][parent.cursor_x] parent.contents[parent.cursor_y][parent.cursor_x] := "" } parent.clear_mark() end method undo_impl() local t if /ch then { t := parent.contents[self.cursor_y][self.cursor_x:0] parent.contents[self.cursor_y][self.cursor_x:0] := "" insert(parent.contents, self.cursor_y + 1, t) } else { if self.cursor_x > *parent.contents[self.cursor_y] then parent.contents[self.cursor_y] ||:= ch else parent.contents[self.cursor_y][self.cursor_x] := ch || parent.contents[self.cursor_y][self.cursor_x] } end initially(parent) self.EditableTextListEdit.initially(parent) end class EditableTextListDeleteLeftEdit:EditableTextListEdit(ch, cut) method redo_impl() if parent.cursor_x = 1 then { # We know parent.cursor_y > 1 from the handle method cut := parent.cursor_x := *parent.contents[parent.cursor_y - 1] + 1 parent.contents[parent.cursor_y - 1] ||:= parent.contents[parent.cursor_y] parent.contents := parent.contents[1:parent.cursor_y] ||| parent.contents[parent.cursor_y + 1 : 0] parent.cursor_y -:= 1 parent.long_line := &null } else { # parent.cursor_x > 1 ch := parent.contents[parent.cursor_y][parent.cursor_x - 1] parent.contents[parent.cursor_y][parent.cursor_x - 1] := "" parent.cursor_x -:= 1 } parent.clear_mark() end method undo_impl() local t if /ch then { t := parent.contents[self.cursor_y - 1][cut:0] parent.contents[self.cursor_y - 1][cut:0] := "" insert(parent.contents, self.cursor_y, t) } else { if self.cursor_x - 1 > *parent.contents[self.cursor_y] then parent.contents[self.cursor_y] ||:= ch else parent.contents[self.cursor_y][self.cursor_x - 1] := ch || parent.contents[self.cursor_y][self.cursor_x - 1] } end initially(parent) self.EditableTextListEdit.initially(parent) end class EditableTextListDeleteLineEdit:EditableTextListEdit(s) method redo_impl() s := parent.contents[parent.cursor_y] if parent.cursor_y = *parent.contents then parent.contents[parent.cursor_y] := "" else delete(parent.contents, parent.cursor_y) if parent.long_line = parent.cursor_y then parent.long_line := &null else if parent.long_line > parent.cursor_y then parent.long_line -:= 1 parent.cursor_x := 1 parent.clear_mark() end method undo_impl() if self.cursor_y = *parent.contents then parent.contents[self.cursor_y] := s else insert(parent.contents, self.cursor_y, s) end initially(parent) self.EditableTextListEdit.initially(parent) end class EditableTextListDeleteRegionEdit:EditableTextListEdit(l, pos) method redo_impl() l := [] if parent.mark_y < parent.cursor_y then { pos := parent.mark_y put(l, parent.contents[parent.mark_y]) parent.contents[parent.mark_y] := parent.contents[parent.mark_y][1:parent.mark_x] || parent.contents[parent.cursor_y][parent.cursor_x:0] every parent.mark_y + 1 to parent.cursor_y do { put(l, parent.contents[parent.mark_y + 1]) delete(parent.contents, parent.mark_y + 1) } parent.cursor_x := parent.mark_x parent.cursor_y := parent.mark_y parent.long_line := &null } else if parent.mark_y > parent.cursor_y then { pos := parent.cursor_y put(l, parent.contents[parent.cursor_y]) parent.contents[parent.cursor_y] := parent.contents[parent.cursor_y][1:parent.cursor_x] || parent.contents[parent.mark_y][parent.mark_x:0] every parent.cursor_y + 1 to parent.mark_y do { put(l, parent.contents[parent.cursor_y + 1]) delete(parent.contents, parent.cursor_y + 1) } parent.long_line := &null } else { # parent.mark_y = cursor_y pos := parent.cursor_y put(l, parent.contents[parent.cursor_y]) if parent.mark_x < parent.cursor_x then { parent.contents[parent.cursor_y][parent.mark_x:parent.cursor_x] := "" parent.cursor_x := parent.mark_x } else { parent.contents[parent.cursor_y][parent.cursor_x:parent.mark_x] := "" } } parent.clear_mark() end method undo_impl() delete(parent.contents, pos) if pos > *parent.contents then while put(parent.contents, pop(l)) else while insert(parent.contents, pos, pull(l)) end initially(parent) self.EditableTextListEdit.initially(parent) end class EditableTextListPasteEdit:EditableTextListEdit(s, pre, n) method redo_impl() local t, nl n := 0 pre := parent.contents[parent.cursor_y] write("s=",image(s)) s ? repeat { t := tab(upto('\n') | 0) if any('\n') then { nl := parent.contents[parent.cursor_y][parent.cursor_x:0] parent.contents[parent.cursor_y] := parent.contents[parent.cursor_y][1:parent.cursor_x] || t if parent.cursor_y = *parent.contents then put(parent.contents, nl) else insert(parent.contents, parent.cursor_y + 1, nl) n +:= 1 parent.cursor_y +:= 1 parent.cursor_x := 1 move(1) } else { parent.contents[parent.cursor_y] := parent.contents[parent.cursor_y][1:parent.cursor_x] || t || parent.contents[parent.cursor_y][parent.cursor_x:0] parent.cursor_x +:= *t break } } parent.long_line := &null end method undo_impl() parent.contents[self.cursor_y] := pre every 1 to n do delete(parent.contents, self.cursor_y + 1) end initially(parent, s) self.EditableTextListEdit.initially(parent) self.s := s end