4949from cStringIO import StringIO
5050from urlparse import urljoin
5151from xmlrpclib import ServerProxy , Error as XMLRPCError
52+ from ConfigParser import ConfigParser , NoSectionError , NoOptionError
5253
5354# These are used for syntax hilighting.
54- from pygments import highlight
55+ from pygments import format
5556from pygments .lexers import PythonLexer
57+ from pygments .token import Token
5658from bpython .formatter import BPythonFormatter
59+ from itertools import chain
60+
61+ # This for import completion
62+ from bpython import importcompletion
5763
5864# And these are used for argspec.
5965from pyparsing import Forward , Suppress , QuotedString , dblQuotedString , \
@@ -109,24 +115,12 @@ def readlines(self, x):
109115OPTS = Struct ()
110116DO_RESIZE = False
111117
112-
113- # Set default values. (Overridden by loadrc())
114- OPTS .tab_length = 4
115- OPTS .auto_display_list = True
116- OPTS .syntax = True
117- OPTS .arg_spec = True
118- OPTS .hist_file = '~/.pythonhist'
119- OPTS .hist_length = 100
120- OPTS .flush_output = True
121-
122118# TODO:
123119#
124120# C-l doesn't repaint the screen yet.
125121#
126122# Tab completion does not work if not at the end of the line.
127123#
128- # Triple-quoted strings over multiple lines are not colourised correctly.
129- #
130124# Numerous optimisations can be made but it seems to do all the lookup stuff
131125# fast enough on even my crappy server so I'm not too bothered about that
132126# at the moment.
@@ -168,6 +162,18 @@ def make_colours():
168162 return c
169163
170164
165+ def next_token_inside_string (s , inside_string ):
166+ """Given a code string s and an initial state inside_string, return
167+ whether the next token will be inside a string or not."""
168+ for token , value in PythonLexer ().get_tokens (s ):
169+ if token is Token .String and value in ['"""' , "'''" , '"' , "'" ]:
170+ if not inside_string :
171+ inside_string = value
172+ elif value == inside_string :
173+ inside_string = False
174+ return inside_string
175+
176+
171177class Interpreter (code .InteractiveInterpreter ):
172178
173179 def __init__ (self ):
@@ -194,6 +200,7 @@ def showsyntaxerror(self, filename=None):
194200 sys .last_type = type
195201 sys .last_value = value
196202 if filename and type is SyntaxError :
203+ self .inside_string = False
197204 # Work hard to stuff the correct filename in the exception
198205 try :
199206 msg , (dummy_filename , lineno , offset , line ) = value
@@ -302,6 +309,7 @@ def __init__(self, scr, interp, statusbar=None, idle=None):
302309 self .matches = []
303310 self .argspec = None
304311 self .s = ''
312+ self .inside_string = False
305313 self .list_win_visible = False
306314 self ._C = {}
307315 sys .stdin = FakeStdin (self )
@@ -535,26 +543,31 @@ def _complete(self, unused_tab=False):
535543 if not cw :
536544 self .matches = []
537545
538- try :
539- self .completer .complete (cw , 0 )
540- except Exception :
546+ # Check for import completion
547+ e = False
548+ matches = importcompletion .complete (self .s , cw )
549+ if not matches :
550+ # Nope, no import, continue with normal completion
551+ try :
552+ self .completer .complete (cw , 0 )
553+ except Exception :
541554# This sucks, but it's either that or list all the exceptions that could
542555# possibly be raised here, so if anyone wants to do that, feel free to send me
543556# a patch. XXX: Make sure you raise here if you're debugging the completion
544557# stuff !
545- e = True
546- else :
547- e = False
558+ e = True
559+ else :
560+ matches = self . completer . matches
548561
549- if e or not self . completer . matches :
562+ if e or not matches :
550563 self .matches = []
551564 if not self .argspec :
552565 self .scr .redrawwin ()
553566 return False
554567
555- if not e and self . completer . matches :
568+ if not e and matches :
556569# remove duplicates and restore order
557- self .matches = sorted (set (self . completer . matches ))
570+ self .matches = sorted (set (matches ))
558571
559572 if len (self .matches ) == 1 and not OPTS .auto_display_list :
560573 self .list_win_visible = True
@@ -744,13 +757,15 @@ def mkargspec(self, topline, down):
744757 self .list_win .addstr (', ' , curses .color_pair (self ._C ["g" ]+ 1 ))
745758
746759 if _args :
747- self .list_win .addstr (', ' ,
748- curses .color_pair (self ._C ["g" ]+ 1 ))
760+ if args :
761+ self .list_win .addstr (', ' ,
762+ curses .color_pair (self ._C ["g" ]+ 1 ))
749763 self .list_win .addstr ('*%s' % (_args , ),
750764 curses .color_pair (self ._C ["m" ]+ 1 ))
751765 if _kwargs :
752- self .list_win .addstr (', ' ,
753- curses .color_pair (self ._C ["g" ]+ 1 ))
766+ if args or _args :
767+ self .list_win .addstr (', ' ,
768+ curses .color_pair (self ._C ["g" ]+ 1 ))
754769 self .list_win .addstr ('**%s' % (_kwargs , ),
755770 curses .color_pair (self ._C ["m" ]+ 1 ))
756771 self .list_win .addstr (')' , curses .color_pair (self ._C ["y" ]+ 1 ))
@@ -1376,6 +1391,9 @@ def lf(self):
13761391 for _ in range (self .cpos ):
13771392 self .mvc (- 1 )
13781393
1394+ self .inside_string = next_token_inside_string (self .s ,
1395+ self .inside_string )
1396+
13791397 self .echo ("\n " )
13801398
13811399 def addstr (self , s ):
@@ -1397,7 +1415,16 @@ def print_line(self, s, clr=False):
13971415 clr = True
13981416
13991417 if OPTS .syntax :
1400- o = highlight (s , PythonLexer (), BPythonFormatter ())
1418+ if self .inside_string :
1419+ # A string started in another line is continued in this
1420+ # line
1421+ tokens = PythonLexer ().get_tokens (self .inside_string + s )
1422+ token , value = tokens .next ()
1423+ if token is Token .String .Doc :
1424+ tokens = chain ([(Token .String , value [3 :])], tokens )
1425+ else :
1426+ tokens = PythonLexer ().get_tokens (s )
1427+ o = format (tokens , BPythonFormatter ())
14011428 else :
14021429 o = s
14031430
@@ -1683,6 +1710,7 @@ def idle(caller):
16831710
16841711 global stdscr
16851712
1713+ importcompletion .find_coroutine ()
16861714 caller .statusbar .check ()
16871715
16881716 if DO_RESIZE :
@@ -1707,59 +1735,38 @@ def do_resize(caller):
17071735 caller .resize ()
17081736# The list win resizes itself every time it appears so no need to do it here.
17091737
1710-
1711- def loadrc ():
1712- """Use the shlex module to make a simple lexer for the settings,
1713- it also attempts to convert any integers to Python ints, otherwise
1714- leaves them as strings and handles hopefully all the sane ways of
1715- representing a boolean."""
1716-
1738+ def loadini ():
1739+ """Loads .ini configuration file and stores its values in OPTS"""
1740+ class CP (ConfigParser ):
1741+ def safeget (self , section , option , default ):
1742+ """safet get method using default values"""
1743+ try :
1744+ v = self .get (section , option )
1745+ except NoSectionError , NoOptionError :
1746+ v = default
1747+ if isinstance (v , bool ):
1748+ return v
1749+ try :
1750+ return int (v )
1751+ except ValueError :
1752+ return v
1753+
17171754 if len (sys .argv ) > 2 :
17181755 path = sys .argv [2 ]
17191756 else :
1720- path = os .path .expanduser ('~/.bpythonrc' )
1721-
1722- if not os .path .isfile (path ):
1723- return
1724-
1725- f = open (path )
1726- parser = shlex .shlex (f )
1727-
1728- bools = {
1729- 'true' : True ,
1730- 'yes' : True ,
1731- 'on' : True ,
1732- 'false' : False ,
1733- 'no' : False ,
1734- 'off' : False
1735- }
1736-
1737- config = {}
1738- while True :
1739- k = parser .get_token ()
1740- v = None
1741-
1742- if not k :
1743- break
1744-
1745- k = k .lower ()
1746-
1747- if parser .get_token () == '=' :
1748- v = parser .get_token () or None
1749-
1750- if v is not None :
1751- try :
1752- v = int (v )
1753- except ValueError :
1754- if v .lower () in bools :
1755- v = bools [v .lower ()]
1757+ configfile = os .path .expanduser ('~/.bpython.ini' )
1758+
1759+ config = CP ()
1760+ config .read (configfile )
17561761
1757- config [k ] = v
1758- f .close ()
1762+ OPTS .tab_length = config .safeget ('general' , 'tab_length' , 4 )
1763+ OPTS .auto_display_list = config .safeget ('general' , 'auto_display_list' , True )
1764+ OPTS .syntax = config .safeget ('general' , 'syntax' , True )
1765+ OPTS .arg_spec = config .safeget ('general' , 'arg_spec' , True )
1766+ OPTS .hist_file = config .safeget ('general' , 'hist_file' , '~/.pythonhist' )
1767+ OPTS .hist_length = config .safeget ('general' , 'hist_length' , 100 )
1768+ OPTS .flush_output = config .safeget ('general' , 'flush_output' , True )
17591769
1760- for k , v in config .iteritems ():
1761- if hasattr (OPTS , k ):
1762- setattr (OPTS , k , v )
17631770
17641771class FakeDict (object ):
17651772 """Very simple dict-alike that returns a constant value for any key -
@@ -1784,7 +1791,7 @@ def main_curses(scr):
17841791 global DO_RESIZE
17851792 DO_RESIZE = False
17861793 signal .signal (signal .SIGWINCH , lambda * _ : sigwinch (scr ))
1787- loadrc ()
1794+ loadini ()
17881795 stdscr = scr
17891796 try :
17901797 curses .start_color ()
0 commit comments