Skip to content

Commit

Permalink
Allow pasting multiple lines in the Python Console (nvaccess#9776)
Browse files Browse the repository at this point in the history
  • Loading branch information
JulienCochuyt committed Jun 24, 2019
1 parent 4a78085 commit 8dc77b7
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 2 deletions.
4 changes: 2 additions & 2 deletions developerGuide.t2t
Expand Up @@ -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.
Expand Down
52 changes: 52 additions & 0 deletions source/pythonConsole.py
Expand Up @@ -13,6 +13,7 @@
import __builtin__
import os
import code
import codeop
import sys
import pydoc
import re
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -102,6 +123,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.prompt = ">>>"
self.compile = CommandCompiler()

def _set_prompt(self, prompt):
self._prompt = prompt
Expand Down Expand Up @@ -175,6 +197,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)
Expand Down Expand Up @@ -335,6 +358,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()
Expand Down

0 comments on commit 8dc77b7

Please sign in to comment.