Files
babi/tests/highlight_test.py
2020-03-21 14:19:51 -07:00

560 lines
16 KiB
Python

from babi.highlight import Grammars
from babi.highlight import highlight_line
from babi.highlight import Region
def test_grammar_matches_extension_only_name():
data = {'scopeName': 'shell', 'patterns': [], 'fileTypes': ['bashrc']}
grammars = Grammars([data])
compiler = grammars.compiler_for_file('.bashrc', 'alias nano=babi')
assert compiler.root_state.entries[0].scope[0] == 'shell'
def _compiler_state(*grammar_dcts):
grammars = Grammars(grammar_dcts)
compiler = grammars.compiler_for_scope(grammar_dcts[0]['scopeName'])
return compiler, compiler.root_state
def test_backslash_a():
grammar = {
'scopeName': 'test',
'patterns': [{'name': 'aaa', 'match': r'\Aa+'}],
}
compiler, state = _compiler_state(grammar)
state, (region_0,) = highlight_line(compiler, state, 'aaa', True)
state, (region_1,) = highlight_line(compiler, state, 'aaa', False)
# \A should only match at the beginning of the file
assert region_0 == Region(0, 3, ('test', 'aaa'))
assert region_1 == Region(0, 3, ('test',))
BEGIN_END_NO_NL = {
'scopeName': 'test',
'patterns': [{
'begin': 'x',
'end': 'x',
'patterns': [
{'match': r'\Ga', 'name': 'ga'},
{'match': 'a', 'name': 'noga'},
],
}],
}
def test_backslash_g_inline():
compiler, state = _compiler_state(BEGIN_END_NO_NL)
_, regions = highlight_line(compiler, state, 'xaax', True)
assert regions == (
Region(0, 1, ('test',)),
Region(1, 2, ('test', 'ga')),
Region(2, 3, ('test', 'noga')),
Region(3, 4, ('test',)),
)
def test_backslash_g_next_line():
compiler, state = _compiler_state(BEGIN_END_NO_NL)
state, regions1 = highlight_line(compiler, state, 'x\n', True)
state, regions2 = highlight_line(compiler, state, 'aax\n', False)
assert regions1 == (
Region(0, 1, ('test',)),
Region(1, 2, ('test',)),
)
assert regions2 == (
Region(0, 1, ('test', 'noga')),
Region(1, 2, ('test', 'noga')),
Region(2, 3, ('test',)),
Region(3, 4, ('test',)),
)
def test_end_before_other_match():
compiler, state = _compiler_state(BEGIN_END_NO_NL)
state, regions = highlight_line(compiler, state, 'xazzx', True)
assert regions == (
Region(0, 1, ('test',)),
Region(1, 2, ('test', 'ga')),
Region(2, 4, ('test',)),
Region(4, 5, ('test',)),
)
BEGIN_END_NL = {
'scopeName': 'test',
'patterns': [{
'begin': r'x$\n?',
'end': 'x',
'patterns': [
{'match': r'\Ga', 'name': 'ga'},
{'match': 'a', 'name': 'noga'},
],
}],
}
def test_backslash_g_captures_nl():
compiler, state = _compiler_state(BEGIN_END_NL)
state, regions1 = highlight_line(compiler, state, 'x\n', True)
state, regions2 = highlight_line(compiler, state, 'aax\n', False)
assert regions1 == (
Region(0, 2, ('test',)),
)
assert regions2 == (
Region(0, 1, ('test', 'ga')),
Region(1, 2, ('test', 'noga')),
Region(2, 3, ('test',)),
Region(3, 4, ('test',)),
)
def test_backslash_g_captures_nl_next_line():
compiler, state = _compiler_state(BEGIN_END_NL)
state, regions1 = highlight_line(compiler, state, 'x\n', True)
state, regions2 = highlight_line(compiler, state, 'aa\n', False)
state, regions3 = highlight_line(compiler, state, 'aax\n', False)
assert regions1 == (
Region(0, 2, ('test',)),
)
assert regions2 == (
Region(0, 1, ('test', 'ga')),
Region(1, 2, ('test', 'noga')),
Region(2, 3, ('test',)),
)
assert regions3 == (
Region(0, 1, ('test', 'ga')),
Region(1, 2, ('test', 'noga')),
Region(2, 3, ('test',)),
Region(3, 4, ('test',)),
)
def test_while_no_nl():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [{
'begin': '> ',
'while': '> ',
'contentName': 'while',
'patterns': [
{'match': r'\Ga', 'name': 'ga'},
{'match': 'a', 'name': 'noga'},
],
}],
})
state, regions1 = highlight_line(compiler, state, '> aa\n', True)
state, regions2 = highlight_line(compiler, state, '> aa\n', False)
state, regions3 = highlight_line(compiler, state, 'after\n', False)
assert regions1 == (
Region(0, 2, ('test',)),
Region(2, 3, ('test', 'while', 'ga')),
Region(3, 4, ('test', 'while', 'noga')),
Region(4, 5, ('test', 'while')),
)
assert regions2 == (
Region(0, 2, ('test', 'while')),
Region(2, 3, ('test', 'while', 'ga')),
Region(3, 4, ('test', 'while', 'noga')),
Region(4, 5, ('test', 'while')),
)
assert regions3 == (
Region(0, 6, ('test',)),
)
def test_complex_captures():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [
{
'match': '(<).([^>]+)(>)',
'captures': {
'1': {'name': 'lbracket'},
'2': {
'patterns': [
{'match': 'a', 'name': 'a'},
{'match': 'z', 'name': 'z'},
],
},
'3': {'name': 'rbracket'},
},
},
],
})
state, regions = highlight_line(compiler, state, '<qabz>', first_line=True)
assert regions == (
Region(0, 1, ('test', 'lbracket')),
Region(1, 2, ('test',)),
Region(2, 3, ('test', 'a')),
Region(3, 4, ('test',)),
Region(4, 5, ('test', 'z')),
Region(5, 6, ('test', 'rbracket')),
)
def test_captures_multiple_applied_to_same_capture():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [
{
'match': '((a)) ((b) c) (d (e)) ((f) )',
'name': 'matched',
'captures': {
'1': {'name': 'g1'},
'2': {'name': 'g2'},
'3': {'name': 'g3'},
'4': {'name': 'g4'},
'5': {'name': 'g5'},
'6': {'name': 'g6'},
'7': {
'patterns': [
{'match': 'f', 'name': 'g7f'},
{'match': ' ', 'name': 'g7space'},
],
},
# this one has to backtrack some
'8': {'name': 'g8'},
},
},
],
})
state, regions = highlight_line(compiler, state, 'a b c d e f ', True)
assert regions == (
Region(0, 1, ('test', 'matched', 'g1', 'g2')),
Region(1, 2, ('test', 'matched')),
Region(2, 3, ('test', 'matched', 'g3', 'g4')),
Region(3, 5, ('test', 'matched', 'g3')),
Region(5, 6, ('test', 'matched')),
Region(6, 8, ('test', 'matched', 'g5')),
Region(8, 9, ('test', 'matched', 'g5', 'g6')),
Region(9, 10, ('test', 'matched')),
Region(10, 11, ('test', 'matched', 'g7f', 'g8')),
Region(11, 12, ('test', 'matched', 'g7space')),
)
def test_captures_ignores_empty():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [{
'match': '(.*) hi',
'captures': {'1': {'name': 'before'}},
}],
})
state, regions1 = highlight_line(compiler, state, ' hi\n', True)
state, regions2 = highlight_line(compiler, state, 'o hi\n', False)
assert regions1 == (
Region(0, 3, ('test',)),
Region(3, 4, ('test',)),
)
assert regions2 == (
Region(0, 1, ('test', 'before')),
Region(1, 4, ('test',)),
Region(4, 5, ('test',)),
)
def test_captures_ignores_invalid_out_of_bounds():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [{'match': '.', 'captures': {'1': {'name': 'oob'}}}],
})
state, regions = highlight_line(compiler, state, 'x', first_line=True)
assert regions == (
Region(0, 1, ('test',)),
)
def test_captures_begin_end():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [
{
'begin': '(""")',
'end': '(""")',
'beginCaptures': {'1': {'name': 'startquote'}},
'endCaptures': {'1': {'name': 'endquote'}},
},
],
})
state, regions = highlight_line(compiler, state, '"""x"""', True)
assert regions == (
Region(0, 3, ('test', 'startquote')),
Region(3, 4, ('test',)),
Region(4, 7, ('test', 'endquote')),
)
def test_captures_while_captures():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [
{
'begin': '(>) ',
'while': '(>) ',
'beginCaptures': {'1': {'name': 'bblock'}},
'whileCaptures': {'1': {'name': 'wblock'}},
},
],
})
state, regions1 = highlight_line(compiler, state, '> x\n', True)
state, regions2 = highlight_line(compiler, state, '> x\n', False)
assert regions1 == (
Region(0, 1, ('test', 'bblock')),
Region(1, 2, ('test',)),
Region(2, 4, ('test',)),
)
assert regions2 == (
Region(0, 1, ('test', 'wblock')),
Region(1, 2, ('test',)),
Region(2, 4, ('test',)),
)
def test_captures_implies_begin_end_captures():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [
{
'begin': '(""")',
'end': '(""")',
'captures': {'1': {'name': 'quote'}},
},
],
})
state, regions = highlight_line(compiler, state, '"""x"""', True)
assert regions == (
Region(0, 3, ('test', 'quote')),
Region(3, 4, ('test',)),
Region(4, 7, ('test', 'quote')),
)
def test_captures_implies_begin_while_captures():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [
{
'begin': '(>) ',
'while': '(>) ',
'captures': {'1': {'name': 'block'}},
},
],
})
state, regions1 = highlight_line(compiler, state, '> x\n', True)
state, regions2 = highlight_line(compiler, state, '> x\n', False)
assert regions1 == (
Region(0, 1, ('test', 'block')),
Region(1, 2, ('test',)),
Region(2, 4, ('test',)),
)
assert regions2 == (
Region(0, 1, ('test', 'block')),
Region(1, 2, ('test',)),
Region(2, 4, ('test',)),
)
def test_include_self():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [
{
'begin': '<',
'end': '>',
'contentName': 'bracketed',
'patterns': [{'include': '$self'}],
},
{'match': '.', 'name': 'content'},
],
})
state, regions = highlight_line(compiler, state, '<<_>>', first_line=True)
assert regions == (
Region(0, 1, ('test',)),
Region(1, 2, ('test', 'bracketed')),
Region(2, 3, ('test', 'bracketed', 'bracketed', 'content')),
Region(3, 4, ('test', 'bracketed', 'bracketed')),
Region(4, 5, ('test', 'bracketed')),
)
def test_include_repository_rule():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [{'include': '#impl'}],
'repository': {
'impl': {
'patterns': [
{'match': 'a', 'name': 'a'},
{'match': '.', 'name': 'other'},
],
},
},
})
state, regions = highlight_line(compiler, state, 'az', first_line=True)
assert regions == (
Region(0, 1, ('test', 'a')),
Region(1, 2, ('test', 'other')),
)
def test_include_other_grammar():
compiler, state = _compiler_state(
{
'scopeName': 'test',
'patterns': [
{
'begin': '<',
'end': '>',
'name': 'angle',
'patterns': [{'include': 'other.grammar'}],
},
{
'begin': '`',
'end': '`',
'name': 'tick',
'patterns': [{'include': 'other.grammar#backtick'}],
},
],
},
{
'scopeName': 'other.grammar',
'patterns': [
{'match': 'a', 'name': 'roota'},
{'match': '.', 'name': 'rootother'},
],
'repository': {
'backtick': {
'patterns': [
{'match': 'a', 'name': 'ticka'},
{'match': '.', 'name': 'tickother'},
],
},
},
},
)
state, regions1 = highlight_line(compiler, state, '<az>\n', True)
state, regions2 = highlight_line(compiler, state, '`az`\n', False)
assert regions1 == (
Region(0, 1, ('test', 'angle')),
Region(1, 2, ('test', 'angle', 'roota')),
Region(2, 3, ('test', 'angle', 'rootother')),
Region(3, 4, ('test', 'angle')),
Region(4, 5, ('test',)),
)
assert regions2 == (
Region(0, 1, ('test', 'tick')),
Region(1, 2, ('test', 'tick', 'ticka')),
Region(2, 3, ('test', 'tick', 'tickother')),
Region(3, 4, ('test', 'tick')),
Region(4, 5, ('test',)),
)
def test_include_base():
compiler, state = _compiler_state(
{
'scopeName': 'test',
'patterns': [
{
'begin': '<',
'end': '>',
'name': 'bracket',
# $base from root grammar includes itself
'patterns': [{'include': '$base'}],
},
{'include': 'other.grammar'},
{'match': 'z', 'name': 'testz'},
],
},
{
'scopeName': 'other.grammar',
'patterns': [
{
'begin': '`',
'end': '`',
'name': 'tick',
# $base from included grammar includes the root
'patterns': [{'include': '$base'}],
},
],
},
)
state, regions1 = highlight_line(compiler, state, '<z>\n', True)
state, regions2 = highlight_line(compiler, state, '`z`\n', False)
assert regions1 == (
Region(0, 1, ('test', 'bracket')),
Region(1, 2, ('test', 'bracket', 'testz')),
Region(2, 3, ('test', 'bracket')),
Region(3, 4, ('test',)),
)
assert regions2 == (
Region(0, 1, ('test', 'tick')),
Region(1, 2, ('test', 'tick', 'testz')),
Region(2, 3, ('test', 'tick')),
Region(3, 4, ('test',)),
)
def test_rule_with_begin_and_no_end():
compiler, state = _compiler_state({
'scopeName': 'test',
'patterns': [
{
'begin': '!', 'end': '!', 'name': 'bang',
'patterns': [{'begin': '--', 'name': 'invalid'}],
},
],
})
state, regions = highlight_line(compiler, state, '!x! !--!', True)
assert regions == (
Region(0, 1, ('test', 'bang')),
Region(1, 2, ('test', 'bang')),
Region(2, 3, ('test', 'bang')),
Region(3, 4, ('test',)),
Region(4, 5, ('test', 'bang')),
Region(5, 7, ('test', 'bang', 'invalid')),
Region(7, 8, ('test', 'bang', 'invalid')),
)