b92e3b7d51c67033e39dfa4a3595ed56d5cbc57c
[blender-staging.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                 if context.area.show_menus:
19                         row = layout.row()
20                         row.itemM("CONSOLE_MT_console")
21                 
22                 row = layout.row()
23                 row.scale_x = 0.9
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")
31
32
33 class CONSOLE_MT_console(bpy.types.Menu):
34         __space_type__ = "CONSOLE"
35         __label__ = "Console"
36
37         def draw(self, context):
38                 layout = self.layout
39                 sc = context.space_data
40
41                 layout.column()
42                 layout.itemO("console.clear")
43
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)
47
48 def get_console(console_id):
49         '''
50         helper function for console operators
51         currently each text datablock gets its own console - code.InteractiveConsole()
52         ...which is stored in this function.
53         
54         console_id can be any hashable type
55         '''
56         import sys, code
57         
58         try:    consoles = get_console.consoles
59         except:consoles = get_console.consoles = {}
60         
61         # clear all dead consoles, use text names as IDs
62         # TODO, find a way to clear IDs
63         '''
64         for console_id in list(consoles.keys()):
65                 if console_id not in bpy.data.texts:
66                         del consoles[id]
67         '''
68         
69         try:
70                 namespace, console, stdout, stderr = consoles[console_id]
71         except:
72                 namespace = {'__builtins__':__builtins__} # locals()
73                 namespace['bpy'] = bpy
74                 
75                 console = code.InteractiveConsole(namespace)
76                 
77                 if sys.version.startswith('3'):
78                         import io
79                         stdout = io.StringIO()
80                         stderr = io.StringIO()
81                 elif sys.version.startswith('2.6'):
82                         import io
83                         stdout = io.BytesIO()  # Py2x support
84                         stderr = io.BytesIO()
85                 else:
86                         import cStringIO
87                         stdout = cStringIO.StringIO()
88                         stderr = cStringIO.StringIO()
89
90         
91                 consoles[console_id]= namespace, console, stdout, stderr
92                 
93         return namespace, console, stdout, stderr
94
95 class CONSOLE_OT_exec(bpy.types.Operator):
96         '''
97         Operator documentatuon text, will be used for the operator tooltip and python docs.
98         '''
99         __label__ = "Console Execute"
100         __register__ = True
101         
102         # Both prompts must be the same length
103         PROMPT = '>>> ' 
104         PROMPT_MULTI = '... '
105         
106         # is this working???
107         '''
108         def poll(self, context):
109                 return (context.space_data.type == 'PYTHON')
110         ''' # its not :|
111         
112         def execute(self, context):
113                 import sys
114                 
115                 sc = context.space_data
116                 
117                 try:
118                         line = sc.history[-1].line
119                 except:
120                         return ('CANCELLED',)
121                 
122                 if sc.type != 'PYTHON':
123                         return ('CANCELLED',)
124                 
125                 namespace, console, stdout, stderr = get_console(hash(context.region))
126                 
127                 # redirect output
128                 sys.stdout = stdout
129                 sys.stderr = stderr
130                 
131                 # run the console
132                 if not line.strip():
133                         line_exec = '\n' # executes a multiline statement
134                 else:
135                         line_exec = line
136                 
137                 is_multiline = console.push(line_exec)
138                 
139                 stdout.seek(0)
140                 stderr.seek(0)
141                 
142                 output = stdout.read()
143                 output_err = stderr.read()
144         
145                 # cleanup
146                 sys.stdout = sys.__stdout__
147                 sys.stderr = sys.__stderr__
148                 sys.last_traceback = None
149                 
150                 # So we can reuse, clear all data
151                 stdout.truncate(0)
152                 stderr.truncate(0)
153                 
154                 bpy.ops.console.scrollback_append(text = sc.prompt+line, type='INPUT')
155                 
156                 if is_multiline:        sc.prompt = self.PROMPT_MULTI
157                 else:                           sc.prompt = self.PROMPT
158                 
159                 # insert a new blank line
160                 bpy.ops.console.history_append(text="", current_character=0)
161                 
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')
166                 
167                 
168                 return ('FINISHED',)
169
170
171 def autocomp(bcon):
172         '''
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.
176         
177         could be moved into its own module.
178         '''
179         
180         
181         def is_delimiter(ch):
182                 '''
183                 For skipping words
184                 '''
185                 if ch == '_':
186                         return False
187                 if ch.isalnum():
188                         return False
189                 
190                 return True
191         
192         def is_delimiter_autocomp(ch):
193                 '''
194                 When autocompleteing will earch back and 
195                 '''
196                 if ch in '._[] "\'':
197                         return False
198                 if ch.isalnum():
199                         return False
200                 
201                 return True
202
203         
204         def do_autocomp(autocomp_prefix, autocomp_members):
205                 '''
206                 return text to insert and a list of options
207                 '''
208                 autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
209                 
210                 print("AUTO: '%s'" % autocomp_prefix)
211                 print("MEMBERS: '%s'" % str(autocomp_members))
212                 
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'
218                         
219                         # get the shortest member
220                         min_len = min([len(v) for v in autocomp_members])
221                         
222                         autocomp_prefix_ret = ''
223                         
224                         for i in range(len(autocomp_prefix), min_len):
225                                 char_soup = set()
226                                 for v in autocomp_members:
227                                         char_soup.add(v[i])
228                                 
229                                 if len(char_soup) > 1:
230                                         break
231                                 else:
232                                         autocomp_prefix_ret += char_soup.pop()
233                                 
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):], []
238                 else:
239                         return '', []
240         
241
242         def BCon_PrevChar(bcon):
243                 cursor = bcon['cursor']-1
244                 if cursor<0:
245                         return None
246                         
247                 try:
248                         return bcon['edit_text'][cursor]
249                 except:
250                         return None
251                 
252                 
253         def BCon_NextChar(bcon):
254                 try:
255                         return bcon['edit_text'][bcon['cursor']]
256                 except:
257                         return None
258         
259         def BCon_cursorLeft(bcon):
260                 bcon['cursor'] -= 1
261                 if bcon['cursor'] < 0:
262                         bcon['cursor'] = 0
263
264         def BCon_cursorRight(bcon):
265                         bcon['cursor'] += 1
266                         if bcon['cursor'] > len(bcon['edit_text']):
267                                 bcon['cursor'] = len(bcon['edit_text'])
268         
269         def BCon_AddScrollback(bcon, text):
270                 
271                 bcon['scrollback'] = bcon['scrollback'] + text
272                 
273         
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
279                 else:
280                         bcon['edit_text'] = bcon['edit_text'][:bcon['cursor']] + ch + bcon['edit_text'][bcon['cursor']:]
281                         
282                 bcon['cursor'] 
283                 if bcon['cursor'] > len(bcon['edit_text']):
284                         bcon['cursor'] = len(bcon['edit_text'])
285                 BCon_cursorRight(bcon)
286         
287         
288         TEMP_NAME = '___tempname___'
289         
290         cursor_orig = bcon['cursor']
291         
292         ch = BCon_PrevChar(bcon)
293         while ch != None and (not is_delimiter(ch)):
294                 ch = BCon_PrevChar(bcon)
295                 BCon_cursorLeft(bcon)
296         
297         if ch != None:
298                 BCon_cursorRight(bcon)
299         
300         #print (cursor_orig, bcon['cursor'])
301         
302         cursor_base = bcon['cursor']
303         
304         autocomp_prefix = bcon['edit_text'][cursor_base:cursor_orig]
305         
306         print("PREFIX:'%s'" % autocomp_prefix)
307         
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)
315                 
316                 cursor_new = bcon['cursor']
317                 
318                 if ch != None:
319                         cursor_new+=1
320                 
321                 pytxt = bcon['edit_text'][cursor_new:cursor_base-1].strip()
322                 print("AUTOCOMP EVAL: '%s'" % pytxt)
323                 #try:
324                 if pytxt:
325                         bcon['console'].runsource(TEMP_NAME + '=' + pytxt, '<input>', 'single')
326                         # print val
327                 else: ##except:
328                         val = None
329                 
330                 try:
331                         val = bcon['namespace'][TEMP_NAME]
332                         del bcon['namespace'][TEMP_NAME]
333                 except:
334                         val = None
335                 
336                 if val:
337                         autocomp_members = dir(val)
338                         
339                         autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
340                         
341                         bcon['cursor'] = cursor_orig
342                         for v in autocomp_prefix_ret:
343                                 BCon_cursorInsertChar(bcon, v)
344                         cursor_orig = bcon['cursor']
345                         
346                         if autocomp_members:
347                                 BCon_AddScrollback(bcon, ', '.join(autocomp_members))
348                 
349                 del val
350                 
351         else:
352                 # Autocomp global namespace
353                 autocomp_members = bcon['namespace'].keys()
354                 
355                 if autocomp_prefix:
356                         autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
357                 
358                 autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
359                 
360                 bcon['cursor'] = cursor_orig
361                 for v in autocomp_prefix_ret:
362                         BCon_cursorInsertChar(bcon, v)
363                 cursor_orig = bcon['cursor']
364                 
365                 if autocomp_members:
366                         BCon_AddScrollback(bcon, ', '.join(autocomp_members))
367         
368         bcon['cursor'] = cursor_orig
369
370
371 class CONSOLE_OT_autocomplete(bpy.types.Operator):
372         '''
373         Operator documentatuon text, will be used for the operator tooltip and python docs.
374         '''
375         __label__ = "Console Autocomplete"
376         __register__ = True
377         
378         def poll(self, context):
379                 return context.space_data.type == 'PYTHON'
380         
381         def execute(self, context):
382                 
383                 sc = context.space_data
384                 
385                 namespace, console, stdout, stderr = get_console(hash(context.region))
386                 
387                 current_line = sc.history[-1]
388                 line = current_line.line
389                 
390                 if not console:
391                         return ('CANCELLED',)
392                 
393                 if sc.type != 'PYTHON':
394                         return ('CANCELLED',)
395                 
396                 # fake cursor, use for autocomp func.
397                 bcon = {}
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
403                 
404                 
405                 # This function isnt aware of the text editor or being an operator
406                 # just does the autocomp then copy its results back
407                 autocomp(bcon)
408                 
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')
413                 
414                 # copy back
415                 current_line.line = bcon['edit_text']
416                 current_line.current_character = bcon['cursor']
417                 
418                 context.area.tag_redraw()
419                 
420                 return ('FINISHED',)
421
422
423
424 bpy.types.register(CONSOLE_HT_header)
425 bpy.types.register(CONSOLE_MT_console)
426
427 bpy.ops.add(CONSOLE_OT_exec)
428 bpy.ops.add(CONSOLE_OT_autocomplete)
429