Skip to content
Permalink
Browse files

Allow pasting multiple lines in the Python Console (PR #9781)

In the Python Console, the input field now supports pasting multiple lines from the clipboard.

Fixes #9776
  • Loading branch information...
JulienCochuyt authored and feerrenrut committed Jul 1, 2019
1 parent b6c1a92 commit 80acce2051a2e00cd22a700c9275ef0ebfd1f9b2
Showing with 54 additions and 2 deletions.
  1. +2 −2 developerGuide.t2t
  2. +52 −0 source/pythonConsole.py
@@ -764,8 +764,8 @@ See [Snapshot Variables #PythonConsoleSnapshotVariables] for more details.
-

The console is similar to the standard interactive Python interpreter.
Input is accepted one line at a time.
The current line is processed when enter is pressed.
Input is accepted one line at a time and processed when enter is pressed.
Multiple lines can be pasted at once from the clipboard and will be processed one by one.
You can navigate through the history of previously entered lines using the up and down arrow keys.

Output (responses from the interpreter) will be spoken when enter is pressed.
@@ -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()

0 comments on commit 80acce2

Please sign in to comment.
You can’t perform that action at this time.