4 import bpy_ops # XXX - should not need to do this
7 class CONSOLE_HT_header(bpy.types.Header):
8 __space_type__ = "CONSOLE"
9 __idname__ = "CONSOLE_HT_header"
11 def draw(self, context):
12 sc = context.space_data
16 layout.template_header()
18 if context.area.show_menus:
20 row.itemM("CONSOLE_MT_console")
24 row.itemR(sc, "type", expand=True)
25 if sc.type == 'REPORT':
26 row.itemR(sc, "show_report_debug")
27 row.itemR(sc, "show_report_info")
28 row.itemR(sc, "show_report_operator")
29 row.itemR(sc, "show_report_warn")
30 row.itemR(sc, "show_report_error")
33 class CONSOLE_MT_console(bpy.types.Menu):
34 __space_type__ = "CONSOLE"
37 def draw(self, context):
39 sc = context.space_data
42 layout.itemO("console.clear")
44 def add_scrollback(text, text_type):
45 for l in text.split('\n'):
46 bpy.ops.console.scrollback_append(text=l.replace('\t', ' '), type=text_type)
48 def get_console(console_id):
50 helper function for console operators
51 currently each text datablock gets its own console - code.InteractiveConsole()
52 ...which is stored in this function.
54 console_id can be any hashable type
58 try: consoles = get_console.consoles
59 except:consoles = get_console.consoles = {}
61 # clear all dead consoles, use text names as IDs
62 # TODO, find a way to clear IDs
64 for console_id in list(consoles.keys()):
65 if console_id not in bpy.data.texts:
70 namespace, console, stdout, stderr = consoles[console_id]
72 namespace = {'__builtins__':__builtins__} # locals()
73 namespace['bpy'] = bpy
75 console = code.InteractiveConsole(namespace)
77 if sys.version.startswith('3'):
79 stdout = io.StringIO()
80 stderr = io.StringIO()
81 elif sys.version.startswith('2.6'):
83 stdout = io.BytesIO() # Py2x support
87 stdout = cStringIO.StringIO()
88 stderr = cStringIO.StringIO()
91 consoles[console_id]= namespace, console, stdout, stderr
93 return namespace, console, stdout, stderr
95 class CONSOLE_OT_exec(bpy.types.Operator):
97 Operator documentatuon text, will be used for the operator tooltip and python docs.
99 __label__ = "Console Execute"
102 # Both prompts must be the same length
104 PROMPT_MULTI = '... '
108 def poll(self, context):
109 return (context.space_data.type == 'PYTHON')
112 def execute(self, context):
115 sc = context.space_data
118 line = sc.history[-1].line
120 return ('CANCELLED',)
122 if sc.type != 'PYTHON':
123 return ('CANCELLED',)
125 namespace, console, stdout, stderr = get_console(hash(context.region))
133 line_exec = '\n' # executes a multiline statement
137 is_multiline = console.push(line_exec)
142 output = stdout.read()
143 output_err = stderr.read()
146 sys.stdout = sys.__stdout__
147 sys.stderr = sys.__stderr__
148 sys.last_traceback = None
150 # So we can reuse, clear all data
154 bpy.ops.console.scrollback_append(text = sc.prompt+line, type='INPUT')
156 if is_multiline: sc.prompt = self.PROMPT_MULTI
157 else: sc.prompt = self.PROMPT
159 # insert a new blank line
160 bpy.ops.console.history_append(text="", current_character=0)
162 # Insert the output into the editor
163 # not quite correct because the order might have changed, but ok 99% of the time.
164 if output: add_scrollback(output, 'OUTPUT')
165 if output_err: add_scrollback(output_err, 'ERROR')
173 This function has been taken from a BGE console autocomp I wrote a while ago
174 the dictionaty bcon is not needed but it means I can copy and paste from the old func
175 which works ok for now.
177 could be moved into its own module.
181 def is_delimiter(ch):
192 def is_delimiter_autocomp(ch):
194 When autocompleteing will earch back and
204 def do_autocomp(autocomp_prefix, autocomp_members):
206 return text to insert and a list of options
208 autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
210 print("AUTO: '%s'" % autocomp_prefix)
211 print("MEMBERS: '%s'" % str(autocomp_members))
213 if not autocomp_prefix:
214 return '', autocomp_members
215 elif len(autocomp_members) > 1:
216 # find a common string between all members after the prefix
217 # 'ge' [getA, getB, getC] --> 'get'
219 # get the shortest member
220 min_len = min([len(v) for v in autocomp_members])
222 autocomp_prefix_ret = ''
224 for i in range(len(autocomp_prefix), min_len):
226 for v in autocomp_members:
229 if len(char_soup) > 1:
232 autocomp_prefix_ret += char_soup.pop()
234 print(autocomp_prefix_ret)
235 return autocomp_prefix_ret, autocomp_members
236 elif len(autocomp_members) == 1:
237 return autocomp_members[0][len(autocomp_prefix):], []
242 def BCon_PrevChar(bcon):
243 cursor = bcon['cursor']-1
248 return bcon['edit_text'][cursor]
253 def BCon_NextChar(bcon):
255 return bcon['edit_text'][bcon['cursor']]
259 def BCon_cursorLeft(bcon):
261 if bcon['cursor'] < 0:
264 def BCon_cursorRight(bcon):
266 if bcon['cursor'] > len(bcon['edit_text']):
267 bcon['cursor'] = len(bcon['edit_text'])
269 def BCon_AddScrollback(bcon, text):
271 bcon['scrollback'] = bcon['scrollback'] + text
274 def BCon_cursorInsertChar(bcon, ch):
275 if bcon['cursor']==0:
276 bcon['edit_text'] = ch + bcon['edit_text']
277 elif bcon['cursor']==len(bcon['edit_text']):
278 bcon['edit_text'] = bcon['edit_text'] + ch
280 bcon['edit_text'] = bcon['edit_text'][:bcon['cursor']] + ch + bcon['edit_text'][bcon['cursor']:]
283 if bcon['cursor'] > len(bcon['edit_text']):
284 bcon['cursor'] = len(bcon['edit_text'])
285 BCon_cursorRight(bcon)
288 TEMP_NAME = '___tempname___'
290 cursor_orig = bcon['cursor']
292 ch = BCon_PrevChar(bcon)
293 while ch != None and (not is_delimiter(ch)):
294 ch = BCon_PrevChar(bcon)
295 BCon_cursorLeft(bcon)
298 BCon_cursorRight(bcon)
300 #print (cursor_orig, bcon['cursor'])
302 cursor_base = bcon['cursor']
304 autocomp_prefix = bcon['edit_text'][cursor_base:cursor_orig]
306 print("PREFIX:'%s'" % autocomp_prefix)
308 # Get the previous word
309 if BCon_PrevChar(bcon)=='.':
310 BCon_cursorLeft(bcon)
311 ch = BCon_PrevChar(bcon)
312 while ch != None and is_delimiter_autocomp(ch)==False:
313 ch = BCon_PrevChar(bcon)
314 BCon_cursorLeft(bcon)
316 cursor_new = bcon['cursor']
321 pytxt = bcon['edit_text'][cursor_new:cursor_base-1].strip()
322 print("AUTOCOMP EVAL: '%s'" % pytxt)
325 bcon['console'].runsource(TEMP_NAME + '=' + pytxt, '<input>', 'single')
331 val = bcon['namespace'][TEMP_NAME]
332 del bcon['namespace'][TEMP_NAME]
337 autocomp_members = dir(val)
339 autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
341 bcon['cursor'] = cursor_orig
342 for v in autocomp_prefix_ret:
343 BCon_cursorInsertChar(bcon, v)
344 cursor_orig = bcon['cursor']
347 BCon_AddScrollback(bcon, ', '.join(autocomp_members))
352 # Autocomp global namespace
353 autocomp_members = bcon['namespace'].keys()
356 autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
358 autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
360 bcon['cursor'] = cursor_orig
361 for v in autocomp_prefix_ret:
362 BCon_cursorInsertChar(bcon, v)
363 cursor_orig = bcon['cursor']
366 BCon_AddScrollback(bcon, ', '.join(autocomp_members))
368 bcon['cursor'] = cursor_orig
371 class CONSOLE_OT_autocomplete(bpy.types.Operator):
373 Operator documentatuon text, will be used for the operator tooltip and python docs.
375 __label__ = "Console Autocomplete"
378 def poll(self, context):
379 return context.space_data.type == 'PYTHON'
381 def execute(self, context):
383 sc = context.space_data
385 namespace, console, stdout, stderr = get_console(hash(context.region))
387 current_line = sc.history[-1]
388 line = current_line.line
391 return ('CANCELLED',)
393 if sc.type != 'PYTHON':
394 return ('CANCELLED',)
396 # fake cursor, use for autocomp func.
398 bcon['cursor'] = current_line.current_character
399 bcon['console'] = console
400 bcon['edit_text'] = line
401 bcon['namespace'] = namespace
402 bcon['scrollback'] = '' # nor from the BGE console
405 # This function isnt aware of the text editor or being an operator
406 # just does the autocomp then copy its results back
409 # Now we need to copy back the line from blender back into the text editor.
410 # This will change when we dont use the text editor anymore
411 if bcon['scrollback']:
412 add_scrollback(bcon['scrollback'], 'INFO')
415 current_line.line = bcon['edit_text']
416 current_line.current_character = bcon['cursor']
418 context.area.tag_redraw()
424 bpy.types.register(CONSOLE_HT_header)
425 bpy.types.register(CONSOLE_MT_console)
427 bpy.ops.add(CONSOLE_OT_exec)
428 bpy.ops.add(CONSOLE_OT_autocomplete)