[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [21556] branches/blender2.5/blender/ release/ui/space_text.py: Console Autocomplete ( Alt+Enter in the text editor), should be moved out of the text editor soon.

Campbell Barton ideasman42 at gmail.com
Mon Jul 13 11:31:35 CEST 2009


Revision: 21556
          http://projects.blender.org/plugins/scmsvn/viewcvs.php?view=rev&root=bf-blender&revision=21556
Author:   campbellbarton
Date:     2009-07-13 11:31:35 +0200 (Mon, 13 Jul 2009)

Log Message:
-----------
Console Autocomplete (Alt+Enter in the text editor), should be moved out of the text editor soon.

The autocomplete function is generic and could be made into its own module.
Examples:
 b -> bpy

 bpy.data.mes -> bpy.data.meshes

 bpy.ops.OB -> bpy.ops.OBJECT_OT_

 bpy.data.objects[0].a -> (options)
active_material, active_material_index, active_particle_system, active_particle_system_index, active_shape_key, active_shape_key_index, active_vertex_group, active_vertex_group_index, animation_data

Modified Paths:
--------------
    branches/blender2.5/blender/release/ui/space_text.py

Modified: branches/blender2.5/blender/release/ui/space_text.py
===================================================================
--- branches/blender2.5/blender/release/ui/space_text.py	2009-07-13 08:41:37 UTC (rev 21555)
+++ branches/blender2.5/blender/release/ui/space_text.py	2009-07-13 09:31:35 UTC (rev 21556)
@@ -227,8 +227,44 @@
 		layout.itemM("TEXT_MT_edit_to3d")
 
 
-class TEXT_OT_line_console(bpy.types.Operator):
+def get_console(text):
 	'''
+	helper function for console operators
+	currently each text datablock gets its own console - code.InteractiveConsole()
+	...which is stored in this function.
+	'''
+	import sys, code, io
+	
+	try:	consoles = get_console.consoles
+	except:consoles = get_console.consoles = {}
+	
+	# clear all dead consoles, use text names as IDs
+	for id in list(consoles.keys()):
+		if id not in bpy.data.texts:
+			del consoles[id]
+	
+	if not text:
+		return None, None, None
+		
+	id = text.name
+	
+	try:
+		namespace, console, stdout = consoles[id]
+	except:
+		namespace = locals()
+		namespace['bpy'] = bpy
+		
+		console = code.InteractiveConsole(namespace)
+		
+		if sys.version.startswith('2'):	stdout = io.BytesIO()  # Py2x support
+		else:								stdout = io.StringIO()
+	
+		consoles[id]= namespace, console, stdout
+	
+	return namespace, console, stdout
+
+class TEXT_OT_console_exec(bpy.types.Operator):
+	'''
 	Operator documentatuon text, will be used for the operator tooltip and python docs.
 	'''
 	__label__ = "Console Execute"
@@ -243,38 +279,15 @@
 	def execute(self, context):
 		import sys
 		
-		# clear all dead consoles, use text names as IDs
-		for id in list(self.__class__.console.keys()):
-			if id not in bpy.data.texts:
-				del self.__class__.console[id]
-		
-		# print("Selected: " + context.active_object.name)
 		st = context.space_data
 		text = st.text
 		
 		if not text:
 			return ('CANCELLED',)
 		
-		line = st.text.current_line.line
-		id = text.name
+		namespace, console, stdout = get_console(text)
 		
-		try:
-			namespace, console, stdout = self.__class__.console[id]
-		except:
-			import code, io
-			
-			namespace = locals().update({'bpy':bpy})
-			console = code.InteractiveConsole(namespace)
-			
-			if sys.version.startswith('2'): # Py2.x support
-				stdout = io.BytesIO()
-			else:
-				stdout = io.StringIO()
-			
-			
-			
-			self.__class__.console[id]= namespace, console, stdout
-			del code, io
+		line = text.current_line.line
 		
 		# redirect output
 		sys.stdout = stdout
@@ -284,8 +297,8 @@
 		if not line.strip():
 			line = '\n' # executes a multiline statement
 		
-		if line.startswith(self.__class__.PROMPT_MULTI) or line.startswith(self.__class__.PROMPT):
-			line = line[len(self.__class__.PROMPT):]
+		if line.startswith(self.PROMPT_MULTI) or line.startswith(self.PROMPT):
+			line = line[len(self.PROMPT):]
 			was_prefix = True
 		else:
 			was_prefix = False
@@ -305,9 +318,9 @@
 		stdout.truncate(0)
 		
 		if is_multiline:
-			prefix = self.__class__.PROMPT_MULTI
+			prefix = self.PROMPT_MULTI
 		else:
-			prefix = self.__class__.PROMPT
+			prefix = self.PROMPT
 		
 		# Kindof odd, add the prefix if we didnt have one. makes it easier to re-read.
 		if not was_prefix:
@@ -320,7 +333,269 @@
 		bpy.ops.TEXT_OT_insert(text= '\n' + output + prefix)
 		
 		return ('FINISHED',)
+
+
+def autocomp(bcon):
+	'''
+	This function has been taken from a BGE console autocomp I wrote a while ago
+	the dictionaty bcon is not needed but it means I can copy and paste from the old func
+	which works ok for now.
 	
+	could be moved into its own module.
+	'''
+	
+	
+	def is_delimiter(ch):
+		'''
+		For skipping words
+		'''
+		if ch == '_':
+			return False
+		if ch.isalnum():
+			return False
+		
+		return True
+	
+	def is_delimiter_autocomp(ch):
+		'''
+		When autocompleteing will earch back and 
+		'''
+		if ch in '._[] "\'':
+			return False
+		if ch.isalnum():
+			return False
+		
+		return True
+
+	
+	def do_autocomp(autocomp_prefix, autocomp_members):
+		'''
+		return text to insert and a list of options
+		'''
+		autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
+		
+		print("AUTO: '%s'" % autocomp_prefix)
+		print("MEMBERS: '%s'" % str(autocomp_members))
+		
+		if not autocomp_prefix:
+			return '', autocomp_members
+		elif len(autocomp_members) > 1:
+			# find a common string between all members after the prefix 
+			# 'ge' [getA, getB, getC] --> 'get'
+			
+			# get the shortest member
+			min_len = min([len(v) for v in autocomp_members])
+			
+			autocomp_prefix_ret = ''
+			
+			for i in range(len(autocomp_prefix), min_len):
+				char_soup = set()
+				for v in autocomp_members:
+					char_soup.add(v[i])
+				
+				if len(char_soup) > 1:
+					break
+				else:
+					autocomp_prefix_ret += char_soup.pop()
+				
+			print(autocomp_prefix_ret)
+			return autocomp_prefix_ret, autocomp_members
+		elif len(autocomp_members) == 1:
+			return autocomp_members[0][len(autocomp_prefix):], []
+		else:
+			return '', []
+	
+
+	def BCon_PrevChar(bcon):
+		cursor = bcon['cursor']-1
+		if cursor<0:
+			return None
+			
+		try:
+			return bcon['edit_text'][cursor]
+		except:
+			return None
+		
+		
+	def BCon_NextChar(bcon):
+		try:
+			return bcon['edit_text'][bcon['cursor']]
+		except:
+			return None
+	
+	def BCon_cursorLeft(bcon):
+		bcon['cursor'] -= 1
+		if bcon['cursor'] < 0:
+			bcon['cursor'] = 0
+
+	def BCon_cursorRight(bcon):
+			bcon['cursor'] += 1
+			if bcon['cursor'] > len(bcon['edit_text']):
+				bcon['cursor'] = len(bcon['edit_text'])
+	
+	def BCon_AddScrollback(bcon, text):
+		
+		bcon['scrollback'] = bcon['scrollback'] + text
+		
+	
+	def BCon_cursorInsertChar(bcon, ch):
+		if bcon['cursor']==0:
+			bcon['edit_text'] = ch + bcon['edit_text']
+		elif bcon['cursor']==len(bcon['edit_text']):
+			bcon['edit_text'] = bcon['edit_text'] + ch
+		else:
+			bcon['edit_text'] = bcon['edit_text'][:bcon['cursor']] + ch + bcon['edit_text'][bcon['cursor']:]
+			
+		bcon['cursor'] 
+		if bcon['cursor'] > len(bcon['edit_text']):
+			bcon['cursor'] = len(bcon['edit_text'])
+		BCon_cursorRight(bcon)
+	
+	
+	TEMP_NAME = '___tempname___'
+	
+	cursor_orig = bcon['cursor']
+	
+	ch = BCon_PrevChar(bcon)
+	while ch != None and (not is_delimiter(ch)):
+		ch = BCon_PrevChar(bcon)
+		BCon_cursorLeft(bcon)
+	
+	if ch != None:
+		BCon_cursorRight(bcon)
+	
+	#print (cursor_orig, bcon['cursor'])
+	
+	cursor_base = bcon['cursor']
+	
+	autocomp_prefix = bcon['edit_text'][cursor_base:cursor_orig]
+	
+	print("PREFIX:'%s'" % autocomp_prefix)
+	
+	# Get the previous word
+	if BCon_PrevChar(bcon)=='.':
+		BCon_cursorLeft(bcon)
+		ch = BCon_PrevChar(bcon)
+		while ch != None and is_delimiter_autocomp(ch)==False:
+			ch = BCon_PrevChar(bcon)
+			BCon_cursorLeft(bcon)
+		
+		cursor_new = bcon['cursor']
+		
+		if ch != None:
+			cursor_new+=1
+		
+		pytxt = bcon['edit_text'][cursor_new:cursor_base-1].strip()
+		print("AUTOCOMP EVAL: '%s'" % pytxt)
+		#try:
+		if pytxt:
+			bcon['console'].runsource(TEMP_NAME + '=' + pytxt, '<input>', 'single')
+			# print val
+		else: ##except:
+			val = None
+		
+		try:
+			val = bcon['namespace'][TEMP_NAME]
+			del bcon['namespace'][TEMP_NAME]
+		except:
+			val = None
+		
+		if val:
+			autocomp_members = dir(val)
+			
+			autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
+			
+			bcon['cursor'] = cursor_orig
+			for v in autocomp_prefix_ret:
+				BCon_cursorInsertChar(bcon, v)
+			cursor_orig = bcon['cursor']
+			
+			if autocomp_members:
+				BCon_AddScrollback(bcon, ', '.join(autocomp_members))
+		
+		del val
+		
+	else:
+		# Autocomp global namespace
+		autocomp_members = bcon['namespace'].keys()
+		
+		if autocomp_prefix:
+			autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
+		
+		autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
+		
+		bcon['cursor'] = cursor_orig
+		for v in autocomp_prefix_ret:
+			BCon_cursorInsertChar(bcon, v)
+		cursor_orig = bcon['cursor']
+		
+		if autocomp_members:
+			BCon_AddScrollback(bcon, ', '.join(autocomp_members))
+	
+	bcon['cursor'] = cursor_orig
+
+
+class TEXT_OT_console_autocomplete(bpy.types.Operator):
+	'''
+	Operator documentatuon text, will be used for the operator tooltip and python docs.
+	'''
+	__label__ = "Console Autocomplete"
+	
+	def execute(self, context):
+		
+		st = context.space_data
+		text = st.text
+		
+		namespace, console, stdout = get_console(text)
+		
+		line = text.current_line.line
+		
+		if not console:
+			return ('CANCELLED',)
+		
+		
+		# fake cursor, use for autocomp func.
+		bcon = {}
+		bcon['cursor'] = text.current_character
+		bcon['console'] = console
+		bcon['edit_text'] = line
+		bcon['namespace'] = namespace
+		bcon['scrollback'] = '' # nor from the BGE console
+		
+		
+		# This function isnt aware of the text editor or being an operator
+		# just does the autocomp then copy its results back
+		autocomp(bcon)
+		
+		# Now we need to copy back the line from blender back into the text editor.
+		# This will change when we dont use the text editor anymore
+		
+		# clear the line
+		bpy.ops.TEXT_OT_move(type='LINE_END')
+		bpy.ops.TEXT_OT_move_select(type = 'LINE_BEGIN')
+		bpy.ops.TEXT_OT_delete(type = 'PREVIOUS_CHARACTER')
+		
+		if bcon['scrollback']:
+			bpy.ops.TEXT_OT_move_select(type = 'LINE_BEGIN')
+			bpy.ops.TEXT_OT_insert(text = bcon['scrollback'].strip() + '\n')
+			bpy.ops.TEXT_OT_move_select(type='LINE_BEGIN')
+		
+		bpy.ops.TEXT_OT_insert(text = bcon['edit_text'])
+		
+		# Read only
+		if 0:
+			text.current_character = bcon['cursor']
+		else:
+			bpy.ops.TEXT_OT_move(type = 'LINE_BEGIN')
+			
+			for i in range(bcon['cursor']):
+				bpy.ops.TEXT_OT_move(type='NEXT_CHARACTER')
+			
+		
+		return ('FINISHED',)
+	
+
+
 bpy.types.register(TEXT_HT_header)
 bpy.types.register(TEXT_PT_properties)
 bpy.types.register(TEXT_PT_find)
@@ -332,5 +607,6 @@
 bpy.types.register(TEXT_MT_edit_markers)
 bpy.types.register(TEXT_MT_edit_to3d)
 
-bpy.ops.add(TEXT_OT_line_console)
+bpy.ops.add(TEXT_OT_console_exec)
+bpy.ops.add(TEXT_OT_console_autocomplete)
 





More information about the Bf-blender-cvs mailing list