You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
393 lines
13 KiB
393 lines
13 KiB
6 years ago
|
# IDLEX EXTENSION
|
||
|
from __future__ import print_function
|
||
|
## """
|
||
|
## Copyright(C) 2011 The Board of Trustees of the University of Illinois.
|
||
|
## All rights reserved.
|
||
|
##
|
||
|
## Developed by: Roger D. Serwy
|
||
|
## University of Illinois
|
||
|
##
|
||
|
## Permission is hereby granted, free of charge, to any person obtaining
|
||
|
## a copy of this software and associated documentation files (the
|
||
|
## "Software"), to deal with the Software without restriction, including
|
||
|
## without limitation the rights to use, copy, modify, merge, publish,
|
||
|
## distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
## permit persons to whom the Software is furnished to do so, subject to
|
||
|
## the following conditions:
|
||
|
##
|
||
|
## + Redistributions of source code must retain the above copyright
|
||
|
## notice, this list of conditions and the following disclaimers.
|
||
|
## + Redistributions in binary form must reproduce the above copyright
|
||
|
## notice, this list of conditions and the following disclaimers in the
|
||
|
## documentation and/or other materials provided with the distribution.
|
||
|
## + Neither the names of Roger D. Serwy, the University of Illinois, nor
|
||
|
## the names of its contributors may be used to endorse or promote
|
||
|
## products derived from this Software without specific prior written
|
||
|
## permission.
|
||
|
##
|
||
|
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
|
## OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||
|
## IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||
|
## ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||
|
## CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||
|
## THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
|
||
|
##
|
||
|
##
|
||
|
##
|
||
|
##
|
||
|
## Cython Extension - provides basic Cython support for IDLE
|
||
|
##
|
||
|
## This intercepts calls for running the module and routes it here.
|
||
|
## Also, it installs a colorizer and patches EditorWindow so it works.
|
||
|
##
|
||
|
## Run Cython Module:
|
||
|
## - restarts shell
|
||
|
## - does a "from MODULE import *"
|
||
|
##
|
||
|
## Import/Reload Cython Module
|
||
|
## - does "import MODULE" if not in sys.modules
|
||
|
## else does a "reload(MODULE)"
|
||
|
## - does not restart shell
|
||
|
##
|
||
|
## To properly patch the idlelib source tree, you'd need to modify
|
||
|
## EditorWindow.py's "ispythonsource" to include .pyx files as well as create
|
||
|
## an appropriate color delegator. Since this is not
|
||
|
## likely to be accepted into IDLE, I'll stick to small monkey-patching.
|
||
|
##
|
||
|
##
|
||
|
## """
|
||
|
|
||
|
config_extension_def = """
|
||
|
|
||
|
[CythonScript]
|
||
|
enable=1
|
||
|
|
||
|
[CythonScript_cfgBindings]
|
||
|
cython-run=<Control-Key-e>
|
||
|
cython-import=<Control-Shift-Key-E>
|
||
|
cython-reload=
|
||
|
cython-pyximport-install=
|
||
|
|
||
|
"""
|
||
|
|
||
|
import sys
|
||
|
import os
|
||
|
import re
|
||
|
import time
|
||
|
from idlelib import PyShell
|
||
|
from idlelib.ColorDelegator import ColorDelegator, make_pat
|
||
|
from idlelib.configHandler import idleConf
|
||
|
import idlelib.IOBinding
|
||
|
import imp
|
||
|
|
||
|
DEBUG = False
|
||
|
|
||
|
HAS_CYTHON = True
|
||
|
HAS_RELOAD = False
|
||
|
|
||
|
try:
|
||
|
imp.find_module('cython')
|
||
|
except ImportError:
|
||
|
HAS_CYTHON = False
|
||
|
|
||
|
try:
|
||
|
import pyximport
|
||
|
except ImportError:
|
||
|
HAS_CYTHON = False
|
||
|
|
||
|
|
||
|
if HAS_CYTHON:
|
||
|
if 'reload_support' in pyximport.install.__code__.co_varnames:
|
||
|
HAS_RELOAD = True
|
||
|
else:
|
||
|
HAS_RELOAD = False
|
||
|
|
||
|
|
||
|
|
||
|
if sys.version < '3':
|
||
|
import tkMessageBox
|
||
|
else:
|
||
|
import tkinter.messagebox as tkMessageBox
|
||
|
|
||
|
|
||
|
|
||
|
CYTHON_IMPORT = 'import pyximport; pyximport.install(reload_support=True)'
|
||
|
|
||
|
def any_re(groupname, re_list):
|
||
|
return "(?P<%s>" % groupname + "|".join(re_list) + ")"
|
||
|
|
||
|
|
||
|
cythonlist = "cdef, cpdef, intern, struct, union, enum, ctypedef, void, double, public, inline, api, nogil, gil, except, cimport, NULL, char, DEF, IF, ELSEIF, ELSE, readonly, bint, by, include".split(', ')
|
||
|
cython_re = r"([^.'\"\\#]\b|^)" + any_re("cython", cythonlist) + r"\b"
|
||
|
|
||
|
|
||
|
CYTHON_BACKGROUND = '#EEDDFF'
|
||
|
|
||
|
def get_cfg(cfg, type="bool", default=True):
|
||
|
return idleConf.GetOption("extensions", "CythonScript",
|
||
|
cfg, type=type, default=default)
|
||
|
|
||
|
def set_cfg(cfg, b):
|
||
|
return idleConf.SetOption("extensions", "CythonScript",
|
||
|
cfg,'%s' % b)
|
||
|
|
||
|
def dbprint(*args, **kwargs):
|
||
|
args = (' CythonScript:',) + tuple(args)
|
||
|
if DEBUG:
|
||
|
print(*args, **kwargs)
|
||
|
|
||
|
|
||
|
if HAS_CYTHON or True:
|
||
|
# add to filetypes in IOBinding
|
||
|
f = idlelib.IOBinding.IOBinding.filetypes
|
||
|
f.insert(1, ("Cython files", "*.pyx"))
|
||
|
f.insert(0, ("Python/Cython files", "*.py *.pyw *.pyx", "TEXT"))
|
||
|
|
||
|
|
||
|
class CythonScript(object):
|
||
|
menudefs = [
|
||
|
('run', [None,
|
||
|
('Run Cython Module', '<<cython-run>>'),
|
||
|
('Import/Reload Cython Module', '<<cython-import>>'),
|
||
|
]),
|
||
|
('shell', [('Install PyxImport', '<<cython-pyximport-install>>')]),
|
||
|
]
|
||
|
|
||
|
def __init__(self, editwin):
|
||
|
|
||
|
self.editwin = editwin
|
||
|
self.text = editwin.text
|
||
|
|
||
|
if isinstance(editwin, PyShell.PyShell):
|
||
|
dbprint('running in shell', editwin)
|
||
|
self.inshell = True
|
||
|
return
|
||
|
else:
|
||
|
dbprint('running in editor', editwin)
|
||
|
self.inshell = False
|
||
|
self.ispythonsource_orig = editwin.ispythonsource
|
||
|
editwin.ispythonsource = self.ispythonsource
|
||
|
|
||
|
self.ec_id = None
|
||
|
if self.iscython():
|
||
|
self.text.after(10, self.editwin.ResetColorizer)
|
||
|
|
||
|
self.text.after(10, self.ec_timer)
|
||
|
|
||
|
|
||
|
def close(self): # Extension is being unloaded
|
||
|
if not self.inshell:
|
||
|
self.editwin.ispythonsource = self.ispythonsource_orig
|
||
|
self.ispythonsource_orig = None
|
||
|
text = self.text
|
||
|
text.unbind(self.cython_run_event)
|
||
|
text.unbind(self.check_cython_event)
|
||
|
self.ec_cancel()
|
||
|
self.editwin = None
|
||
|
self.text = None
|
||
|
self.shell = None
|
||
|
|
||
|
def _has_cython(func): # decorator function
|
||
|
def f(self, *args, **kwargs):
|
||
|
if HAS_CYTHON:
|
||
|
return func(self, *args, **kwargs)
|
||
|
else:
|
||
|
tkMessageBox.showerror("Cython not Found",
|
||
|
"Cython was not found by IDLE. Make sure it is installed or disable the CythonScript extension. "
|
||
|
"For more details about Cython, visit http://cython.org/",
|
||
|
parent=self.editwin.text)
|
||
|
return "break"
|
||
|
return f
|
||
|
|
||
|
|
||
|
|
||
|
def ec_timer(self, ev=None):
|
||
|
""" Periodically try to install the colorizer. """
|
||
|
self.ec_cancel()
|
||
|
self.extend_colorizer()
|
||
|
self.ec_id = self.text.after(1000, self.ec_timer)
|
||
|
|
||
|
def ec_cancel(self):
|
||
|
if self.ec_id is not None:
|
||
|
self.text.after_cancel(self.ec_id)
|
||
|
self.ec_id = None
|
||
|
|
||
|
def extend_colorizer(self):
|
||
|
""" Provides syntax highlighting for cython by patching the existing colorizer. """
|
||
|
color = self.editwin.color
|
||
|
if color is None:
|
||
|
return
|
||
|
|
||
|
p = color.prog.pattern
|
||
|
if 'cython' in p:
|
||
|
return # already extended
|
||
|
|
||
|
if self.iscython() == False:
|
||
|
return
|
||
|
dbprint('extending colorizer')
|
||
|
|
||
|
color.tagdefs['cython'] = color.tagdefs['KEYWORD'].copy()
|
||
|
#color.tagdefs['cython']['background'] = CYTHON_BACKGROUND
|
||
|
color.config_colors()
|
||
|
|
||
|
rlist = []
|
||
|
rlist.append(cython_re)
|
||
|
rlist.append(p)
|
||
|
|
||
|
color.prog = re.compile('|'.join(rlist), re.S)
|
||
|
color.notify_range('1.0', 'end')
|
||
|
|
||
|
@_has_cython
|
||
|
def cython_pyximport_install_event(self, ev=None):
|
||
|
dbprint('install_pyximport_event')
|
||
|
if self.inshell:
|
||
|
text = self.text
|
||
|
runit = self.editwin.runit
|
||
|
else:
|
||
|
shell = self.editwin.flist.open_shell()
|
||
|
text = shell.text
|
||
|
runit = shell.runit
|
||
|
|
||
|
text.delete('iomark', 'end-1c')
|
||
|
text.insert('iomark', CYTHON_IMPORT)
|
||
|
text.mark_set('insert', 'end-1c')
|
||
|
|
||
|
text.after(1, runit) # allow colorizer to kick in
|
||
|
|
||
|
|
||
|
def ispythonsource(self, filename):
|
||
|
""" Patch to EditorWindow's ispythonsource to detect .pyx files """
|
||
|
# The ResetColorizer code calls into ispythonsource. Trick it.
|
||
|
if filename:
|
||
|
base, ext = os.path.splitext(os.path.basename(filename))
|
||
|
if os.path.normcase(ext) in (".pyx",):
|
||
|
return True
|
||
|
return self.ispythonsource_orig(filename)
|
||
|
|
||
|
|
||
|
def iscython(self, filename=None):
|
||
|
if filename is None:
|
||
|
filename = self.editwin.io.filename # in case nothing specified
|
||
|
if filename is None: # still? this means the buffer is not saved
|
||
|
return False
|
||
|
base, ext = os.path.splitext(os.path.basename(filename))
|
||
|
ext_list = (".pyx", ".pxd")
|
||
|
if os.path.normcase(ext) in ext_list:
|
||
|
return True
|
||
|
else:
|
||
|
return False
|
||
|
|
||
|
@_has_cython
|
||
|
def check_cython_event(self, ev=None):
|
||
|
dbprint('check_cython_event')
|
||
|
return "break"
|
||
|
|
||
|
@_has_cython
|
||
|
def add_cython_event(self, ev=None):
|
||
|
self.text.insert('insert', CYTHON_IMPORT)
|
||
|
|
||
|
def getfilename(self):
|
||
|
if self.inshell:
|
||
|
return None
|
||
|
|
||
|
filename = self.editwin.io.filename
|
||
|
if filename is None:
|
||
|
tkMessageBox.showerror("No File Name",
|
||
|
"Please save the buffer (with a .pyx extension).",
|
||
|
parent=self.editwin.text)
|
||
|
return filename
|
||
|
|
||
|
@_has_cython
|
||
|
def cython_import_event(self, ev=None):
|
||
|
dbprint('cython_import_event')
|
||
|
self.cython_run_event(do_import=True)
|
||
|
|
||
|
@_has_cython
|
||
|
def cython_run_event(self, ev=None, do_import=False):
|
||
|
self.text.tag_remove("ERROR", "1.0", "end")
|
||
|
|
||
|
filename = self.getfilename()
|
||
|
if filename is None:
|
||
|
return
|
||
|
|
||
|
if not self.iscython(filename):
|
||
|
dbprint('filename not cython')
|
||
|
self.not_cython_message()
|
||
|
return
|
||
|
|
||
|
base, ext = os.path.splitext(os.path.basename(filename))
|
||
|
modname = base
|
||
|
dirname = os.path.dirname(filename)
|
||
|
|
||
|
try:
|
||
|
f = imp.find_module(modname)
|
||
|
if f[2][0] != '.pyx':
|
||
|
dbprint('Conflicting with .py', f)
|
||
|
self.pycy_conflict(f[1])
|
||
|
return "break"
|
||
|
|
||
|
except ImportError as err:
|
||
|
# good, .py or .pyc not found
|
||
|
pass
|
||
|
|
||
|
|
||
|
self.editwin.io.save(None) # save the cython module so it is reloaded properly
|
||
|
|
||
|
self.shell = shell = self.editwin.flist.open_shell()
|
||
|
interp = shell.interp
|
||
|
if PyShell.use_subprocess and not do_import:
|
||
|
shell.restart_shell()
|
||
|
|
||
|
interp.prepend_syspath(filename)
|
||
|
src = r"""if 1:
|
||
|
_filename = %(filename)r
|
||
|
import sys as _sys
|
||
|
from os.path import basename as _basename
|
||
|
if (not _sys.argv or
|
||
|
_basename(_sys.argv[0]) != _basename(_filename)):
|
||
|
_sys.argv = [_filename]
|
||
|
import os as _os
|
||
|
_os.chdir(%(dirname)r)
|
||
|
del _filename, _basename, _os
|
||
|
|
||
|
if "pyximport" not in _sys.modules:
|
||
|
import pyximport
|
||
|
if %(has_reload)s:
|
||
|
pyximport.install(reload_support=True)
|
||
|
else:
|
||
|
pyximport.install()
|
||
|
try:
|
||
|
if not %(do_import)s:
|
||
|
from %(modname)s import *
|
||
|
else:
|
||
|
if "%(modname)s" not in _sys.modules:
|
||
|
#print('importing')
|
||
|
import %(modname)s
|
||
|
elif %(has_reload)s:
|
||
|
#print('reloading')
|
||
|
import %(modname)s
|
||
|
reload(%(modname)s)
|
||
|
else:
|
||
|
print('\nUnable to reload module. Your Cython version does not support module reloading.\n')
|
||
|
except ImportError as err:
|
||
|
print('\nThe Cython Module could not be built.\n')
|
||
|
|
||
|
""" % {'filename':filename,
|
||
|
'dirname':dirname,
|
||
|
'modname':modname,
|
||
|
'do_import':do_import,
|
||
|
'has_reload':str(HAS_RELOAD)}
|
||
|
interp.runcode(src.strip()) # BUGFIX: .strip() for Python 2.6
|
||
|
return "break"
|
||
|
|
||
|
def not_cython_message(self):
|
||
|
tkMessageBox.showerror("Not a Cython Script",
|
||
|
"This buffer is not a Cython script.\n" + \
|
||
|
"Save with a .pyx extension.",
|
||
|
parent=self.editwin.text)
|
||
|
def pycy_conflict(self, filename):
|
||
|
tkMessageBox.showerror("Cython Import Collision",
|
||
|
'The file "%s"\nconflicts with importing this Cython script. Please relocate the file or rename this buffer.' % (filename),
|
||
|
parent=self.editwin.text)
|