Skip to content

Commit

Permalink
Allow pasting multiple lines in the Python Console (PR #9781)
Browse files Browse the repository at this point in the history
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 80acce2
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 2 deletions.
4 changes: 2 additions & 2 deletions developerGuide.t2t
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -764,8 +764,8 @@ See [Snapshot Variables #PythonConsoleSnapshotVariables] for more details.
- -


The console is similar to the standard interactive Python interpreter. The console is similar to the standard interactive Python interpreter.
Input is accepted one line at a time. Input is accepted one line at a time and processed when enter is pressed.
The current line is 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. 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. Output (responses from the interpreter) will be spoken when enter is pressed.
Expand Down
52 changes: 52 additions & 0 deletions source/pythonConsole.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import __builtin__ import __builtin__
import os import os
import code import code
import codeop
import sys import sys
import pydoc import pydoc
import re 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. # Just because something is callable doesn't always mean we want to call it.
return word 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): class PythonConsole(code.InteractiveConsole, AutoPropertyObject):
"""An interactive Python console for NVDA which directs output to supplied functions. """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. This is necessary for a Python console with input/output other than stdin/stdout/stderr.
Expand All @@ -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! # Can't use super here because stupid code.InteractiveConsole doesn't sub-class object. Grrr!
code.InteractiveConsole.__init__(self, locals=self.namespace, **kwargs) code.InteractiveConsole.__init__(self, locals=self.namespace, **kwargs)
self.compile = CommandCompiler()
self.prompt = ">>>" self.prompt = ">>>"
self.lastResult = None self.lastResult = None


Expand Down Expand Up @@ -199,6 +221,7 @@ def __init__(self, parent):
inputSizer.Add(self.promptLabel, flag=wx.EXPAND) 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 = 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_CHAR, self.onInputChar)
self.inputCtrl.Bind(wx.EVT_TEXT_PASTE, self.onInputPaste)
inputSizer.Add(self.inputCtrl, proportion=1, flag=wx.EXPAND) inputSizer.Add(self.inputCtrl, proportion=1, flag=wx.EXPAND)
mainSizer.Add(inputSizer, proportion=1, flag=wx.EXPAND) mainSizer.Add(inputSizer, proportion=1, flag=wx.EXPAND)
self.SetSizer(mainSizer) self.SetSizer(mainSizer)
Expand Down Expand Up @@ -359,6 +382,35 @@ def onInputChar(self, evt):
self.Close() self.Close()
return return
evt.Skip() 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): def onOutputKeyDown(self, evt):
key = evt.GetKeyCode() key = evt.GetKeyCode()
Expand Down

0 comments on commit 80acce2

Please sign in to comment.