Repository: nvaccess/nvda

Issue:
Allow pasting multiple lines in the Python Console
### Is your feature request related to a problem? Please describe.
It is currently impossible to paste more than one line in the python console. It would be handy if this could be possible, as it will make copying and testing code from the console easier.

### Describe the solution you'd like
I think we should make the input control multi line as well, however it should of course keep the navigable history feature.



Discussion:
On my own Python console tweaking addon, I kept the field single line but added support to paste multiple lines like in a regular Python console.
I could prepare a PR out of it if you like.

Btw, other tweaks I use that one might find handy:
 - Preserve the underscore variable that stores the result of the last execution, while keeping the global gettext untouched.
 - Key bindings in the output pane to jump to next/previous result.
Wow, I think these things are certainly handy to be in core. So if you could file a pr for them, I'd be pretty interested.


Resolving patch:
diff --git a/source/pythonConsole.py b/source/pythonConsole.py
--- a/source/pythonConsole.py
+++ b/source/pythonConsole.py
@@ -13,6 +13,7 @@
 import __builtin__
 import os
 import code
+import codeop
 import sys
 import pydoc
 import re
@@ -64,6 +65,26 @@ def _callable_postfix(self, val, word):
 		# Just because something is callable doesn't always mean we want to call it.
 		return word
 
+class CommandCompiler(codeop.CommandCompiler):
+	"""
+	A L{codeop.CommandCompiler} exposing the status of the last compilation.
+	"""
+
+	def __init__(self):
+		# Old-style class
+		codeop.CommandCompiler.__init__(self)
+		#: Whether the last compilation was on error.
+		#: @type: bool
+		self.error = False
+
+	def __call__(self, *args, **kwargs):
+		self.error = False
+		try:
+			return codeop.CommandCompiler.__call__(self, *args, **kwargs)
+		except:
+			self.error = True
+			raise
+
 class PythonConsole(code.InteractiveConsole, AutoPropertyObject):
 	"""An interactive Python console for NVDA which directs output to supplied functions.
 	This is necessary for a Python console with input/output other than stdin/stdout/stderr.
@@ -89,6 +110,7 @@ def __init__(self, outputFunc, setPromptFunc, exitFunc, echoFunc=None, **kwargs)
 
 		# Can't use super here because stupid code.InteractiveConsole doesn't sub-class object. Grrr!
 		code.InteractiveConsole.__init__(self, locals=self.namespace, **kwargs)
+		self.compile = CommandCompiler()
 		self.prompt = ">>>"
 		self.lastResult = None
 
@@ -199,6 +221,7 @@ def __init__(self, parent):
 		inputSizer.Add(self.promptLabel, flag=wx.EXPAND)
 		self.inputCtrl = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_DONTWRAP | wx.TE_PROCESS_TAB)
 		self.inputCtrl.Bind(wx.EVT_CHAR, self.onInputChar)
+		self.inputCtrl.Bind(wx.EVT_TEXT_PASTE, self.onInputPaste)
 		inputSizer.Add(self.inputCtrl, proportion=1, flag=wx.EXPAND)
 		mainSizer.Add(inputSizer, proportion=1, flag=wx.EXPAND)
 		self.SetSizer(mainSizer)
@@ -359,6 +382,35 @@ def onInputChar(self, evt):
 			self.Close()
 			return
 		evt.Skip()
+	
+	def onInputPaste(self, evt):
+		cpText = api.getClipData()
+		if not cpText.strip():
+			evt.Skip()
+			return
+		cpLines = cpText.splitlines()
+		inputLine = self.inputCtrl.GetValue()
+		from_, to_ = self.inputCtrl.GetSelection()
+		prefix = inputLine[:from_]
+		suffix = inputLine[to_:]
+		for index, line in enumerate(cpLines):
+			if index == 0:
+				# First pasted line: Prepend the input text before the cursor
+				line = prefix + line
+			if index == len(cpLines) - 1:
+				# Last pasted line: Append the input text after the cursor
+				self.inputCtrl.ChangeValue(line + suffix)
+				self.inputCtrl.SetInsertionPoint(len(line))
+				return
+			self.inputCtrl.ChangeValue(line)
+			self.execute()
+			if self.console.compile.error:
+				# A compilation error occurred: Unlike in the standard Python
+				# Console, restore the original input text after the cursor and
+				# stop here to avoid execution of the remaining lines and ease
+				# reading of output errors.
+				self.inputCtrl.ChangeValue(suffix)
+				break
 
 	def onOutputKeyDown(self, evt):
 		key = evt.GetKeyCode()


Test patch: