del l[0:2] assert(l == ['e', 'f']) del l[1:2] assert(l == ['e']) del l[0:1] assert(l == []) ##################### print("------------------------------------------------") print(" UPDATE TEST") print("--------------------- - - - - - - - - - - - - - - -") l = ['a', 'b', 'c', 'd', 'e', 'f'] q = SBList(l) print('Original: ' + repr(q)) for j in range(0, len(q)): q.update(j, 'new' + q[j]) #print(q.show_state()) #print(repr(q)) #print("- - - - - - ") q.update(len(q) - 1, 'new last row') print(repr(q)) assert(repr(q) == '[newa, newb, newc, newd, newe, new last row]') ###################################################################### # # # l = ['a', 'b', 'c', 'zz', 'd', 'e', 'f'] q = SBList(l) print('Original: ' + repr(q))
class SBString(object): class StateLog(object): state_id = None vstate_id = None prior_state_id = None def __init__(self, state_id, vstate_id, prior_state_id): object.__init__(self) self.state_id = state_id self.vstate_id = vstate_id self.prior_state_id = prior_state_id def __repr__(self): return('[' + str(self.state_id) + ', ' + str(self.vstate_id) + ', ' \ + str(self.prior_state_id) + ']') def __init__(self, txt, nickname=''): '''SBString.__init__() ''' object.__init__(self) # The underlying list object begins as a list # that contains only one string. Insertions of character # into the string will be appended to the end of self.l and # presented in the correct place in the virtual view. self.l = [txt] # nickname might be useful for debugging when you have many # instances of thie object self.buff_len = SBList([len(txt)]) self.nickname = nickname # self.point will be keyed with self.state_id self.point = [0] self.state_id = 0 # self.virtview holds pointers that allow me to reassemble # self.l into the intended text. self.virtview = VirtView(VirtView.StateEntry(0, len(txt), 0)) # state_log saves all relevant state IDs (state ID for l is len-1): # main, view, prior # I think I need this because deletions affect only the *view* # object and inserts affect both *l* and *view*, and batch # changes can increment l and view without incrementing the main # state_id self.state_log = [self.StateLog(self.state_id, self.virtview.get_state_id(), -1)] assert(self.virtview.get_state_id() == self.state_id) def __len__(self): # REPLACE THIS TO USE THE STATIC BUFF_LEN OR STR_LEN() FUNCTIONS #return(len(self.get_string())) return(self.str_len()) def __repr__(self): '''SBString.__repr__() Return a string representation of this object. ''' return(self.get_string()) def delete(self, start, count, batch=False, state_id=-1): '''SBString.delete() Delete the characters starting at *start* for *count* characters. ''' # Note that the delete range might span more # than one text chunk and require redefining # two *range* values at the end points of the # deletion zone. # # Function: # 1) find all of the entries in self.virtview # that span the deletion zone. # 2) modify *range* values that are only partially # in the deletion zone and delete others. # 3) do not delete any of the underlying text, because # that will be retained internally for possible # undeletion. end = start + count # end pt in python slice() format if state_id == -1: state_id = self.state_id s_len = len(self) if start >= s_len: return(0) if end > s_len: end = s_len del_len = end - start #(low_offset, low_adj, low_l_idx, string_id) = self.virtview._get_vl_idx(start) low = self.virtview._get_vl_idx(start) # WARNING, high.state_offset is not in slice() format to avoid pointing # to the next state offset, so I will try fudging the high.state_adj value # to restore it to slice() format. #(high_offset, high.state_adj, high_l_idx, string_id) \ high = self.virtview._get_vl_idx(end - 1) high.state_adj += 1 # # get_range() now returns a StateEntry object low_range = self.virtview.get_range(s_offset=low.state_offset) high_range = self.virtview.get_range(s_offset=high.state_offset) incr_offset = 0 batch_code = batch for j in range(low.state_offset, high.state_offset + 1): # Delete the state *range* stored at low.state_offset # and the higher states will fall into the slot # for the next deletion if needed. # Note: allow no more that one transaction to affect self.virtview.l.state_id # by making the first iteration use the orginal value of 'batch,' then # the other iterations using batch=True: self.virtview.delete_state(low.state_offset, batch=batch_code) batch_code = True if high.state_offset > low.state_offset: # at least two of the old ranges were affected by the deletiong if low.state_adj == 0: # The entire first range was destroyed by the delete pass else: # Load a modified first range self.virtview.insert_state(low.state_offset, VirtView.StateEntry(low_range.start_pt, \ low_range.start_pt + low.state_adj, low_range.list_idx), batch=True) incr_offset += 1 # high_vrow = end - high.state_adj + (high_range.end_pt - high_range.start_pt) if high_vrow == end: # The entire seconde range was destroyed by the delete pass else: # load a modified second range: # (the incr_offset is an adjustment in case a prevous state range was # inserted above. self.virtview.insert_state(low.state_offset + incr_offset, \ VirtView.StateEntry(high_range.start_pt + high.state_adj, high_range.end_pt, high_range.list_idx), \ batch=True) else: # No more than one old range was affected by the deletion old_rng_len = low_range.end_pt - low_range.start_pt if del_len == old_rng_len: # the existing range object is the exact length of # the deletion range, so don't restore it pass else: if low.state_adj == 0: # The character(s) that I want to delete is the first # in this range, so modify the range. self.virtview.insert_state(low.state_offset, VirtView.StateEntry(low_range.start_pt \ + low.state_adj + del_len, low_range.end_pt, low_range.list_idx), batch=True) else: # I have to split the range entry in half self.virtview.insert_state(low.state_offset, VirtView.StateEntry(low_range.start_pt, \ low_range.start_pt + low.state_adj, low_range.list_idx)) self.virtview.insert_state(low.state_offset + 1, VirtView.StateEntry(low_range.start_pt \ + low.state_adj + del_len, low_range.end_pt, low_range.list_idx), batch=True) # # capture state_ids if not batch: # (end - start) reflects any error correction if the user tries to delete beynd EOL. self.incr_str_len(-1 * (end - start), state_id=self.state_id) self.incr_state_id()# This also rolls point to the new state # Save to sate log: current state, viewstate, prior state self.state_log.append(self.StateLog(self.state_id, \ self.virtview.get_state_id(), state_id)) else: self.incr_str_len(-1 * (end - start), state_id=state_id) # Save to sate log: current state, viewstate, prior state self.state_log[self.state_id] = self.StateLog(self.state_id, \ self.virtview.get_state_id(), state_id) return(0)# end of delete def get_char(self, offset, state_id=-1): if state_id == -1: state_id = self.state_id vstate_id = self.virtview.get_state_id() else: vstate_id = self.state_log[state_id].vstate_id idx = 0 if offset >= len(self): return('') s = '' for j in range(len(self.virtview)): # v is a StateEntry object with start_pt, end_pt, list_idx... v = self.virtview.get_item(j, state_id=vstate_id) # Accumulate the count of characters that WILL have been # counted by the end of this chunk of text. Do this by # finding the difference between the # starting and ending slice() indexes into this piece # of text: if (idx + v.length) > offset: start = offset - idx + v.start_pt #s = self.l[v.list_idx][start: start+ 1] s = self.l[v.list_idx][start: start+ 1] dprint('found get_char: ' + repr( self.l[v.list_idx]) \ + ' offset = ' + str(offset) + ' vstar=' + str(v.start_pt) + ' idx = ' + str(idx) + ' start=' + str(start) + ' len=' + str(v.length)) break idx += v.length return(s) def get_state_id(self): '''SBString.get_state_id() ''' return(self.state_id) def get_string(self, state_id=-1): '''SBString.get_string() ''' if state_id == -1: self.dump() vstate_id = self.state_log[-1].vstate_id else: vstate_id = self.state_log[state_id].vstate_id tmp_l = [] for j in range(len(self.virtview)): # v = self.virtview[j] v = self.virtview.get_item(j, state_id=vstate_id) #tmp_l.append(self.l[v[2]][v[0]:v[1]]) tmp_l.append(self.l[v.list_idx][v.start_pt:v.end_pt]) return(''.join(tmp_l)) def get_point(self, state_id=-1): if state_id == -1: state_id = self.state_id #else: return(self.point[state_id]) def incr_point(self, incr, state_id=-1): """SBString.incr_point() Let the editor call this, don't call it here Set the point to the specified value, but correct for errors if the value goes below zero or beyond EOL. """ if state_id == -1: state_id = self.state_id #else: self.set_point(self.point[state_id] + incr) return(0) def incr_str_len(self, incr, state_id=-1): """SBString.incr_str_len() Increment the stored value for the logical length of the string. This does not alter any state_id values. When incr_point is called, that routine advances the buff_len to the next state. This was added for performance reasons because navigating the state list became slow after 100 edits. Storing this value in an SBList object allows for the length to be correct after undo() or redo() actions, but perhaps I could use a regular list as was done with incr_point (if that works). """ if state_id == -1: state_id = self.state_id ##self.set_point(self.point[state_id] + incr) self.buff_len.update(0, \ self.buff_len[0] + incr, \ state_id=state_id, batch=True) return(self.point[state_id]) def incr_state_id(self): '''SBString.incr_state_id() Always use this to increment the state after a insert, delete or other such operation (in the future). This routine is needed to set the state_id properly after an undo is made. ''' self.buff_len.append(self.buff_len[0]) self.point.append(self.point[self.state_id]) self.state_id = len(self.state_log) return(0) def insert(self, start, txt, state_id=-1, batch=False): '''SBString.insert() ''' if state_id == -1: save_state_id = self.state_id vstate_id = -1 else: save_state_id = state_id vstate_id = self.state_log[state_id].vstate_id # New text is always appended to the end of self.l, self.l.append(txt)# Never use batch here. # ... but then the index from l is inserted into the # logical view of the string: self.virtview.insert(start, VirtView.StateEntry(0, len(txt), len(self.l) - 1), \ state_id=vstate_id, batch=batch) ##### The point here is not used and is probably wrong !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ####self.point.append(self.point[save_state_id]) # Never use batch here. # capture state_ids if not batch: self.incr_state_id() #self.incr_point(len(txt), state_id=state_id) self.incr_str_len(len(txt), state_id=self.state_id) # Save to sate log: current state, lstate, viewstate, prior state self.state_log.append(self.StateLog(self.state_id, \ self.virtview.get_state_id(), save_state_id)) else: #self.incr_point(len(txt), state_id=state_id) self.incr_str_len(len(txt), state_id=state_id) # Save to sate log: current state, lstate, viewstate, prior state self.state_log[self.state_id] = self.StateLog(self.state_id, \ self.virtview.get_state_id(), save_state_id) return(0)# end of insert() def set_point(self, pt, state_id=-1): if state_id == -1: state_id = self.state_id #else: if pt > len(self): # apr 22, 00:004, was =lstate_id self.point[state_id] = self.str_len(state_id=state_id) - 1 elif pt < 0: self.point[state_id] = 0 else: self.point[state_id] = pt return(0) def show_state(self, state_id=-1): '''SBString.get_string() Return the set of *range* items that point to the underlying strings. ''' if state_id == -1: vstate_id = -1 else: vstate_id = self.state_log[state_id].vstate_id return('view state: ' + self.virtview.show_state(state_id=vstate_id)) def str_len(self, state_id=-1): """SBString.str_len() Return the length of the virtual string at the specified state ID (or current state if the state_id is not specified. """ ###if state_id == -1: ### state_id = self.state_id #### was =lstate_id apr 22 ###return(len(self.get_string(state_id=state_id))) return(self.buff_len[0]) def test_vl(self, str_offset): return(self.virtview._get_vl_idx(str_offset)) def undo(self, count=1): """SBString.undo() """ ###self.state_id -= count ##j = len(self.state_log) - 1 ##while j >= 0 and self.state_log[j - 1][0] == self.state_id: # Access the previous state id value from the log. # Remember that if the user used undo and then made edits, # the previous state value could point to nearly any of the valid states. self.state_id = self.state_log[-1].prior_state_id if self.state_id < 0: self.state_id = 0 # Find the state IDs for the underlying list and the view-manager: vstate_id = self.state_log[self.state_id].vstate_id ##self.l.set_state_id(lstate_id) self.virtview.set_state_id(vstate_id) return(self.state_id) def dump(self): return(0) print('-------------------------------------------------SBString Dump Start') print('state_id=' + str(self.state_id)) print('point = ' + repr(self.point)) print('bufflen ' + repr(self.buff_len)) print('virtview ' + repr(self.virtview)) print('log=' + repr(self.state_log)) print('l ' + repr(self.l))