Console Autocomplete (Alt+Enter in the text editor), should be moved out of the text...
authorCampbell Barton <ideasman42@gmail.com>
Mon, 13 Jul 2009 09:31:35 +0000 (09:31 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Mon, 13 Jul 2009 09:31:35 +0000 (09:31 +0000)
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

release/ui/space_text.py

index c0bdff15ddfe58530618bd5830181045561f0d1c..c6ce1cb71d6d6ea533b981dd7b10070526645fc0 100644 (file)
@@ -227,7 +227,43 @@ class TEXT_MT_edit(bpy.types.Menu):
                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.
        '''
@@ -243,38 +279,15 @@ class TEXT_OT_line_console(bpy.types.Operator):
        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 @@ class TEXT_OT_line_console(bpy.types.Operator):
                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 @@ class TEXT_OT_line_console(bpy.types.Operator):
                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 @@ class TEXT_OT_line_console(bpy.types.Operator):
                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_select)
 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)