>')])]
+
+ def __init__(self,editwin):
+ self.editwin=editwin
+ self.text=editwin.text
+
+ def idle2html_event(self, event=None):
+ filetypes = [
+ ("All HTML files", "*.html *.htm", "TEXT"),
+ ("All files", "*"),
+ ]
+ filename=tkFileDialog.SaveAs(master=self.text,
+ filetypes=filetypes,
+ title="Export to HTML",
+ ).show()
+ if filename:
+ f=open(filename,'w')
+ f.write(self.idle2html())
+ f.close()
+
+ def idle2html(self):
+ """format tags 2 html
+ """
+ out=['\n']
+ out.append('\n\nIDLE2HTML\n')
+ out.append('\n' % __version__)
+ out.append('\n')
+ out.append('\n\n')
+ inside_error=0
+ for tagname,content,dummy in self.text.dump('1.0',self.text.index('end')):
+ if tagname=='tagon' and not (content.upper() in ('SYNC','TODO','SEL')):
+ if not inside_error:
+ out.append('' % content)
+ if content.upper() == 'ERROR':
+ inside_error=1
+ if tagname=='text':
+ out.append(cgi.escape(content))
+ if tagname=='tagoff' and not (content.upper() in ('SYNC','TODO','SEL')):
+ if content.upper() == 'ERROR':
+ inside_error=0
+ if not inside_error:
+ out.append('')
+ out.append('
\n\n')
+ return ''.join(out)
+
diff --git a/idlex-1.11.2/idlexlib/extensions/IPyIDLE.py b/idlex-1.11.2/idlexlib/extensions/IPyIDLE.py
new file mode 100644
index 0000000..802d736
--- /dev/null
+++ b/idlex-1.11.2/idlexlib/extensions/IPyIDLE.py
@@ -0,0 +1,2034 @@
+# IDLEX EXTENSION
+from __future__ import print_function
+##
+## Copyright(C) 2012 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.
+##
+##
+##
+## An extension to allow IPython to work with IDLE's shell environment.
+## The latest 0.12 version uses a two-process model with ZMQ.
+##
+
+
+config_extension_def = """
+[IPyIDLE]
+enable = 1
+enable_shell = 1
+enable_editor = 1
+
+ipython = True
+allow_restart = True
+
+"""
+
+
+# This extension relies on Terminal.py's behavior
+# for the command line.
+#
+# This also expects that AutoComplete Calltips extensions
+# are enabled (which is the default in IDLE)
+#
+#
+
+#-----------------------------------------------------------------------------
+# Parts of this code derives from IPython 0.12 which has the following notice:
+#
+# Copyright (C) 2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+# Kernel manager launch code based on VIM integration
+# by Paul Ivanov (http://pirsquared.org)
+
+#----------------------------------------------
+# Imports
+#----------------------------------------------
+
+HAS_PIL = True
+try:
+ from PIL import ImageTk
+except:
+ HAS_PIL = False
+
+
+# python stdlib
+import bdb
+import signal
+import sys
+import time
+import re
+import marshal
+import types
+import traceback
+from pprint import pprint
+import time
+from threading import Event, Lock, Thread
+import string
+import threading
+import os
+from base64 import decodestring
+import tempfile
+import shutil
+
+if sys.version < '3':
+ import Tkinter as tk
+ import tkSimpleDialog
+ import tkMessageBox
+ import tkFileDialog
+ from Queue import Queue, Empty
+ from io import BytesIO
+ import copy_reg as copyreg
+
+else:
+ import tkinter as tk
+ import tkinter.simpledialog as tkSimpleDialog
+ import tkinter.messagebox as tkMessageBox
+ import tkinter.filedialog as tkFileDialog
+ from queue import Queue, Empty
+ from io import BytesIO
+ import copyreg
+ unicode = str # runcode needs "unicode" for Python2
+
+
+# IPython breaks code pickling in IDLE, must correct it...
+try:
+ _code_pickle_original = copyreg.dispatch_table.get(types.CodeType, None)
+except:
+ _code_pickle_original = None
+
+HAS_IPYTHON = True
+
+try:
+ import IPython
+ if IPython.__version__ < '0.12':
+ HAS_IPYTHON = False
+except:
+ HAS_IPYTHON = False
+
+if HAS_IPYTHON:
+ # IPython
+ from IPython.core import page
+ from IPython.utils.warn import warn, error, fatal
+ from IPython.utils import io as ipython_io
+ from IPython.core.oinspect import call_tip
+ from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
+ from IPython.utils.traitlets import Type
+ from IPython.zmq.kernelmanager import (KernelManager, SubSocketChannel, HBSocketChannel,
+ ShellSocketChannel, StdInSocketChannel)
+
+ #from IPython.frontend.consoleapp import IPythonConsoleApp
+ from IPython.config.loader import KeyValueConfigLoader
+ from IPython.zmq.kernelapp import kernel_aliases
+ from IPython.lib.kernel import find_connection_file
+
+else:
+ # pacify later code from errors
+ TerminalInteractiveShell = object
+ KernelManager = SubSocketChannel = HBSocketChannel = object
+ ShellSocketChannel = StdInSocketChannel = object
+ Type = lambda x: None
+
+if _code_pickle_original:
+ # FIX THE PICKLE DISPATCH TABLE
+ copyreg.dispatch_table[types.CodeType] = _code_pickle_original
+
+
+# IDLE
+from idlelib.configHandler import idleConf
+from idlelib.Delegator import Delegator
+from idlelib.IdleHistory import History
+import idlelib.AutoComplete # circular import avoidance for AutoCompleteWindow
+import idlelib.AutoCompleteWindow as AutoCompleteWindow
+import idlelib.CallTipWindow as CallTipWindow
+from idlelib.UndoDelegator import UndoDelegator
+import idlelib.PyShell as PyShell
+from idlelib.PyShell import ModifiedInterpreter
+from idlelib.HyperParser import HyperParser
+from idlelib.Percolator import Percolator
+from idlelib import SearchDialog
+from idlelib import Bindings
+from idlelib.MultiCall import MultiCallCreator
+
+# IdleX
+from idlexlib import idlexMain
+from idlexlib.extensions.SearchBar import SearchBar
+
+#------------------------------------
+# basic functions
+#------------------------------------
+
+jn = lambda x,y: '%i.%i' % (x,y) # join integers to text coordinates
+sp = lambda x: list(map(int, x.split('.'))) # convert tkinter Text coordinate to a li
+
+
+def background(f): # from IPython project
+ """call a function in a simple thread, to prevent blocking"""
+ t = Thread(target=f)
+ t.start()
+ return t
+
+def debug(*args, **kw):
+ print(*args, **kw)
+debug = lambda *args, **kw: None
+
+orig_ps1 = getattr(sys, 'ps1', None)
+orig_ps2 = getattr(sys, 'ps2', None)
+
+#-----------------------------------------------------------------------------
+# IdleX IPython Kernel Channels
+#-----------------------------------------------------------------------------
+
+# AFAIK, Tkinter can't handle placing events on its event queue from a thread
+# other than MainThread. IPython's 0MQ channels provides events in different
+# threads, so these messages are placed into a Queue for Tkinter to handle
+# from its MainThread.
+
+##DISPLAY_MESSAGES = True
+##GET_HISTORY = not DISPLAY_MESSAGES
+
+DISPLAY_MESSAGES = False
+GET_HISTORY = True
+
+
+ChannelLock = Lock()
+def channel_lock(func): # lock decorator
+ def f(*arg, **kw):
+ with ChannelLock:
+ return func(*arg,**kw)
+ return f
+
+MessageQueue = Queue() # place all messages into here
+
+class SimpleChannel(object):
+ @channel_lock
+ def call_handlers(self, msg):
+ if DISPLAY_MESSAGES:
+ print('[[%s]]' % self.channel_name, '-'*50)
+ print('Thread: %r' % threading.currentThread().getName())
+ if isinstance(msg, dict):
+ msg2 = msg.copy()
+ del msg2['parent_header']
+ else:
+ msg2 = msg
+ pprint(msg2)
+ # Place all messaged into a Queue so that
+ # the Tkinter event loop in MainThread can process them.
+ MessageQueue.put((self.channel_name, msg))
+
+
+class IdleXSubSocketChannel(SimpleChannel, SubSocketChannel):
+ channel_name = 'sub'
+
+class IdleXShellSocketChannel(SimpleChannel, ShellSocketChannel):
+ channel_name = 'shell'
+
+class IdleXStdInSocketChannel(SimpleChannel, StdInSocketChannel):
+ channel_name = 'stdin'
+
+class IdleXHBSocketChannel(SimpleChannel, HBSocketChannel):
+ channel_name = 'hb'
+ time_to_dead = 1.0
+
+class IdleXKernelManager(KernelManager):
+ shell_channel_class = Type(IdleXShellSocketChannel)
+ sub_channel_class = Type(IdleXSubSocketChannel)
+ stdin_channel_class = Type(IdleXStdInSocketChannel)
+ hb_channel_class = Type(IdleXHBSocketChannel)
+
+
+class IPyProxy(object):
+
+ shell_images = []
+ socket = 0
+
+ def __init__(self):
+
+ self.active = None # reference to IPyIDLE instance
+ self.active_pyshell = None # reference to active PyShell editor instance
+ self.switching = False # flag for switching shell
+ self.allow_restart = True # flag for ScriptBinding.py behavior
+ self._onetime_kernel_restart_message = False
+ self.kernel_manager = None
+ self.argv_changed = False
+ self._shell = None
+
+ self._saved_history = None
+ self._reset()
+
+ def _reset(self):
+ self._output = Queue() # for output to .text widget of PyShell
+ self._output_pyerr = Queue()
+ self._execute_queue = Queue()
+ self.execution_state = None
+ self._do_input = None
+ self._got_pyin = False
+ self._expected_execute_replies = []
+ self.calltipwindow = None
+ self.readline_canceled = False
+
+
+ def get_fake_socket(self):
+ self.socket += 1
+ return self.socket
+
+ @property
+ def connection_file(self):
+ return idlexMain.ipython_connection_file
+
+ def start_km(self):
+ self.stop_km() # make sure old was stopped
+ self._expected_execute_replies = []
+ kwargs = {}
+
+ found_cf = ''
+
+ cf = self.connection_file
+ if cf:
+ found_cf = self.get_connection_file(cf)
+
+ if idlexMain.ipython_argv:
+ kwargs['extra_arguments'] = idlexMain.ipython_argv
+
+
+ km = IdleXKernelManager()
+ self.kernel_manager = km
+
+ if found_cf:
+ msg = '\n IPython using connection file:\n %s\n' % found_cf
+ try:
+ km.connection_file = found_cf
+ km.load_connection_file()
+ except IOError:
+ connection_file = ''
+ msg = '\n Unable to load connection file:\n %s\n' % found_cf
+ km.connection_file = ''
+ self.kernel_manager = km
+
+ e = self.active_pyshell
+ print(msg, file=sys.stderr)
+ if e:
+ self._output.put((True, 'stderr', msg))
+ #e.text.insert('end', msg, tags='stderr')
+
+ if not found_cf:
+ km.start_kernel(**kwargs)
+
+ km.start_channels()
+ if GET_HISTORY:
+ msg_id = km.shell_channel.history(hist_access_type='tail', n=1000)
+ #km.shell_channel.execute('None', silent=True)
+ self.switching = False
+ km.hb_channel.unpause()
+ self.active_pyshell.executing = 0
+
+ def _windows_error(self, err):
+ print('A Windows-specific error occurred:', file=sys.stderr)
+ print(err,file=sys.stderr)
+
+ def stop_km(self):
+ km = self.kernel_manager
+ if km:
+ if not self.connection_file:
+ try:
+ km.shutdown_kernel(restart=False)
+ except WindowsError as err:
+ self._windows_error(err)
+ except RuntimeError as err:
+ pass
+ km.cleanup_connection_file()
+ background(km.stop_channels)
+ self.shell.execution_count = 1
+ self.kernel_manager = None
+
+ self._execute_queue = Queue()
+ self._expected_execute_replies = []
+
+ self._saved_history = None
+ self.switching = False
+
+ def interrupt(self):
+ self.readline_canceled = True
+ self.kernel_manager.interrupt_kernel()
+
+ def request_kernel_restart(self):
+ if self.switching:
+ self.switching = False
+ return False
+
+ if not self.allow_restart:
+ e = self.active_pyshell
+ if not self._onetime_kernel_restart_message:
+ self._onetime_kernel_restart_message = True
+ msg = ('\n Kernel restart is not allowed.\n ' +
+ 'To allow it, check "Allow IPython Kernel Restart" under "Shell".')
+ self._output.put((True, 'stderr', msg))
+ return False
+ else:
+ self._onetime_kernel_restart_message = False
+
+ if self.argv_changed:
+ self.argv_changed = False
+ self.stop_km()
+ self.start_km()
+ else:
+ if self.connection_file:
+ e = self.active_pyshell
+ msg = ('\n Will not restart kernel when connected ' +
+ 'to a remote kernel:\n %s\n' % self.connection_file)
+ self._output.put((True, 'stderr', msg))
+ return False
+ self._expected_execute_replies = []
+ self.kernel_manager.restart_kernel()
+ if GET_HISTORY:
+ self.kernel_manager.shell_channel.history(hist_access_type='tail', n=1000)
+ self.kernel_manager.shell_channel.execute('None', silent=True)
+ self.shell.execution_count = 1
+ self.regenerate_prompt_string()
+ self.execution_state = 'idle'
+ self.readline_canceled = True
+ return True
+
+ def cleanup_connection_file(self):
+ if not self.connection_file:
+ km = self.kernel_manager
+ if km and km.has_kernel:
+ km.cleanup_connection_file()
+
+ @property
+ def autocompletewindow(self):
+ return self.active.autocompletewindow
+
+ @property
+ def executing(self):
+ return ((self._execute_queue.qsize() > 0) or
+ (len(self._expected_execute_replies) > 0))
+
+ def enqueue_execute(self, source, *args, **kw):
+ self._execute_queue.put((source, args, kw))
+
+ def _exec_if_idle(self):
+ # called from poll_subprocess
+ #print('what', self.execution_state, len(self._expected_execute_replies))
+ if (self.execution_state == 'idle' and
+ len(self._expected_execute_replies) == 0):
+ try:
+ m = self._execute_queue.get_nowait()
+ except Empty:
+ return
+ self.active_pyshell.executing = 0 # in case Ctrl+C aborted execution
+ source, args, kw = m
+ exec_callback = kw.pop('exec_callback')
+ finish_callback = kw.pop('finish_callback')
+ km = self.kernel_manager
+ if exec_callback:
+ try:
+ exec_callback()
+ except Exception as err:
+ print('Exception in callback: %r' % err)
+ msg_id = km.shell_channel.execute(source, *args, **kw)
+ #print('sending: %s: %r' % (msg_id, source))
+ self._expected_execute_replies.append((msg_id, finish_callback))
+
+ if self.execution_state is None:
+ self.execution_state = 'idle' # never got initialized
+
+ if self.execution_state not in ['busy', 'idle']:
+ print('Unknown execution state: %r' % self.execution_state)
+
+ # TODO: failsafe if execute reply never gets received....
+ #print('_exec_if_idle', self.execution_state, self._expected_execute_replies)
+
+ def _get_console_output(self):
+ # combine all the output for the console
+ _output = self._output
+ _output_pyerr = self._output_pyerr
+ retval = []
+ while 1: # flush the stdout/stderr queue
+ try:
+ stuff = _output.get(block=False)
+ retval.append(stuff)
+ except Empty:
+ break
+ while 1: # flush the pyerr queue
+ try:
+ stuff = _output_pyerr.get(block=False)
+ retval.append(stuff)
+ except Empty:
+ break
+ return retval
+
+ def write_console_output(self):
+ # handle console output, write before the prompt
+ tkconsole = self.active_pyshell
+ out = self._get_console_output()
+ if out:
+ text = tkconsole.text
+ for before, name, data in out:
+ if before:
+ # remember the location of iomark
+ text.mark_set('iomark2', 'iomark')
+ text.mark_gravity('iomark2', text.mark_gravity('iomark'))
+
+
+ text.mark_set('iomark', 'before_prompt') # Undo Delegator
+ text.mark_gravity("before_prompt", "right")
+ if name in ['stdout', 'stderr']:
+ #print('%r' % data)
+ text.insert('before_prompt', data, tags=name)
+
+ elif name == 'image/png':
+ if HAS_PIL:
+ prior_root = tk._default_root # Work around a bug in IDLE
+ tk._default_root=tkconsole.top
+
+ si = ShellImage(text, data, format='image/png')
+ text.window_create('before_prompt', window=si)
+ self.shell_images.append(si)
+
+ tk._default_root = prior_root
+
+ else:
+ text.insert('before_prompt',
+ ("\nMissing 'ImageTk' from PIL to display image/png.\n" +
+ 'Please install Python Imaging Library with Tkinter support.\n' +
+ 'http://www.pythonware.com/products/pil/'
+ ))
+ else:
+ print('Unsupported image type: %r' % name)
+
+ text.mark_gravity("before_prompt", "left")
+ if tkconsole.text.compare('iomark2', '<', 'before_prompt'):
+ tkconsole.text.mark_set('iomark2', 'before_prompt')
+
+ # restore iomark
+ text.mark_set('iomark', 'iomark2')
+ text.mark_unset('iomark2')
+
+ else: # after prompt
+ if name in ['stdout', 'stderr']:
+ text.insert('end', data, tags=name)
+
+ tkconsole.text.see('insert')
+
+ def clear_shell_images(self):
+ for i in self.shell_images:
+ i.destroy()
+ self.shell_images = []
+
+ def regenerate_prompt_string(self):
+ shell = self.shell
+ shell.hooks.pre_prompt_hook()
+ try:
+ prompt = shell.separate_in + shell.prompt_manager.render('in')
+ except Exception:
+ traceback.print_exc()
+ shell.showtraceback()
+
+ sys.ps1 = prompt
+
+ try:
+ prompt = shell.prompt_manager.render('in2')
+ except Exception:
+ traceback.print_exc()
+ shell.showtraceback()
+
+ sys.ps2 = prompt
+
+ @property
+ def shell(self):
+ if self._shell is None:
+ e = self.active_pyshell
+ idle_io = (e, e.stdout, e.stderr)
+ km = ipyProxy.kernel_manager
+ self._shell = IPyIDLEShell(user_ns={},
+ kernel_manager=km,
+ io=idle_io)
+ return self._shell
+
+ def call_message_handlers(self, m):
+ channel_name, msg = m
+ if channel_name != 'hb':
+ msg_type = msg['header']['msg_type']
+ h = getattr(self, '_handle_%s_%s' % (channel_name, msg_type), None)
+ if h:
+ h(msg)
+ else:
+ print('Not Implemented: %s on %s channel' % (msg_type, channel_name))
+ pprint(msg)
+ else:
+ print('heartbeat:', msg)
+
+ def get_the_calltip(self, obj):
+ # called from FakeSubProcess
+ #print('get_the_calltip: %r' % obj)
+ msg_id = self.kernel_manager.shell_channel.object_info(obj)
+
+
+ def restore_IDLE_history(self):
+ # forget the IPython command history and restore
+ # what the normal IDLE shell had
+ if self.active_pyshell and self._saved_history:
+ h = History(active_pyshell.text)
+ h.history = self._saved_history
+ h.history_prefix = None
+ h.history_pointer = None
+ self._saved_history = None
+ active_pyshell.history = h
+
+ def set_IPython_history(self, items):
+ #print('set_ipython_history', items)
+ e = self.active_pyshell
+ if e:
+ history = IPythonHistory(e.text)
+ history.history = items
+ e.history = history
+ else:
+ print('Unable to set history - not active pyshell', file=sys.stderr)
+
+
+ def _from_this_session(self, msg):
+ km_session = ipyProxy.kernel_manager.session.session
+ parent = msg['parent_header']
+ if not parent:
+ return True
+ else:
+ return parent.get('session') == km_session
+
+ def get_connection_file(self, connection_file):
+ try:
+ connection_file = find_connection_file(connection_file)
+ except IOError as err:
+ s = 'Unable to find: %r' % connection_file
+ print(s, file=sys.stderr)
+ if self.active:
+ tkMessageBox.showerror('IPython Connection File', message=s,
+ parent=self.active.editwin.text)
+ return ''
+ return connection_file
+
+ #-------------------------------
+ # Channel Message Handlers
+ #-------------------------------
+ def check_session(func):
+ def f(self, msg, *arg, **kw):
+ if not self._from_this_session(msg):
+ #print('not from session, call to %s' % func)
+ return
+ else:
+ #print('is from session, call to %s' % func)
+ return func(self, msg, *arg, **kw)
+ return f
+
+
+ @check_session
+ def _handle_sub_stream(self, msg):
+ datetime = msg['header']['date']
+ before = True
+ content = msg['content']
+ name = content['name'] # stout or stderr
+ data = content['data']
+ self._output.put((before, name, data))
+
+ @check_session
+ def _handle_sub_status(self, msg):
+ content = msg['content']
+ ec = content['execution_state']
+ self.execution_state = ec
+ png_data = content.get('image/png')
+ self._output_png_data(png_data)
+
+ def _handle_sub_pyerr(self, msg):
+ pass
+
+ @check_session
+ def _handle_sub_pyout(self, msg):
+ content = msg['content']
+ execution_count = int(content["execution_count"])
+ self.shell.execution_count = execution_count
+ format_dict = msg["content"]["data"]
+ # from displayhook.py
+ outprompt = self.shell.prompt_manager.render('out') #write_output_prompt
+ result_repr = format_dict['text/plain']
+ if '\n' in result_repr:
+ prompt_template = self.shell.prompt_manager.out_template
+ if prompt_template and not prompt_template.endswith('\n'):
+ result_repr = '\n' + result_repr
+ last = self.shell.separate_out2
+
+ out = outprompt + result_repr + last
+ out += '\n'
+ self._output.put((True, 'stdout', out))
+ self._output_png_data(format_dict.get('image/png', None))
+
+
+ def _handle_sub_pyin(self, msg):
+ # acknowledgment of sent command
+ pass
+
+ def _handle_sub_shutdown_reply(self, msg):
+ self._expected_execute_replies = []
+
+ def _handle_shell_shutdown_reply(self, msg):
+ pass
+
+ @check_session
+ def _handle_shell_execute_reply(self, msg):
+ self.kernel_manager.sub_channel.flush()
+
+ parent_id = msg['parent_header']['msg_id']
+
+ content = msg['content']
+ status = content['status']
+
+ expecting = self._expected_execute_replies
+ finish_callback = None
+
+ expected_msg_id = [msg_id for msg_id,callback in expecting]
+
+ a = [n for n, i in enumerate(expecting) if i[0] == parent_id]
+ if a:
+ if a[0] == 0:
+ #print('in order')
+ pass
+ else:
+ #print('out of order')
+ pass
+
+ emsgid, finish_callback = expecting.pop(a[0])
+ else:
+ #print('not expecting')
+ pass
+
+ ec = content.get("execution_count", None)
+ if ec:
+ if int(ec + 1) != self.shell.execution_count:
+ self.shell.execution_count = int(ec + 1)
+ self.regenerate_prompt_string()
+ if status == 'error':
+ name = 'stderr'
+ self._output_pyerr.put((True, 'stderr', '\n'))
+ for frame in content["traceback"]:
+ self._output_pyerr.put((True, name, frame))
+ self._output_pyerr.put((True, 'stderr', '\n'))
+
+ if 'payload' in content:
+ for item in content["payload"]:
+ text = item.get('text', None)
+ if text:
+ Pager(self.active.editwin.top,
+ 'IDLE IPython Pager',
+ text)
+
+ filename = item.get('filename', None)
+ if filename:
+ flist = self.active_pyshell.flist
+ flist.open(filename)
+ # TODO: handle going to a line number
+
+ if finish_callback:
+ finish_callback()
+
+ def _handle_shell_history_reply(self, msg):
+ content = msg['content']
+ items = content['history']
+
+ # TODO: handle case if history request failed
+ history_items = content['history']
+ items = []
+ last_cell = ""
+ for _, _, cell in history_items:
+ cell = cell.rstrip()
+ if cell != last_cell:
+ items.append(cell)
+ last_cell = cell
+
+ self.set_IPython_history(items)
+
+ def _handle_shell_object_info_reply(self, msg):
+ """ Display a call tip """
+ # show call tip
+ if self.calltipwindow:
+ self.calltipwindow.hidetip()
+
+ editwin = self.active.editwin
+ self.calltipwindow = ctw = CallTipWindow.CallTip(editwin.text)
+
+ content = msg['content']
+ call_info, doc = call_tip(content, format_call=True)
+ maxlines = 15
+ # code from Qt console call_tip_widget.py
+ if doc:
+ match = re.match("(?:[^\n]*\n){%i}" % maxlines, doc)
+ if match:
+ doc = doc[:match.end()] + '\n[Documentation continues...]'
+ else:
+ doc = ''
+
+ if call_info:
+ doc = '\n\n'.join([call_info, doc])
+
+ hp = HyperParser(editwin, "insert")
+ sur_paren = hp.get_surrounding_brackets('(')
+ try:
+ if sur_paren:
+ p1, p2 = sur_paren
+ MARK_RIGHT = "calltipwindowregion_right"
+ editwin.text.mark_set(MARK_RIGHT, p2)
+ ctw.showtip(doc, p1, p2)
+ except:
+ print('CALLTIP ERROR', '-'*50)
+ traceback.print_exc()
+
+
+ def _handle_shell_complete_reply(self, msg):
+ """ Display an AutoCompleteWindow with this reply """
+ self.autocompletewindow.hide_window()
+ m = msg['content'].get('matches', None)
+ if not m:
+ return
+
+ comp_start = msg['content']['matched_text']
+
+ # remove leading matched_text from the results
+ # up to the last "."
+ p = re.split("[./]", comp_start)
+
+ if len(p) > 1:
+ ignore = '.'.join(p[:-1])
+ remain = p[-1]
+ offset = len(ignore) + 1 # "+ 1" for the last .
+ m = [i[offset:] for i in m]
+ comp_start=remain
+
+ # Take the result and make it compatible
+ # with IDLE's autocomplete extension
+ comp_lists = (m,m)
+ mode = AutoCompleteWindow.COMPLETE_ATTRIBUTES
+ userWantsWin = True
+ complete = True
+ self.autocompletewindow.show_window(comp_lists,
+ "insert-%dc" % len(comp_start),
+ complete,
+ mode,
+ userWantsWin)
+
+ def _handle_stdin_input_request(self, msg):
+ self.kernel_manager.sub_channel.flush()
+
+ prompt = msg["content"]["prompt"]
+ if not isinstance(prompt, (str, unicode)):
+ prompt = str(prompt)
+
+ self._do_input = prompt
+ # _do_input is a flag used in poll_subprocess
+ # to initialize readline.
+ tkconsole = self.active_pyshell
+
+ text = tkconsole.text
+ text.mark_set('before_prompt', 'iomark linestart')
+ text.mark_gravity("before_prompt", "left")
+ text.insert('before_prompt', prompt)
+ text.mark_set('iomark', 'end-1c')
+
+ self.readline_canceled = False
+ debug('ENTER READLINE')
+ raw_data = tkconsole.readline()
+ debug('EXIT READLINE: %r' % raw_data)
+ if tkconsole.closing:
+ # Note: tkconsole.closing must be tested in case
+ # PyShell is closed during readline
+ return
+
+ if self.readline_canceled:
+ # Ctrl+C or restart of kernel
+ self.readline_canceled = False
+ return
+
+ raw_data = raw_data[:-1] # eliminate \n
+ debug('SENDING RAW_INPUT: %r' % raw_data)
+ ipyProxy.kernel_manager.stdin_channel.input(raw_data)
+ text.mark_set('before_prompt', 'insert')
+ if self.active_pyshell:
+ self.active_pyshell.resetoutput()
+
+ def _handle_sub_display_data(self, msg):
+ data = msg['content']['data']
+ png_data = data.get('image/png', None)
+ self._output_png_data(png_data)
+
+ def _output_png_data(self, png_data):
+ if png_data:
+ png = decodestring(png_data.encode('ascii'))
+ self._output.put((True, 'image/png', png))
+
+
+ipyProxy = IPyProxy() # Launch a singleton for IPyProxy
+
+
+class ShellImage(tk.Label):
+ def __init__(self, text, data, format=None):
+ # format - TODO allow for SVG in future
+ # For now, only handle .png
+
+ fid = BytesIO(data)
+ im = ImageTk.Image.open(fid)
+ try:
+ tkim = ImageTk.PhotoImage(im)
+ except ImportError:
+ tkim = None
+ pass
+ tk.Label.__init__(self, text, image=tkim, takefocus=0, borderwidth=0)
+
+ self.text = text
+ self.tkim = tkim
+ self.im = im
+ self.bind('<4>', lambda e: text.event_generate('<4>'))
+ self.bind('<5>', lambda e: text.event_generate('<5>'))
+ self.bind('<3>', self.rmenu)
+
+ def rmenu(self, event=None):
+ # Do "save image"
+ # Tkinter can not place an image on the clipboard.
+ rmenu = tk.Menu(self.text, tearoff=0)
+
+ rmenu.add_command(label='Save Image As...', command=self.saveas)
+ rmenu.tk_popup(event.x_root, event.y_root)
+
+ def saveas(self):
+ s = tkFileDialog.SaveAs(parent=self.text,
+ filetypes=[('Portable Network Graphic', '*.png')])
+ initial = str(self.tkim) + '.png'
+ filename = s.show(initialfile=initial)
+ if filename:
+ try:
+ self.im.save(filename, format='png')
+ except Exception as err:
+ traceback.print_exc()
+
+
+#----------------------
+# Delegators for Text
+#----------------------
+# Based on code from: http://wiki.ipython.org/Old_Embedding/Tkinter
+ansi_colors = {'0;30': '#000000', # black
+ '0;31': '#880000', # red
+ '0;32': '#008800', # green
+ '0;33': '#664400', # brown
+ '0;34': '#000088', # blue
+ '0;35': '#880088', # purple
+ '0;36': '#008888', # cyan
+ '0;37': '#AAAAAA', # white
+
+ '1;30': '#555555', # light black
+ '1;31': '#AA0000', # light red
+ '1;32': '#00AA00', # light green
+ '1;33': '#888800', # light brown
+ '1;34': '#0000AA', # light blue
+ '1;35': '#AA00AA', # light purple
+ '1;36': '#00AAAA', # light cyan
+ '1;37': '#000000', # light white
+
+ '01;30': '#555555', # light black
+ '01;31': '#AA0000', # light red
+ '01;32': '#00AA00', # light green
+ '01;33': '#888800', # light brown
+ '01;34': '#0000AA', # light blue
+ '01;35': '#AA00AA', # light purple
+ '01;36': '#00AAAA', # light cyan
+ '01;37': '#000000', # light white
+ }
+
+
+ansi_re = re.compile(r'\x01?\x1b\[(.*?)m\x02?')
+def strip_ansi(s):
+ return ansi_re.sub("", s)
+
+class AnsiColorDelegator(Delegator):
+ # Colorize IPython ansi color codes
+ def __init__(self, *args, **kw):
+ Delegator.__init__(self, *args, **kw)
+
+ def insert(self, index, chars, tags=None):
+ chars = chars.replace('\r', '')
+ delegate = self.delegate
+
+ index = delegate.index(index)
+ if tags:
+ tags = tags.replace('stdout', '')
+
+ m = ansi_re.search(chars)
+ if not m:
+ delegate.insert(index, chars, tags)
+ else:
+ if tags is None:
+ tags = ''
+ active_tag = ''
+ prior = 0
+ ic = 0
+ while m:
+ s = m.start()
+ e = m.end()
+ b = chars[prior:s]
+ if len(b):
+ delegate.insert(index + '+%ic' % ic, b, tags + ' ' + active_tag)
+ ic += len(b)
+
+ active_tag = ansi_re.split(chars[s:e])[1]
+ prior = e
+ m = ansi_re.search(chars, prior)
+
+ delegate.insert(index + '+%ic' % ic, chars[prior:], tags + ' ' + active_tag)
+
+
+class IPythonHistory(History):
+
+ def history_store(self, source):
+ debug('storing: ', source, self)
+ source = source.rstrip() # preserve leading whitespace
+ if len(source) > 0: # avoid duplicates
+ try:
+ if source in self.history[-10:]:
+ self.history.remove(source)
+ except ValueError:
+ pass
+ self.history.append(source)
+ self.history_pointer = None
+ self.history_prefix = None
+
+
+#-----------------------------------------
+# PyShell Subprocess-to-IPython Interface
+#-----------------------------------------
+eloop = """try:
+ __IDLE_eventloop()
+except NameError:
+ pass
+except Exception as err:
+ __IDLE_eventloop_error(err)
+""" # EventLoop.py support
+
+class FakeSubProcess(object):
+ # used by BridgeInterpreter
+ restarting = False
+ def __init__(self, interp):
+ self.interp = interp # pointer to idleIPython instance
+ self.sock = ipyProxy.get_fake_socket()
+
+ def __getattr__(self, name):
+ print('__getattr sub__', name)
+ def f(*args, **kwargs):
+ return None
+ return f
+
+ def putmessage(self, message):
+ seq, resq = message
+ how = resq[0]
+ if how in ("CALL", "QUEUE"):
+ cmd, method, code, d = resq[1]
+ if cmd == 'exec' and method =='runcode':
+ #self.interp.runcommand(code[0]) # for EventLoop.py
+ self.interp.runcommand(eloop) # eventloop.py support only
+
+ def remotecall(self, oid, methodname, args, kwargs):
+ # pacify IDLE's default autocomplete and calltip extensions
+ #print('remotecall', oid, methodname, args, kwargs)
+ if methodname == "get_the_completion_list":
+ # the event handles autocomplete in class IPyIDLE
+ return [], []
+ elif methodname == "get_the_calltip":
+ obj = args[0] # get tip for this object string
+ ipyProxy.get_the_calltip(obj)
+ return None
+
+ def close(self):
+ pass
+
+
+class BridgeInterpreter(ModifiedInterpreter):
+ # IDLE creates a new instance of ModifiedInterpreter on each restart.
+ # This relies on ipyProxy to do the actual interface to the IPython kernel
+
+ def __init__(self, tkconsole):
+ ModifiedInterpreter.__init__(self, tkconsole)
+ ipyProxy.interp = self
+ self.top = self.tkconsole.top
+ self.poll_id = None # poll_subprocess .after id
+ self.debugger = None
+
+ self.IMAGES = []
+
+ def set_before_prompt(self):
+ # mark set the "before_prompt" in the console window
+ backlines = sys.ps1.count('\n')
+ text = self.tkconsole.text
+ text.mark_set('before_prompt', 'iomark linestart')
+ if backlines:
+ text.mark_set('before_prompt', 'before_prompt -%i lines' % backlines)
+ text.mark_gravity("before_prompt", "left")
+
+ def beginexecuting(self):
+ debug('beginexecuting')
+ tkconsole = self.tkconsole
+ text = getattr(tkconsole, 'text', None)
+ if text is None:
+ # text can be None if the shell is closed after
+ # the execution has been queued, but before it gets
+ # executed. For example, press F5 and then quickly close the shell.
+ print('.text not found during beginexecuting')
+ return
+
+ a = text.get('iomark', 'end')
+
+ # remove excess whitespace due to newline and indent
+ i1 = a.rfind('\n',0, len(a)-1) + 1
+ text.delete('iomark+%ic' % i1, 'end-1c')
+
+ tkconsole.resetoutput()
+ text.mark_set('before_prompt', 'iomark linestart')
+ text.mark_gravity("before_prompt", "left")
+ tkconsole.executing = 1
+ text.see('insert')
+
+ def endexecuting(self):
+ debug('endexecuting')
+ tkconsole = self.tkconsole
+ text = getattr(tkconsole, 'text', None)
+ if text is None:
+ print('.text not found during endexecuting')
+ return
+ ipyProxy.regenerate_prompt_string()
+ tkconsole.resetoutput()
+ tkconsole.showprompt()
+
+ if not ipyProxy.executing:
+ tkconsole.executing = 0
+ tkconsole.canceled = 0
+
+
+ def _finalize_init(self):
+ # For an unknown reason (possibly a race condition), IPython
+ # sometimes fails to send an execute reply over the 0MQ channels
+ # for this no-op "None" command. For now, ignore it.
+ # FIXME: Determine why IPython fails to send execute reply sometimes.
+ return # skip the execution of this command.
+ def fcb():
+ self.tkconsole.executing = 0
+ ipyProxy.enqueue_execute('None',
+ silent=True,
+ exec_callback=None,
+ finish_callback=fcb)
+
+ def start_subprocess(self):
+ #print('start_subprocess')
+ welcome = ' Welcome to the IdleX IPython shell. '
+ B = ['-'*len(welcome),
+ welcome,
+ '-'*len(welcome),
+ ipyProxy.shell.banner]
+
+ PyShell.PyShell.COPYRIGHT = '\n'.join(B)
+ ipyProxy.regenerate_prompt_string()
+ self._poll_cancel(reschedule=True)
+ self.rpcclt = FakeSubProcess(self)
+ self.transfer_path(with_cwd=True)
+ self._finalize_init()
+ self.restarting = False
+ text = self.tkconsole.text
+ return self.rpcclt
+
+ def restart_subprocess(self, with_cwd=False):
+ if self.restarting:
+ return self.rpcclt
+ self.rpcclt.restarting = True
+ self.restarting = True
+
+ if ipyProxy.request_kernel_restart():
+ pass
+ else:
+ print('unable to restart kernel', file=sys.stderr)
+ self.restarting = False
+ self.rpcclt.restarting = False
+ return
+
+ console = self.tkconsole
+ was_executing = console.executing
+
+ if console.reading and ipyProxy.readline_canceled == True:
+ console.reading = False
+ console.top.quit() # IDLE workaround
+
+ # annotate restart in shell window and mark it
+ console.text.delete("iomark", "end-1c")
+ console.write('\n')
+ if was_executing:
+ console.write('\n')
+ halfbar = ((int(console.width) - 16) // 2) * '='
+ console.write(halfbar + ' RESTART ' + halfbar)
+ console.text.mark_set("restart", "end-1c")
+ console.text.mark_gravity("restart", "left")
+
+ ipyProxy.regenerate_prompt_string()
+ console.showprompt()
+ self.set_before_prompt()
+ self.transfer_path(with_cwd=with_cwd)
+ self._finalize_init()
+ self._poll_cancel(reschedule=True)
+ self.rpcclt = FakeSubProcess(self) # fake out IDLE
+ self.restarting = False
+ return self.rpcclt
+
+ def poll_subprocess(self):
+ tkconsole = self.tkconsole
+ km = ipyProxy.kernel_manager
+
+ if (not self.restarting and not ipyProxy.kernel_manager.is_alive
+ and not tkconsole.closing):
+ self.tkconsole.write("\n\n Kernel Died.\n Returing to IDLE's shell...\n")
+ self.tkconsole.text.event_generate('<>')
+ return
+
+ if not tkconsole.closing:
+ self._poll_cancel(reschedule=True)
+
+ while 1:
+ try:
+ m = MessageQueue.get_nowait()
+ except Empty:
+ break
+ ipyProxy.call_message_handlers(m)
+
+ ipyProxy.write_console_output()
+ ipyProxy._exec_if_idle()
+
+
+ def _poll_cancel(self, reschedule=False):
+ """ For managing the poll_subprocess timer """
+ if self.poll_id:
+ self.top.after_cancel(self.poll_id)
+ if reschedule:
+ self.poll_id = self.top.after(25, self.poll_subprocess)
+
+ def interrupt_subprocess(self):
+ ipyProxy.interrupt()
+ c = self.tkconsole
+
+ def kill_subprocess(self):
+ ipyProxy.restore_IDLE_history()
+ self._poll_cancel()
+ self.poll_id = None
+ #self.compile = None
+
+ def runsource(self, source):
+ # Used by PyShell for the interactive prompt
+ # Returns True if the prompt can accept more lines.
+ #print('runsource %r' % source)
+ shell = ipyProxy.shell
+ r = shell.input_splitter.source_raw_reset()
+ shell.input_splitter.push(source + '\n')
+ more = shell.input_splitter.push_accepts_more()
+ if not more:
+ self.runcode(source, store_history=True, do_exec=True)
+ return more
+
+ def runcommand(self, source):
+ # Used to run commands silently in IDLE,
+ # like path initialization, etc...
+ source = source.rstrip() # Python 2.6 compatibility
+ exec_callback = finish_callback = None
+ ipyProxy.enqueue_execute(source, silent=True,
+ exec_callback=exec_callback,
+ finish_callback=finish_callback)
+
+ def runcode(self, source, store_history=False, do_exec=True):
+ # Called from runsource usually. ScriptBinding, SubCode, and RunSelection
+ # call this as well. Used to run code objects originally.
+ #print('runcode: %r' % source)
+ if isinstance(source, types.CodeType):
+ store_history = False
+ f = source.co_filename
+ if os.path.isfile(f):
+ if sys.version < '3':
+ # Python3 really did fix the unicode problem of Python2.
+ try:
+ f = unicode(f.decode('utf8'))
+ except Exception as err:
+ print('Unable to decode filename: %r' % f)
+ pass
+ # ScriptBinding's Run Module should invoke this code path...
+ #source = "%%run %s" % f # Only works with IPython 0.13
+ # Using get_ipython() is compatible with 0.12 and 0.13
+ f = f.replace('"', '\\"')
+ f = f.replace("'", "\\'")
+
+ runmagic = '%%run "%s"' % f
+ source = """if 1:
+ __iP__=get_ipython();
+ __iP__.run_cell(%r); del(__iP__)""" % runmagic
+ # run_line_magic only available in 0.13
+ else:
+ # regular code object - RunSelection.py should invoke this code path...
+ source = """if 1: # Running a Code Object from IPyIDLE
+ import marshal as __marshal
+ exec(__marshal.loads(%r)); del __marshal""" % marshal.dumps(source)
+
+ elif isinstance(source, (str, unicode)):
+ if not store_history:
+ # SubCode should invoke this codepath...
+ source = """__iP__=get_ipython();__iP__.run_cell(%r); del(__iP__)""" % source
+ else:
+ # A string of the command from the interactive shell invokes this path...
+ pass
+
+ def do_exec_cb():
+ self.beginexecuting()
+
+ if do_exec:
+ exec_callback = do_exec_cb
+ finish_callback = self.endexecuting
+ else:
+ exec_callback = None
+ def fcb():
+ self.tkconsole.executing = 0
+ finish_callback = fcb
+
+ self.tkconsole.executing = 1 # console is executing
+ ipyProxy.enqueue_execute(source, silent=not store_history,
+ exec_callback=exec_callback,
+ finish_callback=finish_callback)
+
+ if not store_history:
+ ipyProxy.enqueue_execute("""if 1:
+ try: # if pylab inline
+ from IPython.zmq.pylab.backend_inline import flush_figures as __flush
+ __flush(); del __flush
+ except: pass""",
+ silent=True,
+ exec_callback=None,
+ finish_callback=None)
+
+ def getdebugger(self):
+ pass
+
+ def transfer_path(self, with_cwd=False):
+ if with_cwd: # Issue 13506
+ path = [''] # include Current Working Directory
+ path.extend(sys.path)
+ else:
+ path = sys.path
+
+ self.runcommand("""if 1:
+ import sys as _sys
+ _sys.path = %r
+ del _sys
+ \n""" % (path,))
+
+
+class IPyIDLEShell(TerminalInteractiveShell):
+ """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
+ _executing = False
+
+ # The only reason this class exists is to access its input_splitter
+ # and its prompt generating routines...
+
+ def __init__(self, *args, **kwargs):
+ self.km = kwargs.pop('kernel_manager')
+ self.io = kwargs.pop('io')
+ self.session_id = self.km.session.session
+ super(IPyIDLEShell, self).__init__(*args, **kwargs)
+
+
+ def init_io(self):
+ stdin, stdout, stderr = self.io
+ io = ipython_io
+ io.stdout = stdout
+ io.stdin = stdin
+ io.stderr = stderr
+
+ def init_completer(self):
+ from IPython.core.completerlib import (module_completer,
+ magic_run_completer, cd_completer)
+
+ self.set_hook('complete_command', module_completer, str_key = 'import')
+ self.set_hook('complete_command', module_completer, str_key = 'from')
+ self.set_hook('complete_command', magic_run_completer, str_key = '%run')
+ self.set_hook('complete_command', cd_completer, str_key = '%cd')
+
+
+class Pager(tk.Toplevel): #textView.py modified
+ """A simple text viewer dialog for IDLE
+
+ """
+ def __init__(self, parent, title, raw_text):
+
+ tk.Toplevel.__init__(self, parent)
+ self.configure(borderwidth=5)
+
+ self.geometry("=%dx%d+%d+%d" % (
+ parent.winfo_width(),
+ max([parent.winfo_height() - 40, 200]),
+ parent.winfo_rootx() + 10,
+ parent.winfo_rooty() + 10))
+
+ self.bg = '#ffffff'
+ self.fg = '#000000'
+
+ self.CreateWidgets()
+ self.title(title)
+ self.protocol("WM_DELETE_WINDOW", self.Ok)
+ self.parent = parent
+ self.text.focus_set()
+ self.bind('',self.Ok) #dismiss dialog
+ self.bind('',self.Ok) #dismiss dialog
+ self.per = Percolator(self.text)
+ self.ansi = AnsiColorDelegator()
+ self.doc = PyShell.ModifiedUndoDelegator()
+
+ self.per.insertfilter(self.ansi)
+ self.per.insertfilter(self.doc)
+
+ for code in ansi_colors:
+ self.text.tag_config(code,
+ foreground=ansi_colors[code])
+
+ self.text.insert(0.0, raw_text)
+ self.apply_bindings()
+
+ # get SearchBar working with this
+ self.top = self
+ self.sb = SearchBar(self)
+
+ self.text.mark_set('iomark', 'end')
+ self.text.mark_set('before_prompt', 'end')
+ self.text.mark_set('insert', '1.0')
+
+
+
+ def search(self):
+ self.text.event_generate("<>")
+
+
+ def CreateWidgets(self):
+
+ frameText = tk.Frame(self, relief=tk.SUNKEN, height=700)
+ frameButtons = tk.Frame(self)
+ self.buttonOk = tk.Button(frameButtons, text='Close',
+ command=self.Ok, takefocus=tk.FALSE)
+ self.buttonSearch = tk.Button(frameButtons, text='Search /',
+ command=self.search, takefocus=tk.FALSE)
+
+ self.scrollbarView = tk.Scrollbar(frameText, orient=tk.VERTICAL,
+ takefocus=tk.FALSE, highlightthickness=0)
+ width = idleConf.GetOption('main','EditorWindow','width')
+ height = idleConf.GetOption('main', 'EditorWindow', 'height')
+
+ text_options = {
+ 'name': 'text',
+ 'padx': 5,
+ 'wrap':tk.WORD,
+ 'highlightthickness':0,
+ 'fg':self.fg,
+ 'bg':self.bg,
+ 'width': width,
+ 'height': height}
+
+
+ self.text = text = MultiCallCreator(tk.Text)(frameText, **text_options)
+ fontWeight='normal'
+ if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
+ fontWeight='bold'
+ self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
+ idleConf.GetOption('main','EditorWindow','font-size'),
+ fontWeight))
+
+
+ self.scrollbarView.config(command=self.text.yview)
+ self.text.config(yscrollcommand=self.scrollbarView.set)
+ self.buttonSearch.pack(side=tk.LEFT)
+ self.buttonOk.pack(side=tk.LEFT)
+ self.scrollbarView.pack(side=tk.RIGHT,fill=tk.Y)
+ self.text.pack(side=tk.LEFT,expand=tk.TRUE,fill=tk.BOTH)
+ frameButtons.pack(side=tk.BOTTOM,fill=tk.X)
+ frameText.pack(side=tk.TOP,expand=tk.TRUE,fill=tk.BOTH)
+
+ # editwin mock (so SearchBar works)
+ self.status_bar = frameButtons
+
+ def Ok(self, event=None):
+ self.destroy()
+
+ def apply_bindings(self, keydefs=None):
+ if keydefs is None:
+ keydefs = Bindings.default_keydefs
+ text = self.text
+ text.keydefs = keydefs
+
+ invalid = []
+ for event, keylist in keydefs.items():
+ if keylist:
+ try:
+ text.event_add(event, *keylist)
+ except tk.TclError as err:
+ invalid.append((event, keylist))
+ text.event_add('<>', '/') # VIM key binding for search
+
+#-------------------------------------
+# The IDLE Extension
+#-------------------------------------
+
+EXT_NAME = 'IPyIDLE'
+
+def get_cfg(cfg, type="bool", default=True):
+ return idleConf.GetOption("extensions", EXT_NAME,
+ cfg, type=type, default=default)
+
+def set_cfg(cfg, b):
+ return idleConf.SetOption("extensions", EXT_NAME,
+ cfg,'%s' % b)
+
+
+
+
+COMPLETE_CHARS = string.ascii_letters + string.digits + "_" + '.' + '/'
+
+class OverBinding(object):
+ """ class for replacing bindings of MultiCall-wrapped Widget """
+ # Tkinter has no facility to recover the original Python function
+ # bound to a Tk event. This is fundamentally a broken design.
+ # Now, add on top of that broken design the MultiCall handler...
+
+ def __init__(self, tkobj, basewidget=None):
+ self.tkobj = tkobj
+ if basewidget is None:
+ raise Exception('Must specifiy a base widget class')
+ self.basewidget = basewidget
+ self.old = {}
+
+
+ _tk_callback = r"""(?<=if {"\[)[\d]*[\S]*"""
+ def _get_cb(self, cb):
+ """ Parse out the TCL proc """
+ # Tkinter .bind returns the TCL command that calls the internal CallWrapper
+ # instance in the tkinter code. It needs to be parsed
+ # to remove the actual callback...
+
+ # For example, .bind may return
+ # if {"[139690432888560callback %# %b %f %h %k %s %t %w %x %y %A %E %K %N %W %T %X %Y %D]" == "break"} break
+ # when it ought to return 139690432888560callback
+
+ m = re.search(self._tk_callback, cb)
+ if m:
+ cb = m.group()
+ return cb
+
+
+ def bind(self, event, newbind):
+ cb = self.tkobj.bind(event)
+ self.old[event] = cb
+ self.tkobj.bind(event, newbind)
+
+ def call_original(self, event, tkev):
+ """ Pass the event to the originally bound function. """
+
+ tkobj = self.tkobj
+ if event in self.old:
+ if event.startswith('<<'):
+ e = self._get_cb(self.old[event])
+ # Assuming that the virtual event takes no arguments...
+ tkobj.tk.call(e)
+ else:
+ func = tkobj.bind(event)
+ tkobj.bind(event, self.old[event])
+ tkobj.event_generate(event)
+ tkobj.bind(event, func)
+ else:
+ tkobj.event_generate(event)
+
+ def restore(self):
+ # need to use basewidget because
+ # Tkinter uses old-style classes in 2.x series,
+ # so "super" doesn't work...
+ # The purpose is to bypass MultiCall
+ tkobj = self.tkobj
+ old = self.old
+ for virtual in old:
+ cb = old[virtual]
+ if virtual.startswith('<<'):
+ cb = self._get_cb(cb)
+ #print('Restoring: %r : %r' % (virtual, cb))
+ self.basewidget.bind(tkobj, virtual, cb)
+
+ipyList = [] # list of IPyIDLE instances
+
+class IPyIDLE(object):
+ menudefs = [('shell', [('!IPython Shell', '<>'),
+ ('!Allow IPython Kernel Restart', '<>'),
+ ('Adjust Kernel Arguments', '<>'),
+ ])]
+
+ def check_ipython(func):
+ def f(self, *arg, **kw):
+ if HAS_IPYTHON:
+ return func(self, *arg, **kw)
+ else:
+ t = self.editwin.top
+ tkMessageBox.showinfo('IPython Missing',
+ 'IPython not available',
+ parent=t)
+ self.editwin.setvar('<>', 0)
+ self.editwin.setvar('<>', 0)
+ return "break"
+ return f
+
+
+ def __init__(self, editwin):
+ ipyList.append(self)
+ # If IPython gets installed into the shell, then all open editors need
+ # to be modified as well, hence the saving of IPyIDLE instances.
+
+ self.text_binding = OverBinding(editwin.text, tk.Text)
+ self._save_ps1 = None
+ self._save_ps2 = None
+ self._save_shell_title = None
+ self._save_copyright = None
+ self._save_squeezer = None
+
+ self.first_load = True # flag if shell is first starting and ipython needs to be loaded
+ self.editwin = editwin
+ self.console = editwin
+ self.text = editwin.text
+
+ if isinstance(editwin, PyShell.PyShell):
+ self.is_shell = True
+ self.text.bind('<>', self.toggle_IPython)
+ self.text.bind('<>', self.toggle_restart)
+ self.text.bind('<>', self.adjust_command_line)
+ else:
+ self.is_shell = False
+
+ if not HAS_IPYTHON:
+ return
+
+ self.last_codeline = ''
+
+ # each editorwindow object gets an autocomplete window
+ self.autocompletewindow = AutoCompleteWindow.AutoCompleteWindow(self.text)
+ editwin.text.bind('', self.focus_event, '+')
+
+
+ enable = get_cfg('ipython', type="bool", default=True)
+ if idlexMain.ipython_argv or idlexMain.ipython_connection_file:
+ enable = True
+
+ self.allow_restart = get_cfg('allow_restart', type="bool", default=True)
+ if idlexMain.ipython_argv:
+ enable = True
+ self.use_ipython = enable
+
+ if self.is_shell:
+ if self.use_ipython:
+ self.set_ipython_state(True)
+ else:
+ self.first_load = False # not loading ipython on pyshell init
+ self.set_restart_state(self.allow_restart)
+ else:
+ if self.use_ipython:
+ for i in ipyList:
+ if i.is_shell:
+ self.install()
+ break
+
+ def close(self):
+ if self.is_shell:
+ PyShell.ModifiedInterpreter = ModifiedInterpreter
+
+ # restore IDLE's default event handlers
+ # for the editors if shell is close
+ self._uninstall_editors()
+
+ ipyProxy.cleanup_connection_file()
+ ipyProxy.stop_km()
+
+ self.uninstall()
+
+ if self in ipyList:
+ ipyList.remove(self)
+ else:
+ print('IPY:', self)
+
+ def focus_event(self, event=None):
+ ipyProxy.active = self
+
+ @check_ipython
+ def toggle_IPython(self, ev=None):
+ self.set_ipython_state(not self.use_ipython)
+
+ def set_ipython_state(self, b=False):
+ self.use_ipython = b
+ set_cfg('ipython', '%s' % b)
+ if b:
+ self.install()
+ else:
+ self.uninstall()
+
+ if self.is_shell:
+ editwin = self.editwin
+ try:
+ editwin.setvar('<>', b)
+ except Exception:
+ print('Exception should not occur', file=sys.stderr)
+ traceback.print_exc()
+
+ @check_ipython
+ def toggle_restart(self, ev=None):
+ self.set_restart_state(not self.allow_restart)
+
+ def set_restart_state(self, b=False):
+ self.allow_restart = b
+ set_cfg('allow_restart', '%s' % b)
+ self.editwin.setvar('<>', b)
+ ipyProxy.allow_restart = b
+
+ @check_ipython
+ def adjust_command_line(self, event=None):
+ current_argv = idlexMain.ipython_argv
+ cf = idlexMain.ipython_connection_file
+ args = ' '.join(current_argv)
+ if cf:
+ args += ' --existing %s' % cf
+
+ while True:
+ new_args = tkSimpleDialog.askstring('IPython Kernel Arguments',
+ 'Adjust the kernel start arguments:',
+ parent=self.editwin.text,
+ initialvalue=args
+ )
+ if new_args is None:
+ idlexMain.ipython_argv = current_argv
+ break
+ else:
+ new_argv = new_args.split()
+ ipy_argv = idlexMain.handle_ipython_argv(new_argv)
+ if new_argv:
+ # there should not be anything left in this:
+ tkMessageBox.showwarning('IDLE IPython',
+ ('IDLE IPython does not support:\n' +
+ ' '.join(new_argv)),
+ parent=self.editwin.top)
+ args = new_args # allow for repeat
+ else:
+ if current_argv != ipy_argv:
+ ipyProxy.argv_changed = True
+ else:
+ ipyProxy.argv_changed = False
+ break
+
+ if ipyProxy.argv_changed:
+ restart = tkMessageBox.askyesno('IDLE IPython',
+ ('Changes to the kernel will be applied on (re)start of kernel.\n\n' +
+ 'Do you want to start a new kernel?'),
+ default=tkMessageBox.NO,
+ parent=self.editwin.top)
+ print('restart: %r %s' % (restart, type(restart)))
+ if restart == True:
+ if self.use_ipython:
+ self.editwin.interp.restart_subprocess()
+ else:
+ self.set_ipython_state(True)
+
+
+ def _install_editors(self):
+ for i in ipyList:
+ if not i.is_shell:
+ i.install()
+
+ def _uninstall_editors(self):
+ for i in ipyList:
+ if not i.is_shell:
+ i.uninstall()
+
+ def switch_message(self):
+ if self.first_load:
+ return
+ hb = '=' * 10
+ msg = '\n %(halfbar)s SWITCHING SHELL %(halfbar)s \n\n' % {'halfbar':hb}
+ self.text.insert('end', msg)
+ self.text.mark_set('iomark', 'end')
+
+
+ def reset_shell(self):
+ #print('reset_shell')
+ e = self.editwin
+ if e.reading:
+ e.top.quit()
+ e.reading = False
+ if e.interp.rpcclt:
+ e.interp.kill_subprocess()
+
+ e.executing = False
+
+
+ #------------------------------
+ # UnInstall this instance
+ #-----------------------------
+
+ def uninstall(self): # uninstall ipython and return to IDLE
+ e = self.editwin
+
+ self.text_binding.restore()
+
+ s = self.editwin.extensions.get('CallTips', None)
+ if s:
+ try:
+ self.text.bind('<>', s.refresh_calltip_event)
+ except Exception as err:
+ print('Error restoring calltip binding: %r' % err)
+ pass
+
+ # Restore values
+ if self.is_shell:
+ closing = e.closing
+ if not closing: # switching
+ self.reset_shell()
+ self.switch_message()
+
+ ipyProxy.stop_km()
+ ipyProxy.restore_IDLE_history()
+ self._uninstall_editors()
+ ipyProxy.active_pyshell = None
+ ipyProxy.calltipwindow = None
+
+ if self._save_shell_title:
+ PyShell.PyShell.shell_title = self._save_shell_title
+ if self._save_copyright:
+ PyShell.PyShell.COPYRIGHT = self._save_copyright
+ if self._save_ps1 is not None:
+ sys.ps1 = self._save_ps1
+ if self._save_ps2 is not None:
+ sys.ps2 = self._save_ps2
+ PyShell.ModifiedInterpreter = ModifiedInterpreter
+
+ s = self.editwin.extensions.get('Squeezer', None)
+ if s and self._save_squeezer:
+ s._MAX_NUM_OF_LINES = self._save_squeezer
+
+ if not closing: # then switching
+ e.interp._poll_cancel()
+ e.interp = ModifiedInterpreter(e)
+ e.console.write('\n')
+ e.per.removefilter(self.ansi)
+ e.history = self._save_history
+ e.executing = False
+ e.begin()
+
+
+ #---------------------------
+ # Install into this instance
+ #---------------------------
+
+ def install(self):
+
+ e = self.editwin
+
+ # Save existing values
+ if self.is_shell:
+ self.text.mark_set('before_prompt', '1.0')
+ self.reset_shell()
+ ipyProxy._reset()
+ self.switch_message()
+ ipyProxy.active_pyshell = self.editwin
+ ipyProxy.active = self
+ ipyProxy.start_km()
+ e.executing = 0
+ self._save_shell_title = PyShell.PyShell.shell_title
+ self._save_copyright = PyShell.PyShell.COPYRIGHT
+
+ if self._save_ps1 is None:
+ self._save_ps1 = getattr(sys, 'ps1', None)
+ self._save_ps2 = getattr(sys, 'ps2', None)
+
+
+ sys.ps1 = '>>> ' # temporary
+ sys.ps2 = '... ' # temporary
+
+ self._save_undo = e.undo
+
+ e.interp = BridgeInterpreter(e) # replace the existing interpreter
+ PyShell.PyShell.shell_title = "IPython Shell"
+ PyShell.ModifiedInterpreter = BridgeInterpreter
+
+ def delay_install():
+ # the purpose of delay_install is if "install_shell" is called
+ # as part of the init routine for pyshell
+
+ if self.is_shell:
+ e.closing = False
+ self._install_editors()
+ self._save_history = e.history
+
+ # Add Ansi Color Delegator
+ self.ansi = AnsiColorDelegator()
+ e.per.insertfilter(self.ansi)
+ self.config_ansi_colors()
+
+ s = self.editwin.extensions.get('Squeezer', None)
+ if s:
+ self._save_squeezer = s._MAX_NUM_OF_LINES
+ s._MAX_NUM_OF_LINES = 10000
+
+ e.console.write('\n')
+
+
+ if not self.first_load:
+ ipyProxy.switching = True # flag to avoid double launching kernels
+ e.begin()
+ ipyProxy.switching = False # flag to avoid double launching kernels
+ e.executing = False
+ self.first_load = False # no longer the case that the shell opened and needs ipython
+ e.text.mark_set('before_prompt', 'iomark linestart')
+ e.text.mark_gravity("before_prompt", "left")
+
+ #self.text.configure(wrap="word") # Uncomment to enable word-wrapping like QtConsole
+ self.text_binding.bind('', self.tab_event)
+ if self.is_shell:
+ self.text_binding.bind('', self.control_enter_callback)
+ self.text_binding.bind('<>', self.clear_window)
+ self.text_binding.bind('<>', self.toggle_debugger)
+ self.text_binding.bind('<>', self.refresh_calltip_event)
+
+ self.text.after(1, delay_install)
+
+ def config_ansi_colors(self):
+ """ Configure the ansi color tags for the .text widget """
+ text = self.text
+ for code in ansi_colors:
+ text.tag_config(code,
+ foreground=ansi_colors[code])
+
+ def refresh_calltip_event(self, event=None):
+ # this is a hack to make calltip hiding work with
+ # CallTips.py refresh_calltip_event
+ ctw = ipyProxy.calltipwindow
+ if ctw:
+ ctw.hidetip()
+ self.text.event_generate('<>')
+
+ def tab_event(self, event=None):
+ # AutoCompleteWindow event
+ text = self.text
+
+ lineno, col = sp(text.index('insert')) # where the cursor is
+
+ # Determine the start of the code for context
+ if self.is_shell:
+ IOMARK = 'iomark'
+ else:
+ IOMARK = 'insert linestart'
+
+ ioline, iocol = sp(text.index(IOMARK)) # where the start of code is
+ if lineno > ioline:
+ scol = 0
+ codecol = col
+ else:
+ scol = iocol
+ codecol = col - iocol
+
+ buf = text.get(IOMARK, 'insert') # full cell for context
+ codeline = text.get(jn(lineno, scol), 'insert') # the actual line of code
+
+
+ # Assume that we will do a completion, unless it is not needed
+ do_complete = True
+
+ if not codeline.strip():
+ if self.is_shell:
+ if text.compare('insert', '>', 'iomark'):
+ do_complete = False # So TAB works in multiline input
+ else:
+ # To mimic the behavior of the IPython shell,
+ # do the completion when the prompt is blank
+ pass
+ else:
+ do_complete = False # blank code line, not needed
+
+ elif not self.is_shell:
+ if codeline[-1] not in COMPLETE_CHARS:
+ do_complete = False
+
+ # Check if the window is already open
+ if self.autocompletewindow and self.autocompletewindow.is_active():
+ if codeline == self.last_codeline:
+ # send twice to select it - logic of IDLE's ACW
+ self.autocompletewindow.keypress_event(event)
+ self.autocompletewindow.keypress_event(event)
+ return "break"
+
+ if do_complete:
+ msg_id = ipyProxy.kernel_manager.shell_channel.complete(
+ '', # text
+ codeline, # line
+ codecol, # cursor_pos
+ buf, # block
+ )
+ self.last_codeline = codeline
+ else:
+ # call original binding
+ self.text_binding.call_original('', event)
+ return "break"
+
+ def toggle_debugger(self, event=None):
+ tkMessageBox.showinfo('IDLE IPython',
+ 'IDLE Debugger is not compatible with IPython (yet). Try using %pdb',
+ parent=self.editwin.top)
+ self.editwin.setvar('<>', 0)
+ return "break"
+
+ def control_enter_callback(self, event=None):
+ # only in shell
+ e = self.editwin
+ if e.executing or e.reading:
+ self.text_binding.call_original('', event)
+ return "break"
+
+ if self.text.compare('insert', '>=', 'iomark'):
+ e.newline_and_indent_event(event)
+ return "break"
+
+ self.text_binding.call_original('', event)
+ return "break"
+
+ def clear_window(self, event):
+ # forget all images
+ ipyProxy.clear_shell_images()
+ self.text_binding.call_original('<>', event)
diff --git a/idlex-1.11.2/idlexlib/extensions/LineNumbers.py b/idlex-1.11.2/idlexlib/extensions/LineNumbers.py
new file mode 100644
index 0000000..e0dcc82
--- /dev/null
+++ b/idlex-1.11.2/idlexlib/extensions/LineNumbers.py
@@ -0,0 +1,353 @@
+# IDLEX EXTENSION
+## """
+## 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.
+##
+##
+##
+## LineNumbers Extension
+##
+## Provides line numbers to the left of the source code.
+##
+## The width of the line numbers adapts. Limit of 99,999 lines (for proper display).
+##
+## """
+
+config_extension_def = """
+
+[LineNumbers]
+enable=1
+enable_shell=0
+visible=True
+
+[LineNumbers_cfgBindings]
+linenumbers-show=
+
+"""
+
+import sys
+if sys.version < '3':
+ from Tkinter import END, Text, LEFT, Y, NONE, RIGHT, NORMAL, DISABLED, Label, TOP, Frame, X
+else:
+ from tkinter import END, Text, LEFT, Y, NONE, RIGHT, NORMAL, DISABLED, Label, TOP, Frame, X
+
+from idlelib.configHandler import idleConf
+from idlelib.Delegator import Delegator
+from idlelib.Percolator import Percolator
+
+
+FONTUPDATEINTERVAL = 1000 # milliseconds
+
+_AFTER_UNDO = True # Flag to have the LineNumberDelegator inserted after the undo delegator
+
+jn = lambda x,y: '%i.%i' % (x,y) # join integers to text coordinates
+sp = lambda x: map(int, x.split('.')) # convert tkinter Text coordinate to a line and column tuple
+
+
+def dbprint(func): # A decorator for debugging
+ def f(*args, **kwargs):
+ print(func, args, kwargs)
+ return func(*args, **kwargs)
+ return f
+
+class LineNumbers(object):
+
+ menudefs = [('options', [('!Show Line Numbers', '<>')])]
+
+ def __init__(self, editwin):
+ self.editwin = editwin
+ self.text = self.editwin.text
+ self.textfont = None
+ self.width = 2
+ self.after_id = None
+
+ self.create_linenumbers()
+
+ e = idleConf.GetOption("extensions", "LineNumbers",
+ "visible", type="bool", default=True)
+ self.set_visible(e)
+
+ self.code_context_fix()
+
+ def close(self):
+ if self.after_id:
+ self.text.after_cancel(self.after_id)
+ self.visible = False
+
+ def adjust_font(self):
+ try:
+ # taken from CodeContext.py
+ newtextfont = self.editwin.text["font"]
+ if self.textln and newtextfont != self.textfont:
+ self.textfont = newtextfont
+ self.textln["font"] = self.textfont
+ if self._cc_text:
+ self._cc_text["font"] = self.textfont
+ self.update_numbers()
+ except Exception as err:
+ import traceback; traceback.print_exc()
+
+ def font_timer(self):
+ if not self.visible:
+ return
+
+ self.adjust_font()
+
+ if self.after_id:
+ self.text.after_cancel(self.after_id)
+ self.after_id = self.text.after(FONTUPDATEINTERVAL, self.font_timer)
+ if not _AFTER_UNDO:
+ self.update_numbers() # fixes a bug due to this percolator being ahead of undo percolator.
+
+ def set_visible(self, b=True):
+ self.visible = b
+
+ if self.visible:
+ self.text.after(1, self.font_timer) # avoid a start-up bug
+ self.show()
+ # use .after to avoid a start-up error caused by update_idletasks in update_numbers
+ self.text.after(1, self.update_numbers)
+ else:
+ self.hide()
+
+ idleConf.SetOption("extensions", "LineNumbers",
+ "visible", '%s' % self.visible)
+
+ self.editwin.setvar("<>", self.visible)
+
+ def linenumbers_show_event(self, ev=None):
+ self.set_visible(not self.visible)
+ self._code_context_toggle()
+
+ def create_linenumbers(self):
+ """ Create the widget for displaying line numbers. """
+ editwin = self.editwin
+ text = self.text
+ text_frame = editwin.text_frame
+ self.textln = textln = Text(text_frame, width=self.width,
+ height=1, wrap=NONE)
+
+ # adjust font
+ textln.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
+ idleConf.GetOption('main', 'EditorWindow', 'font-size')))
+
+ textln.bind("", self.focus_in_event)
+ textln.bind('', self.button_ignore)
+ textln.bind('', self.button_ignore)
+ textln.bind('', self.button_ignore)
+ textln.bind('', self.button_ignore)
+ textln.bind('', self.button_ignore)
+ textln.bind('', self.button_ignore)
+
+ textln.bind("", self.button4)
+ textln.bind("", self.button5)
+
+ textln.tag_config('LINE', justify=RIGHT)
+ textln.insert(END, '1')
+ textln.tag_add('LINE', '1.0', END)
+
+ # start the line numbers
+ self.per = per = Percolator(textln)
+ self.line_delegator = LineDelegator()
+ per.insertfilter(self.line_delegator)
+ textln._insert = self.line_delegator.delegate.insert
+ textln._delete = self.line_delegator.delegate.delete
+
+
+ lines = LineNumberDelegator(self)
+ if _AFTER_UNDO:
+ # Percolator.py's .insertfilter should have an "after=" argument
+ lines.setdelegate(editwin.undo.delegate)
+ editwin.undo.setdelegate(lines)
+ else:
+ editwin.per.insertfilter(lines)
+
+ editwin.vbar['command'] = self.vbar_split
+ editwin.text['yscrollcommand'] = self.yscroll_split
+
+ def button4(self, ev=None):
+ self.text.event_generate("")
+ return "break"
+
+ def button5(self, ev=None):
+ self.text.event_generate("")
+ return "break"
+
+ def button_ignore(self, ev=None):
+ return "break"
+
+ def show(self):
+ self.textln.pack(side=LEFT, fill=Y, before=self.editwin.text)
+
+ def hide(self):
+ self.textln.pack_forget()
+
+ def focus_in_event(self, event=None):
+ self.editwin.text.focus_set()
+ self.textln.tag_remove('sel', '1.0', 'end')
+ #self.editwin.text.event_generate("<>")
+
+ def generate_goto_event(self, event=None):
+ self.editwin.text.event_generate("<>")
+ return "break"
+
+ def vbar_split(self, *args, **kwargs):
+ """ split scrollbar commands to the editor text widget and the line number widget """
+ self.textln.yview(*args, **kwargs)
+ self.text.yview(*args, **kwargs)
+
+ def yscroll_split(self, *args, **kwargs):
+ """ send yview commands to both the scroll bar and line number listing """
+ #import traceback; traceback.print_stack()
+ self.editwin.vbar.set(*args)
+ self.textln.yview_moveto(args[0])
+
+ def update_numbers(self, add=None, remove=None):
+ if not self.visible: return
+
+ textln = self.textln
+ text = self.editwin.text
+
+
+ endline1, col1 = sp(text.index(END))
+ endline2, col2 = sp(textln.index(END))
+
+
+ if endline1 < endline2:
+ # delete numbers
+ textln._delete('%i.0' % endline1, END)
+ elif endline1 > endline2:
+ # add numbers
+ q = range(endline2, endline1)
+ r = map(lambda x: '%i' % x, q)
+ s = '\n' + '\n'.join(r)
+ textln._insert(END, s)
+ textln.tag_add('LINE', '1.0', END)
+
+ # adjust width of textln, if needed. (counts from 1, not zero)
+ if endline1 <= 100:
+ width = 2
+ elif endline1 <= 1000:
+ width = 3
+ elif endline1 <= 10000:
+ width = 4
+ else:
+ width = 5 # more than 9999 lines in IDLE? Really?
+
+ # XXX: If your code requires width>5, i.e > 100,000 lines of code,
+ # you probably should not be using IDLE.
+
+ if width > self.width: # 2011-12-18 - only grow, not shrink
+ self.width = width
+ textln.configure(width=width)
+ if self._cc_text: # adjust CC width
+ self._cc_text.configure(width=width)
+
+ self.textln.update_idletasks()
+ a = self.text.yview()
+ self.textln.yview_moveto(a[0])
+
+ def code_context_fix(self):
+ self._cc_text = None
+ self._cc_frame = None
+ def f():
+ self.text.bind('<>', self._code_context_toggle, '+')
+ self._code_context_toggle()
+ self.text.after(10, f)
+
+ def _code_context_toggle(self, event=None):
+ cc = self.editwin.extensions.get('CodeContext', None)
+ if cc is None:
+ return
+
+ if not self.visible:
+ if self._cc_frame:
+ L = cc.label
+ L.pack_forget()
+ self._cc_frame.destroy()
+ L.pack(side=TOP, fill=X, expand=False,
+ before=self.editwin.text_frame)
+ return
+
+
+ editwin = self.editwin
+ text = self.text
+ text_frame = editwin.text_frame
+
+ # repack the Label in a frame
+ if cc.label:
+ cc.label.pack_forget()
+ F = Frame(self.editwin.top)
+ F.lower() # fix Z-order
+ t = Text(F, width=self.width, height=1,
+ takefocus=0)
+ t.bind("", lambda x: self.text.focus())
+ t["font"] = self.textln.cget('font')
+ t.pack(side=LEFT, fill=Y)
+ cc.label.pack(in_=F, fill=X, expand=False)
+
+ F.pack(side=TOP, before=text_frame, fill=X, expand=False)
+ self._cc_frame = F
+ self._cc_text = t
+ else:
+ if self._cc_frame:
+ self._cc_frame.destroy()
+ self._cc_frame = None
+ self._cc_text = None
+
+
+
+
+class LineNumberDelegator(Delegator):
+ # for editwin.text
+ def __init__(self, line_number_instance):
+ Delegator.__init__(self)
+ self.ext = line_number_instance
+
+ def insert(self, index, chars, tags=None):
+ self.delegate.insert(index, chars, tags)
+ if '\n' in chars:
+ self.ext.update_numbers()#add=chars.count('\n'))
+ def delete(self, index1, index2=None):
+ t = self.get(index1, index2)
+ self.delegate.delete(index1, index2)
+ if '\n' in t:
+ self.ext.update_numbers()#remove=t.count('\n'))
+
+
+class LineDelegator(Delegator):
+ # for textln
+ def insert(self, *args, **kargs):
+ pass
+
+ def delete(self, *args, **kargs):
+ pass
diff --git a/idlex-1.11.2/idlexlib/extensions/MultiLineRun.py b/idlex-1.11.2/idlexlib/extensions/MultiLineRun.py
new file mode 100644
index 0000000..4fc5814
--- /dev/null
+++ b/idlex-1.11.2/idlexlib/extensions/MultiLineRun.py
@@ -0,0 +1,170 @@
+# IDLEX EXTENSION
+## """
+## Copyright(C) 2012 The Board of Trustees of the University of Illinois.
+## All rights reserved.
+## Developed by: Roger D. Serwy
+## University of Illinois
+## License: See LICENSE.txt
+## """
+
+
+
+##
+## This extension allows for pasting of multiple lines of code into the shell
+## for execution. This addresses http://bugs.python.org/issue3559
+##
+
+
+from __future__ import print_function
+
+config_extension_def = """
+[MultiLineRun]
+enable=1
+enable_editor=0
+enable_shell=1
+"""
+
+from idlelib.configHandler import idleConf
+from idlelib.Delegator import Delegator
+import time
+import re
+import sys
+import traceback
+
+class MultiLineDelegator(Delegator):
+ def __init__(self, callback):
+ Delegator.__init__(self)
+ self.callback = callback
+ self.paste = False
+
+ def insert(self, index, chars, tags=None):
+ do_insert = True
+ if self.paste:
+ self.paste = False
+ try:
+ do_insert = self.callback(chars)
+ except Exception as err:
+ # Must catch exception else IDLE closes
+ print(' MultiLineRun Internal Error', file=sys.stderr)
+ traceback.print_exc()
+
+ if do_insert:
+ self.delegate.insert(index, chars, tags)
+
+ def delete(self, index1, index2=None):
+ self.delegate.delete(index1, index2)
+
+
+class MultiLineRun:
+
+ # eol code from IOBinding.py
+ eol = r"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
+ eol_re = re.compile(eol)
+
+ def __init__(self, editwin):
+ self.editwin = editwin # reference to the editor window
+ self.text = text = self.editwin.text
+
+ self.mld = MultiLineDelegator(self.paste_intercept)
+ self.editwin.per.insertfilter(self.mld)
+
+ self.text.bind('<>', self.paste, '+')
+
+ wsys = text.tk.call('tk', 'windowingsystem')
+ if wsys == 'x11':
+ self.text.bind('', self.paste, '+') # For X11 middle click
+
+ self.playback_list = []
+
+ def paste(self, event=None):
+ self.mld.paste = True
+
+ def paste_intercept(self, chars):
+ self.mld.paste = False
+ if self.editwin.executing:
+ return True
+
+ self.play(chars)
+
+ def play(self, chars):
+ chars = self.eol_re.sub(r"\n", chars)
+
+ L = [] # list of entries to play into the shell
+ index = 0
+ while True:
+ next_index = chars.find('\n', index)
+ if next_index > -1:
+ line = chars[index:next_index]
+ L.append((line, True))
+ else:
+ line = chars[index:]
+ L.append((line, False))
+ break
+ index = next_index + 1
+
+ L = self.dedent(L)
+ self.playback_list = L
+ self._pre_playback()
+ self.do_playback()
+
+ def dedent(self, L):
+ return L
+
+ # TODO: make this work
+ # Multiline strings may make code appear less indented than it is...
+ Lcode = [line for line, ret in L if line.rstrip() and not line.startswith('#')]
+
+ dedent_tab =0
+ while all(map(Lcode.startswith('\t'))):
+ Lcode = [x[1:] for x in Lcode]
+ dedent_tab += 1
+
+ if not dedent_tab:
+ dedent_space = 0
+ while all(map(Lcode.startswith(' '))):
+ Lcode = [x[1:] for x in Lcode]
+ dedent_space += 1
+ depth = dedent_space
+ src = '\n'.join(L)
+ src = re.sub(r"(?')
+ t.see('insert')
+
+ t.after(10, self.do_playback)
+
+
diff --git a/idlex-1.11.2/idlexlib/extensions/PastePyShell.py b/idlex-1.11.2/idlexlib/extensions/PastePyShell.py
new file mode 100644
index 0000000..3dd0198
--- /dev/null
+++ b/idlex-1.11.2/idlexlib/extensions/PastePyShell.py
@@ -0,0 +1,257 @@
+# IDLEX EXTENSION
+## """
+## 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.
+##
+## PastePyShell.py
+##
+## Handle code formatting of PyShell text, per issue11838.
+## Also addresses "issue7676 - IDLE shell shouldn't use TABs"
+## where TABs are a problem due to pasting into the editor from PyShell.
+##
+##
+## """
+
+config_extension_def = """
+[PastePyShell]
+enable=1
+enable_shell=0
+enable_editor=1
+
+[PastePyShell_cfgBindings]
+paste-code=
+paste-code-only=
+"""
+# NOTE: the "Key" in is needed for it to work.
+
+
+# get the IDLE configuration handler
+from idlelib.configHandler import idleConf
+from idlelib.EditorWindow import classifyws
+import re
+
+class PastePyShell:
+
+ # A more proper place is to add the menu entry in "Bindings.py"
+ # just after "Paste" under "edit".
+ menudefs = [
+ ('edit', [
+ ('Paste from S_hell', '<>'),
+ ('Paste from Shell (only code)', '<>')
+ ]),]
+
+
+ def __init__(self, editwin):
+ self.editwin = editwin # reference to the editor window
+ self.text = text = self.editwin.text
+ self.text.bind("<>", self.paste_code_event)
+
+ r = editwin.rmenu_specs
+ # See Issue1207589 patch
+ if len(r) > 0:
+ if len(r[0]) == 2:
+ specs = [('Paste from Shell', '<>'),
+ ('Paste from Shell (only code)', '<>')]
+ elif len(r[0]) == 3:
+ specs = [('Paste from Shell', '<>', None),
+ ('Paste from Shell (only code)', '<>', None)]
+ else:
+ specs = []
+
+ for m in specs:
+ if m not in editwin.rmenu_specs:
+ editwin.rmenu_specs.append(m)
+
+ def paste_code_only_event(self, ev=None):
+ self.paste_code(comments=False)
+ return "break"
+
+ def paste_code_event(self, ev=None):
+ self.paste_code()
+ return "break"
+
+ def paste_code(self, comments=True):
+ editwin = self.editwin
+ text = self.text
+ per = self.editwin.per
+
+ try:
+ chars = editwin.top.selection_get(selection="CLIPBOARD")
+ except Exception:
+ return "break"
+
+ pc = PastePyShellProcessor(comments=comments)
+ res = pc._paste_process(chars)
+ text.insert('insert', res)
+ return "break"
+
+class PastePyShellProcessor:
+ def __init__(self, tabwidth=8, usetabs=False, comments=True):
+ self.tabwidth = tabwidth
+ self.usetabs = usetabs
+ self.comments = comments
+
+ # TODO - allow "email" and "diff" pasting
+ self.prompts = [r'^>>> ', # standard Python ps1
+ r'^\.\.\. ', # standard Python ps2
+ r'^[ ]*In \[[\d]*\]: ', # IPython ps1
+ r'^[ ]*[\.]*: '] # IPython ps2
+
+ def starts_with_prompt(self, L):
+ for p in self.prompts:
+ m = re.match(p, L)
+ if m:
+ return True
+ else:
+ return False
+
+ def remove_prompt(self, L):
+ for p in self.prompts:
+ m = re.match(p, L)
+ if m:
+ L = re.sub(p, '', L, count=1)
+ break
+ return L
+
+ def _paste_process(self, chars):
+ """ Handle code formatting of PyShell text, per issue11838. """
+
+ # This code implements a two-state machine, where
+ # "iscode" indicates the state.
+
+ insert_blankline = False # flag to insert blank lines around code
+ include_comments = self.comments
+
+ lines = chars.split('\n')
+ iscode = False # state indicator for >>>
+
+ has_prompt = False
+
+ INDENT_LIST = [' ', '\t']
+
+ NL = []
+ for L in lines:
+ # handle state machine transition
+ if iscode:
+ # Leave "iscode" state if the line is not indented
+ # and the line is not another prompt.
+ if L:
+ if L[0] not in INDENT_LIST:
+
+ if not self.starts_with_prompt(L):
+ iscode = False
+ if insert_blankline: NL.append('')
+ else:
+ #pass
+ continue # skip double blank line at end of multi-line
+ else:
+ # Enter "iscode" state is the line begins with a prompt.
+ if self.starts_with_prompt(L):
+ if insert_blankline: NL.append('')
+ iscode = True
+ has_prompt = True
+
+
+ # handle output of state machine
+ if not iscode:
+ if include_comments:
+ NL.append('#%s' % L) # comment output
+ else:
+ # remove >>> and tabs (if necessary)
+ if self.starts_with_prompt(L):
+ L = self.remove_prompt(L)
+
+ # convert to spaces
+ raw, effective = classifyws(L, self.tabwidth)
+ L = self._make_blanks(effective) + L[raw:]
+
+ # handle shell RESTART
+ if "= RESTART =" in L:
+ L = '#%s' % L
+
+ NL.append(L)
+
+ return '\n'.join(NL)
+
+## # Code to fall back to normal Paste if no prompts detected in buffer
+## if has_prompt:
+## return '\n'.join(NL)
+## else:
+## return chars
+
+ def _make_blanks(self, n): # from EditorWindow.py
+ # copied over in case EditorWindow's implementation changes.
+ if self.usetabs:
+ ntabs, nspaces = divmod(n, self.tabwidth)
+ return '\t' * ntabs + ' ' * nspaces
+ else:
+ return ' ' * n
+
+
+
+test_code = r"""Python 2.7.1+ (r271:86832, Apr 11 2011, 18:13:53)
+[GCC 4.5.2] on linux2
+Type "copyright", "credits" or "license()" for more information.
+>>> ================================ RESTART ================================
+>>> if 1:
+ print(123)
+ print(456)
+
+
+123
+456
+>>> import sys
+>>> print sys.version
+2.7.1+ (r271:86832, Apr 11 2011, 18:13:53)
+[GCC 4.5.2]
+>>> for x in range(3):
+ print(x**2)
+
+
+0
+1
+4
+>>> print('>>> ') # This output will be treated as a prompt...
+>>>
+>>>
+>>>
+>>> print('\tThis line will be considered code.')
+ This line will be considered code.
+>>>
+"""
+
+if __name__ == '__main__':
+ # test
+ pc = PastePyShellProcessor()
+ res = pc._paste_process(test_code)
+ for i in res.split('\n'): # work-around squeezer
+ print(i)
diff --git a/idlex-1.11.2/idlexlib/extensions/PersistentHistory.py b/idlex-1.11.2/idlexlib/extensions/PersistentHistory.py
new file mode 100644
index 0000000..8b4f69b
--- /dev/null
+++ b/idlex-1.11.2/idlexlib/extensions/PersistentHistory.py
@@ -0,0 +1,116 @@
+# IDLEX EXTENSION
+
+## """
+## Copyright(C) 2011 The Board of Trustees of the University of Illinois.
+## All rights reserved.
+## Developed by: Roger D. Serwy
+## University of Illinois
+## License: See LICENSE.txt
+## """
+
+config_extension_def = """
+[PersistentHistory]
+enable=1
+enable_editor=0
+enable_shell=1
+
+keep = 500
+
+"""
+
+from idlelib.configHandler import idleConf
+import pickle
+import os
+import sys
+import idlelib.PyShell as PyShell
+
+# TODO: decide if having history tied to the python version is a good thing.
+
+class PersistentHistory:
+
+ cfg_dir = idleConf.GetUserCfgDir()
+ history_file = os.path.join(cfg_dir,
+ 'shell-history.dat')
+ #'shell-history-%X.dat' % sys.hexversion)
+
+ menudefs = [
+ ('options', [
+ ('Clear History', '<>'),
+ ]),]
+
+ def __init__(self, editwin):
+ if not isinstance(editwin, PyShell.PyShell):
+ print('Not a PyShell instance - not running Persistent History')
+ self.close = lambda *args, **kwargs: None
+ return
+
+ self.editwin = editwin
+ self.text = text = editwin.text
+ self.history = None
+
+
+ self.text.bind('<>', self.history_clear_event)
+ self.keep = idleConf.GetOption('extensions',
+ 'PersistentHistory',
+ 'keep',
+ type='int',
+ default=500)
+ self.delay_init()
+
+ def close(self):
+ try:
+ self.save_history()
+ except Exception as err:
+ print(' An error occurred during the close of PersistentHistory.py: %s' % str(err))
+
+ def delay_init(self):
+ if hasattr(self.editwin, 'history'):
+ self.history = self.editwin.history
+ self.load_history()
+ #self._showit() # for testing
+ return
+ self.text.after(100, self.delay_init)
+
+ def save_history(self):
+ hist = self.history.history[:]
+ if len(hist) > self.keep: # limit the amount kept
+ hist = hist[len(hist)-self.keep:]
+
+ f = open(self.history_file, 'wb')
+ pickle.dump(hist, f, 1) # protocol to be compatible with 2/3
+ f.close()
+
+
+ def load_history(self):
+ if not os.path.exists(self.history_file):
+ return
+
+ f = open(self.history_file, 'rb')
+ try:
+ data = pickle.load(f)
+ except Exception as err:
+ print('Unable to load history: %s ' % str(err))
+ data = []
+ f.close()
+
+
+ data.extend(self.history.history) # Just in case a history already had
+ # contents before being loaded. (Not supposed to happen)
+ self.history.history = data
+
+
+ def history_clear_event(self, ev=None):
+ h = self.history
+ h.history_prefix = None
+ h.history_pointer = None
+ h.history = []
+ return "break"
+
+
+ def _showit(self): # for testing
+ h = self.history
+ print('-'*80)
+ print(h.history)
+ print(h.history_prefix)
+ print(h.history_pointer)
+ self.text.after(250, self.showit)
diff --git a/idlex-1.11.2/idlexlib/extensions/ReindentExtension.py b/idlex-1.11.2/idlexlib/extensions/ReindentExtension.py
new file mode 100644
index 0000000..1af9939
--- /dev/null
+++ b/idlex-1.11.2/idlexlib/extensions/ReindentExtension.py
@@ -0,0 +1,372 @@
+# IDLEX EXTENSION
+##
+## 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.
+##
+##
+##
+## Reindent Extension:
+##
+## Relies on ScriptBinding.py extension
+##
+## Provides "Re-indent" on the Format menu.
+## Based on "reindent.py" code released to the public domain, by Tim Peters, 03 October 2000.
+##
+## Part of Python Bug, issue 5150 Editing.
+##
+## Modified to use IdleX October 2011
+##
+##
+## This code includes a patch from http://bugs.python.org/issue12930
+## to disallow strings from being modified. That code is under the PSF license,
+## based on the contributor's agreement with the PSF.
+##
+
+config_extension_def = """
+
+[ReindentExtension]
+enable=1
+enable_shell=0
+enable_editor=1
+
+[ReindentExtension_cfgBindings]
+reindent-apply=
+
+"""
+
+import sys
+import tokenize
+
+if sys.version < '3':
+ from StringIO import StringIO
+else:
+ from io import StringIO
+ xrange = range
+
+
+change_strings = False # flag for reindent code patch
+
+def _rstrip(line, JUNK='\n \t'):
+ """Return line stripped of trailing spaces, tabs, newlines.
+
+ Note that line.rstrip() instead also strips sundry control characters,
+ but at least one known Emacs user expects to keep junk like that, not
+ mentioning Barry by name or anything .
+ """
+
+ i = len(line)
+ while i > 0 and line[i-1] in JUNK:
+ i -= 1
+ return line[:i]
+
+class Reindenter:
+
+ def __init__(self, f):
+ self.find_stmt = 1 # next token begins a fresh stmt?
+ self.level = 0 # current indent level
+
+ # Raw file lines.
+ self.raw = f.readlines()
+
+ # File lines, rstripped & tab-expanded. Dummy at start is so
+ # that we can use tokenize's 1-based line numbering easily.
+ # Note that a line is all-blank iff it's "\n".
+ self.lines = [_rstrip(line).expandtabs() + "\n"
+ for line in self.raw]
+ self.lines.insert(0, None)
+ self.index = 1 # index into self.lines of next line
+
+ # List of (lineno, indentlevel) pairs, one for each stmt and
+ # comment line. indentlevel is -1 for comment lines, as a
+ # signal that tokenize doesn't know what to do about them;
+ # indeed, they're our headache!
+ self.stats = []
+
+ # Save the newlines found in the file so they can be used to
+ # create output without mutating the newlines.
+ self.newlines = '\n' # RDS - default in IDLE
+
+ def rstrip_and_expand_tabs(self):
+ self.lines = [line for line in self.raw]
+ self.lines.insert(0, None)
+ # Only apply rstrip if the line is not part of a multiline string
+ # and expand tabs only if not within in a string.
+ tokens = tokenize.generate_tokens(self.getline)
+
+ strmask = {}
+ def addmask(line, scol, ecol):
+ # Keep track of start/end columns for a string on a particular
+ # line. Each element is used to toggle the string state on a line.
+ if line in strmask:
+ strmask[line].extend([scol, ecol])
+ else:
+ strmask[line] = [scol, ecol]
+
+ lines = self.lines
+ for _token in tokens:
+ _type, string, slinecol, elinecol, line = _token
+ if _type == tokenize.STRING:
+ sl, sc = slinecol
+ el, ec = elinecol
+ if sl == el:
+ # Single line string
+ addmask(sl, sc, ec)
+ else:
+ # Multi-line string
+ addmask(sl, sc, len(lines[sl]))
+ strmask[sl].append(-1) # Start multi-line
+ strmask[el] = [-1] # Stop multi-line
+ addmask(el, 0, ec)
+
+ self.index = 1 # reset index for self.getline
+
+ n = 1
+ multi = False
+ while n < len(lines):
+ line = lines[n]
+ strtoggle = strmask.get(n, None)
+ if strtoggle is None:
+ if not multi:
+ lines[n] = _rstrip(line).expandtabs() + '\n'
+ else:
+ # Process strings on a single line
+ isstr = False
+ scol = 0
+ processed = []
+ while strtoggle:
+ ecol = strtoggle.pop(0)
+ if ecol == -1: # toggle multiline mode
+ if not multi:
+ ecol = len(line)
+ multi = not multi
+ isstr = multi
+ continue
+ if isstr:
+ processed.append(line[scol:ecol])
+ else:
+ processed.append(line[scol:ecol].expandtabs())
+
+ scol = ecol
+ isstr = not isstr
+
+ if not multi:
+ processed.append(_rstrip(line[ecol:]).expandtabs() + '\n')
+ else:
+ processed.append(line[ecol:-1])
+
+ lines[n] = ''.join(processed)
+ n += 1
+
+ def run(self):
+ if not change_strings:
+ self.rstrip_and_expand_tabs()
+ tokens = tokenize.generate_tokens(self.getline)
+ for _token in tokens:
+ self.tokeneater(*_token)
+ # Remove trailing empty lines.
+ lines = self.lines
+ while lines and lines[-1] == "\n":
+ lines.pop()
+ # Sentinel.
+ stats = self.stats
+ stats.append((len(lines), 0))
+ # Map count of leading spaces to # we want.
+ have2want = {}
+ # Program after transformation.
+ after = self.after = []
+ # Copy over initial empty lines -- there's nothing to do until
+ # we see a line with *something* on it.
+ i = stats[0][0]
+ after.extend(lines[1:i])
+ for i in range(len(stats) - 1):
+ thisstmt, thislevel = stats[i]
+ nextstmt = stats[i + 1][0]
+ have = getlspace(lines[thisstmt])
+ want = thislevel * 4
+ if want < 0:
+ # A comment line.
+ if have:
+ # An indented comment line. If we saw the same
+ # indentation before, reuse what it most recently
+ # mapped to.
+ want = have2want.get(have, -1)
+ if want < 0:
+ # Then it probably belongs to the next real stmt.
+ for j in range(i + 1, len(stats) - 1):
+ jline, jlevel = stats[j]
+ if jlevel >= 0:
+ if have == getlspace(lines[jline]):
+ want = jlevel * 4
+ break
+ if want < 0: # Maybe it's a hanging
+ # comment like this one,
+ # in which case we should shift it like its base
+ # line got shifted.
+ for j in range(i - 1, -1, -1):
+ jline, jlevel = stats[j]
+ if jlevel >= 0:
+ want = have + (getlspace(after[jline - 1]) -
+ getlspace(lines[jline]))
+ break
+ if want < 0:
+ # Still no luck -- leave it alone.
+ want = have
+ else:
+ want = 0
+ assert want >= 0
+ have2want[have] = want
+ diff = want - have
+ if diff == 0 or have == 0:
+ after.extend(lines[thisstmt:nextstmt])
+ else:
+ for line in lines[thisstmt:nextstmt]:
+ if diff > 0:
+ if line == "\n":
+ after.append(line)
+ else:
+ after.append(" " * diff + line)
+ else:
+ remove = min(getlspace(line), -diff)
+ after.append(line[remove:])
+ return self.raw != self.after
+
+ def write(self, f):
+ f.writelines(self.after)
+
+ # Line-getter for tokenize.
+ def getline(self):
+ if self.index >= len(self.lines):
+ line = ""
+ else:
+ line = self.lines[self.index]
+ self.index += 1
+ return line
+
+ # Line-eater for tokenize.
+ def tokeneater(self, type, token, slinecol, end, line,
+ INDENT=tokenize.INDENT,
+ DEDENT=tokenize.DEDENT,
+ NEWLINE=tokenize.NEWLINE,
+ COMMENT=tokenize.COMMENT,
+ NL=tokenize.NL):
+
+ if type == NEWLINE:
+ # A program statement, or ENDMARKER, will eventually follow,
+ # after some (possibly empty) run of tokens of the form
+ # (NL | COMMENT)* (INDENT | DEDENT+)?
+ self.find_stmt = 1
+
+ elif type == INDENT:
+ self.find_stmt = 1
+ self.level += 1
+
+ elif type == DEDENT:
+ self.find_stmt = 1
+ self.level -= 1
+
+ elif type == COMMENT:
+ if self.find_stmt:
+ self.stats.append((slinecol[0], -1))
+ # but we're still looking for a new stmt, so leave
+ # find_stmt alone
+
+ elif type == NL:
+ pass
+
+ elif self.find_stmt:
+ # This is the first "real token" following a NEWLINE, so it
+ # must be the first token of the next program statement, or an
+ # ENDMARKER.
+ self.find_stmt = 0
+ if line: # not endmarker
+ self.stats.append((slinecol[0], self.level))
+
+
+# Count number of leading blanks.
+def getlspace(line):
+ i, n = 0, len(line)
+ while i < n and line[i] == " ":
+ i += 1
+ return i
+
+
+class ReindentExtension:
+
+ menudefs = [('format', [('Apply Reindent', '<>')])]
+
+ def __init__(self, editwin):
+ self.editwin = editwin
+
+
+ def reindent_apply_event(self, event=None):
+
+ text = self.editwin.text
+ undo = self.editwin.undo
+
+ f_in = StringIO()
+ source = text.get('0.0', 'end -1 char') # -1 char to avoid trailing \n,
+ # otherwise the file is always
+ # marked as "changed"
+ f_in.write(source)
+ f_in.seek(0)
+
+ r = Reindenter(f_in)
+ try:
+ changed = r.run()
+ except (IndentationError, SyntaxError) as err:
+ msg, (errorfilename, lineno, offset, line) = err
+
+ sb = self.editwin.extensions['ScriptBinding']
+ sb.colorize_syntax_error(msg, lineno, offset)
+ sb.errorbox("Syntax error",
+ "There's an error in your program:\n" + msg)
+ return 'break'
+
+ if not changed:
+ return 'break'
+
+ f_out = StringIO()
+ r.write(f_out)
+ f_out.seek(0)
+
+ CUR = text.index('insert') # save cursor index
+ loc = text.yview()[0]
+
+ undo.undo_block_start()
+ text.delete('0.0', text.index('end'))
+ text.insert('0.0', f_out.read())
+ undo.undo_block_stop()
+
+ text.mark_set('insert', CUR) # restore cursor index
+ text.yview_moveto(loc)
+
+ return 'break'
diff --git a/idlex-1.11.2/idlexlib/extensions/RightClickMenu.py b/idlex-1.11.2/idlexlib/extensions/RightClickMenu.py
new file mode 100644
index 0000000..1d31bb1
--- /dev/null
+++ b/idlex-1.11.2/idlexlib/extensions/RightClickMenu.py
@@ -0,0 +1,128 @@
+# IDLEX EXTENSION
+## """
+## Copyright(C) 2012 The Board of Trustees of the University of Illinois.
+## All rights reserved.
+## Developed by: Roger D. Serwy
+## University of Illinois
+## License: See LICENSE.txt
+## """
+
+
+config_extension_def = """
+[RightClickMenu]
+enable=1
+enable_editor=1
+enable_shell=1
+visible=True
+"""
+
+
+# Python 2/3 compatibility
+import sys
+if sys.version < '3':
+ import Tkinter as tk
+else:
+ import tkinter as tk
+
+from pprint import pprint
+
+# get the IDLE configuration handler
+from idlelib.configHandler import idleConf
+from idlelib import macosxSupport
+from idlelib.PyShell import PyShell
+
+
+# A right-click menu has been added. See http://bugs.python.org/issue1207589
+# from 2012-11-01. As a result, this extension is no longer necessary.
+
+
+class RightClickMenu: # must be the same name as the file for EditorWindow.py
+ # to load it.
+
+
+ def __init__(self, editwin):
+ self.editwin = editwin
+ self.text = editwin.text
+ self.is_shell = isinstance(editwin, PyShell)
+
+ m = editwin.rmenu_specs
+ if len(m) > 0:
+ if len(m[0]) == 3:
+ # RightClickMenu.py no longer needed. See issue1207589
+ return
+
+ self.rmenu_specs = [None,
+ ('Cut', '<>'),
+ ('Copy', '<>'),
+ ('Paste', '<>'),
+ None,
+ ('Select All', '<>'),
+ ]
+
+ editwin.text.after(1, self.delay)
+
+ def delay(self):
+ e = self.editwin
+ m = e.rmenu_specs
+ try:
+ # This functionality in OutputWindow.py
+ # requires the cursor to leave the input area in the shell.
+ # IDLE should not do that.
+ m.remove(('Go to file/line', '<>'))
+
+ except:
+ pass
+
+ text = self.text
+ if macosxSupport.runningAsOSXApp():
+ # Some OS X systems have only one mouse button,
+ # so use control-click for pulldown menus there.
+ # (Note, AquaTk defines <2> as the right button if
+ # present and the Tk Text widget already binds <2>.)
+ text.bind("",self.right_menu_event)
+ else:
+ # Elsewhere, use right-click for pulldown menus.
+ text.bind("<3>",self.right_menu_event)
+
+
+ bmenu = [None] # breakpoint options
+ for i in m:
+ if 'breakpoint' in i[0].lower():
+ bmenu.append(i)
+ else:
+ self.rmenu_specs.append(i)
+
+ self.rmenu_specs.extend(bmenu)
+
+
+ self.make_rmenu()
+
+ def make_rmenu(self):
+ rmenu = tk.Menu(self.text, tearoff=0)
+ for entry in self.rmenu_specs:
+ if not entry:
+ rmenu.add_separator()
+ else:
+ label, eventname = entry
+ def command(text=self.text, eventname=eventname):
+ text.event_generate(eventname)
+ rmenu.add_command(label=label, command=command)
+
+ self.editwin.rmenu = rmenu
+
+ def right_menu_event(self, event):
+
+ sel_first = self.text.index('sel.first')
+ if not sel_first and not self.is_shell:
+ self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
+
+ e = self.editwin
+ if not e.rmenu:
+ e.make_rmenu()
+ rmenu = e.rmenu
+ iswin = sys.platform[:3] == 'win'
+ if iswin:
+ e.text.config(cursor="arrow")
+ rmenu.tk_popup(event.x_root, event.y_root)
+ if iswin:
+ e.text.config(cursor="ibeam")
diff --git a/idlex-1.11.2/idlexlib/extensions/RunSelection.py b/idlex-1.11.2/idlexlib/extensions/RunSelection.py
new file mode 100644
index 0000000..0367bc9
--- /dev/null
+++ b/idlex-1.11.2/idlexlib/extensions/RunSelection.py
@@ -0,0 +1,679 @@
+# IDLEX EXTENSION
+## """
+## 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.
+##
+##
+##
+## Run Selection Extension - run parts of source code in IDLE
+##
+## About:
+##
+## This code executes the currently highlighted text
+## from the editor in the shell window, or a single line.
+##
+## How to Use:
+##
+## Run Selection or Line:
+##
+## * To run a single statement (including indented code)
+## move cursor to the line and press F9.
+##
+## * To run a selection of statements, select the statements
+## to run and press F9
+##
+##
+## Run Region
+##
+## A Run Region behaves like a selection, but it is tagged
+## with a blue background. A group of commands can be tagged
+## as a Run Region and executed using Ctrl+F9.
+##
+## Tag/Join Run Region
+## - Tags a line or selection of code as a Run Region
+## - Can join separate Run Regions
+##
+## Untag/Split Run Region
+## - Untags a line or selection of code
+## - Can split a Run Region into two separate Run Regions
+##
+## """
+config_extension_def = """
+
+[RunSelection]
+enable=1
+enable_shell=0
+enable_editor=1
+[RunSelection_cfgBindings]
+run-region=
+"""
+
+TAG_REGIONS = False # allow for selections to be tagged (EXPERIMENTAL)
+TAG_REGIONS = True
+
+if TAG_REGIONS:
+ config_extension_def += """
+run-tagged=
+region-tagged-join=
+region-tagged-split=
+"""
+
+RUNREGION_BACKGROUND = '#BBE0FF' # background color for a runregion
+
+import sys
+
+if sys.version < '3':
+ from Tkinter import *
+ import tkMessageBox
+else:
+ from tkinter import *
+ import tkinter.messagebox as tkMessageBox
+ xrange = range
+
+
+from idlelib.EditorWindow import classifyws
+from idlelib.configHandler import idleConf
+
+import ast
+import tokenize
+import re
+
+jn = lambda x,y: '%i.%i' % (x,y) # join integers to text coordinates
+sp = lambda x: list(map(int, x.split('.'))) # convert tkinter Text coordinate to a line and column tuple
+
+def index(text, marker='insert', lineoffset=0):
+ """ helper function to split a marker location of a text object """
+ line, col = sp(text.index(marker))
+ return (line + lineoffset, col)
+
+
+#---------------------
+# AST helper functions
+#---------------------
+
+def find_sel_range(y, sline, eline=None, first=None, last=None, EF=None):
+ """ Returns the first and last nodes in AST for given line range.
+
+ y: module in the AST
+ sline: start line number
+ eline: end line number
+ first: a tuple of the first module and line
+ last: a tuple of the last module and line
+ EF: an optional callback for using EndFinder to find the true endline of a module.
+
+ """
+ if EF is None:
+ EF = lambda x: x
+
+ if eline is None:
+ eline = sline
+
+ def helper(M, first=None, last=None):
+ for n, stmt in enumerate(M):
+ if sline <= EF(stmt.lineno):
+ if stmt.lineno <= eline:
+ last = (M, n) # last found statement in range
+ if first is None:
+ first = (M, n) # first found statement in rangef
+ first, last = find_sel_range(stmt, sline, eline, first, last, EF)
+ return first, last
+
+ for elt in ['body', 'orelse', 'handlers', 'finalbody']:
+ M = getattr(y, elt, None)
+ if M is not None:
+ first, last = helper(M, first, last)
+
+ return first, last
+
+
+def get_module_endline(y):
+ if not hasattr(y, 'body'):
+ return y.lineno
+ lastnode = y.body[-1]
+
+ for elt in ['orelse', 'handlers', 'finalbody']:
+ M = getattr(lastnode, elt, None)
+ if M is not None:
+ if M:
+ lastnode = M[-1]
+
+ v = get_module_endline(lastnode)
+ if v is None:
+ return lastnode.lineno
+ else:
+ return v
+
+
+class EndFinder(object):
+
+ def __init__(self, src):
+ self.lines = src.split('\n')
+ self.length = len(self.lines)
+ self.offset = 0
+
+ def _make_readline(self, offset):
+ self.offset = offset - 1
+ def readline():
+ if self.offset < self.length:
+ # Strip trailing whitespace to avoid issue16152
+ ret = self.lines[self.offset].rstrip() + '\n'
+ self.offset += 1
+ else:
+ ret = ''
+ return ret
+ return readline
+
+ def __call__(self, lastline):
+ endreader = self._make_readline(lastline)
+ opener = ['(', '{', '[']
+ closer = [')', '}', ']']
+ op_stack = []
+ lastline_offset = 1 # default value for no effective offset
+ try:
+ for tok in tokenize.generate_tokens(endreader):
+ t_type, t_string, t_srow_scol, t_erow_ecol, t_line = tok
+ if t_type == tokenize.OP:
+ if t_string in closer:
+ if op_stack:
+ c = op_stack.pop()
+ if t_string in opener:
+ op_stack.append(t_string)
+
+ if t_type == tokenize.NEWLINE and not op_stack:
+ lastline_offset = t_erow_ecol[0]
+ break
+ except tokenize.TokenError:
+ pass
+
+ lastline += (lastline_offset - 1)
+
+ return lastline
+
+
+#---------------
+# The extension
+#----------------
+
+class RunSelection(object):
+
+ _menu = [None,
+ ('Run Selection or Line', '<>')]
+
+ if TAG_REGIONS:
+ _menu.extend([
+ ('Run Tagged Region', '<>'),
+ ('Tag/Join Region', '<>'),
+ ('Untag/Split Region', '<>')])
+ menudefs = [
+ ('run', _menu),
+ ]
+
+ def __init__(self, editwin):
+ self.editwin = editwin
+ text = self.text = editwin.text
+
+ text.tag_configure('RUNREGION', **{'background':RUNREGION_BACKGROUND,
+ #'borderwidth':2,
+ #'relief':GROOVE,
+ })
+ text.tag_raise('sel')
+ text.tag_raise('ERROR')
+
+ r = editwin.rmenu_specs
+ # See Issue1207589 patch
+ if len(r) > 0:
+ if len(r[0]) == 2:
+ specs = [('Run Selection or Line', '<>')]
+ elif len(r[0]) == 3:
+ specs = [('Run Selection or Line', '<>', None)]
+
+ for m in specs:
+ if m not in editwin.rmenu_specs:
+ editwin.rmenu_specs.append(m)
+
+
+ #---------------------------------
+ # Event Handlers
+ #---------------------------------
+
+ def run_region_event(self, event=None):
+ # <> was triggered
+
+ sel = self.get_sel_range()
+ if sel:
+ # Run the selected code.
+ code_region = self.send_code(*sel)
+ else:
+ # Run the code on current line
+ lineno, col = sp(self.text.index('insert'))
+ code_region = self.send_code(lineno, lineno)
+
+ self.text.tag_remove('sel', '1.0', END)
+ if code_region:
+ self.shrink_region(*code_region, tag='sel')
+
+
+ def run_tagged_event(self, event=None):
+ # <> was triggered
+ lineno, col = sp(self.text.index('insert'))
+ r = self.get_tagged_region(lineno)
+ if r:
+ # Run the code contained in the tagged run region.
+ self.untag_entire_region(r[0])
+ self.tag_run_region(*r)
+ self.adjust_cursor(*r)
+ code_region = self.send_code(*r)
+ if code_region:
+ self.shrink_region(*code_region, tag='RUNREGION')
+
+ def region_tagged_join_event(self, event=None):
+ sel = self.get_sel_range()
+ if sel:
+ self.tag_run_region(*sel)
+ self.clear_sel()
+ else:
+ lineno, col = sp(self.text.index('insert'))
+ self.tag_run_region(lineno, lineno)
+ return "break"
+
+ def region_tagged_split_event(self, event=None):
+ sel = self.get_sel_range()
+ if sel:
+ self.untag_run_region(*sel)
+ self.clear_sel()
+ else:
+ lineno, col = sp(self.text.index('insert'))
+ self.untag_run_region(lineno, lineno)
+ return "break"
+
+
+ #--------------------------
+ # Tag/Untag Run Region Code
+ #--------------------------
+
+ def _tag_region(self, first, last, tag):
+ self.text.tag_add(tag, '%i.0' % (first),
+ '%i.0' % (last+1))
+
+ def _untag_region(self, first, last, tag):
+ self.text.tag_remove(tag, '%i.0' % (first),
+ '%i.0' % (last+1))
+
+ def get_tagged_region(self, line):
+ """ Return the Region at line number.
+
+ None If no region
+ (first, last) Line numbers
+
+ """
+ if line < 0:
+ return None
+
+ text = self.text
+ loc = '%i.0+1c' % line
+ p = text.tag_prevrange("RUNREGION", loc)
+ if p:
+ if text.compare(p[0], '<=', loc) and \
+ text.compare(p[1], '>=', loc):
+ first, col = sp(p[0])
+ last, col = sp(p[1])
+ return first, last-1
+ return None
+
+
+ def tag_run_region(self, first, last):
+ """ Tag a given span of lines as a Run Region """
+ if first > last:
+ first, last = last, first
+
+ tree = self.get_tree()
+ active = self.get_active_statements(first, last, tree=tree)
+
+ if active:
+ # Active statements are in the given range.
+ # Tag this range.
+ mod, firstline, lastline, offset = active
+ self._tag_region(firstline, lastline, 'RUNREGION')
+
+ # Check if joining regions. This may happen
+ # if the user expanded an existing region
+ # but the expansion contains no active code.
+ firstregion = self.get_tagged_region(first)
+ lastregion = self.get_tagged_region(last)
+
+ if firstregion:
+ if lastregion:
+ # join the separated run regions
+ r1 = self.get_active_statements(*firstregion, tree=tree)
+ r2 = self.get_active_statements(*lastregion, tree=tree)
+ # make sure both regions at same indentation
+ if r1 and r2:
+ if r1[3] == r2[3]: # make sure offsets are same
+ firstline = firstregion[0]
+ lastline = lastregion[0]
+ self._tag_region(firstline, lastline, 'RUNREGION')
+ else:
+ #print('wrong offsets for join')
+ msg = 'Could not join regions due to indentation mismatch.'
+ self.show_error('Join region error', msg)
+ pass
+ else:
+ # expand downward
+ self.tag_run_region(firstregion[0], last)
+
+ def untag_run_region(self, first, last):
+ """ Untags a run region over the given range of lines. """
+ if first > last:
+ first, last = last, first
+
+ tag = 'RUNREGION'
+
+ r1 = self.get_tagged_region(first)
+ r2 = self.get_tagged_region(last)
+
+ self._untag_region(first, last, tag) # untag the given range
+
+ T = self.get_tree()
+
+ # shrink the surrounding run regions if they exist
+ firstregion = self.get_tagged_region(first-1)
+ lastregion = self.get_tagged_region(last+2)
+
+ def retag(region):
+ if region is None:
+ return
+ F, L = region
+ self._untag_region(F, L, tag)
+ active = self.get_active_statements(F, L, tree=T)
+ if active:
+ mod, F, L, offset = active
+ self._tag_region(F, L, tag)
+
+ retag(firstregion)
+ retag(lastregion)
+
+ # If RUNREGION tag still exists within [first, last]
+ # then restore prior tags
+ t1 = self.text.tag_names('%i.0' % first)
+ t2 = self.text.tag_names('%i.0-1c' % last)
+
+ restore = False
+ if first != last:
+ if tag in t1 or tag in t2:
+ #print('could not untag')
+ restore = True
+ else:
+ if tag in t1:
+ #print('could not untag2')
+ restore = True
+ if restore:
+ msg = 'Could not untag because line %i is tagged.' % firstregion[0]
+ self._untag_region(first, last, tag)
+ retag(r1)
+ retag(r2)
+ self.show_error('Split region error', msg)
+
+ def untag_entire_region(self, line):
+ """ Untags an entire region containing the given line. """
+ r = self.get_tagged_region(line)
+ if r:
+ self.untag_run_region(*r)
+
+
+ def shrink_region(self, first, last, tag):
+ text = self.text
+ if first == last:
+ endsel = '%i.0 lineend' % first
+ else:
+ endsel = '%i.0' % (last + 1)
+ text.tag_add(tag, '%i.0' % first, endsel)
+
+
+ #---------------------
+ # Editor-handling code
+ #---------------------
+
+ def adjust_cursor(self, start, end):
+ """ Move the cursor in case run region shrinks """
+ text = self.text
+ if text.compare('insert', '>', '%i.0' % end):
+ text.mark_set('insert', '%i.0' % end + ' lineend')
+ elif text.compare('insert', '<', '%i.0' % start):
+ text.mark_set('insert','%i.0' % start)
+
+ def get_sel_range(self):
+ """ Return the first and last line containing the selection """
+ text = self.text
+ sel_first = text.index('sel.first')
+ if sel_first:
+ firstline, firstcol = sp(sel_first)
+ lastline, lastcol = sp(text.index('sel.last'))
+ if lastcol == 0:
+ lastline -= 1
+ return firstline, lastline
+ else:
+ return None
+
+ def clear_sel(self):
+ self.text.tag_remove('sel', '1.0', 'end')
+
+ def focus_editor(self, ev=None):
+ self.editwin.text.focus_set()
+ self.editwin.top.lift()
+
+
+ def show_error(self, title, msg):
+ tkMessageBox.showerror(title=title,
+ message=msg,
+ parent = self.text)
+ pass
+
+ #-------------
+ # Code-parsing
+ #-------------
+
+ def send_code(self, first, last):
+ """ Sends the code contained in the selection """
+ text = self.text
+ active = self.get_active_statements(first, last)
+ if active is not None:
+ mod, firstline, lastline, offset = active
+ # return the code that can be executed
+ if firstline != lastline:
+ msg = '# Run Region [%i-%i]' % (firstline, lastline)
+ else:
+ msg = '# Run Region [line %i]' % firstline
+
+ src = text.get('%i.0' % firstline,
+ '%i.0' % (lastline + 1))
+
+ if offset: # dedent indented code
+ src = re.sub(r"(? 5 ]
+123
+
+"""
+ e = EndFinder(src)
+ print(e(1))
+
+
+ root = Tk()
+ class ignore:
+ def __getattr__(self, name):
+ print('ignoring %s' % name)
+ return lambda *args, **kwargs: None
+
+ class EditorWindow(ignore):
+ text = Text(root)
+ text.tag_raise = lambda *args, **kw: None
+ text.insert('1.0', src)
+ rmenu_specs = []
+
+
+ editwin = EditorWindow()
+ rs = RunSelection(editwin)
+ tree = rs.get_tree()
+ s = rs.get_active_statements(1.0, 1.0, tree=tree)
+
+ print(s)
+
diff --git a/idlex-1.11.2/idlexlib/extensions/SearchBar.py b/idlex-1.11.2/idlexlib/extensions/SearchBar.py
new file mode 100644
index 0000000..581c81d
--- /dev/null
+++ b/idlex-1.11.2/idlexlib/extensions/SearchBar.py
@@ -0,0 +1,944 @@
+# IDLEX EXTENSION
+
+## """SearchBar.py - An IDLE extension for searching for text in windows.
+##
+## Copyright (c) 2011 Tal Einat
+## All rights reserved.
+##
+## Developed by: Tal Einat
+##
+## 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 name of Tal Einat, 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.
+##
+##
+##
+## The interface is a small bar which appears on the bottom of the window,
+## and dissapears when the user stops searching.
+##
+## This extension implements the usual search options, as well as regular
+## expressions.
+##
+## Another nice feature is that while searching all matches are highlighted.
+##
+##
+## Original Author: Tal Einat
+##
+## Modified by Roger D. Serwy to work with idlex and Python 3,
+## as well as some bugfixes and improvements.
+##
+##
+##
+## """
+
+config_extension_def = """
+[SearchBar]
+enable=1
+is_incremental=1
+reg_exp=0
+match_case=0
+whole_word=0
+wrap_around=0
+
+[Searchbar_cfgBindings]
+toggle-search-bar=
+
+"""
+
+import time
+import string
+import re
+import sys
+
+if sys.version < '3':
+ import Tkinter
+ from Tkconstants import TOP, BOTTOM, LEFT, RIGHT, X, NONE
+else:
+ import tkinter as Tkinter
+ from tkinter.constants import TOP, BOTTOM, LEFT, RIGHT, X, NONE
+
+EXTNAME = 'SearchBar'
+
+from idlelib.configHandler import idleConf
+from idlelib.SearchEngine import SearchEngine
+
+
+class SearchBarSearchEngine(SearchEngine):
+ """ Silence regex errors.
+ Incremental highlighting doesn't play well with mal-formed regex.
+
+ """
+
+ def __init__(self, *args, **kw):
+ SearchEngine.__init__(self, *args, **kw)
+ self._error_callback_ptr = None
+ self._incrementalSearch = False
+ self.varlist = [self.revar, self.casevar, self.wordvar, self.wrapvar]
+ self.tracelist = []
+
+ def report_error(self, pat, msg, col=-1):
+ #print('report_error', pat, msg, col,self._incrementalSearch)
+ if self._incrementalSearch:
+ if self._error_callback_ptr:
+ return self._error_callback_ptr(pat, msg, col)
+ else:
+ return None
+ else:
+ return SearchEngine.report_error(self, pat, msg, col)
+
+ def error_callback(self, ptr):
+ # This is set by FindBar and ReplaceBar instances so that it
+ # calls the correct callback
+ self._error_callback_ptr = ptr
+
+ def load_cfg(self):
+ # Load settings from configuration handler - RDS 2012-02-03
+ self.revar.set(get_cfg('reg_exp', default=False))
+ self.casevar.set(get_cfg('match_case', default=False))
+ self.wordvar.set(get_cfg('whole_word', default=False))
+ self.wrapvar.set(get_cfg('wrap_around', default=False))
+
+ def save_cfg(self):
+ set_cfg('reg_exp', '%s' % self.revar.get())
+ set_cfg('match_case', '%s' % self.casevar.get())
+ set_cfg('whole_word', '%s' % self.wordvar.get())
+ set_cfg('wrap_around', '%s' % self.wrapvar.get())
+
+ def set_var_trace(self, ptr):
+ obs = []
+ for v in self.varlist:
+ obs.append(v.trace("w", ptr))
+ self.tracelist = zip(obs, self.varlist)
+
+ def remove_var_trace(self):
+ for obs, v in self.tracelist:
+ v.trace_vdelete('w', obs)
+ self.tracelist = []
+
+
+
+def get_cfg(cfg, type="bool", default=True):
+ return idleConf.GetOption("extensions", EXTNAME,
+ cfg, type=type, default=default)
+
+def set_cfg(cfg, b):
+ return idleConf.SetOption("extensions", EXTNAME,
+ cfg,'%s' % b)
+
+class SearchBar:
+ menudefs = []
+ def __init__(self, editwin):
+ text = editwin.text
+ self.engine = engine = SearchBarSearchEngine(text)
+ self.fb = find_bar = FindBar(editwin, editwin.status_bar, engine)
+ self.rb = replace_bar = ReplaceBar(editwin, editwin.status_bar, engine)
+
+ def find_event(event):
+ replace_bar.hide_findbar_event(event, focus=False)
+ find_bar.show_findbar_event(event)
+ return "break"
+ text.bind("<>", find_event)
+
+ def find_again_event(event):
+ find_bar.search_again_event(event)
+ return "break"
+ text.bind("<>", find_again_event)
+
+ def find_selection_event(event):
+ find_bar.search_selection_event(event)
+ return "break"
+ text.bind("<>", find_selection_event)
+
+ def replace_event(event):
+ find_bar.hide_findbar_event(event, focus=False)
+ replace_bar.show_findbar_event(event)
+ return "break"
+ text.bind("<>", replace_event)
+
+ def close(self):
+ self.engine.save_cfg()
+
+
+
+
+def FindBar(editwin, pack_after, engine):
+ return SearchBarWidget(editwin, pack_after, engine, is_replace=False)
+def ReplaceBar(editwin, pack_after, engine):
+ return SearchBarWidget(editwin, pack_after, engine, is_replace=True)
+
+class SearchBarWidget:
+ def __init__(self, editwin, pack_after, engine, is_replace=False):
+ self.text = editwin.text
+ self.root = self.text._root()
+ self.engine = engine
+ self.window_engine = get_window_engine(editwin)
+ self.is_replace = is_replace
+
+ self.top = editwin.top
+ self.pack_after = pack_after
+
+ self.widgets_built = False
+ self.shown = False
+
+ self.find_var = Tkinter.StringVar(self.root)
+
+ # The text widget's selection isn't shown when it doesn't have the
+ # focus. Let's replicate it so it will be seen while searching as well.
+ self.text.tag_configure("findsel",
+ background=self.text.tag_cget("sel","background"),
+ foreground=self.text.tag_cget("sel","foreground"))
+
+ self._is_incremental = None
+ self._expand_state = None
+
+ self.text.bind('', self.text_focusin_event, '+')
+
+ def toggle_search_bar_event(self, event=None): # RDS - 2011-10-18
+ self.text.event_generate('<>')
+ return "break"
+
+ def _show(self):
+ if not self.widgets_built:
+ self._build_widgets()
+
+ if not self.shown:
+ self.bar_frame.pack(side=BOTTOM, fill=X, expand=0, pady=1,
+ after=self.pack_after)
+ self.window_engine.show_find_marks()
+ self.shown = True # must be _before_ reset_selection()!
+ # Add the "findsel" tag, which looks like the selection
+ self._reset_selection()
+
+ self._is_incremental = self.is_incremental()
+ self._expand_state = None
+ self.engine.error_callback(self._error_callback)
+ self.engine.load_cfg()
+ self.engine.set_var_trace(self._incremental_callback)
+
+ def _hide(self, setcursor=False):
+ if self.widgets_built and self.shown:
+ v = self.text.yview()
+ self.bar_frame.pack_forget()
+ self.text.update_idletasks()
+ try:
+ self.text.yview_moveto(v[0]) # Tkinter work-around
+ except Exception as err: # This should never happen
+ print('SearchBar._hide', err)
+
+ self.window_engine.reset()
+ self.window_engine.hide_find_marks()
+
+ sel = self._get_selection()
+ self.shown = False # must be _after_ get_selection()!
+ if setcursor:
+ if sel:
+ self._set_selection(sel[0], sel[1])
+ self.text.mark_set("insert", sel[0])
+ else:
+ self._reset_selection()
+ self.text.see("insert")
+
+ self.text.tag_remove("findsel","1.0","end")
+ self._is_incremental = None
+ self._expand_state = None
+ self.engine.error_callback(None)
+ self.engine.save_cfg()
+ self.engine.remove_var_trace()
+
+ def _error_callback(self, pat, msg, col=-1):
+ # A callback for the SearchBarSearchEngine .report_error method
+ self.window_engine.reset()
+ pass
+
+ def is_incremental(self):
+ if self._is_incremental is None:
+ return get_cfg("is_incremental", default=False)
+ else:
+ return self._is_incremental
+
+ def _incremental_callback(self, *args):
+ self.engine._incrementalSearch = True
+
+ if self.shown and self.is_incremental():
+ if self.find_var.get():
+ self._safe_search(start=self.text.index("insert"))
+ else:
+ self.window_engine.reset()
+ self._clear_selection()
+ self.text.see("insert")
+
+ self.engine._incrementalSearch = False
+
+
+ def _build_widgets(self):
+ if not self.widgets_built:
+ def _make_entry(parent, label, var):
+ l = Tkinter.Label(parent, text=label)
+ l.pack(side=LEFT, fill=NONE, expand=0)
+ e = Tkinter.Entry(parent, textvariable=var, exportselection=0,
+ width=30, border=1)
+ e.pack(side=LEFT, fill=NONE, expand=0)
+ e.bind("", self.hide_findbar_event)
+ return e
+
+ def _make_checkbutton(parent, label, var):
+ btn = Tkinter.Checkbutton(parent, anchor="w",
+ text=label, variable=var)
+ btn.pack(side=LEFT, fill=NONE, expand=0)
+ btn.bind("", self.hide_findbar_event)
+ return btn
+
+ def _make_button(parent, label, command):
+ btn = Tkinter.Button(parent, text=label, command=command)
+ btn.pack(side=LEFT, fill=NONE, expand=0)
+ btn.bind("", self.hide_findbar_event)
+ return btn
+
+ # Frame for the entire bar
+ self.bar_frame = Tkinter.Frame(self.top, border=1, relief="flat")
+
+ # Frame for the 'Find:' / 'Replace:' entry and direction
+ self.find_frame = Tkinter.Frame(self.bar_frame, border=0)
+
+ # Frame for the 'Find:' options
+ self.find_frame_options = Tkinter.Frame(self.bar_frame, border=0) # RDS - 2011-11-12
+
+
+ tabstop_top = Tkinter.Label(self.find_frame, takefocus=1, text='',
+ highlightthickness=0)
+ tabstop_top.pack(side=LEFT)
+
+
+ # 'Find:' / 'Replace:' entry
+ if not self.is_replace: tmp = "Find:"
+ else: tmp = "Replace:"
+
+
+ self.find_ent = _make_entry(self.find_frame,
+ tmp, self.find_var)
+
+
+ # Regular expression checkbutton
+ btn = _make_checkbutton(self.find_frame_options,
+ "Reg-Exp", self.engine.revar)
+ if self.engine.isre():
+ btn.select()
+ self.reg_btn = btn
+
+ # Match case checkbutton
+ btn = _make_checkbutton(self.find_frame_options,
+ "Match case", self.engine.casevar)
+ if self.engine.iscase():
+ btn.select()
+ self.case_btn = btn
+
+ # Whole word checkbutton
+ btn = _make_checkbutton(self.find_frame_options,
+ "Whole word", self.engine.wordvar)
+ if self.engine.isword():
+ btn.select()
+ self.word_btn = btn
+
+ # Wrap checkbutton
+ btn = _make_checkbutton(self.find_frame_options,
+ "Wrap around", self.engine.wrapvar)
+ if self.engine.iswrap():
+ btn.select()
+ self.wrap_btn = btn
+
+
+ # Direction checkbutton
+ Tkinter.Label(self.find_frame, text="Direction:").pack(side=LEFT,
+ fill=NONE,
+ expand=0,padx=6)
+
+ self.direction_txt_var = Tkinter.StringVar(self.root)
+ btn = Tkinter.Checkbutton(self.find_frame,
+ textvariable=self.direction_txt_var,
+ variable=self.engine.backvar,
+ command=self._update_direction_button,
+ indicatoron=0,
+ width=5,
+ )
+ btn.config(selectcolor=btn.cget("bg"))
+ btn.pack(side=LEFT, fill=NONE, expand=0)
+
+ if self.engine.isback():
+ btn.select()
+ self.direction_txt_var.set("Up")
+ else:
+ btn.deselect()
+ self.direction_txt_var.set("Down")
+ btn.bind("",self.hide_findbar_event)
+ self.direction_btn = btn
+
+ self.find_frame.pack(side=TOP, fill=X, expand=1)
+ self.find_frame_options.pack(side=TOP, fill=X, expand=1)
+
+
+ if self.is_replace:
+ # Frame for the 'With:' entry + replace options
+ self.replace_frame = Tkinter.Frame(self.bar_frame, border=0)
+ self.replace_frame_buttons = Tkinter.Frame(self.bar_frame, border=0)
+
+ tmp = Tkinter.Label(self.replace_frame, takefocus=0, text='',
+ highlightthickness=0)
+ tmp.pack(side=LEFT)
+
+
+ self.replace_with_var = Tkinter.StringVar(self.root)
+ self.replace_ent = _make_entry(self.replace_frame,"With:",
+ self.replace_with_var)
+
+ self.find_btn = _make_button(self.replace_frame_buttons, "Find",
+ self._search)
+ self.replace_btn = _make_button(self.replace_frame_buttons, "Replace",
+ self._replace_event)
+ self.replace_find_btn = _make_button(self.replace_frame_buttons, "Replace+Find",
+ self._replace_find_event)
+ self.replace_all_btn = _make_button(self.replace_frame_buttons, "Replace All",
+ self._replace_all_event)
+
+ self.replace_frame.pack(side=TOP, fill=X, expand=0)
+ self.replace_frame_buttons.pack(side=TOP, fill=X, expand=0)
+
+ self.widgets_built = True
+
+ # Key bindings for the 'Find:' / 'Replace:' Entry widget
+ self.find_ent.bind("", self._safe_search)
+ self.find_ent.bind("", self._safe_search)
+ self.find_ent.bind("", self._toggle_reg_event)
+ self.find_ent.bind("", self._toggle_case_event)
+ self.find_ent.bind("", self._toggle_wrap_event)
+ self.find_ent.bind("", self._toggle_direction_event)
+ self.find_ent_expander = EntryExpander(self.find_ent, self.text)
+ self.find_ent_expander.bind("")
+
+ callback = self.find_ent._register(self._incremental_callback)
+ self.find_ent.tk.call("trace", "variable", self.find_var, "w",
+ callback)
+
+ keySetName = idleConf.CurrentKeys()
+ find_bindings = idleConf.GetKeyBinding(keySetName, '<>')
+ for key_event in find_bindings:
+ self.find_ent.bind(key_event, self._search) # RDS - 2011-11-03
+
+ if not self.is_replace:
+ # Key bindings for the 'Find:' Entry widget
+ self.find_ent.bind("", self._safe_search)
+
+
+ def tab_fix1(ev):
+ if ev.state & 1 == 0: # Windows Fix
+ self.find_ent.focus()
+ return "break"
+
+ self.wrap_btn.bind('', tab_fix1)
+
+ def tab_fix2(ev):
+ self.wrap_btn.focus()
+ return "break"
+ tabstop_top.bind('', tab_fix2)
+
+ else:
+ # Key bindings for the 'Replace:' Entry widget
+ self.find_ent.bind("", self._replace_bar_find_entry_return_event)
+
+ # Key bindings for the 'With:' Entry widget
+ self.replace_ent.bind("", self._replace_event)
+ self.replace_ent.bind("", self._safe_search)
+ self.replace_ent.bind("", self._safe_search)
+ self.replace_ent.bind("", self._safe_search)
+ self.replace_ent.bind("", self._toggle_reg_event)
+ self.replace_ent.bind("", self._toggle_case_event)
+ self.replace_ent.bind("", self._toggle_wrap_event)
+ self.replace_ent.bind("", self._toggle_direction_event)
+ self.replace_ent_expander = EntryExpander(self.replace_ent,
+ self.text)
+ self.replace_ent_expander.bind("")
+ for key_event in find_bindings:
+ self.replace_ent.bind(key_event, self._search) # RDS - 2011-11-19
+
+ def tab_fix1(ev):
+ if ev.state & 1 == 0: # Windows Fix
+ self.find_ent.focus()
+ return "break"
+ self.replace_all_btn.bind('', tab_fix1)
+
+ def tab_fix2(x):
+ self.replace_all_btn.focus()
+ return "break"
+ tabstop_top.bind('', tab_fix2)
+
+
+
+ def _destroy_widgets(self):
+ if self.widgets_built:
+ self.bar_frame.destroy()
+
+ def show_findbar_event(self, event):
+ self.text.tag_raise('findmark')
+ self.text.tag_raise('findsel')
+ self.text.tag_raise('sel')
+
+ # Get the current selection
+ sel = self._get_selection()
+ if sel:
+ # Put the current selection in the "Find:" entry
+ # FIXME: don't overwrite regexp if it matches the selection
+ self.find_var.set(self.text.get(sel[0],sel[1]))
+ self._clear_selection()
+
+ # Now show the FindBar in all it's glory!
+ self._show()
+
+ # Set the focus to the "Find:"/"Replace:" entry
+ self.find_ent.focus()
+
+ # Select all of the text in the "Find:"/"Replace:" entry
+ self.find_ent.selection_range(0,"end")
+
+ # Hide the findbar if the focus is lost
+ #self.bar_frame.bind("", self.hide_findbar_event)
+ # RDS - 2012-02-02 - Don't hide on focus_out, since regex error messages
+ # trigger this.
+
+ # Focus traversal (Tab or Shift-Tab) shouldn't return focus to
+ # the text widget
+ self.prev_text_takefocus_value = self.text.cget("takefocus")
+ self.text.config(takefocus=0)
+ self._incremental_callback()
+ return "break"
+
+ def text_focusin_event(self, event=None): # RDS - 2012-02-02
+ if not self.shown:
+ return
+ else:
+ self.hide_findbar_event(setcursor=False)
+
+ def hide_findbar_event(self, event=None, setcursor=True, focus=True):
+ if not self.shown:
+ return "break"
+
+ self._hide(setcursor=setcursor)
+ if focus:
+ self.text.focus()
+
+ return "break"
+
+
+ def search_again_event(self, event):
+ if self.engine.getpat():
+ return self._search(event)
+ else:
+ return self.show_findbar_event(event)
+
+ def search_selection_event(self, event):
+ # Get the current selection
+ sel = self._get_selection()
+ if not sel:
+ # No selection - beep and leave
+ self.text.bell()
+ return "break"
+
+ # Set the window's search engine's pattern to the current selection
+ self.find_var.set(self.text.get(sel[0],sel[1]))
+
+ return self._search(event)
+
+ def _toggle_reg_event(self, event):
+ self.reg_btn.invoke()
+ return "break"
+
+ def _toggle_case_event(self, event):
+ self.case_btn.invoke()
+ return "break"
+
+ def _toggle_wrap_event(self, event):
+ self.wrap_btn.invoke()
+ return "break"
+
+ def _toggle_direction_event(self, event):
+ self.direction_btn.invoke()
+ return "break"
+
+ def _update_direction_button(self):
+ if self.engine.backvar.get():
+ self.direction_txt_var.set("Up")
+ else:
+ self.direction_txt_var.set("Down")
+
+ def _replace_bar_find_entry_return_event(self, event=None):
+ # Set the focus to the "With:" entry
+ self.replace_ent.focus()
+ # Select all of the text in the "With:" entry
+ self.replace_ent.selection_range(0,"end")
+ return "break"
+
+ def _search_text(self, start, is_safe):
+ self.engine.patvar.set(self.find_var.get())
+ regexp = self.engine.getprog()
+
+ if not regexp:
+ # an error occurred.
+ return None
+
+ direction = not self.engine.isback()
+ wrap = self.engine.iswrap()
+ sel = self._get_selection()
+
+ if start is None:
+ if sel:
+ start = sel[0]
+ else:
+ start = self.text.index("insert")
+ if ( direction and sel and start == sel[0] and
+ regexp.match(self.text.get(sel[0],sel[1])) ):
+ _start = start + "+1c"
+ else:
+ _start = start
+ res = self.window_engine.findnext(regexp,
+ _start, direction, wrap, is_safe)
+
+ # ring the bell if the selection was found again
+ if sel and start == sel[0] and res == sel:
+ self.text.bell()
+
+ return res
+
+ def _search(self, event=None, start=None, is_safe=False):
+ t = time.time()
+ res = self._search_text(start, is_safe)
+ if res:
+ first, last = res
+ self._clear_selection()
+ self._set_selection(first, last)
+ self.text.see(first)
+ if not self.shown:
+ self.text.mark_set("insert", first)
+ else:
+ self._clear_selection()
+ self.text.bell()
+ return "break"
+
+ def _safe_search(self, event=None, start=None):
+ return self._search(event=event, start=start, is_safe=True)
+
+ def _replace_event(self, event=None):
+ self.engine.patvar.set(self.find_var.get())
+ regexp = self.engine.getprog()
+ if not regexp:
+ return "break"
+
+ # Replace if appropriate
+ sel = self._get_selection()
+ if sel and regexp.match(self.text.get(sel[0], sel[1])):
+ replace_with = self.replace_with_var.get()
+
+ self.text.undo_block_start()
+ if sel[0] != sel[1]:
+ self.text.delete(sel[0], sel[1])
+ if replace_with:
+ self.text.insert(sel[0], replace_with)
+ self.text.undo_block_stop()
+
+ self._clear_selection()
+ self._set_selection(sel[0], sel[0] + '+%ic' % len(replace_with))
+ self.text.mark_set("insert", sel[0] + '+%ic' % len(replace_with))
+
+ return "break"
+
+ def _replace_find_event(self, event=None): # RDS - 2011-10-18
+ self._replace_event(event)
+ return self._search(event, is_safe=False)
+
+ def _replace_all_event(self, event=None):
+ self.engine.patvar.set(self.find_var.get())
+ regexp = self.engine.getprog()
+ if not regexp:
+ return "break"
+
+ direction = not self.engine.isback()
+ wrap = self.engine.iswrap()
+ self.window_engine.replace_all(regexp, self.replace_with_var.get())
+ return "break"
+
+
+ ### Selection related methods
+ def _clear_selection(self):
+ tagname = self.shown and "findsel" or "sel"
+ self.text.tag_remove(tagname, "1.0", "end")
+
+ def _set_selection(self, start, end):
+ self._clear_selection()
+ tagname = self.shown and "findsel" or "sel"
+ self.text.tag_add(tagname, start, end)
+
+ def _get_selection(self):
+ tagname = self.shown and "findsel" or "sel"
+ return self.text.tag_nextrange(tagname, '1.0', 'end')
+
+ def _reset_selection(self):
+ if self.shown:
+ sel = self.text.tag_nextrange("sel", '1.0', 'end')
+ if sel:
+ self._set_selection(sel[0], sel[1])
+ else:
+ self._clear_selection()
+
+
+class EntryExpander(object):
+ """Expand words in an entry, taking possible words from a text widget."""
+ def __init__(self, entry, text):
+ self.text = text
+ self.entry = entry
+ self.reset()
+
+ self.entry.bind('