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.
370 lines
13 KiB
370 lines
13 KiB
#! /usr/bin/env python
|
|
|
|
## """
|
|
## 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.
|
|
##
|
|
##
|
|
## """
|
|
|
|
|
|
|
|
# This module hotpatches EditorWindow.py to load idlex extensions properly
|
|
|
|
from __future__ import print_function
|
|
import sys
|
|
|
|
from idlexlib.extensionManager import extensionManager
|
|
|
|
import idlelib
|
|
import os
|
|
import __main__
|
|
import imp
|
|
import traceback
|
|
import re
|
|
|
|
from idlelib import macosxSupport
|
|
|
|
version = "1.18" # IdleX version
|
|
|
|
IDLE_DEFAULT_EXT = [] # list of default extensions that IDLE has
|
|
|
|
|
|
if sys.version < '3':
|
|
from StringIO import StringIO
|
|
from Tkinter import *
|
|
import Tkinter as tkinter
|
|
import tkFileDialog
|
|
import tkMessageBox
|
|
else:
|
|
from io import StringIO
|
|
from tkinter import *
|
|
import tkinter
|
|
import tkinter.filedialog as tkFileDialog
|
|
import tkinter.messagebox as tkMessageBox
|
|
|
|
|
|
from idlelib.configHandler import idleConf, IdleConfParser
|
|
|
|
ansi_re = re.compile(r'\x01?\x1b\[(.*?)m\x02?')
|
|
def strip_ansi(s):
|
|
return ansi_re.sub("", s)
|
|
|
|
|
|
def install_idlex_manager():
|
|
""" install IDLEX extension manager into IDLE """
|
|
|
|
# 2011-11-15 Bugfix - change the user config file names for IdleX
|
|
# to avoid a problem on Windows where pythonw.exe refuses to run
|
|
# idle.pyw when an error occurs. However python.exe runs idle.py just fine.
|
|
# See http://bugs.python.org/issue13582
|
|
u = idleConf.userCfg
|
|
for key, value in list(u.items()):
|
|
# add "idlex-" to user config file names
|
|
fullfile = value.file
|
|
directory, filename = os.path.split(fullfile)
|
|
if filename.startswith('idlex-'):
|
|
new_filename = filename
|
|
else:
|
|
new_filename = 'idlex-' + filename
|
|
new_fullfile = os.path.join(directory, new_filename)
|
|
value.file = new_fullfile
|
|
value.Load()
|
|
|
|
mod = extensionManager.load_extension('idlexManager')
|
|
mod.extensionManager = extensionManager
|
|
mod.version = version
|
|
mod.update_globals()
|
|
|
|
|
|
# add idlex to the extension list
|
|
e = idleConf.userCfg['extensions']
|
|
if not e.has_section('idlexManager'):
|
|
e.add_section('idlexManager')
|
|
e.set('idlexManager', 'enable', '1')
|
|
|
|
|
|
def _printExt():
|
|
a = []
|
|
for i in idleConf.defaultCfg['extensions'].sections():
|
|
if i.endswith('_cfgBindings') or i.endswith('_bindings'):
|
|
continue
|
|
a.append(i)
|
|
print('Extensions: %s' % a)
|
|
|
|
|
|
|
|
###########################################################################
|
|
##
|
|
## HOTPATCHING CODE
|
|
##
|
|
###########################################################################
|
|
|
|
def fix_tk86():
|
|
tkinter._Tk = tkinter.Tk
|
|
def wrapper(func, name):
|
|
Tcl_Obj = tkinter._tkinter.Tcl_Obj
|
|
def f(*args, **kwargs):
|
|
#print(name, 'wrapped', args, kwargs)
|
|
#t = [i for i in args if isinstance(i, Tcl_Obj)]
|
|
#for i in t:
|
|
# print(name, 'FOUND arg:', repr(i), type(i), str(i))
|
|
|
|
args = [i if not isinstance(i, Tcl_Obj) else str(i)
|
|
for i in args]
|
|
for key, value in kwargs.items():
|
|
if isinstance(value, Tcl_Obj):
|
|
#print(name, 'FOUND kwarg:', key, value)
|
|
kwargs[key] = str(value)
|
|
return func(*args, **kwargs)
|
|
return f
|
|
|
|
class TkReflector(object):
|
|
def __init__(self, tk):
|
|
self.tk = tk
|
|
def __getattribute__(self, name):
|
|
a = getattr(object.__getattribute__(self, 'tk'), name)
|
|
if name in ['splitlist']:
|
|
#if hasattr(a, '__call__'):
|
|
return wrapper(a, name)
|
|
else:
|
|
return a
|
|
|
|
class TkFix(tkinter.Tk):
|
|
def __init__(self, *args, **kwargs):
|
|
tkinter._Tk.__init__(self, *args, **kwargs)
|
|
self.__tk = self.tk
|
|
version = self.tk.call('info', 'patchlevel')
|
|
if version.startswith('8.6'):
|
|
self.tk = TkReflector(self.__tk)
|
|
|
|
tkinter.Tk = TkFix
|
|
|
|
|
|
|
|
def _hotpatch():
|
|
# Fix numerous outstanding IDLE issues...
|
|
import idlelib.EditorWindow
|
|
|
|
|
|
EditorWindowOrig = idlelib.EditorWindow.EditorWindow
|
|
class EditorWindow(EditorWindowOrig):
|
|
|
|
|
|
_invalid_keybindings = [] # keep track of invalid keybindings encountered
|
|
_valid_keybindings = []
|
|
|
|
# Work around a bug in IDLE for handling events bound to menu items.
|
|
# The <<event-variables>> are stored globally, not locally to
|
|
# each editor window. Without this, toggling a checked menu item
|
|
# in one editor window toggles the item in ALL editor windows.
|
|
# Issue 13179
|
|
def __init__(self, flist=None, filename=None, key=None, root=None):
|
|
if flist is not None:
|
|
flist.vars = {}
|
|
EditorWindowOrig.__init__(self, flist, filename, key, root)
|
|
|
|
# FIXME: Do not transfer custom keybindings if IDLE keyset is set to default
|
|
|
|
# Fix broken keybindings that has plagued IDLE for years.
|
|
# Issue 12387, 4765, 13071, 6739, 5707, 11437
|
|
def apply_bindings(self, keydefs=None): # SUBCLASS to catch errors
|
|
#return EditorWindowOrig.apply_bindings(self, keydefs)
|
|
if keydefs is None:
|
|
keydefs = self.Bindings.default_keydefs
|
|
text = self.text
|
|
text.keydefs = keydefs
|
|
invalid = []
|
|
for event, keylist in keydefs.items():
|
|
for key in keylist:
|
|
try:
|
|
text.event_add(event, key)
|
|
except TclError as err:
|
|
#print(' Apply bindings error:', event, key)
|
|
invalid.append((event, key))
|
|
if invalid: # notify errors
|
|
self._keybinding_error(invalid)
|
|
|
|
|
|
def RemoveKeybindings(self): # SUBCLASS to catch errors
|
|
"Remove the keybindings before they are changed."
|
|
EditorWindow._invalid_keybindings = []
|
|
# Called from configDialog.py
|
|
self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
|
|
for event, keylist in keydefs.items():
|
|
for key in keylist:
|
|
try:
|
|
self.text.event_delete(event, key)
|
|
except Exception as err:
|
|
print(' Caught event_delete error:', err)
|
|
print(' For %s, %s' % (event, key))
|
|
pass
|
|
|
|
for extensionName in self.get_standard_extension_names():
|
|
xkeydefs = idleConf.GetExtensionBindings(extensionName)
|
|
if xkeydefs:
|
|
for event, keylist in xkeydefs.items():
|
|
for key in keylist:
|
|
try:
|
|
self.text.event_delete(event, key)
|
|
except Exception as err:
|
|
print(' Caught event_delete error:', err)
|
|
print(' For %s, %s' % (event, key))
|
|
pass
|
|
|
|
|
|
def _keybinding_error(self, invalid):
|
|
""" Create an error message about keybindings. """
|
|
|
|
new_invalid = [i for i in invalid if i not in EditorWindow._invalid_keybindings]
|
|
if new_invalid:
|
|
msg = ['There are invalid key bindings:', '']
|
|
for ev, k in new_invalid:
|
|
while ev[0] == '<' and ev[-1] == '>':
|
|
ev = ev[1:-1]
|
|
msg.append('Action:%s' % ev)
|
|
msg.append('Key:%s' % k)
|
|
msg.append('')
|
|
|
|
msg.extend(['Please reconfigure these bindings.'])
|
|
def errormsg(msg=msg):
|
|
tkMessageBox.showerror(title='Invalid Key Bindings',
|
|
message='\n'.join(msg),
|
|
master=self.top,
|
|
parent=self.top)
|
|
|
|
EditorWindow._invalid_keybindings.extend(new_invalid)
|
|
self.top.after(100, errormsg)
|
|
|
|
def load_standard_extensions(self):
|
|
for name in self.get_standard_extension_names():
|
|
|
|
try:
|
|
if name in extensionManager.IDLE_EXTENSIONS:
|
|
self.load_extension(name)
|
|
else:
|
|
self.load_idlex_extension(name)
|
|
except:
|
|
print("Failed to load extension", repr(name))
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def load_idlex_extension(self, name):
|
|
# import from idlex
|
|
mod = extensionManager.load_extension(name)
|
|
if mod is None:
|
|
print("\nFailed to import IDLEX extension: %s" % name)
|
|
return
|
|
|
|
cls = getattr(mod, name)
|
|
keydefs = idleConf.GetExtensionBindings(name)
|
|
if hasattr(cls, "menudefs"):
|
|
self.fill_menus(cls.menudefs, keydefs)
|
|
ins = cls(self)
|
|
|
|
self.extensions[name] = ins
|
|
if keydefs:
|
|
self.apply_bindings(keydefs)
|
|
for vevent in keydefs.keys():
|
|
methodname = vevent.replace("-", "_")
|
|
while methodname[:1] == '<':
|
|
methodname = methodname[1:]
|
|
while methodname[-1:] == '>':
|
|
methodname = methodname[:-1]
|
|
methodname = methodname + "_event"
|
|
if hasattr(ins, methodname):
|
|
self.text.bind(vevent, getattr(ins, methodname))
|
|
|
|
idlelib.EditorWindow.EditorWindow = EditorWindow
|
|
|
|
def macosx_workaround():
|
|
# restore "Options" menu on MacOSX
|
|
if not macosxSupport.runningAsOSXApp():
|
|
return
|
|
|
|
def restore(menu_specs):
|
|
c = [a for a,b in menu_specs]
|
|
if "options" not in c:
|
|
menu_specs.insert(-2, ("options", "Options"))
|
|
|
|
import idlelib.EditorWindow
|
|
restore(idlelib.EditorWindow.EditorWindow.menu_specs)
|
|
|
|
import idlelib.PyShell
|
|
restore(idlelib.PyShell.PyShell.menu_specs)
|
|
|
|
|
|
class devnull:
|
|
# For pythonw.exe on Windows...
|
|
def __init__(self):
|
|
pass
|
|
def write(self, *args, **kwargs):
|
|
pass
|
|
def __getattr__(self, *args, **kwargs):
|
|
return self.write
|
|
|
|
def pythonw_workaround():
|
|
# Work around a bug in pythonw.exe that prevents IdleX from starting.
|
|
if sys.stderr is None:
|
|
sys.stderr = devnull()
|
|
if sys.stdout is None:
|
|
sys.stdout = devnull()
|
|
|
|
|
|
def main():
|
|
|
|
pythonw_workaround()
|
|
_hotpatch()
|
|
fix_tk86()
|
|
|
|
try:
|
|
macosx_workaround()
|
|
except:
|
|
pass # Tk on OSX should not be this fragile...
|
|
|
|
install_idlex_manager()
|
|
|
|
extensionManager.load_idlex_extensions()
|
|
|
|
# Force a reset on Bindings...
|
|
# Without this, user reconfiguration of the key bindings within IDLE may
|
|
# generate an error on MultiCall unbind.
|
|
import idlelib.Bindings
|
|
idlelib.Bindings.default_keydefs = idleConf.GetCurrentKeySet()
|
|
|
|
import idlelib.PyShell
|
|
idlelib.PyShell.main()
|
|
|
|
if __name__ == '__main__':
|
|
# start up IDLE with IdleX
|
|
main()
|