#	Programmer:	Daniel Pozmanter
#	E-mail:		drpython@bluebottle.com
#	Note:		You must reply to the verification e-mail to get through.
#
#	Copyright 2003-2005 Daniel Pozmanter
#
#	Distributed under the terms of the GPL (GNU Public License)
#
#    DrPython is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#The Prompt

import sys, os.path, re, traceback
import wx
import wx.stc
from drProperty import *
import drEncoding
import drKeywords
import drSTC

reserved = [wx.stc.STC_CMD_NEWLINE, wx.stc.STC_CMD_CHARLEFT,
wx.stc.STC_CMD_CHARRIGHT, wx.stc.STC_CMD_LINEUP, wx.stc.STC_CMD_LINEDOWN,
wx.stc.STC_CMD_DELETEBACK, wx.stc.STC_CMD_HOME]

class DrPrompt(drSTC.DrStyledTextControl):
	def __init__(self, parent, id, grandparent):
		drSTC.DrStyledTextControl.__init__(self, parent, id, grandparent)

		#Maximum Number of Commands to Keep Track of in Prompt
		self.MAX_PROMPT_COMMANDS = 25
		self.CommandArray = []
		self.CommandArrayPos = -1
		
		self.IsAPrompt = True
		
		self.editpoint = 0

		#Process
		self.process = None
		self.pid = -1
		self.pythonintepreter = 0

		self.commandinprogress = False

		#Goto Traceback:
		self.reTFilename = re.compile('\".*\"')
		self.reTLinenumber = re.compile('line.*\d')

		self.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)

		self.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModified, id=id)
		self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
		self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
		self.Bind(wx.EVT_UPDATE_UI,  self.RunCheck, id=id)

		self.Bind(wx.EVT_IDLE, self.OnIdle)

		self.Bind(wx.EVT_LEFT_DCLICK, self.OnGotoTraceback)

	def _addoutput(self, checkinput=True):
		wx.Usleep(25)
		a = ''
		if checkinput and self.process.IsInputAvailable():
			a = self._getoutput(self.inputstream)
		if self.process.IsErrorAvailable():
			a += self._getoutput(self.errorstream)
		self.AddText(a)
		return a

	def _getoutput(self, targetstream):
		#used to get the output of a command in the prompt, and immediately add it to the prompt.
		added = False
		while (not added):
			if targetstream.CanRead():
				text = targetstream.read()
				added = True
		return text

	def _waitforoutput(self, targetoutput):
		'''
		Dangerous!  Only use this if you know what you are doing.
		Waits for output that has not yet appeared to show up
		in either the stdout or stderr of the current process.
		'''
		
		text = ''
		while True:
			if text.find(targetoutput) > -1:
				return
			text = self._addoutput()
	
	def AddEncodedText(self, text):
		try:
			etext = drEncoding.EncodeText(self.grandparent, text)
			wx.stc.StyledTextCtrl.AddText(self, etext)
		except:
			print 'Error Encoding Text'

	def AddText(self, text):
		ro = self.GetReadOnly()
		self.SetReadOnly(0)
		self.AddEncodedText(text)
		self.SetReadOnly(ro)

	def ExecuteCommands(self, text):
		'''Executes Commands Separated by '\n' '''
		if len(text) == 0:
			return
		self._addoutput()
		self.commandinprogress = True
		text = text.rstrip()
		commands = text.split('\n')
		for command in commands:
			command += '\n'
			self._addoutput()
			try:
				etext = drEncoding.EncodeText(self.grandparent, command)
			except:
				print 'Error Encoding Text'
				return
			self.outputstream.write(etext)
			self.GotoPos(self.GetLength())
			wx.stc.StyledTextCtrl.AddText(self, etext)
			self._addoutput((len(command) > 1))
			self.editpoint = self.GetLength()
			self.ScrollToLine(self.LineFromPosition(self.editpoint))
		self.commandinprogress = False

	def GetEditPoint(self):
		return self.editpoint

	def InsertEncodedText(self, pos, text):
		try:
			etext = drEncoding.EncodeText(self.grandparent, text)
			wx.stc.StyledTextCtrl.InsertText(self, pos, etext)
		except:
			print 'Error Encoding Text'

	def InsertText(self, pos, text):
		ro = self.GetReadOnly()
		self.SetReadOnly(0)
		self.InsertEncodedText(pos, text)
		self.SetReadOnly(ro)

	def OnIdle(self, event):
		if (self.process is not None) and (not self.commandinprogress):
			if self.inputstream.CanRead():
				text = self.inputstream.read()
				self.AddEncodedText(text)
				self.EmptyUndoBuffer()
				self.editpoint = self.GetLength()
				self.GotoPos(self.editpoint)
				self.ScrollToLine(self.LineFromPosition(self.editpoint))
			if self.errorstream.CanRead():
				text = self.errorstream.read()
				self.AddEncodedText(text)
				self.EmptyUndoBuffer()
				self.editpoint = self.GetLength()
				self.GotoPos(self.editpoint)
				self.ScrollToLine(self.LineFromPosition(self.editpoint))

	def OnGotoTraceback(self, event):
		line = self.GetLine(self.GetCurrentLine())
		fn = self.reTFilename.search(line)
		ln = self.reTLinenumber.search(line)
		if (fn is not None) and (ln is not None):
			filename = fn.group().strip('\"').replace('\\', '/')
			try:
				linenumber = int(ln.group().strip('line ')) - 1
			except:
				linenumber = 0
			if os.path.exists(filename):
				alreadyopen = map(lambda x: x.filename, self.grandparent.txtDocumentArray)
				if filename in alreadyopen:
					i = alreadyopen.index(filename)
					self.grandparent.setDocumentTo(i)
				else:
					self.grandparent.OpenFile(filename, (len(self.grandparent.txtDocument.filename) > 0))
				self.grandparent.txtDocument.ScrollToLine(linenumber)
				self.grandparent.txtDocument.GotoLine(linenumber)
				self.grandparent.txtDocument.EnsureCaretVisible()
				self.grandparent.txtDocument.SetFocus()

	def OnKeyDown(self, event):
		if self.pid == -1:
			return
		result = self.grandparent.RunShortcuts(event)
		if result > -1:
			pos = self.GetCurrentPos()
			if (not self.pid == -1):
				if (pos >= self.editpoint) and (result == wx.stc.STC_CMD_NEWLINE):
					self.commandinprogress = True

					text = self.GetTextRange(self.editpoint, self.GetLength())
					l = len(self.CommandArray)
					if (l < self.MAX_PROMPT_COMMANDS):
						if text in self.CommandArray:
							self.CommandArray.pop(self.CommandArray.index(text))
						self.CommandArray.insert(0, text)
						self.CommandArrayPos = -1
					else:
						self.CommandArray.pop()
						self.CommandArray.insert(0, text)
						self.CommandArrayPos = -1
					if len(text) == 0:
						text = '\n'
						self.GotoPos(self.GetLength())
						self.AddText(self.GetEndOfLineCharacter())
					elif text[-1] != '\n':
						text += '\n'
						self.GotoPos(self.GetLength())
						self.AddText(self.GetEndOfLineCharacter())
					try:
						etext = drEncoding.EncodeText(self.grandparent, text)
					except:
						print 'Error Encoding Text'
						return

					self.outputstream.write(etext)
					self.GotoPos(self.GetLength())

					self._addoutput()

					self.editpoint = self.GetLength()
					self.ScrollToLine(self.LineFromPosition(self.editpoint))
					self.commandinprogress = False
				elif (result == wx.stc.STC_CMD_LINEUP):
					l = len(self.CommandArray)
					if (len(self.CommandArray) > 0):
						if (self.CommandArrayPos + 1) < l:
							self.GotoPos(self.editpoint)
							self.SetTargetStart(self.editpoint)
							self.SetTargetEnd(self.GetLength())
							self.CommandArrayPos = self.CommandArrayPos + 1
							self.ReplaceTarget(self.CommandArray[self.CommandArrayPos])

				elif (result == wx.stc.STC_CMD_LINEDOWN):
					if (len(self.CommandArray) > 0):
						self.GotoPos(self.editpoint)
						self.SetTargetStart(self.editpoint)
						self.SetTargetEnd(self.GetLength())
						if (self.CommandArrayPos - 1) > -1:
							self.CommandArrayPos = self.CommandArrayPos - 1
							self.ReplaceTarget(self.CommandArray[self.CommandArrayPos])
						else:
							if (self.CommandArrayPos - 1) > -2:
								self.CommandArrayPos = self.CommandArrayPos - 1
							self.ReplaceTarget("")
							
			if (((pos > self.editpoint) and (result in reserved)) or
			(pos >= self.editpoint) and (result == wx.stc.STC_CMD_CHARRIGHT)):
				event.Skip()

	def OnKeyUp(self, event):
		if self.pid == -1:
			event.Skip()
			return
		keycode = event.GetKeyCode()
		#franz: pos was not used
		if keycode == wx.WXK_HOME:
			if (self.GetCurrentPos() < self.editpoint):
				self.GotoPos(self.editpoint)
			return
		elif keycode == wx.WXK_PRIOR:
			if (self.GetCurrentPos() < self.editpoint):
				self.GotoPos(self.editpoint)
			return
		event.Skip()

	def OnModified(self, event):
		if not (self.grandparent.prefs.promptwordwrap):
			ll = self.TextWidth(wx.stc.STC_STYLE_DEFAULT, "OOO")
			x = 0
			spaces = ""
			while (x < self.grandparent.prefs.prompttabwidth):
				spaces = spaces + " "
				x = x + 1
			current_width = self.GetScrollWidth()
			line = self.GetCurLine()[0].replace('\t', spaces)
			actual_width = self.TextWidth(wx.stc.STC_STYLE_DEFAULT, line)
			if (current_width < actual_width):
				self.SetScrollWidth(actual_width + ll)

	def RunCheck(self, event):
		if (self.GetCurrentPos() < self.editpoint) or (self.pid == -1):
			self.SetReadOnly(1)
		else:
			self.SetReadOnly(0)

	def SetupPrefsPrompt(self, notmdiupdate = 1):
		self.SetEndAtLastLine(not self.grandparent.prefs.promptscrollextrapage)

		if notmdiupdate:
			self.SetViewWhiteSpace(self.grandparent.prefs.promptwhitespaceisvisible)
			self.SetViewEOL(self.grandparent.prefs.promptwhitespaceisvisible and self.grandparent.prefs.vieweol)

		if (self.grandparent.prefs.promptwordwrap):
			self.SetWrapMode(wx.stc.STC_WRAP_WORD)
		else:
			self.SetWrapMode(wx.stc.STC_WRAP_NONE)
		if (self.grandparent.prefs.prompteolmode == 1):
			self.SetEOLMode(wx.stc.STC_EOL_CRLF)
		elif (self.grandparent.prefs.prompteolmode == 2):
			self.SetEOLMode(wx.stc.STC_EOL_CR)
		else:
			self.SetEOLMode(wx.stc.STC_EOL_LF)
		self.SetTabWidth(self.grandparent.prefs.prompttabwidth)
		self.SetUseTabs(self.grandparent.prefs.promptusetabs)
		self.SetMarginWidth(1, self.grandparent.prefs.promptmarginwidth)

		if self.grandparent.prefs.promptusestyles:

			self.SetKeyWords(0, drKeywords.GetKeyWords(0))

			self.SetLexer(drKeywords.GetLexer(0))

			self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, self.grandparent.prefs.txtPromptStyleDictionary[0])

			self.StyleClearAll()

			self.StartStyling(0, 0xff)

			self.SetCaretWidth(self.grandparent.prefs.promptcaretwidth)

			self.SetCaretForeground(self.grandparent.prefs.txtPromptStyleDictionary[15])

			if (self.grandparent.prefs.promptusestyles < 2):
				self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER, self.grandparent.prefs.txtPromptStyleDictionary[1])
				self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT, self.grandparent.prefs.txtPromptStyleDictionary[2])
				self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD, self.grandparent.prefs.txtPromptStyleDictionary[3])
				self.StyleSetSpec(wx.stc.STC_P_CHARACTER, self.grandparent.prefs.txtPromptStyleDictionary[4])
				self.StyleSetSpec(wx.stc.STC_P_CLASSNAME, self.grandparent.prefs.txtPromptStyleDictionary[5])
				self.StyleSetSpec(wx.stc.STC_P_COMMENTLINE, self.grandparent.prefs.txtPromptStyleDictionary[6])
				self.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, self.grandparent.prefs.txtPromptStyleDictionary[7])
				self.StyleSetSpec(wx.stc.STC_P_DEFNAME, self.grandparent.prefs.txtPromptStyleDictionary[8])
				self.StyleSetSpec(wx.stc.STC_P_WORD, self.grandparent.prefs.txtPromptStyleDictionary[9])
				self.StyleSetSpec(wx.stc.STC_P_NUMBER, self.grandparent.prefs.txtPromptStyleDictionary[10])
				self.StyleSetSpec(wx.stc.STC_P_OPERATOR, self.grandparent.prefs.txtPromptStyleDictionary[11])
				self.StyleSetSpec(wx.stc.STC_P_STRING, self.grandparent.prefs.txtPromptStyleDictionary[12])
				self.StyleSetSpec(wx.stc.STC_P_STRINGEOL, self.grandparent.prefs.txtPromptStyleDictionary[13])
				self.StyleSetSpec(wx.stc.STC_P_TRIPLE, self.grandparent.prefs.txtPromptStyleDictionary[14])
				self.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, self.grandparent.prefs.txtPromptStyleDictionary[14])

				self.SetSelForeground(1, getStyleProperty("fore", self.grandparent.prefs.txtPromptStyleDictionary[16]))
				self.SetSelBackground(1, getStyleProperty("back", self.grandparent.prefs.txtPromptStyleDictionary[16]))

	def SetText(self, text):
		ro = self.GetReadOnly()
		self.SetReadOnly(0)
		wx.stc.StyledTextCtrl.SetText(self, text)
		self.SetReadOnly(ro)

	def SetSelectedText(self, text):
		ro = self.GetReadOnly()
		self.SetReadOnly(0)
		self.SetTargetStart(self.GetSelectionStart())
		self.SetTargetEnd(self.GetSelectionEnd())
		self.ReplaceTarget(text)
		self.SetReadOnly(ro)

syntax highlighted by Code2HTML, v. 0.9.1