##// END OF EJS Templates
TAB on empty line causes crash; with test
Doug Blank -
Show More
@@ -1,70 +1,76 b''
1 """Tests for tokenutil"""
1 """Tests for tokenutil"""
2 # Copyright (c) IPython Development Team.
2 # Copyright (c) IPython Development Team.
3 # Distributed under the terms of the Modified BSD License.
3 # Distributed under the terms of the Modified BSD License.
4
4
5 import nose.tools as nt
5 import nose.tools as nt
6
6
7 from IPython.utils.tokenutil import token_at_cursor
7 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
8
8
9 def expect_token(expected, cell, cursor_pos):
9 def expect_token(expected, cell, cursor_pos):
10 token = token_at_cursor(cell, cursor_pos)
10 token = token_at_cursor(cell, cursor_pos)
11 offset = 0
11 offset = 0
12 for line in cell.splitlines():
12 for line in cell.splitlines():
13 if offset + len(line) >= cursor_pos:
13 if offset + len(line) >= cursor_pos:
14 break
14 break
15 else:
15 else:
16 offset += len(line)
16 offset += len(line)
17 column = cursor_pos - offset
17 column = cursor_pos - offset
18 line_with_cursor = '%s|%s' % (line[:column], line[column:])
18 line_with_cursor = '%s|%s' % (line[:column], line[column:])
19 line
19 line
20 nt.assert_equal(token, expected,
20 nt.assert_equal(token, expected,
21 "Expected %r, got %r in: %r (pos %i)" % (
21 "Expected %r, got %r in: %r (pos %i)" % (
22 expected, token, line_with_cursor, cursor_pos)
22 expected, token, line_with_cursor, cursor_pos)
23 )
23 )
24
24
25 def test_simple():
25 def test_simple():
26 cell = "foo"
26 cell = "foo"
27 for i in range(len(cell)):
27 for i in range(len(cell)):
28 expect_token("foo", cell, i)
28 expect_token("foo", cell, i)
29
29
30 def test_function():
30 def test_function():
31 cell = "foo(a=5, b='10')"
31 cell = "foo(a=5, b='10')"
32 expected = 'foo'
32 expected = 'foo'
33 # up to `foo(|a=`
33 # up to `foo(|a=`
34 for i in range(cell.find('a=') + 1):
34 for i in range(cell.find('a=') + 1):
35 expect_token("foo", cell, i)
35 expect_token("foo", cell, i)
36 # find foo after `=`
36 # find foo after `=`
37 for i in [cell.find('=') + 1, cell.rfind('=') + 1]:
37 for i in [cell.find('=') + 1, cell.rfind('=') + 1]:
38 expect_token("foo", cell, i)
38 expect_token("foo", cell, i)
39 # in between `5,|` and `|b=`
39 # in between `5,|` and `|b=`
40 for i in range(cell.find(','), cell.find('b=')):
40 for i in range(cell.find(','), cell.find('b=')):
41 expect_token("foo", cell, i)
41 expect_token("foo", cell, i)
42
42
43 def test_multiline():
43 def test_multiline():
44 cell = '\n'.join([
44 cell = '\n'.join([
45 'a = 5',
45 'a = 5',
46 'b = hello("string", there)'
46 'b = hello("string", there)'
47 ])
47 ])
48 expected = 'hello'
48 expected = 'hello'
49 start = cell.index(expected) + 1
49 start = cell.index(expected) + 1
50 for i in range(start, start + len(expected)):
50 for i in range(start, start + len(expected)):
51 expect_token(expected, cell, i)
51 expect_token(expected, cell, i)
52 expected = 'there'
52 expected = 'there'
53 start = cell.index(expected) + 1
53 start = cell.index(expected) + 1
54 for i in range(start, start + len(expected)):
54 for i in range(start, start + len(expected)):
55 expect_token(expected, cell, i)
55 expect_token(expected, cell, i)
56
56
57 def test_attrs():
57 def test_attrs():
58 cell = "foo(a=obj.attr.subattr)"
58 cell = "foo(a=obj.attr.subattr)"
59 expected = 'obj'
59 expected = 'obj'
60 idx = cell.find('obj') + 1
60 idx = cell.find('obj') + 1
61 for i in range(idx, idx + 3):
61 for i in range(idx, idx + 3):
62 expect_token(expected, cell, i)
62 expect_token(expected, cell, i)
63 idx = cell.find('.attr') + 2
63 idx = cell.find('.attr') + 2
64 expected = 'obj.attr'
64 expected = 'obj.attr'
65 for i in range(idx, idx + 4):
65 for i in range(idx, idx + 4):
66 expect_token(expected, cell, i)
66 expect_token(expected, cell, i)
67 idx = cell.find('.subattr') + 2
67 idx = cell.find('.subattr') + 2
68 expected = 'obj.attr.subattr'
68 expected = 'obj.attr.subattr'
69 for i in range(idx, len(cell)):
69 for i in range(idx, len(cell)):
70 expect_token(expected, cell, i)
70 expect_token(expected, cell, i)
71
72 def test_line_at_cursor():
73 cell = ""
74 (line, offset) = line_at_cursor(cell, cursor_pos=11)
75 assert line == "", ("Expected '', got %r" % line)
76 assert offset == 0, ("Expected '', got %r" % line)
@@ -1,108 +1,110 b''
1 """Token-related utilities"""
1 """Token-related utilities"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import, print_function
6 from __future__ import absolute_import, print_function
7
7
8 from collections import namedtuple
8 from collections import namedtuple
9 from io import StringIO
9 from io import StringIO
10 from keyword import iskeyword
10 from keyword import iskeyword
11
11
12 from . import tokenize2
12 from . import tokenize2
13 from .py3compat import cast_unicode_py2
13 from .py3compat import cast_unicode_py2
14
14
15 Token = namedtuple('Token', ['token', 'text', 'start', 'end', 'line'])
15 Token = namedtuple('Token', ['token', 'text', 'start', 'end', 'line'])
16
16
17 def generate_tokens(readline):
17 def generate_tokens(readline):
18 """wrap generate_tokens to catch EOF errors"""
18 """wrap generate_tokens to catch EOF errors"""
19 try:
19 try:
20 for token in tokenize2.generate_tokens(readline):
20 for token in tokenize2.generate_tokens(readline):
21 yield token
21 yield token
22 except tokenize2.TokenError:
22 except tokenize2.TokenError:
23 # catch EOF error
23 # catch EOF error
24 return
24 return
25
25
26 def line_at_cursor(cell, cursor_pos=0):
26 def line_at_cursor(cell, cursor_pos=0):
27 """Return the line in a cell at a given cursor position
27 """Return the line in a cell at a given cursor position
28
28
29 Used for calling line-based APIs that don't support multi-line input, yet.
29 Used for calling line-based APIs that don't support multi-line input, yet.
30
30
31 Parameters
31 Parameters
32 ----------
32 ----------
33
33
34 cell: text
34 cell: text
35 multiline block of text
35 multiline block of text
36 cursor_pos: integer
36 cursor_pos: integer
37 the cursor position
37 the cursor position
38
38
39 Returns
39 Returns
40 -------
40 -------
41
41
42 (line, offset): (text, integer)
42 (line, offset): (text, integer)
43 The line with the current cursor, and the character offset of the start of the line.
43 The line with the current cursor, and the character offset of the start of the line.
44 """
44 """
45 offset = 0
45 offset = 0
46 lines = cell.splitlines(True)
46 lines = cell.splitlines(True)
47 for line in lines:
47 for line in lines:
48 next_offset = offset + len(line)
48 next_offset = offset + len(line)
49 if next_offset >= cursor_pos:
49 if next_offset >= cursor_pos:
50 break
50 break
51 offset = next_offset
51 offset = next_offset
52 else:
53 line = ""
52 return (line, offset)
54 return (line, offset)
53
55
54 def token_at_cursor(cell, cursor_pos=0):
56 def token_at_cursor(cell, cursor_pos=0):
55 """Get the token at a given cursor
57 """Get the token at a given cursor
56
58
57 Used for introspection.
59 Used for introspection.
58
60
59 Parameters
61 Parameters
60 ----------
62 ----------
61
63
62 cell : unicode
64 cell : unicode
63 A block of Python code
65 A block of Python code
64 cursor_pos : int
66 cursor_pos : int
65 The location of the cursor in the block where the token should be found
67 The location of the cursor in the block where the token should be found
66 """
68 """
67 cell = cast_unicode_py2(cell)
69 cell = cast_unicode_py2(cell)
68 names = []
70 names = []
69 tokens = []
71 tokens = []
70 offset = 0
72 offset = 0
71 for tup in generate_tokens(StringIO(cell).readline):
73 for tup in generate_tokens(StringIO(cell).readline):
72
74
73 tok = Token(*tup)
75 tok = Token(*tup)
74
76
75 # token, text, start, end, line = tup
77 # token, text, start, end, line = tup
76 start_col = tok.start[1]
78 start_col = tok.start[1]
77 end_col = tok.end[1]
79 end_col = tok.end[1]
78 # allow '|foo' to find 'foo' at the beginning of a line
80 # allow '|foo' to find 'foo' at the beginning of a line
79 boundary = cursor_pos + 1 if start_col == 0 else cursor_pos
81 boundary = cursor_pos + 1 if start_col == 0 else cursor_pos
80 if offset + start_col >= boundary:
82 if offset + start_col >= boundary:
81 # current token starts after the cursor,
83 # current token starts after the cursor,
82 # don't consume it
84 # don't consume it
83 break
85 break
84
86
85 if tok.token == tokenize2.NAME and not iskeyword(tok.text):
87 if tok.token == tokenize2.NAME and not iskeyword(tok.text):
86 if names and tokens and tokens[-1].token == tokenize2.OP and tokens[-1].text == '.':
88 if names and tokens and tokens[-1].token == tokenize2.OP and tokens[-1].text == '.':
87 names[-1] = "%s.%s" % (names[-1], tok.text)
89 names[-1] = "%s.%s" % (names[-1], tok.text)
88 else:
90 else:
89 names.append(tok.text)
91 names.append(tok.text)
90 elif tok.token == tokenize2.OP:
92 elif tok.token == tokenize2.OP:
91 if tok.text == '=' and names:
93 if tok.text == '=' and names:
92 # don't inspect the lhs of an assignment
94 # don't inspect the lhs of an assignment
93 names.pop(-1)
95 names.pop(-1)
94
96
95 if offset + end_col > cursor_pos:
97 if offset + end_col > cursor_pos:
96 # we found the cursor, stop reading
98 # we found the cursor, stop reading
97 break
99 break
98
100
99 tokens.append(tok)
101 tokens.append(tok)
100 if tok.token == tokenize2.NEWLINE:
102 if tok.token == tokenize2.NEWLINE:
101 offset += len(tok.line)
103 offset += len(tok.line)
102
104
103 if names:
105 if names:
104 return names[-1]
106 return names[-1]
105 else:
107 else:
106 return ''
108 return ''
107
109
108
110
General Comments 0
You need to be logged in to leave comments. Login now