# This file patches PyConsole.py to add history substitution to the IDE.
# (See the module comment for more specific information.)
# Requires http://www.strout.net/python/mac/PatchUtils.py
#
# Version 2 released to the public domain 23 October 1999
# by Oliver Steele, steele@cs.brandeis.edu

""" CommandHistory patch, version 2

FEATURES

Ctl-uparrow in the console window grabs the previous command from the
buffer, and replaces the current command with it.  Repeated grabs move
backwards through the history until they reach the beginning of the window,
and then wrap around.

Ctl-downarrow grabs the next command, and wraps from the bottom to the top.

Return anywhere on the current input line executes all the text to the end
on the line.  (The old behavior was to execute the text up to the insertion
point, ignoring any following text.)

Return on a previous line appends that line (minus the prompt) to the current
input line.

Any other non-navigation key on a previous appends that line to the input line,
moves the selection to the corresponding position within the appended text,
and then types the key.

INSTALLATION INSTRUCTIONS

Retrieve http://www.strout.net/python/mac/PatchUtils.py, put it in the
Python search path or in the same directory as this file, and drag this
file onto PythonInterpreter.  """

# Change history:
# 10/23/99 2
#  -changed option-p to ctl-uparrow
# - added ctl-downarrow
# - typing in a previous line copies the line and moves the selection
# 10/13/99 0.2
# - changed '\r' to '\\r', '\t' to '\\t' in the patch script
# 10/9/99 0.1
# - initial release
#
# To do:
# - uparrow/downarrow should move past the prompt
# -Clear with no selection should erase the current input line
# - should Next and Previous skip repeated lines?

import os
import sys
import string
from PatchUtils import *

basePath = sys.exec_prefix + 'Mac:Tools:IDE:'

class ExtendedPatch(Patch):
	"""An extension to PatchUtils.Patch, that adds ReplaceLines for multiple-line search
	replacement."""
	
	def _FindLines(self, searchLines, callerName):
		lineno = None
		for i in range(len(self.lines)):
			if self.lines[i:i+len(searchLines)] == searchLines:
				if lineno is not None:
					raise callerName+"_NotUnique", searchLines[0][:-1] + '...'
				lineno = i
		if lineno is None:
			raise callerName+"_NotFound", searchLines[0][:-1] + '...'
		return lineno
	
	def ReplaceLines(self, searchString, replaceString):
		searchLines = map(lambda line:line + '\n', string.split(searchString, '\n'))
		replaceLines = map(lambda line:line + '\n', string.split(replaceString, '\n'))
		startline = self._FindLines(searchLines, "ReplaceLines")
		endline = startline + len(searchLines)
		# we've found it; now, replace this line with the given ones
		print "Substituting %d line(s) for lines %d:%d" % (len(replaceLines), startline, endline)
		self.lines[startline:endline] = replaceLines
		self.qtyLines = len(self.lines)

######################################################################
PyEdit = ExtendedPatch(basePath, "PyConsole.py", "CommandHistory")

PyEdit.ReplaceLines(
r"""			if char not in Wkeys.navigationkeys:
				self.checkselection()
			if char == Wkeys.enterkey:
				char = Wkeys.returnkey
			selstart, selend = self.getselection()""",
r"""			modifierKeys = modifiers & (Events.cmdKey | Events.shiftKey | Events.optionKey | Events.controlKey)
			selstart, selend = self.getselection()
			if char == Wkeys.enterkey:
				char = Wkeys.returnkey
			if char not in Wkeys.navigationkeys:
				if selend < self._inputstart:
					# Copy the whole line, with the prompt stripped
					lineno = self.ted.WEOffsetToLine(selstart)
					copystart, copyend = self.ted.WEGetLineRange(lineno)
					text = self.get()[copystart:copyend][:-1]
					for prompt in (sys.ps1, sys.ps2):
						if text[:len(prompt)] == prompt:
							text = text[len(prompt):]
							copystart = copystart + len(prompt)
							break
					insertionpos = len(self.get())
					self.ted.WESetSelection(insertionpos, insertionpos)
					self.ted.WEInsert(text, None, None)
					if char == Wkeys.returnkey:
						return
					# Move the selection to the corresponding position in the new line
					selstart = selstart + insertionpos - copystart
					selend = selend + insertionpos - copystart
					self.ted.WESetSelection(selstart, selend)
				self.checkselection()
				selstart, selend = self.getselection()
			if char == chr(30) and modifierKeys == Events.controlKey:	# cmd-uparrow
				self.previousCommand()
				return
			elif char == chr(31) and modifierKeys == Events.controlKey:	# cmd-downarrow
				self.previousCommand(delta=1)
				return""")

PyEdit.ReplaceLine("			self.ted.WEKey(ord(char), modifiers)",
r"""			if char != Wkeys.returnkey:
				self.ted.WEKey(ord(char), modifiers)""")

PyEdit.ReplaceLines(
r"""			self.updatescrollbars()
			if char == Wkeys.returnkey:""",
r"""			self.updatescrollbars()
			if char == Wkeys.returnkey:
				text = self.get()[selend:] + '\r'
				if '\r' in text:
					selstart = selend = selend + string.find(text, '\r')
					self.ted.WESetSelection(selstart, selend)
				self.ted.WEKey(ord(char), modifiers)""")

PyEdit.InsertAfter("				self._inputstart = selstart",
r'''				self.commandHistoryCursor = None
	
	def previousCommand(self, delta=-1):
		def commandLineText(line):
			"""Return the line stripped of the prompt, if it begins with one, else None."""
			if line[:len(sys.ps1)] == sys.ps1:
				return line[len(sys.ps1):]
			elif line[:len(sys.ps2)] == sys.ps2:
				command = line[len(sys.ps2):]
		# Retrieve the lines, ignoring the last one, which is the current input
		lines = string.split(self.get(), '\r')[:-1]
		# Retrieve the input strings, and remove lines that either aren't command
		# lines (None), or have empty inputs ('')
		lines = filter(None, map(lambda line,f=commandLineText:f(line), lines))
		if not lines:
			return
		# Either pick up where the last history command left off, or start at the
		# current line (which isn't a valid index position, but the % fixes this.
		index = getattr(self, 'commandHistoryCursor', None) or len(lines)
		# Then take one step, and wrap around
		index = (index + delta) % len(lines)
		command = lines[index]
		self.commandHistoryCursor = index
		selstart, selend = self.getselection()
		self.ted.WESetSelection(self._inputstart, len(self.get()))
		self.ted.WEInsert(command, None, None)''')

######################################################################

# All files have been patched in memory, so write to disk.
print

PyEdit.Write()

print "\nQuit the IDE and restart it to use the new functionality."