#!/usr/bin/env python ##################################################################### ## ## MODULE : tm_python.scm ## DESCRIPTION : Initialize python plugin ## COPYRIGHT : (C) 2004 Ero Carrera, address@hidden ## (C) 2012 Adrian Soto ## This software falls under the GNU general public license version 3 or later. ## It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE ## in the root directory or . import os import traceback import keyword import re import string from sympy import latex # added to import sympy latex method DATA_BEGIN = chr(2) DATA_END = chr(5) DATA_ESCAPE = chr(27) DATA_COMMAND = chr(16) __version__='1.0' __author__='Ero Carrera, Adrian Soto' class Capture: """Capture python output. Class in charge of recording the output of the statements/expressions entered in the TeXmacs session and executed in Python. """ def __init__(self): self.text = '' def write(self, str): self.text += str def getOutput(self): return self.text def flush(self): os.sys.stdout.flush()#Needed? self.text = '' def data_begin(): """Signal the beginning of data to TeXmacs.""" os.sys.stdout.write(chr(2)) def data_end(): """Signal the end of data to TeXmacs.""" os.sys.stdout.write(chr(5)) os.sys.stdout.flush() def texmacs_out(out_str): """Feed data back to TeXmacs. Output results back to TeXmacs, with the DATA_BEGIN, DATA_END control characters.""" data_begin() os.sys.stdout.write(out_str) data_end() os.sys.stdout.flush() def ps_out(ps_file): """Outputs Postscript within TeXmacs. According the the type of the argument the following scenarios can take place: If the argument is a string and has more than one line, it will be processed as raw Postscript data. If the argument is a string, it's supposed to contain the filename of a Postscript file which will be read ( if the file has no extension, the defaults .ps and .eps will be tried.) If the argument is a file or other object which provides a 'read' method, data will be obtained by calling such method. Implemented from suggestion by Alvaro Tejero Cantero. Implementation partially based on information provided by Mark Arrasmith. """ if 'read' in dir(ps_file): data = ps_file.read() return chr(2)+'ps:'+data+chr(5) if ps_file.find('\n')>0: return chr(2)+'ps:'+ps_file+chr(5) ext_list = ['', '.eps', '.ps'] if isinstance(ps_file, str): for ext in ext_list: if os.path.exists(ps_file+ext): ps_fd = file(ps_file+ext, 'r') data = ps_fd.read() ps_fd.close() break else: raise IOError('File \''+ps_file+'+'+str(ext_list)+'\' not found.') return chr(2)+'ps:'+data+chr(5) def compose_output(data): """Do some parsing on the output according to its type.""" if isinstance(data, str) and data == "": return 'verbatim: %s' % str(data) if isinstance(data, str) and data.find('Traceback') != -1: return 'verbatim: %s' % str(data) if isinstance(data, str) and data.find('Error:') != -1: return 'verbatim: %s' % str(data) if isinstance(data, int): return 'verbatim: %d' % data if isinstance(data, float): return 'verbatim: %f' % data if isinstance(data, unicode): data2=r'' for c in data: if c not in string.printable: data2+='\\x%x' % ord(c) else: data2+=c data=data2 return 'verbatim: %s' % str(data) data = latex(data,mode='inline') return 'latex: %s' % str(data) def do_module_hierarchy(mod, attr): """Explore an object's hierarchy. Go through the objects hierarchy looking for attributes/methods to provide as autocompletion options. """ dot = attr.find('.') if dot>0: if hasattr(mod, attr[:dot]): next = getattr(mod, attr[:dot]) return do_module_hierarchy(next, attr[dot+1:]) if isinstance(mod, dict): return dir(mod) else: return dir(mod) def find_completion_candidates(cmpl_str, my_globals): """Harvest candidates to provide as autocompletion options.""" haystack = my_globals.keys()+dir(my_globals['__builtins__'])+keyword.kwlist dot = cmpl_str.rfind('.') offset = None if dot>0: offset = len(cmpl_str[dot+1:]) first_dot = cmpl_str[:dot].find('.') if first_dot<0: mod_name = cmpl_str[:dot] r_str = cmpl_str[dot+1:] else: mod_name = cmpl_str[:first_dot] r_str = cmpl_str[first_dot+1:] if mod_name in keyword.kwlist: return None, [] if os.sys.modules.has_key(mod_name): haystack = do_module_hierarchy(os.sys.modules[mod_name], r_str) elif mod_name in my_globals.keys(): haystack = do_module_hierarchy(my_globals[mod_name], r_str) else: haystack = do_module_hierarchy(type(mod_name), r_str) return offset, filter(lambda x:x.find(cmpl_str[dot+1:]) == 0, haystack) def name_char(c): """Check whether a character is a valid symbol.""" if c in '+-*/%<>&|^~ = !,:()[]{}': return ' ' else: return c def complete(cmd, my_globals): """Parse autocomplete command. Parse the command and return a suitable answer to give back to TeXmacs. """ # Parse Texmacs command and extract string to # complete and offset to complete from. cmd = cmd.strip()[:-1] cmd_re = re.compile(r'"(.*)"\s+(\d+)') res = cmd_re.match(cmd) # if we don't match anything we return # no completion possibilities. if res is None: return 'scheme:(tuple "" "")' cmpl_str = res.group(1) pos_str = int(res.group(2)) cmpl_str = cmpl_str[:pos_str] if len(cmpl_str) == 0: return 'scheme:(tuple "" "")' # We get the string after the last space character. # no completion is done for strings with spaces # within cmpl_str = str().join(map(name_char, cmpl_str)) cmpl_str = cmpl_str.split()[-1] pos = len(cmpl_str) # no string after last space? return empty # completion if len(cmpl_str) == 0: return 'scheme:(tuple "" "")' # Find completion candidates and form a suitable # answer to Texmacs offset, cand = find_completion_candidates(cmpl_str, my_globals) if len(cand) == 0: res = '""' else: res = '' for c in cand: if offset is not None: pos = offset res += '"%s" ' % c[pos:] return 'scheme:(tuple "'+cmpl_str+'" '+res+')' ##FIXME: It should display the Python version, just as when one starts python. texmacs_out("""verbatim:Python plugin for TeXmacs. Please see the documentation in Help -> plugins -> Python """) my_globals = {} # We insert into the session's namespace the 'ps_out' method. my_globals['ps_out'] = ps_out # As well as some documentation. my_globals['__doc__'] = """Python plugin for TeXmacs. It provides autocompletion, and can insert postscript files into TeXmacs. """ capt = Capture() stdout_saved, os.sys.stdout = os.sys.stdout, capt co = compile('import __builtin__ as __builtins__', 'tm_python', 'exec') eval(co, my_globals) os.sys.stdout = stdout_saved # Main session loop. while 1: line=raw_input(); if line[0] == DATA_COMMAND: if line[1:].find('(complete ') == 0: #The replace is due to texmacs sending " as two characters texmacs_out(complete(line[11:].replace("\\\"","\""), my_globals)) continue lines=[line] while line<>"": line=raw_input() if line == '': continue lines.append(line) text='\n'.join(lines[:-1]) capt=Capture() try: out = eval(text, my_globals) result = out except: try: stdout_saved, os.sys.stdout = os.sys.stdout, capt co = compile(text, 'tm_python', 'exec') eval(co, my_globals) os.sys.stdout = stdout_saved result = capt.getOutput() except Exception: traceback.print_exc(file = os.sys.stdout, limit = 0) os.sys.stdout = stdout_saved result = capt.getOutput() del capt out = compose_output(result) texmacs_out(out.strip())