Skip to content

Commit

Permalink
In the Python Console, you can now press the tab key to complete the …
Browse files Browse the repository at this point in the history
…current identifier.

Fixes #433.
  • Loading branch information
jcsteh committed Dec 16, 2013
2 parents 2334fc9 + 42351d0 commit 89f5a46
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 0 deletions.
97 changes: 97 additions & 0 deletions source/pythonConsole.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#pythonConsole.py
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2008-2013 NV Access Limited

"""Provides an interactive Python console which can be run from within NVDA.
To use, call L{initialize} to create a singleton instance of the console GUI. This can then be accessed externally as L{consoleUI}.
"""
Expand All @@ -7,6 +13,9 @@
import code
import sys
import pydoc
import re
import itertools
import rlcompleter
import wx
from baseObject import AutoPropertyObject
import speech
Expand Down Expand Up @@ -47,6 +56,12 @@ def __call__(self):
#: The singleton Python console UI instance.
consoleUI = None

class Completer(rlcompleter.Completer):

def _callable_postfix(self, val, word):
# Just because something is callable doesn't always mean we want to call it.
return word

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 @@ -163,6 +178,8 @@ def __init__(self, parent):
mainSizer.Fit(self)

self.console = PythonConsole(outputFunc=self.output, echoFunc=self.echo, setPromptFunc=self.setPrompt, exitFunc=self.Close)
self.completer = Completer(namespace=self.console.namespace)
self.completionAmbiguous = False
# Even the most recent line has a position in the history, so initialise with one blank line.
self.inputHistory = [""]
self.inputHistoryPos = 0
Expand Down Expand Up @@ -216,8 +233,88 @@ def historyMove(self, movement):
self.inputCtrl.SetInsertionPointEnd()
return True

RE_COMPLETE_UNIT = re.compile(r"[\w.]*$")
def complete(self):
try:
original = self.RE_COMPLETE_UNIT.search(self.inputCtrl.GetValue()).group(0)
except AttributeError:
return False

completions = list(self._getCompletions(original))
if self.completionAmbiguous:
menu = wx.Menu()
for comp in completions:
item = menu.Append(wx.ID_ANY, comp)
self.Bind(wx.EVT_MENU,
lambda evt, completion=comp: self._insertCompletion(original, completion),
item)
self.PopupMenu(menu)
menu.Destroy()
return True
self.completionAmbiguous = len(completions) > 1

completed = self._findBestCompletion(original, completions)
if not completed:
return False
self._insertCompletion(original, completed)
return not self.completionAmbiguous

def _getCompletions(self, original):
for state in itertools.count():
completion = self.completer.complete(original, state)
if not completion:
break
yield completion

def _findBestCompletion(self, original, completions):
if not completions:
return None
if len(completions) == 1:
return completions[0]

# Find the longest completion.
longestComp = None
longestCompLen = 0
for comp in completions:
compLen = len(comp)
if compLen > longestCompLen:
longestComp = comp
longestCompLen = compLen
# Find the longest common prefix.
for prefixLen in xrange(longestCompLen, 0, -1):
prefix = comp[:prefixLen]
for comp in completions:
if not comp.startswith(prefix):
break
else:
# This prefix is common to all completions.
if prefix == original:
# We didn't actually complete anything.
return None
return prefix
return None

def _insertCompletion(self, original, completed):
self.completionAmbiguous = False
insert = completed[len(original):]
if not insert:
return
self.inputCtrl.SetValue(self.inputCtrl.GetValue() + insert)
queueHandler.queueFunction(queueHandler.eventQueue, speech.speakText, insert)
self.inputCtrl.SetInsertionPointEnd()

def onInputChar(self, evt):
key = evt.GetKeyCode()

if key == wx.WXK_TAB:
line = self.inputCtrl.GetValue()
if line and not line.isspace():
if not self.complete():
wx.Bell()
return
# This is something other than autocompletion, so reset autocompletion state.
self.completionAmbiguous = False

if key == wx.WXK_RETURN:
self.execute()
return
Expand Down
2 changes: 2 additions & 0 deletions user_docs/en/changes.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

== Changes for Developers ==
- AppModules now contain productName and productVersion properties. This info is also now included in Developer Info (NVDA+f1). (#1625)
- In the Python Console, you can now press the tab key to complete the current identifier. (#433)
- If there are multiple possibilities, you can press tab a second time to choose from a list.


= 2013.3 =
Expand Down

0 comments on commit 89f5a46

Please sign in to comment.