def _inside_string(self, line): # Returns True if appending something to the end of this line will be # appending inside of a string last_token = line.parts[-1][0] if not token_is_a(last_token, String): return False # token is a String, check if multi-line try: # (position is 1-indexed, so next line in 0-index is itself) next_line = self.code_box.listing.lines[self.position] except IndexError: # nothing after this, so it must be a closed string return False last_token_next_line = next_line.parts[0][0] if not token_is_a(last_token_next_line, String): # next token is not a string, must be a closed string return False # if you get here we are a String and we have a String as the next # line, this could be because we're inside a multi-line string or # because the code has two doc strings back-to-back, this is the worst # case scenario, loop through the code and count String openers that # are the same as this one, if odd number, we're inside a string inside = False for line in self.code_box.listing.lines[0:self.position]: for part in line.parts: if part.token == last_token: inside = not inside return inside
def render_step(self): line = deepcopy(self.code_box.listing.lines[self.position - 1]) self.undo_line = line token = line.parts[-1][0] if self._inside_string(line): # inside a multi-line string, don't reparse, just append parts = deepcopy(line.parts) parts[-1] = CodePart(token, parts[-1].text + self.source) replace_line = CodeLine(parts, line.lexer) else: # not inside a string if token_is_a(token, String): # last token is a string, parse the source and append source_line = parse_source(self.source, line.lexer)[0] new_parts = line.parts + source_line.parts replace_line = CodeLine(new_parts, line.lexer) else: # last token isn't a string, reparse the whole line text = line.text + self.source replace_line = parse_source(text, line.lexer)[0] if self.cursor: replace_line.parts.append(CodePart(Token, '\u2588')) self.code_box.listing.replace_line(self.position, replace_line)
def test_tokens(self): #--- Test token_is_a() self.assertTrue(token_is_a(Token, Token)) self.assertTrue(token_is_a(Token.Name.Function, Token.Name)) self.assertFalse(token_is_a(Token.Name.Function, Token.Keyword)) #--- Test token_ancestor() token = token_ancestor(Token.Name.Function, [Token.Name.Function]) self.assertEqual(Token.Name.Function, token) token = token_ancestor(Token.Name.Function, [Token.Name]) self.assertEqual(Token.Name, token) token = token_ancestor(Token.Text, [Token.Name]) self.assertEqual(Token, token)
def _line_to_steps(self, line, insert_pos, replace_pos): steps = [] # --- Skip animation for "output" content first_token = line.parts[0].token is_console = self.code.lexer.is_console if is_console and not token_is_a(first_token, Generic.Prompt): # in console mode only lines with prompts get typewriter # animation, everything else is just added directly return [ steplib.InsertRows(self.code_box, insert_pos, line), ] # --- Typewriter animation # insert a blank row first with contents of line changing what is on # it as animation continues dummy_parts = [ CodePart(Token, ''), ] row_line = CodeLine(dummy_parts, self.code.lexer) step = steplib.InsertRows(self.code_box, insert_pos, row_line) steps.append(step) current_parts = [] num_parts = len(line.parts) for count, part in enumerate(line.parts): if part.token in self.continuous: # part is a chunk that gets output all together, replace the # dummy line with the whole contents current_parts.append(part) row_line = CodeLine(deepcopy(current_parts), self.code.lexer) step = steplib.ReplaceRows(self.code_box, replace_pos, row_line) steps.append(step) if part.token == Generic.Prompt: # stop animation if this is a prompt, wait for keypress steps.append(steplib.CellEnd()) elif count == 0 and token_is_a( part.token, Token.Text) and (part.text.rstrip() == ''): # first token is leading whitespace, don't animate it, just # insert it current_parts.append(part) row_line = CodeLine(deepcopy(current_parts), self.code.lexer) step = steplib.ReplaceRows(self.code_box, replace_pos, row_line) steps.append(step) else: new_part = CodePart(part.token, '') current_parts.append(new_part) typewriter = '' for letter in part.text: typewriter += letter new_part = CodePart(part.token, typewriter) current_parts[-1] = new_part output_parts = deepcopy(current_parts) # If not last step in animation, add a cursor to the line is_last_part = (count + 1 == num_parts) is_last_letter = (len(typewriter) == len(part.text)) if not (is_last_part and is_last_letter): output_parts.append(CodePart(Token, '\u2588')) row_line = CodeLine(output_parts, self.code.lexer) step = steplib.ReplaceRows(self.code_box, replace_pos, row_line) steps.append(step) steps.append(steplib.Sleep(self.delay_until_next_letter)) return steps