##// END OF EJS Templates
Addressing various review comments.
Addressing various review comments.

File last commit:

r1947:3fc35df3
r1958:5de9294b
Show More
linefrontendbase.py
372 lines | 13.0 KiB | text/x-python | PythonLexer
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 """
gvaroquaux
Clean up code, names, and docstrings.
r1455 Base front end class for all line-oriented frontends, rather than
block-oriented.
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355
Currently this focuses on synchronous frontends.
"""
__docformat__ = "restructuredtext en"
#-------------------------------------------------------------------------------
# Copyright (C) 2008 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Imports
#-------------------------------------------------------------------------------
import re
Gael Varoquaux
More tests of the frontend. Improve the ease of testing.
r1458 import sys
gvaroquaux
Better handling of errors in multiline inputs.
r1638 import codeop
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355
from frontendbase import FrontEndBase
from IPython.kernel.core.interpreter import Interpreter
Gael Varoquaux
Proper tab completion that actually completes.
r1379 def common_prefix(strings):
gvaroquaux
Clean up code, names, and docstrings.
r1455 """ Given a list of strings, return the common prefix between all
these strings.
"""
Gael Varoquaux
Proper tab completion that actually completes.
r1379 ref = strings[0]
prefix = ''
for size in range(len(ref)):
test_prefix = ref[:size+1]
for string in strings[1:]:
if not string.startswith(test_prefix):
return prefix
prefix = test_prefix
return prefix
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 #-------------------------------------------------------------------------------
# Base class for the line-oriented front ends
#-------------------------------------------------------------------------------
class LineFrontEndBase(FrontEndBase):
gvaroquaux
Clean up code, names, and docstrings.
r1455 """ Concrete implementation of the FrontEndBase class. This is meant
to be the base class behind all the frontend that are line-oriented,
rather than block-oriented.
"""
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355
Gael Varoquaux
Traceback capture now working.
r1360 # We need to keep the prompt number, to be able to increment
# it when there is an exception.
prompt_number = 1
gvaroquaux
More tests....
r1460 # We keep a reference to the last result: it helps testing and
# programatic control of the frontend.
Gael Varoquaux
Improve tab-completion.
r1373 last_result = dict(number=0)
Gael Varoquaux
Traceback capture now working.
r1360
Gael Varoquaux
MERGE Laurent's tweaks. Clean up'
r1893 # The last prompt displayed. Useful for continuation prompts.
last_prompt = ''
gvaroquaux
More code reuse between GUI-independant frontend and Wx frontend: getting...
r1462 # The input buffer being edited
input_buffer = ''
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 # Set to true for debug output
debug = False
Gael Varoquaux
Add a banner.
r1495 # A banner to print at startup
banner = None
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 #--------------------------------------------------------------------------
gvaroquaux
Clean up code, names, and docstrings.
r1455 # FrontEndBase interface
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 #--------------------------------------------------------------------------
Gael Varoquaux
Add a banner.
r1495 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 if shell is None:
shell = Interpreter()
FrontEndBase.__init__(self, shell=shell, history=history)
Gael Varoquaux
Add a banner.
r1495
if banner is not None:
self.banner = banner
Gael Varoquaux
Fix a completion crasher (index error during completion)....
r1624
def start(self):
""" Put the frontend in a state where it is ready for user
interaction.
"""
Gael Varoquaux
Add a banner.
r1495 if self.banner is not None:
self.write(self.banner, refresh=False)
Gael Varoquaux
More tests of the frontend. Improve the ease of testing.
r1458 self.new_prompt(self.input_prompt_template.substitute(number=1))
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355
Gael Varoquaux
Proper tab completion that actually completes.
r1379 def complete(self, line):
"""Complete line in engine's user_ns
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355
Parameters
----------
Gael Varoquaux
Proper tab completion that actually completes.
r1379 line : string
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355
Result
------
Gael Varoquaux
Proper tab completion that actually completes.
r1379 The replacement for the line and the list of possible completions.
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 """
Gael Varoquaux
Proper tab completion that actually completes.
r1379 completions = self.shell.complete(line)
complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
if completions:
prefix = common_prefix(completions)
residual = complete_sep.split(line)[:-1]
line = line[:-len(residual)] + prefix
return line, completions
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355
def render_result(self, result):
gvaroquaux
Clean up code, names, and docstrings.
r1455 """ Frontend-specific rendering of the result of a calculation
that has been sent to an engine.
"""
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 if 'stdout' in result and result['stdout']:
self.write('\n' + result['stdout'])
if 'display' in result and result['display']:
self.write("%s%s\n" % (
Gael Varoquaux
More tests of the frontend. Improve the ease of testing.
r1458 self.output_prompt_template.substitute(
number=result['number']),
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 result['display']['pprint']
) )
Gael Varoquaux
Traceback capture now working.
r1360
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 def render_error(self, failure):
gvaroquaux
Clean up code, names, and docstrings.
r1455 """ Frontend-specific rendering of error.
"""
Gael Varoquaux
More tests of the frontend. Improve the ease of testing.
r1458 self.write('\n\n'+str(failure)+'\n\n')
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 return failure
Gael Varoquaux
Traceback capture now working.
r1360
def is_complete(self, string):
gvaroquaux
Clean up code, names, and docstrings.
r1455 """ Check if a string forms a complete, executable set of
commands.
For the line-oriented frontend, multi-line code is not executed
as soon as it is complete: the users has to enter two line
returns.
"""
Gael Varoquaux
Improve tab-completion.
r1373 if string in ('', '\n'):
gvaroquaux
Clean up code, names, and docstrings.
r1455 # Prefiltering, eg through ipython0, may return an empty
# string although some operations have been accomplished. We
# thus want to consider an empty string as a complete
# statement.
Gael Varoquaux
Improve tab-completion.
r1373 return True
gvaroquaux
More code reuse between GUI-independant frontend and Wx frontend: getting...
r1462 elif ( len(self.input_buffer.split('\n'))>2
Gael Varoquaux
Correct small history bug. Add busy cursor.
r1371 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
Gael Varoquaux
Traceback capture now working.
r1360 return False
else:
gvaroquaux
Capture errors in user's input lines and raises meaningfull exceptions.
r1639 self.capture_output()
try:
# Add line returns here, to make sure that the statement is
Gael Varoquaux
BUG: Integrate bug fixes from Enthought
r1887 # complete (except if '\' was used).
# This should probably be done in a different place (like
# maybe 'prefilter_input' method? For now, this works.
clean_string = string.rstrip('\n')
if not clean_string.endswith('\\'): clean_string +='\n\n'
is_complete = codeop.compile_command(clean_string,
gvaroquaux
Capture errors in user's input lines and raises meaningfull exceptions.
r1639 "<string>", "exec")
self.release_output()
except Exception, e:
# XXX: Hack: return True so that the
# code gets executed and the error captured.
is_complete = True
return is_complete
Gael Varoquaux
More tests of the frontend. Improve the ease of testing.
r1458
gvaroquaux
Fix segfaults under windows.
r1479 def write(self, string, refresh=True):
Gael Varoquaux
More tests of the frontend. Improve the ease of testing.
r1458 """ Write some characters to the display.
Subclass should overide this method.
gvaroquaux
Fix segfaults under windows.
r1479
The refresh keyword argument is used in frontends with an
event loop, to choose whether the write should trigget an UI
refresh, and thus be syncrhonous, or not.
Gael Varoquaux
More tests of the frontend. Improve the ease of testing.
r1458 """
print >>sys.__stderr__, string
Gael Varoquaux
Traceback capture now working.
r1360 def execute(self, python_string, raw_string=None):
gvaroquaux
Clean up code, names, and docstrings.
r1455 """ Stores the raw_string in the history, and sends the
python string to the interpreter.
Gael Varoquaux
Traceback capture now working.
r1360 """
if raw_string is None:
Gael Varoquaux
Nice background color for already entered code....
r1374 raw_string = python_string
Gael Varoquaux
Traceback capture now working.
r1360 # Create a false result, in case there is an exception
Gael Varoquaux
Make code execution more robust.
r1362 self.last_result = dict(number=self.prompt_number)
Fernando Perez
Partial fixes for 2.4 compatibility. Unfinished....
r1706
Gael Varoquaux
Traceback capture now working.
r1360 try:
Fernando Perez
Partial fixes for 2.4 compatibility. Unfinished....
r1706 try:
self.history.input_cache[-1] = raw_string.rstrip()
result = self.shell.execute(python_string)
self.last_result = result
self.render_result(result)
except:
self.show_traceback()
Gael Varoquaux
Traceback capture now working.
r1360 finally:
Gael Varoquaux
Make code execution more robust.
r1362 self.after_execute()
Fernando Perez
Partial fixes for 2.4 compatibility. Unfinished....
r1706
gvaroquaux
Clean up code, names, and docstrings.
r1455 #--------------------------------------------------------------------------
# LineFrontEndBase interface
#--------------------------------------------------------------------------
def prefilter_input(self, string):
gvaroquaux
Misspelling in a docstring.
r1630 """ Prefilter the input to turn it in valid python.
gvaroquaux
Clean up code, names, and docstrings.
r1455 """
string = string.replace('\r\n', '\n')
string = string.replace('\t', 4*' ')
# Clean the trailing whitespace
string = '\n'.join(l.rstrip() for l in string.split('\n'))
return string
Gael Varoquaux
Make code execution more robust.
r1362
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463
Gael Varoquaux
Make code execution more robust.
r1362 def after_execute(self):
""" All the operations required after an execution to put the
terminal back in a shape where it is usable.
"""
self.prompt_number += 1
Gael Varoquaux
More tests of the frontend. Improve the ease of testing.
r1458 self.new_prompt(self.input_prompt_template.substitute(
number=(self.last_result['number'] + 1)))
Gael Varoquaux
Make code execution more robust.
r1362 # Start a new empty history entry
self._add_history(None, '')
Gael Varoquaux
Correct small history bug. Add busy cursor.
r1371 self.history_cursor = len(self.history.input_cache) - 1
Gael Varoquaux
Traceback capture now working.
r1360
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 def complete_current_input(self):
""" Do code completion on current line.
"""
if self.debug:
print >>sys.__stdout__, "complete_current_input",
line = self.input_buffer
new_line, completions = self.complete(line)
if len(completions)>1:
Gael Varoquaux
Fix a completion crasher (index error during completion)....
r1624 self.write_completion(completions, new_line=new_line)
gvaroquaux
More elegant approach to previous buglet.
r1629 elif not line == new_line:
gvaroquaux
Fix buglet in frontend completion.
r1628 self.input_buffer = new_line
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 if self.debug:
Gael Varoquaux
Fix a completion crasher (index error during completion)....
r1624 print >>sys.__stdout__, 'line', line
print >>sys.__stdout__, 'new_line', new_line
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 print >>sys.__stdout__, completions
gvaroquaux
Test completion.
r1464 def get_line_width(self):
""" Return the width of the line in characters.
"""
return 80
Gael Varoquaux
Fix a completion crasher (index error during completion)....
r1624 def write_completion(self, possibilities, new_line=None):
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 """ Write the list of possible completions.
Gael Varoquaux
Fix a completion crasher (index error during completion)....
r1624
new_line is the completed input line that should be displayed
after the completion are writen. If None, the input_buffer
before the completion is used.
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 """
Gael Varoquaux
Fix a completion crasher (index error during completion)....
r1624 if new_line is None:
new_line = self.input_buffer
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463
self.write('\n')
max_len = len(max(possibilities, key=len)) + 1
gvaroquaux
Test completion.
r1464 # Now we check how much symbol we can put on a line...
chars_per_line = self.get_line_width()
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 symbols_per_line = max(1, chars_per_line/max_len)
pos = 1
Gael Varoquaux
BUG: Integrate bug fixes from Enthought
r1887 completion_string = []
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 for symbol in possibilities:
if pos < symbols_per_line:
Gael Varoquaux
BUG: Integrate bug fixes from Enthought
r1887 completion_string.append(symbol.ljust(max_len))
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 pos += 1
else:
Gael Varoquaux
BUG: Integrate bug fixes from Enthought
r1887 completion_string.append(symbol.rstrip() + '\n')
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 pos = 1
Gael Varoquaux
BUG: Integrate bug fixes from Enthought
r1887 self.write(''.join(completion_string))
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 self.new_prompt(self.input_prompt_template.substitute(
number=self.last_result['number'] + 1))
Gael Varoquaux
Fix a completion crasher (index error during completion)....
r1624 self.input_buffer = new_line
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463
def new_prompt(self, prompt):
""" Prints a prompt and starts a new editing buffer.
Subclasses should use this method to make sure that the
terminal is put in a state favorable for a new line
input.
"""
self.input_buffer = ''
self.write(prompt)
Gael Varoquaux
ENH: Add continuation prompts
r1884 def continuation_prompt(self):
"""Returns the current continuation prompt.
Gael Varoquaux
MERGE Laurent's tweaks. Clean up'
r1893 """
return ("."*(len(self.last_prompt)-2) + ': ')
Gael Varoquaux
ENH: Add continuation prompts
r1884
Gael Varoquaux
Add an execute_command to the linefrontendbase API. This method...
r1712 def execute_command(self, command, hidden=False):
""" Execute a command, not only in the model, but also in the
view, if any.
"""
return self.shell.execute(command)
gvaroquaux
Clean up code, names, and docstrings.
r1455 #--------------------------------------------------------------------------
# Private API
#--------------------------------------------------------------------------
Gael Varoquaux
ENH: Enter adds lines at the right position
r1896 def _on_enter(self, new_line_pos=0):
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 """ Called when the return key is pressed in a line editing
buffer.
Gael Varoquaux
ENH: Enter adds lines at the right position
r1896
Parameters
----------
new_line_pos : integer, optional
Position of the new line to add, starting from the
end (0 adds a new line after the last line, -1 before
the last line...)
Returns
-------
True if execution is triggered
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 """
gvaroquaux
More code reuse between GUI-independant frontend and Wx frontend: getting...
r1462 current_buffer = self.input_buffer
Gael Varoquaux
ENH: Add continuation prompts
r1884 # XXX: This string replace is ugly, but there should be no way it
# fails.
prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
'', current_buffer).replace('\n' + self.continuation_prompt(),
'\n')
cleaned_buffer = self.prefilter_input(prompt_less_buffer)
Gael Varoquaux
Correct small history bug. Add busy cursor.
r1371 if self.is_complete(cleaned_buffer):
Gael Varoquaux
Traceback capture now working.
r1360 self.execute(cleaned_buffer, raw_string=current_buffer)
Gael Varoquaux
ENH: Enter adds lines at the right position
r1896 return True
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 else:
Gael Varoquaux
Take in account remarks by Fernando on code review
r1947 # Start a new line.
Gael Varoquaux
ENH: Enter adds lines at the right position
r1896 new_line_pos = -new_line_pos
lines = current_buffer.split('\n')[:-1]
prompt_less_lines = prompt_less_buffer.split('\n')
Gael Varoquaux
Take in account remarks by Fernando on code review
r1947 # Create the new line, with the continuation prompt, and the
# same amount of indent than the line above it.
Gael Varoquaux
ENH: Enter adds lines at the right position
r1896 new_line = self.continuation_prompt() + \
self._get_indent_string('\n'.join(
prompt_less_lines[:new_line_pos-1]))
Gael Varoquaux
Improve auto-indentation
r1899 if len(lines) == 1:
Gael Varoquaux
Take in account remarks by Fernando on code review
r1947 # We are starting a first continuation line. Indent it.
Gael Varoquaux
ENH: Enter adds lines at the right position
r1896 new_line += '\t'
Gael Varoquaux
Improve auto-indentation
r1899 elif current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
Gael Varoquaux
Take in account remarks by Fernando on code review
r1947 # The last line ends with ":", autoindent the new line.
Gael Varoquaux
ENH: Enter adds lines at the right position
r1896 new_line += '\t'
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355
Gael Varoquaux
ENH: Enter adds lines at the right position
r1896 if new_line_pos == 0:
lines.append(new_line)
else:
lines.insert(new_line_pos, new_line)
self.input_buffer = '\n'.join(lines)
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355
def _get_indent_string(self, string):
gvaroquaux
Clean up code, names, and docstrings.
r1455 """ Return the string of whitespace that prefixes a line. Used to
add the right amount of indendation when creating a new line.
"""
Gael Varoquaux
Make code execution more robust.
r1362 string = string.replace('\t', ' '*4)
Gael Varoquaux
Split the frontend base class in different files and add a base class...
r1355 string = string.split('\n')[-1]
indent_chars = len(string) - len(string.lstrip())
indent_string = '\t'*(indent_chars // 4) + \
' '*(indent_chars % 4)
return indent_string