Merge with 2.5 -r 21619:21756.
[blender.git] / release / ui / space_console.py
1
2 import bpy
3
4 import bpy_ops # XXX - should not need to do this
5 del bpy_ops
6
7 class CONSOLE_HT_header(bpy.types.Header):
8         __space_type__ = "CONSOLE"
9         __idname__ = "CONSOLE_HT_header"
10
11         def draw(self, context):
12                 sc = context.space_data
13                 # text = sc.text
14                 layout = self.layout
15
16                 layout.template_header()
17
18                 row = layout.row()
19                 row.itemR(sc, "console_type", expand=True)
20
21                 if sc.type == 'REPORT':
22                         
23                         if context.area.show_menus:
24                                 row = layout.row()
25                                 row.itemM("CONSOLE_MT_report")
26                         
27                         row.itemR(sc, "show_report_debug", text="Debug")
28                         row.itemR(sc, "show_report_info", text="Info")
29                         row.itemR(sc, "show_report_operator", text="Operators")
30                         row.itemR(sc, "show_report_warn", text="Warnings")
31                         row.itemR(sc, "show_report_error", text="Errors")
32                         
33                         row = layout.row()
34                         row.enabled = sc.show_report_operator
35                         row.itemO("console.report_replay")
36                 
37                 else:
38                         if context.area.show_menus:
39                                 row = layout.row()
40                                 row.itemM("CONSOLE_MT_console")
41
42
43 class CONSOLE_MT_console(bpy.types.Menu):
44         __space_type__ = "CONSOLE"
45         __label__ = "Console"
46
47         def draw(self, context):
48                 layout = self.layout
49                 sc = context.space_data
50
51                 layout.column()
52                 layout.itemO("console.clear")
53
54 class CONSOLE_MT_report(bpy.types.Menu):
55         __space_type__ = "CONSOLE"
56         __label__ = "Report"
57
58         def draw(self, context):
59                 layout = self.layout
60                 sc = context.space_data
61
62                 layout.column()
63                 layout.itemO("console.select_all_toggle")
64                 layout.itemO("console.select_border")
65                 layout.itemO("console.report_delete")
66                 layout.itemO("console.report_copy")
67
68 def add_scrollback(text, text_type):
69         for l in text.split('\n'):
70                 bpy.ops.console.scrollback_append(text=l.replace('\t', '    '), type=text_type)
71
72 def get_console(console_id):
73         '''
74         helper function for console operators
75         currently each text datablock gets its own console - code.InteractiveConsole()
76         ...which is stored in this function.
77         
78         console_id can be any hashable type
79         '''
80         import sys, code
81         
82         try:    consoles = get_console.consoles
83         except:consoles = get_console.consoles = {}
84         
85         # clear all dead consoles, use text names as IDs
86         # TODO, find a way to clear IDs
87         '''
88         for console_id in list(consoles.keys()):
89                 if console_id not in bpy.data.texts:
90                         del consoles[id]
91         '''
92         
93         try:
94                 namespace, console, stdout, stderr = consoles[console_id]
95         except:
96                 namespace = {'__builtins__':__builtins__} # locals()
97                 namespace['bpy'] = bpy
98                 
99                 console = code.InteractiveConsole(namespace)
100                 
101                 if sys.version.startswith('3'):
102                         import io
103                         stdout = io.StringIO()
104                         stderr = io.StringIO()
105                 elif sys.version.startswith('2.6'):
106                         import io
107                         stdout = io.BytesIO()  # Py2x support
108                         stderr = io.BytesIO()
109                 else:
110                         import cStringIO
111                         stdout = cStringIO.StringIO()
112                         stderr = cStringIO.StringIO()
113
114         
115                 consoles[console_id]= namespace, console, stdout, stderr
116                 
117         return namespace, console, stdout, stderr
118
119 class CONSOLE_OT_exec(bpy.types.Operator):
120         '''
121         Operator documentatuon text, will be used for the operator tooltip and python docs.
122         '''
123         __idname__ = "console.exec"
124         __label__ = "Console Execute"
125         __register__ = False
126         
127         # Both prompts must be the same length
128         PROMPT = '>>> ' 
129         PROMPT_MULTI = '... '
130         
131         # is this working???
132         '''
133         def poll(self, context):
134                 return (context.space_data.type == 'PYTHON')
135         ''' # its not :|
136         
137         def execute(self, context):
138                 import sys
139                 
140                 sc = context.space_data
141                 
142                 try:
143                         line = sc.history[-1].line
144                 except:
145                         return ('CANCELLED',)
146                 
147                 if sc.type != 'PYTHON':
148                         return ('CANCELLED',)
149                 
150                 namespace, console, stdout, stderr = get_console(hash(context.region))
151                 
152                 # redirect output
153                 sys.stdout = stdout
154                 sys.stderr = stderr
155                 
156                 # run the console
157                 if not line.strip():
158                         line_exec = '\n' # executes a multiline statement
159                 else:
160                         line_exec = line
161                 
162                 is_multiline = console.push(line_exec)
163                 
164                 stdout.seek(0)
165                 stderr.seek(0)
166                 
167                 output = stdout.read()
168                 output_err = stderr.read()
169         
170                 # cleanup
171                 sys.stdout = sys.__stdout__
172                 sys.stderr = sys.__stderr__
173                 sys.last_traceback = None
174                 
175                 # So we can reuse, clear all data
176                 stdout.truncate(0)
177                 stderr.truncate(0)
178                 
179                 bpy.ops.console.scrollback_append(text = sc.prompt+line, type='INPUT')
180                 
181                 if is_multiline:        sc.prompt = self.PROMPT_MULTI
182                 else:                           sc.prompt = self.PROMPT
183                 
184                 # insert a new blank line
185                 bpy.ops.console.history_append(text="", current_character=0)
186                 
187                 # Insert the output into the editor
188                 # not quite correct because the order might have changed, but ok 99% of the time.
189                 if output:                      add_scrollback(output, 'OUTPUT')
190                 if output_err:          add_scrollback(output_err, 'ERROR')
191                 
192                 
193                 return ('FINISHED',)
194
195
196 def autocomp(bcon):
197         '''
198         This function has been taken from a BGE console autocomp I wrote a while ago
199         the dictionaty bcon is not needed but it means I can copy and paste from the old func
200         which works ok for now.
201         
202         could be moved into its own module.
203         '''
204         
205         
206         def is_delimiter(ch):
207                 '''
208                 For skipping words
209                 '''
210                 if ch == '_':
211                         return False
212                 if ch.isalnum():
213                         return False
214                 
215                 return True
216         
217         def is_delimiter_autocomp(ch):
218                 '''
219                 When autocompleteing will earch back and 
220                 '''
221                 if ch in '._[] "\'':
222                         return False
223                 if ch.isalnum():
224                         return False
225                 
226                 return True
227
228         
229         def do_autocomp(autocomp_prefix, autocomp_members):
230                 '''
231                 return text to insert and a list of options
232                 '''
233                 autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
234                 
235                 print("AUTO: '%s'" % autocomp_prefix)
236                 print("MEMBERS: '%s'" % str(autocomp_members))
237                 
238                 if not autocomp_prefix:
239                         return '', autocomp_members
240                 elif len(autocomp_members) > 1:
241                         # find a common string between all members after the prefix 
242                         # 'ge' [getA, getB, getC] --> 'get'
243                         
244                         # get the shortest member
245                         min_len = min([len(v) for v in autocomp_members])
246                         
247                         autocomp_prefix_ret = ''
248                         
249                         for i in range(len(autocomp_prefix), min_len):
250                                 char_soup = set()
251                                 for v in autocomp_members:
252                                         char_soup.add(v[i])
253                                 
254                                 if len(char_soup) > 1:
255                                         break
256                                 else:
257                                         autocomp_prefix_ret += char_soup.pop()
258                                 
259                         print(autocomp_prefix_ret)
260                         return autocomp_prefix_ret, autocomp_members
261                 elif len(autocomp_members) == 1:
262                         return autocomp_members[0][len(autocomp_prefix):], []
263                 else:
264                         return '', []
265         
266
267         def BCon_PrevChar(bcon):
268                 cursor = bcon['cursor']-1
269                 if cursor<0:
270                         return None
271                         
272                 try:
273                         return bcon['edit_text'][cursor]
274                 except:
275                         return None
276                 
277                 
278         def BCon_NextChar(bcon):
279                 try:
280                         return bcon['edit_text'][bcon['cursor']]
281                 except:
282                         return None
283         
284         def BCon_cursorLeft(bcon):
285                 bcon['cursor'] -= 1
286                 if bcon['cursor'] < 0:
287                         bcon['cursor'] = 0
288
289         def BCon_cursorRight(bcon):
290                         bcon['cursor'] += 1
291                         if bcon['cursor'] > len(bcon['edit_text']):
292                                 bcon['cursor'] = len(bcon['edit_text'])
293         
294         def BCon_AddScrollback(bcon, text):
295                 
296                 bcon['scrollback'] = bcon['scrollback'] + text
297                 
298         
299         def BCon_cursorInsertChar(bcon, ch):
300                 if bcon['cursor']==0:
301                         bcon['edit_text'] = ch + bcon['edit_text']
302                 elif bcon['cursor']==len(bcon['edit_text']):
303                         bcon['edit_text'] = bcon['edit_text'] + ch
304                 else:
305                         bcon['edit_text'] = bcon['edit_text'][:bcon['cursor']] + ch + bcon['edit_text'][bcon['cursor']:]
306                         
307                 bcon['cursor'] 
308                 if bcon['cursor'] > len(bcon['edit_text']):
309                         bcon['cursor'] = len(bcon['edit_text'])
310                 BCon_cursorRight(bcon)
311         
312         
313         TEMP_NAME = '___tempname___'
314         
315         cursor_orig = bcon['cursor']
316         
317         ch = BCon_PrevChar(bcon)
318         while ch != None and (not is_delimiter(ch)):
319                 ch = BCon_PrevChar(bcon)
320                 BCon_cursorLeft(bcon)
321         
322         if ch != None:
323                 BCon_cursorRight(bcon)
324         
325         #print (cursor_orig, bcon['cursor'])
326         
327         cursor_base = bcon['cursor']
328         
329         autocomp_prefix = bcon['edit_text'][cursor_base:cursor_orig]
330         
331         print("PREFIX:'%s'" % autocomp_prefix)
332         
333         # Get the previous word
334         if BCon_PrevChar(bcon)=='.':
335                 BCon_cursorLeft(bcon)
336                 ch = BCon_PrevChar(bcon)
337                 while ch != None and is_delimiter_autocomp(ch)==False:
338                         ch = BCon_PrevChar(bcon)
339                         BCon_cursorLeft(bcon)
340                 
341                 cursor_new = bcon['cursor']
342                 
343                 if ch != None:
344                         cursor_new+=1
345                 
346                 pytxt = bcon['edit_text'][cursor_new:cursor_base-1].strip()
347                 print("AUTOCOMP EVAL: '%s'" % pytxt)
348                 #try:
349                 if pytxt:
350                         bcon['console'].runsource(TEMP_NAME + '=' + pytxt, '<input>', 'single')
351                         # print val
352                 else: ##except:
353                         val = None
354                 
355                 try:
356                         val = bcon['namespace'][TEMP_NAME]
357                         del bcon['namespace'][TEMP_NAME]
358                 except:
359                         val = None
360                 
361                 if val:
362                         autocomp_members = dir(val)
363                         
364                         autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
365                         
366                         bcon['cursor'] = cursor_orig
367                         for v in autocomp_prefix_ret:
368                                 BCon_cursorInsertChar(bcon, v)
369                         cursor_orig = bcon['cursor']
370                         
371                         if autocomp_members:
372                                 BCon_AddScrollback(bcon, ', '.join(autocomp_members))
373                 
374                 del val
375                 
376         else:
377                 # Autocomp global namespace
378                 autocomp_members = bcon['namespace'].keys()
379                 
380                 if autocomp_prefix:
381                         autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
382                 
383                 autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
384                 
385                 bcon['cursor'] = cursor_orig
386                 for v in autocomp_prefix_ret:
387                         BCon_cursorInsertChar(bcon, v)
388                 cursor_orig = bcon['cursor']
389                 
390                 if autocomp_members:
391                         BCon_AddScrollback(bcon, ', '.join(autocomp_members))
392         
393         bcon['cursor'] = cursor_orig
394
395
396 class CONSOLE_OT_autocomplete(bpy.types.Operator):
397         '''
398         Operator documentatuon text, will be used for the operator tooltip and python docs.
399         '''
400         __idname__ = "console.autocomplete"
401         __label__ = "Console Autocomplete"
402         __register__ = False
403         
404         def poll(self, context):
405                 return context.space_data.type == 'PYTHON'
406         
407         def execute(self, context):
408                 
409                 sc = context.space_data
410                 
411                 namespace, console, stdout, stderr = get_console(hash(context.region))
412                 
413                 current_line = sc.history[-1]
414                 line = current_line.line
415                 
416                 if not console:
417                         return ('CANCELLED',)
418                 
419                 if sc.type != 'PYTHON':
420                         return ('CANCELLED',)
421                 
422                 # fake cursor, use for autocomp func.
423                 bcon = {}
424                 bcon['cursor'] = current_line.current_character
425                 bcon['console'] = console
426                 bcon['edit_text'] = line
427                 bcon['namespace'] = namespace
428                 bcon['scrollback'] = '' # nor from the BGE console
429                 
430                 
431                 # This function isnt aware of the text editor or being an operator
432                 # just does the autocomp then copy its results back
433                 autocomp(bcon)
434                 
435                 # Now we need to copy back the line from blender back into the text editor.
436                 # This will change when we dont use the text editor anymore
437                 if bcon['scrollback']:
438                         add_scrollback(bcon['scrollback'], 'INFO')
439                 
440                 # copy back
441                 current_line.line = bcon['edit_text']
442                 current_line.current_character = bcon['cursor']
443                 
444                 context.area.tag_redraw()
445                 
446                 return ('FINISHED',)
447
448
449
450 bpy.types.register(CONSOLE_HT_header)
451 bpy.types.register(CONSOLE_MT_console)
452 bpy.types.register(CONSOLE_MT_report)
453
454 bpy.ops.add(CONSOLE_OT_exec)
455 bpy.ops.add(CONSOLE_OT_autocomplete)
456