3333import textwrap
3434import traceback
3535from glob import glob
36+ from itertools import takewhile
3637from locale import getpreferredencoding
3738from urlparse import urljoin
3839from xmlrpclib import ServerProxy , Error as XMLRPCError
@@ -274,7 +275,6 @@ def __init__(self, interp, idle=None):
274275 self .matches_iter = MatchesIterator ()
275276 self .argspec = None
276277 self .current_func = None
277- self .inside_string = False
278278 self .highlighted_paren = None
279279 self .list_win_visible = False
280280 self ._C = {}
@@ -334,23 +334,34 @@ def _callable_postfix(self, value, word):
334334 word += '('
335335 return word
336336
337- def current_string (self ):
338- """Return the current string.
339- Note: This method will not really work for multiline strings."""
340- line = self .current_line ()
341- inside_string = next_token_inside_string (line , self .inside_string )
342- if inside_string :
343- string = list ()
344- next_char = ''
345- for (char , next_char ) in zip (reversed (line ),
346- reversed (line [:- 1 ])):
347- if char == inside_string and next_char != '\\ ' :
348- return '' .join (reversed (string ))
349- string .append (char )
337+ def current_string (self , concatenate = False ):
338+ """Return the current string."""
339+ tokens = self .tokenize (self .current_line ())
340+ string_tokens = list (takewhile (token_is_any_of ([Token .String ,
341+ Token .Text ]),
342+ reversed (tokens )))
343+ if not string_tokens :
344+ return ''
345+ opening = string_tokens .pop ()[1 ]
346+ string = list ()
347+ for (token , value ) in reversed (string_tokens ):
348+ if token is Token .Text :
349+ continue
350+ elif opening is None :
351+ opening = value
352+ elif token is Token .String .Doc :
353+ string .append (value [3 :- 3 ])
354+ opening = None
355+ elif value == opening :
356+ opening = None
357+ if not concatenate :
358+ string = list ()
350359 else :
351- if next_char == inside_string :
352- return '' .join (reversed (string ))
353- return ''
360+ string .append (value )
361+
362+ if opening is None :
363+ return ''
364+ return '' .join (string )
354365
355366 def get_object (self , name ):
356367 if name in self .interp .locals :
@@ -602,8 +613,6 @@ def push(self, s, insert_into_history=True):
602613 if insert_into_history :
603614 self .rl_history .append (s )
604615
605- self .inside_string = next_token_inside_string (s , self .inside_string )
606-
607616 try :
608617 more = self .interp .runsource ('\n ' .join (self .buffer ))
609618 except SystemExit :
@@ -707,36 +716,45 @@ def close(self):
707716
708717 def tokenize (self , s , newline = False ):
709718 """Tokenize a line of code."""
710- if self .inside_string :
711- # A string started in another line is continued in this
712- # line
713- tokens = PythonLexer ().get_tokens (self .inside_string + s )
714- token , value = tokens .next ()
715- if token is Token .String .Doc :
716- tokens = [(Token .String , value [3 :])] + list (tokens )
717- else :
718- tokens = list (PythonLexer ().get_tokens (s ))
719719
720720 source = '\n ' .join (self .buffer + [s ])
721721 cursor = len (source ) - self .cpos
722722 if self .cpos :
723723 cursor += 1
724724 stack = list ()
725725 all_tokens = list (PythonLexer ().get_tokens (source ))
726- i = line = 0
727- pos = 0
726+ # Unfortunately, Pygments adds a trailing newline and strings with
727+ # no size, so strip them
728+ while not all_tokens [- 1 ][1 ]:
729+ all_tokens .pop ()
730+ all_tokens [- 1 ] = (all_tokens [- 1 ][0 ], all_tokens [- 1 ][1 ].rstrip ('\n ' ))
731+ line = pos = 0
728732 parens = dict (zip ('{([' , '})]' ))
729- for (token , value ) in all_tokens :
733+ line_tokens = list ()
734+ saved_tokens = list ()
735+ search_for_paren = True
736+ for (token , value ) in split_lines (all_tokens ):
730737 pos += len (value )
738+ if token is Token .Text and value == '\n ' :
739+ line += 1
740+ # Remove trailing newline
741+ line_tokens = list ()
742+ saved_tokens = list ()
743+ continue
744+ line_tokens .append ((token , value ))
745+ saved_tokens .append ((token , value ))
746+ if not search_for_paren :
747+ continue
731748 under_cursor = (pos == cursor )
732749 if token is Token .Punctuation :
733750 if value in parens :
734751 if under_cursor :
735- tokens [ i ] = (Parenthesis .UnderCursor , value )
752+ line_tokens [ - 1 ] = (Parenthesis .UnderCursor , value )
736753 # Push marker on the stack
737754 stack .append ((Parenthesis , value ))
738755 else :
739- stack .append ((line , i , value ))
756+ stack .append ((line , len (line_tokens ) - 1 ,
757+ line_tokens , value ))
740758 elif value in parens .itervalues ():
741759 saved_stack = list (stack )
742760 try :
@@ -747,58 +765,45 @@ def tokenize(self, s, newline=False):
747765 except IndexError :
748766 # SyntaxError.. more closed parentheses than
749767 # opened or a wrong closing paren
768+ opening = None
750769 if not saved_stack :
751- break
770+ search_for_paren = False
752771 else :
753- opening = None
754772 stack = saved_stack
755773 if opening and opening [0 ] is Parenthesis :
756774 # Marker found
757- tokens [ i ] = (Parenthesis , value )
758- break
775+ line_tokens [ - 1 ] = (Parenthesis , value )
776+ search_for_paren = False
759777 elif opening and under_cursor and not newline :
760778 if self .cpos :
761- tokens [ i ] = (Parenthesis .UnderCursor , value )
779+ line_tokens [ - 1 ] = (Parenthesis .UnderCursor , value )
762780 else :
763781 # The cursor is at the end of line and next to
764782 # the paren, so it doesn't reverse the paren.
765783 # Therefore, we insert the Parenthesis token
766784 # here instead of the Parenthesis.UnderCursor
767785 # token.
768- if i < len (tokens ):
769- # XXX This is a bug in our index
770- # calculation (happens with multiline
771- # strings)
772- tokens [i ] = (Parenthesis , value )
773- (lineno , i , opening ) = opening
786+ line_tokens [- 1 ] = (Parenthesis , value )
787+ (lineno , i , tokens , opening ) = opening
774788 if lineno == len (self .buffer ):
775- self .highlighted_paren = lineno
776- tokens [i ] = (Parenthesis , opening )
789+ self .highlighted_paren = ( lineno , saved_tokens )
790+ line_tokens [i ] = (Parenthesis , opening )
777791 else :
778- line = self .buffer [lineno ]
779- self .highlighted_paren = lineno
792+ self .highlighted_paren = (lineno , list (tokens ))
780793 # We need to redraw a line
781- line_tokens = list (PythonLexer ().get_tokens (line ))
782- line_tokens [i ] = (Parenthesis , opening )
783- self .reprint_line (lineno , line_tokens )
784- break
794+ tokens [i ] = (Parenthesis , opening )
795+ self .reprint_line (lineno , tokens )
796+ search_for_paren = False
785797 elif under_cursor :
786- break
787- elif token is Token .Text and value == '\n ' :
788- line += 1
789- if line == len (self .buffer ):
790- i = - 1
791- elif under_cursor :
792- break
793- i += 1
794- return tokens
798+ search_for_paren = False
799+ if line != len (self .buffer ):
800+ return list ()
801+ return line_tokens
795802
796803 def clear_current_line (self ):
797804 """This is used as the exception callback for the Interpreter instance.
798805 It prevents autoindentation from occuring after a traceback."""
799806
800- self .inside_string = False
801-
802807
803808def next_indentantion (line ):
804809 """Given a code line, return the indentation of the next line."""
@@ -821,3 +826,36 @@ def next_token_inside_string(s, inside_string):
821826 elif value == inside_string :
822827 inside_string = False
823828 return inside_string
829+
830+
831+ def split_lines (tokens ):
832+ for (token , value ) in tokens :
833+ if not value :
834+ continue
835+ while value :
836+ head , newline , value = value .partition ('\n ' )
837+ yield (token , head )
838+ if newline :
839+ yield (Token .Text , newline )
840+
841+ def token_is (token_type ):
842+ """Return a callable object that returns whether a token is of the
843+ given type `token_type`."""
844+ def token_is_type (token ):
845+ """Return whether a token is of a certain type or not."""
846+ token = token [0 ]
847+ while token is not token_type and token .parent :
848+ token = token .parent
849+ return token is token_type
850+
851+ return token_is_type
852+
853+ def token_is_any_of (token_types ):
854+ """Return a callable object that returns whether a token is any of the
855+ given types `token_types`."""
856+ is_token_types = map (token_is , token_types )
857+
858+ def token_is_any_of (token ):
859+ return any (check (token ) for check in is_token_types )
860+
861+ return token_is_any_of
0 commit comments