예제 #1
0
파일: SBLtest01.py 프로젝트: rehoot/SBList
sb.append(0)
sb.append(1)
sb.append(2)
sb.append(3)
assert(sb == [0,1,2,3])
sb.insert(4, 4)
assert(sb == [0,1,2,3,4])
sb.insert(4, 3.5)
assert(sb == [0,1,2,3,3.5,4])
sb.insert(0, 0.5)
assert(sb == [0.5, 0,1,2,3,3.5,4])

# Test a simple undo and some insertions
sl = SBList([1,2,6,3,12,7])
sl.insert(2, 90)
sl.undo()
sl.insert(2, 90)
sl.insert(4, 50)
sl.insert(5, 9999999)
sl.insert(3, 20)
sl.delete(6)
sl.insert(3, 30)
sl
sl.sort()
sl
sl.delete(3)
assert(sl.get_list() == [1, 2, 3, 7, 12, 20, 30, 50, 90])
####

#######################################################################
# Test insert, sort, delete:
예제 #2
0
파일: SBStrList.py 프로젝트: rehoot/SBList
class SBStrList(object):
	'''class SBStrList()
	This is a list of strings that is built
	on state-based objects that facilitate
	undo().
	'''
	class StorageRef(object):
		storage_start = None
		storage_end = None
		logical_start = None
		logical_end = None
		def __init__(self):#, storage_range, logical_range):
			object.__init__(self)
			
	class LogEntry():
			'''class SBStrList.LogEntry
			This holds information about one edit action.
			The properties are populated according to what makes
			sense for the given action.

			t_pt is a pointer into the text of the current line,
			not a global text pointer.
			sbstr will be an SBString object for line-deletion transaction 
			or line cut/paste.

			Action codes:
				'i' for insert text, 
				'd' for delete text,
				'il' = insertline, 
				'dl' = delete line. 
			Possible future codes would be 'm' for move-a-line, or 'u' for
			updated. Update is currently processed by a deletion and 
			an insert.
			'''
			action = None
			time = None
			old_lstate = None
			new_lstate = None
			old_str_state = None
			new_str_state = None
			row_idx = None
			t_pt = None
			txt = None
			sbstr = None
			def __init__(self, action, time, old_lstate, new_lstate, old_str_state, \
				new_str_state, row_idx, del_count=None, t_pt=None, txt=None, sbstr=None):
				'''UndoChanges.__init__
				'''
				self.action = action
				self.time = time
				self.old_lstate = old_lstate
				self.new_lstate = new_lstate
				self.old_str_state = old_str_state
				self.new_str_state = new_str_state
				self.row_idx = row_idx
				# for text edits only (not line insertion or deletion:
				self.t_pt = t_pt
				self.txt = txt
				# for line deletion, cut and paste
				self.sbstr # a SBString object
			def __repr__(self):
				'''log.__repr__
				'''
				return('[' + self.action + ', ' + self.time.strftime("%Y %h %d %H:%M:%S.") \
					+ str(self.time.time().microsecond) + ', ' \
					+ str(self.old_lstate) + ', ' + str(self.new_lstate) + ', ' \
					+ str(self.old_str_state) + ', ' + str(self.new_str_state) + ', ' \
					+ str(self.row_idx) + ', ' + repr(self.t_pt) + ', ' 
					+ repr(self.txt) + ']\n')
					

	def __init__(self, ListOfStrings):
		'''SBStrList.__init__()
		'''
		# MAYBE I SHOULD CODE ATTRIBUTES WITH THE VALUE OF THE
		# CURRENT INSTANCE SO EVERYTHING CAN GRAB THE CURRENT
		# STATE_ID WITHOUT REQUIRING INPUT FROM THE USER -- is that needed?
		object.__init__(self)
	
	
		# Cast the strings as SBSTring objects and load into a new list.
		# I tried embedding this into SBList, but it caused a loop in the 
		# definitions of SBList and SBString.
		self.buff_len = SBList([0])
		temp_l = []
		for j in range(len(ListOfStrings)):
			#if ListOfStrings[j][-1] not in ['\n', '\r'] \
			#and j != (len(ListOfStrings) - 1):
			#	temp_l.append(SBString(ListOfStrings[j]) + '\n')
			#else:
			if ListOfStrings[j][0:-1].count('\n') > 0:
				raise Exception('Your test string contains an embedded EOL in mid-line.')
			temp_l.append(SBString(ListOfStrings[j]))
			self.buff_len[0] += len(ListOfStrings[j])
		self.l = SBList(temp_l)
		#
		self.state_id = 0
		self.point = SBList([0])
		# The main state_id for SBStrList will be the state_id for
		# self.log?  To show the undo list of self.log, display
		# only values in the range self.log[0:self.log.get_state_id].
		# I might have to review how transactions
		# are chunked (such as 'replace' or 'update' transactions).
		self.log = SBList([]) 
		# Maybe use a list or function that lists all the 
		# objects that contain range-based lists so that users
		# can create one and have it automatically updated.
		self.range_lists = []
		self.range_funcs = []
		self.bookmarks = SBList([])
		# The *statements* field might be used to store the start and
		# end points of comment blocks or full statements in a
		# programming language. It might help the logic for applying
		# the syntax highligher by telling it to start on a prior line.
		self.statements = SBList([])
		self.range_lists.append(self.bookmarks)
		self.range_lists.append(self.statements)
		
		self.line_pts = None
		self.rebuild_line_pts()
		tmp_lp = None
		self.range_lists.append(self.line_pts)

	def __getitem__(self, x):
		# This returns an item from the main list based
		# on the virtual row index, *x*.	That index
		# can be a slice(), which is a range() specifed
		# in python format (i.e., the last index is one
		# greater than what you want returned).
		if x > len(self):
			raise IndexError
		if type(x) == type(slice(1,2)):
			temp = []
			if x.step is None:
				step = 1
			else:
				step = x.step
			for j in range(x.start, x.stop, step):
				temp.append(self.l[self.get_lrow_idx(j)])
			return(temp)
		else:
			# The requested item is not a slice:
			return(self.l[x])
			#return(self.l[self.get_lrow_idx(x)])

	def __iter__ (self): 
		# Maybe add a flag that will lock the objects from
		# being altered when iter is active?
		self.iterindex = len(self.l)
		return(self)

	def __len__(self):
		'''SBStrList.__len__()
		Returns the length of the virtual list object.
		'''
		return(len(self.l))

	
	def __next__ (self): 
		'''SBStrList.__next__()
		Return a list entry with each call.
		'''
		self.iterindex -= 1
		if self.iterindex < 0:
			raise StopIteration
		#
		# the countdown starts at the maximum value, but I want
		# to reverse the index to start at zero:
		new_idx = len(self.l) - self.iterindex - 1
		return(self.l[new_idx])

	def __repr__(self):
		#s = ''
		tmp_l = []
		for j in range(self.line_count()):
			#s += self.l[j].get_string()
			tmp_l.append(self.l[j].get_string())
		return(''.join(tmp_l))

	def __len__(self):
		return(len(self.l))

	def _dump(self, msg):
		'''SBStrList._dump()
		Dump many important objects to sys.stderr

		Run this program from the command line like this:
		env python sbed02.py -f infile.txt 2> debug.log
		where 2> is the redirection for stderr.
		'''
		dprint('---------------------------------------DUMP START')
		dprint(msg)
		dprint('')
		dprint('state_id= ' + str(self.state_id))
		dprint('point state id=' + str(self.point.get_state_id()) +' contents = ' + repr(self.point))
		dprint('log state id =' + str(self.log.get_state_id()) + ' contents=\n' + repr(self.log))
		dprint('line_pts state id= ' + str(self.line_pts.get_state_id()) + ', contents:\n' + repr(self.line_pts))
		dprint('self.l:\n' + repr(self.l))
		return(0)

	def add_bookmark(self, range):
		pass

	def add_statement(self, range):
		pass

	def add_range_list(self, lst):
		'''SBStrList.add_range_list(
		Add a list object that contains [start, end]
		pointers into the text using python slice()
		format.

		Lists entered here need corresponding entries
		loaded via add_range_updater() that are run
		via update_range_lists()
		'''
		self.rangelist.append(lst)
		return(0)
	
	def delete(self, pt, count, batch=False):
		# 1) Find the affected line
		# 2) determine if the full line is being deleted
		# 3) delete as needed
		# 4) update line_pts
		# 5) update bookmarks
		# 6) update statements
		idx = self.pt_to_line(pt)
		# Get the pointer within the appropriate text chunk:
		t_pt = self.pt_to_t_pt(pt)
		#
		save_lstate = self.l.get_state_id()
		save_str_state = self.l[idx].get_state_id()
		save_t_pt = t_pt	
		dprint('del a ' + str(self.l[idx].get_point()))
		self.l[idx].delete(t_pt,  count, batch=batch)
		dprint('del b ' + str(self.l[idx].get_point()))
		self.update_line_pts(pt, -1 * count, batch=batch)
		self.log.append(self.LogEntry('d', datetime.datetime.now(), save_lstate, \
			self.l.get_state_id(), save_str_state, \
			self.l[idx].get_state_id(), idx, t_pt=t_pt, del_count=count))
		return(0)

	def get_char(self, pt):
		idx = self.pt_to_line(pt)
		t_pt = self.pt_to_t_pt(pt)
		return(self.l[idx].get_char(t_pt))

	def get_lines(self, idx, count=1):
		pass

	def get_line_pts(self, idx):
		'''SBStrList.get_line_pts()
		return a range that contains the starting and ending
		points for the specified line in [star, end] slice()
		format.
		'''
		if idx > len(self.line_pts) or idx < 0:
			raise IndexError
		return(self.line_pts[idx])

	def get_point(self, state_id=-1):
		pass
	
	def get_str_list(self):
		'''StrList.get_str_list()
		Return a list object that contains the regular text
		stored in this object.
		'''
		tmp_l = []
		for j in range(len(self.l)):
			l.append(self.l[j].get_string())
		return(''.join(tmp_l))

	def goto_bookmark(self, bmark):
		# maybe use a dictionary to handle bookmarks,
		# so this arg would be the string name of the 
		# bookmark.
		pass	

	def insert(self, pt, txt, batch=False):
		# make all these methods so that they operate on
		# the current state (no state_id override)
		# Note: pt is the global point. t_point is the point
		# into the current text object.
		#
		# Function:
		# 1) Find the affected line
		# 2) determine if new lines are being created (embedded EOL)
		# 3) insert as needed
		# 4) update the log
		# 5) update line_pts
		# 6) update bookmarks
		# 7) update statements

		# TODO: consider keeping the EOL marker as the indicator of 
		# where existing SBString object will be copied.  In other
		# words, If I insert a block of text with embedded EOL,
		# the first part of the insertion would generate a new SBString
		# as if the existing EOL is fixed in concrete thereby forcing
		# the inserted lines to be inserted above.  This will help to
		# keep undo history predictable.  If an EOL marker is deleted,
		# then that SBString object will be deleted from the virtual view.
		idx = self.pt_to_line(pt)
		# Get the pointer within the appropriate text chunk:
		t_pt = self.pt_to_t_pt(pt)
		#
		save_lstate = self.l.get_state_id()
		save_str_state = self.l[idx].get_state_id()
		dprint('in SBStrList.insert a: str sid = ' + str(self.l[idx].get_state_id()) \
			+ ' lstateid = ' + str(self.l.get_state_id()) + ' batch is ' + repr(batch))
		save_t_pt = t_pt	
		# DO I NEED EXTRA LOGIC FOR NON-STANDARD EOL?
		lines = txt.split(os.linesep)
		end_txt = ''
		for j in range(len(lines)):
			if j == 0:
				# Insert the first part of the new text:
				self.l[idx].insert(t_pt, lines[j], batch=batch)
				dprint('in SBStrList.insert B: str sid = ' + str(self.l[idx].get_state_id()) \
					+ ' lstateid = ' + str(self.l.get_state_id()))
				self.update_line_pts(pt, len(lines[j]))
				pt += len(lines[j])
				if len(lines) > 1:
					# The insertion contains embedded EOL, so
					# delete the end of the line that was displaced by 
					# inserting the EOL (but leave the existing EOL)
					t_pt = self.pt_to_t_pt(pt)#pointer within this text chunck
					end_txt = self.l[idx].get_string()[t_pt:] 
					#print("multi-line insert, pt=" + str(pt) + ' deleting from tpt=' \
					#	+ str(t_pt) + ' endtxt= ' + end_txt)
					self.l[idx].delete(t_pt, len(end_txt), batch=True)
					self.update_line_pts(pt, -1 * len(end_txt))

					# Insert an EOL after the first insertion
					# (note that the last line in the file might not have had one)
					self.l[idx].insert(t_pt, '\n', batch=True)
					self.update_line_pts(pt, 1)
					pt += 1

					# Push the end of the insertion line to a new line.
					self.l.insert(idx + 1, SBString(end_txt ), batch=True)
					self.rebuild_line_pts()
					#self.update_line_pts(pt, len(end_txt) + 1)
					#pt += 1 #eol
			else:
				# Insert the second (or higher) line of new text.
				if len(lines[j]) != 0:
					if j == (len(lines) - 1):
						# The last portion of the split text in 'lines'
						# If it is empty, don't do anything, others insert 
						# the text into an existing SBText object
						t_pt = self.pt_to_t_pt(pt)#pointer within this text chunck
						self.l[idx + j].insert(t_pt, lines[j], batch=True)
						dprint('in SBStrList.insert B: str sid = ' + str(self.l[idx].get_state_id()) \
							+ ' lstateid = ' + str(self.l.get_state_id()))
						self.update_line_pts(pt, len(lines[j]))
						pt += len(lines[j])
					else:
						# This is a new, full line with EOL.
						# Insert a new line into the file.
						self.l.insert(idx + j, SBString(lines[j]), batch=True)
						# RUN REBUILD_LINE_PTS HERE !!!!!!!!!!!!!!!!!!!!!!!!
						self.update_line_pts(pt, len(lines[j]))
						pt += len(lines[j])
		# Now insert that end text at the end
		#if end_txt != '':
		#	t_pt = self.pt_to_t_pt(pt)#pointer within this text chunck
		#	print('Appending end, pt=' + str(pt) + ', tpt= ' + str(t_pt))
		#	print('chcking line nb ' + str(self.pt_to_line(pt)))
		#	self.l[idx].insert(t_pt, end_txt, batch=False)
		#	self.update_line_pts(pt, len(end_txt))
		#	print('line pts= ' + repr(self.line_pts))
		
			# Construct the log entry:	
		self.log.append(self.LogEntry('i', datetime.datetime.now(), save_lstate, \
			self.l.get_state_id(), save_str_state, \
			self.l[idx].get_state_id(), idx, t_pt=t_pt, txt=txt))
		self.buff_len[0] += len(txt)
		return(0)

	def insert_line(self, before_line, txt):
		pass

	def line_count(self):
		return(len(self.l))

	def list_bookmarks(self):
		pass

	def list_statements(self):
		pass

	def pt_to_line(self, pt):
		'''SBStrList.pt_to_line()
		Return the index number of the line that contains the
		specified point in the current state.
		'''
		#print(repr(self.line_pts))	
		found = False
		j = 0
		while not found and j < len(self.line_pts):
			if self.line_pts[j][0] <= pt and (self.line_pts[j][1] - 1) >= pt:
				found = True	
			j += 1
		return(j - 1)

	def pt_to_t_pt(self, pt):
		'''SBStrList.pt_to_t_pt()
		Convert a global point into the buffer to a t_pt,
		which is a zero-based index into the text at the current line
		'''
		line = self.pt_to_line(pt)
		assert( line >= 0)
		return(pt - self.line_pts[line][0])	

	def rebuild_line_pts(self):
		tmp_lp = []
		tmp_pt = 0
		for j in range(len(self.l)):
			# Record the starting and ending point on each line.
			# This is NOT in slice() format.
			tmp_lp.append([tmp_pt, tmp_pt + len(self.l[j])])
			tmp_pt += len(self.l[j])
		self.line_pts = SBList(tmp_lp)
		return(0)

	def redo(self):
		pass

	def set_bookmark(self, pt_start, len):
		pass

	def show_line_pts(self):
		'''SBStrList.show_line_pts()
		This is for debugging purposes to show the saved
		list of starting and ending points (slice() format)
		for each buffer line.
		'''
		return(repr(self.line_pts))

	def show_undo_list(self):
		return(repr(self.log))

	def set_point(self, pt):
		pass

	def str_len(self):
		'''SbStrList.str_len()
		Return the full length of the buffer in characters.
		'''
		#ln = 0
		#for sbs in self.l:
		#	ln += len(sbs)
		#return(ln)
		reuturn(self.buff_len[0])

	def list_len(self):
		return(len(self.l))

	def undo(self):
		if len(self.log) == 0:
			return(0)

		top_state_id = self.log.get_state_id()
		#top_log_entry = self.log[-1]
		#self.log[0:self.log.get_state_id]
		top_log_entry = self.log[top_state_id]
		top_action_code = top_log_entry.action
		top_idx = top_log_entry.row_idx
		# The undo() below undoes the record of the transaction
		# and thereby leaves the top entry with the l_state and
		# v_state IDs that are desired.
		# ONE UNDO ON THE LOG ENTRY WILL NOT UNDO MANY BATCH ENTRIES !!!!!!!!!!!!!!!!!!!!i!!!!!!!
		self.log.undo() #this undoes only the transaction
		if top_action_code in ['i', 'd']:
			#print('UNDO TEXT EDIT for line idx ' + str(top_idx))
			self.l[top_idx].undo(count=1)
			self.buff_len.undo()
		else:
			raise Exception ("not ready to undo line-oriented actions")
		
		pass

	def update_line_pts(self, pt, incr, batch=False):
		'''SBStrList.update_line_pts()
		Update self.line_pts to reflect inserted or deleted
		text. Do NOT call this if there are new or delete lines.
	  self.line_pts is a list of the starting points
		and ending points for each line.

		see also: rebuild_line_pts
		'''
		idx = self.pt_to_line(pt)
		# I will delete all the existing range entries and then append
		# new ones
		old_lp_state = self.line_pts.get_state_id()
		b_code = batch
		len_pts = len(self.line_pts) 
		save_line_pts = []
		for j in range(len_pts):
			# Save the current state of line_pts because if actions
			# are taken in batch mode, I'll need this to get the 
			# prior state.
			save_line_pts.append(self.line_pts[j])

		for j in range(len_pts -1, idx - 1 , -1):
			self.line_pts.delete(j, batch=b_code)
			b_code = True

		# Now insert the updated entries (there might be new or delete rows)
		for j in range(idx, len(save_line_pts)):
			# The current state of self.line_pts should contain one entry 
			# for each record in self.l. Get that entry to find the starting and
			# ending point for each line:
			low = save_line_pts[j][0]
			high = save_line_pts[j][1]
			if low <= pt:
				self.line_pts.append([low , high+ incr], batch=True)
			else:
				self.line_pts.append([low + incr, high+ incr], batch=True)

		return(0)

	def update_range_lists(self, pt, incr):
		'''SBStrList.update_ranges()
		Update all the list objects that contain ranges of 
		start/end points.  This processes anything that was
		registered with add_range_list()

		see also: rebuild_line_pts
		'''
		# MODIFY THIS TO MAKE IT GENERIC FOR THE REGISTERED LISTSV
		# MODIFY THIS TO MAKE IT GENERIC FOR THE REGISTERED LISTSV
		# MODIFY THIS TO MAKE IT GENERIC FOR THE REGISTERED LISTSV
		# MODIFY THIS TO MAKE IT GENERIC FOR THE REGISTERED LISTSV
		# MODIFY THIS TO MAKE IT GENERIC FOR THE REGISTERED LISTSV
		# MODIFY THIS TO MAKE IT GENERIC FOR THE REGISTERED LISTSV
		# MODIFY THIS TO MAKE IT GENERIC FOR THE REGISTERED LISTSV
		# MODIFY THIS TO MAKE IT GENERIC FOR THE REGISTERED LISTSV
		# MODIFY THIS TO MAKE IT GENERIC FOR THE REGISTERED LISTSV
		idx = self.pt_to_line(pt)
		# I will delete all the existing range entries and then append
		# new ones
		old_lp_state = self.line_pts.get_state_id()
		b_code = False
		for j in range(idx, len(self.line_pts)):
			self.line_pts.delete(idx, batch=b_code)
			b_code = True

		# Now insert the updated entries (there might be new or delete rows)
		for j in range(idx, len(self.l)):
			low = self.line_pts.get_item(j, state_id=old_lp_state)[0]
			high = self.line_pts.get_item(j, state_id=old_lp_state)[1]
			if low < pt:
				self.line_pts.append([low , high+ incr], batch=True)
			else:
				self.line_pts.append([low + incr, high+ incr], batch=True)

		return(0)