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