Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29062628f9 | ||
|
|
1fab2a4b71 | ||
|
|
9f5e8c02cb | ||
|
|
31624856d2 | ||
|
|
97b3b4deef | ||
|
|
41880d5f8c | ||
|
|
effe988f60 | ||
|
|
84b20a4016 | ||
|
|
5d2c9532a3 | ||
|
|
33ff8d9726 | ||
|
|
f0b2af9a9f | ||
|
|
fc21a144aa | ||
|
|
973b4c3cf8 | ||
|
|
bd60977438 |
@@ -1,6 +1,8 @@
|
|||||||
[](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=29&branchName=master)
|
[](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=29&branchName=master)
|
||||||
[](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=29&branchName=master)
|
[](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=29&branchName=master)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
babi
|
babi
|
||||||
====
|
====
|
||||||
|
|
||||||
@@ -12,7 +14,7 @@ a text editor, eventually...
|
|||||||
|
|
||||||
### why is it called babi?
|
### why is it called babi?
|
||||||
|
|
||||||
I usually use the text editor `nano`, frequently I typo this. on a qwerty
|
I used to use the text editor `nano`, frequently I typo this. on a qwerty
|
||||||
keyboard, when the right hand is shifted left by one, `nano` becomes `babi`.
|
keyboard, when the right hand is shifted left by one, `nano` becomes `babi`.
|
||||||
|
|
||||||
### quitting babi
|
### quitting babi
|
||||||
|
|||||||
52
babi/file.py
52
babi/file.py
@@ -6,6 +6,7 @@ import hashlib
|
|||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
import os.path
|
import os.path
|
||||||
|
import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import cast
|
from typing import cast
|
||||||
@@ -38,6 +39,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
TCallable = TypeVar('TCallable', bound=Callable[..., Any])
|
TCallable = TypeVar('TCallable', bound=Callable[..., Any])
|
||||||
|
|
||||||
|
WS_RE = re.compile(r'^\s*')
|
||||||
|
|
||||||
|
|
||||||
def get_lines(sio: IO[str]) -> Tuple[List[str], str, bool, str]:
|
def get_lines(sio: IO[str]) -> Tuple[List[str], str, bool, str]:
|
||||||
sha256 = hashlib.sha256()
|
sha256 = hashlib.sha256()
|
||||||
@@ -650,6 +653,13 @@ class File:
|
|||||||
self.buf.x = 0
|
self.buf.x = 0
|
||||||
self.buf.scroll_screen_if_needed(margin)
|
self.buf.scroll_screen_if_needed(margin)
|
||||||
|
|
||||||
|
def _selection_lines(self) -> Tuple[int, int]:
|
||||||
|
(s_y, _), (e_y, _) = self.selection.get()
|
||||||
|
e_y = min(e_y + 1, len(self.buf) - 1)
|
||||||
|
if self.buf[e_y - 1] == '':
|
||||||
|
e_y -= 1
|
||||||
|
return s_y, e_y
|
||||||
|
|
||||||
@edit_action('sort', final=True)
|
@edit_action('sort', final=True)
|
||||||
def sort(self, margin: Margin, reverse: bool = False) -> None:
|
def sort(self, margin: Margin, reverse: bool = False) -> None:
|
||||||
self._sort(margin, 0, len(self.buf) - 1, reverse=reverse)
|
self._sort(margin, 0, len(self.buf) - 1, reverse=reverse)
|
||||||
@@ -657,12 +667,46 @@ class File:
|
|||||||
@edit_action('sort selection', final=True)
|
@edit_action('sort selection', final=True)
|
||||||
@clear_selection
|
@clear_selection
|
||||||
def sort_selection(self, margin: Margin, reverse: bool = False) -> None:
|
def sort_selection(self, margin: Margin, reverse: bool = False) -> None:
|
||||||
(s_y, _), (e_y, _) = self.selection.get()
|
s_y, e_y = self._selection_lines()
|
||||||
e_y = min(e_y + 1, len(self.buf) - 1)
|
|
||||||
if self.buf[e_y - 1] == '':
|
|
||||||
e_y -= 1
|
|
||||||
self._sort(margin, s_y, e_y, reverse=reverse)
|
self._sort(margin, s_y, e_y, reverse=reverse)
|
||||||
|
|
||||||
|
def _is_commented(self, lineno: int, prefix: str) -> bool:
|
||||||
|
return self.buf[lineno].lstrip().startswith(prefix)
|
||||||
|
|
||||||
|
def _comment_remove(self, lineno: int, prefix: str) -> None:
|
||||||
|
line = self.buf[lineno]
|
||||||
|
ws_match = WS_RE.match(line)
|
||||||
|
assert ws_match is not None
|
||||||
|
ws_len = len(ws_match[0])
|
||||||
|
rest_offset = ws_len + len(prefix)
|
||||||
|
if line.startswith(prefix, ws_len):
|
||||||
|
self.buf[lineno] = f'{ws_match[0]}{line[rest_offset:].lstrip()}'
|
||||||
|
|
||||||
|
def _comment_add(self, lineno: int, prefix: str) -> None:
|
||||||
|
line = self.buf[lineno]
|
||||||
|
ws_match = WS_RE.match(line)
|
||||||
|
assert ws_match is not None
|
||||||
|
ws_len = len(ws_match[0])
|
||||||
|
self.buf[lineno] = f'{ws_match[0]}{prefix} {line[ws_len:]}'
|
||||||
|
|
||||||
|
@edit_action('comment', final=True)
|
||||||
|
def toggle_comment(self, prefix: str) -> None:
|
||||||
|
if self._is_commented(self.buf.y, prefix):
|
||||||
|
self._comment_remove(self.buf.y, prefix)
|
||||||
|
else:
|
||||||
|
self._comment_add(self.buf.y, prefix)
|
||||||
|
|
||||||
|
@edit_action('comment selection', final=True)
|
||||||
|
@clear_selection
|
||||||
|
def toggle_comment_selection(self, prefix: str) -> None:
|
||||||
|
s_y, e_y = self._selection_lines()
|
||||||
|
commented = self._is_commented(s_y, prefix)
|
||||||
|
for lineno in range(s_y, e_y):
|
||||||
|
if commented:
|
||||||
|
self._comment_remove(lineno, prefix)
|
||||||
|
else:
|
||||||
|
self._comment_add(lineno, prefix)
|
||||||
|
|
||||||
DISPATCH = {
|
DISPATCH = {
|
||||||
# movement
|
# movement
|
||||||
b'KEY_UP': up,
|
b'KEY_UP': up,
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ class CompiledRegsetRule(CompiledRule, Protocol):
|
|||||||
class Entry(NamedTuple):
|
class Entry(NamedTuple):
|
||||||
scope: Tuple[str, ...]
|
scope: Tuple[str, ...]
|
||||||
rule: CompiledRule
|
rule: CompiledRule
|
||||||
|
start: Tuple[str, int]
|
||||||
reg: _Reg = ERR_REG
|
reg: _Reg = ERR_REG
|
||||||
boundary: bool = False
|
boundary: bool = False
|
||||||
|
|
||||||
@@ -284,7 +285,7 @@ def _inner_capture_parse(
|
|||||||
scope: Scope,
|
scope: Scope,
|
||||||
rule: CompiledRule,
|
rule: CompiledRule,
|
||||||
) -> Regions:
|
) -> Regions:
|
||||||
state = State.root(Entry(scope + rule.name, rule))
|
state = State.root(Entry(scope + rule.name, rule, (s, 0)))
|
||||||
_, regions = highlight_line(compiler, state, s, first_line=False)
|
_, regions = highlight_line(compiler, state, s, first_line=False)
|
||||||
return tuple(
|
return tuple(
|
||||||
r._replace(start=r.start + start, end=r.end + start) for r in regions
|
r._replace(start=r.start + start, end=r.end + start) for r in regions
|
||||||
@@ -440,7 +441,8 @@ class EndRule(NamedTuple):
|
|||||||
|
|
||||||
boundary = match.end() == len(match.string)
|
boundary = match.end() == len(match.string)
|
||||||
reg = make_reg(expand_escaped(match, self.end))
|
reg = make_reg(expand_escaped(match, self.end))
|
||||||
state = state.push(Entry(next_scope, self, reg, boundary))
|
start = (match.string, match.start())
|
||||||
|
state = state.push(Entry(next_scope, self, start, reg, boundary))
|
||||||
regions = _captures(compiler, scope, match, self.begin_captures)
|
regions = _captures(compiler, scope, match, self.begin_captures)
|
||||||
return state, True, regions
|
return state, True, regions
|
||||||
|
|
||||||
@@ -455,7 +457,16 @@ class EndRule(NamedTuple):
|
|||||||
if m.start() > pos:
|
if m.start() > pos:
|
||||||
ret.append(Region(pos, m.start(), state.cur.scope))
|
ret.append(Region(pos, m.start(), state.cur.scope))
|
||||||
ret.extend(_captures(compiler, state.cur.scope, m, self.end_captures))
|
ret.extend(_captures(compiler, state.cur.scope, m, self.end_captures))
|
||||||
return state.pop(), m.end(), False, tuple(ret)
|
# this is probably a bug in the grammar, but it pushed and popped at
|
||||||
|
# the same position.
|
||||||
|
# we'll advance the highlighter by one position to get past the loop
|
||||||
|
# this appears to be what vs code does as well
|
||||||
|
if state.entries[-1].start == (m.string, m.end()):
|
||||||
|
ret.append(Region(m.end(), m.end() + 1, state.cur.scope))
|
||||||
|
end = m.end() + 1
|
||||||
|
else:
|
||||||
|
end = m.end()
|
||||||
|
return state.pop(), end, False, tuple(ret)
|
||||||
|
|
||||||
def search(
|
def search(
|
||||||
self,
|
self,
|
||||||
@@ -501,7 +512,9 @@ class WhileRule(NamedTuple):
|
|||||||
|
|
||||||
boundary = match.end() == len(match.string)
|
boundary = match.end() == len(match.string)
|
||||||
reg = make_reg(expand_escaped(match, self.while_))
|
reg = make_reg(expand_escaped(match, self.while_))
|
||||||
state = state.push_while(self, Entry(next_scope, self, reg, boundary))
|
start = (match.string, match.start())
|
||||||
|
entry = Entry(next_scope, self, start, reg, boundary)
|
||||||
|
state = state.push_while(self, entry)
|
||||||
regions = _captures(compiler, scope, match, self.begin_captures)
|
regions = _captures(compiler, scope, match, self.begin_captures)
|
||||||
return state, True, regions
|
return state, True, regions
|
||||||
|
|
||||||
@@ -541,7 +554,7 @@ class Compiler:
|
|||||||
self._rule_to_grammar: Dict[_Rule, Grammar] = {}
|
self._rule_to_grammar: Dict[_Rule, Grammar] = {}
|
||||||
self._c_rules: Dict[_Rule, CompiledRule] = {}
|
self._c_rules: Dict[_Rule, CompiledRule] = {}
|
||||||
root = self._compile_root(grammar)
|
root = self._compile_root(grammar)
|
||||||
self.root_state = State.root(Entry(root.name, root))
|
self.root_state = State.root(Entry(root.name, root, ('', 0)))
|
||||||
|
|
||||||
def _visit_rule(self, grammar: Grammar, rule: _Rule) -> _Rule:
|
def _visit_rule(self, grammar: Grammar, rule: _Rule) -> _Rule:
|
||||||
self._rule_to_grammar[rule] = grammar
|
self._rule_to_grammar[rule] = grammar
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import argparse
|
|||||||
import curses
|
import curses
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@@ -138,6 +139,11 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|||||||
else:
|
else:
|
||||||
stdin = ''
|
stdin = ''
|
||||||
|
|
||||||
|
# ignore backgrounding signals, we'll handle those in curses
|
||||||
|
# fixes a problem with ^Z on termination which would break the terminal
|
||||||
|
if sys.platform != 'win32': # pragma: win32 no cover # pragma: no branch
|
||||||
|
signal.signal(signal.SIGTSTP, signal.SIG_IGN)
|
||||||
|
|
||||||
with perf_log(args.perf_log) as perf, make_stdscr() as stdscr:
|
with perf_log(args.perf_log) as perf, make_stdscr() as stdscr:
|
||||||
if args.key_debug:
|
if args.key_debug:
|
||||||
return _key_debug(stdscr, perf)
|
return _key_debug(stdscr, perf)
|
||||||
|
|||||||
117
babi/reg.py
117
babi/reg.py
@@ -6,80 +6,36 @@ from typing import Tuple
|
|||||||
|
|
||||||
import onigurumacffi
|
import onigurumacffi
|
||||||
|
|
||||||
from babi.cached_property import cached_property
|
|
||||||
|
|
||||||
_BACKREF_RE = re.compile(r'((?<!\\)(?:\\\\)*)\\([0-9]+)')
|
_BACKREF_RE = re.compile(r'((?<!\\)(?:\\\\)*)\\([0-9]+)')
|
||||||
|
|
||||||
|
|
||||||
def _replace_esc(s: str, chars: str) -> str:
|
_FLAGS = {
|
||||||
"""replace the given escape sequences of `chars` with \\uffff"""
|
# (first_line, boundary)
|
||||||
for c in chars:
|
(False, False): (
|
||||||
if f'\\{c}' in s:
|
onigurumacffi.OnigSearchOption.NOT_END_STRING |
|
||||||
break
|
onigurumacffi.OnigSearchOption.NOT_BEGIN_STRING |
|
||||||
else:
|
onigurumacffi.OnigSearchOption.NOT_BEGIN_POSITION
|
||||||
return s
|
),
|
||||||
|
(False, True): (
|
||||||
b = []
|
onigurumacffi.OnigSearchOption.NOT_END_STRING |
|
||||||
i = 0
|
onigurumacffi.OnigSearchOption.NOT_BEGIN_STRING
|
||||||
length = len(s)
|
),
|
||||||
while i < length:
|
(True, False): (
|
||||||
try:
|
onigurumacffi.OnigSearchOption.NOT_END_STRING |
|
||||||
sbi = s.index('\\', i)
|
onigurumacffi.OnigSearchOption.NOT_BEGIN_POSITION
|
||||||
except ValueError:
|
),
|
||||||
b.append(s[i:])
|
(True, True): onigurumacffi.OnigSearchOption.NOT_END_STRING,
|
||||||
break
|
}
|
||||||
if sbi > i:
|
|
||||||
b.append(s[i:sbi])
|
|
||||||
b.append('\\')
|
|
||||||
i = sbi + 1
|
|
||||||
if i < length:
|
|
||||||
if s[i] in chars:
|
|
||||||
b.append('\uffff')
|
|
||||||
else:
|
|
||||||
b.append(s[i])
|
|
||||||
i += 1
|
|
||||||
return ''.join(b)
|
|
||||||
|
|
||||||
|
|
||||||
class _Reg:
|
class _Reg:
|
||||||
def __init__(self, s: str) -> None:
|
def __init__(self, s: str) -> None:
|
||||||
self._pattern = s
|
self._pattern = s
|
||||||
|
self._reg = onigurumacffi.compile(self._pattern)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'{type(self).__name__}({self._pattern!r})'
|
return f'{type(self).__name__}({self._pattern!r})'
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def _reg(self) -> onigurumacffi._Pattern:
|
|
||||||
return onigurumacffi.compile(_replace_esc(self._pattern, 'z'))
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def _reg_no_A(self) -> onigurumacffi._Pattern:
|
|
||||||
return onigurumacffi.compile(_replace_esc(self._pattern, 'Az'))
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def _reg_no_G(self) -> onigurumacffi._Pattern:
|
|
||||||
return onigurumacffi.compile(_replace_esc(self._pattern, 'Gz'))
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def _reg_no_A_no_G(self) -> onigurumacffi._Pattern:
|
|
||||||
return onigurumacffi.compile(_replace_esc(self._pattern, 'AGz'))
|
|
||||||
|
|
||||||
def _get_reg(
|
|
||||||
self,
|
|
||||||
first_line: bool,
|
|
||||||
boundary: bool,
|
|
||||||
) -> onigurumacffi._Pattern:
|
|
||||||
if boundary:
|
|
||||||
if first_line:
|
|
||||||
return self._reg
|
|
||||||
else:
|
|
||||||
return self._reg_no_A
|
|
||||||
else:
|
|
||||||
if first_line:
|
|
||||||
return self._reg_no_G
|
|
||||||
else:
|
|
||||||
return self._reg_no_A_no_G
|
|
||||||
|
|
||||||
def search(
|
def search(
|
||||||
self,
|
self,
|
||||||
line: str,
|
line: str,
|
||||||
@@ -87,7 +43,7 @@ class _Reg:
|
|||||||
first_line: bool,
|
first_line: bool,
|
||||||
boundary: bool,
|
boundary: bool,
|
||||||
) -> Optional[Match[str]]:
|
) -> Optional[Match[str]]:
|
||||||
return self._get_reg(first_line, boundary).search(line, pos)
|
return self._reg.search(line, pos, flags=_FLAGS[first_line, boundary])
|
||||||
|
|
||||||
def match(
|
def match(
|
||||||
self,
|
self,
|
||||||
@@ -96,36 +52,18 @@ class _Reg:
|
|||||||
first_line: bool,
|
first_line: bool,
|
||||||
boundary: bool,
|
boundary: bool,
|
||||||
) -> Optional[Match[str]]:
|
) -> Optional[Match[str]]:
|
||||||
return self._get_reg(first_line, boundary).match(line, pos)
|
return self._reg.match(line, pos, flags=_FLAGS[first_line, boundary])
|
||||||
|
|
||||||
|
|
||||||
class _RegSet:
|
class _RegSet:
|
||||||
def __init__(self, *s: str) -> None:
|
def __init__(self, *s: str) -> None:
|
||||||
self._patterns = s
|
self._patterns = s
|
||||||
|
self._set = onigurumacffi.compile_regset(*self._patterns)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
args = ', '.join(repr(s) for s in self._patterns)
|
args = ', '.join(repr(s) for s in self._patterns)
|
||||||
return f'{type(self).__name__}({args})'
|
return f'{type(self).__name__}({args})'
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def _set(self) -> onigurumacffi._RegSet:
|
|
||||||
return onigurumacffi.compile_regset(*self._patterns)
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def _set_no_A(self) -> onigurumacffi._RegSet:
|
|
||||||
patterns = (_replace_esc(p, 'A') for p in self._patterns)
|
|
||||||
return onigurumacffi.compile_regset(*patterns)
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def _set_no_G(self) -> onigurumacffi._RegSet:
|
|
||||||
patterns = (_replace_esc(p, 'G') for p in self._patterns)
|
|
||||||
return onigurumacffi.compile_regset(*patterns)
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def _set_no_A_no_G(self) -> onigurumacffi._RegSet:
|
|
||||||
patterns = (_replace_esc(p, 'AG') for p in self._patterns)
|
|
||||||
return onigurumacffi.compile_regset(*patterns)
|
|
||||||
|
|
||||||
def search(
|
def search(
|
||||||
self,
|
self,
|
||||||
line: str,
|
line: str,
|
||||||
@@ -133,16 +71,7 @@ class _RegSet:
|
|||||||
first_line: bool,
|
first_line: bool,
|
||||||
boundary: bool,
|
boundary: bool,
|
||||||
) -> Tuple[int, Optional[Match[str]]]:
|
) -> Tuple[int, Optional[Match[str]]]:
|
||||||
if boundary:
|
return self._set.search(line, pos, flags=_FLAGS[first_line, boundary])
|
||||||
if first_line:
|
|
||||||
return self._set.search(line, pos)
|
|
||||||
else:
|
|
||||||
return self._set_no_A.search(line, pos)
|
|
||||||
else:
|
|
||||||
if first_line:
|
|
||||||
return self._set_no_G.search(line, pos)
|
|
||||||
else:
|
|
||||||
return self._set_no_A_no_G.search(line, pos)
|
|
||||||
|
|
||||||
|
|
||||||
def expand_escaped(match: Match[str], s: str) -> str:
|
def expand_escaped(match: Match[str], s: str) -> str:
|
||||||
@@ -151,4 +80,4 @@ def expand_escaped(match: Match[str], s: str) -> str:
|
|||||||
|
|
||||||
make_reg = functools.lru_cache(maxsize=None)(_Reg)
|
make_reg = functools.lru_cache(maxsize=None)(_Reg)
|
||||||
make_regset = functools.lru_cache(maxsize=None)(_RegSet)
|
make_regset = functools.lru_cache(maxsize=None)(_RegSet)
|
||||||
ERR_REG = make_reg(')this pattern always triggers an error when used(')
|
ERR_REG = make_reg('$ ^')
|
||||||
|
|||||||
@@ -457,6 +457,13 @@ class Screen:
|
|||||||
for file in self.files:
|
for file in self.files:
|
||||||
file.buf.set_tab_size(parsed_tab_size)
|
file.buf.set_tab_size(parsed_tab_size)
|
||||||
self.status.update('updated!')
|
self.status.update('updated!')
|
||||||
|
elif response == ':comment' or response.startswith(':comment '):
|
||||||
|
_, _, comment = response.partition(' ')
|
||||||
|
comment = (comment or '#').strip()
|
||||||
|
if self.file.selection.start:
|
||||||
|
self.file.toggle_comment_selection(comment)
|
||||||
|
else:
|
||||||
|
self.file.toggle_comment(comment)
|
||||||
else:
|
else:
|
||||||
self.status.update(f'invalid command: {response}')
|
self.status.update(f'invalid command: {response}')
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = babi
|
name = babi
|
||||||
version = 0.0.11
|
version = 0.0.14
|
||||||
description = a text editor
|
description = a text editor
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
long_description_content_type = text/markdown
|
long_description_content_type = text/markdown
|
||||||
@@ -24,7 +24,7 @@ packages = find:
|
|||||||
install_requires =
|
install_requires =
|
||||||
babi-grammars
|
babi-grammars
|
||||||
identify
|
identify
|
||||||
onigurumacffi>=0.0.10
|
onigurumacffi>=0.0.18
|
||||||
importlib_metadata>=1;python_version<"3.8"
|
importlib_metadata>=1;python_version<"3.8"
|
||||||
windows-curses;sys_platform=="win32"
|
windows-curses;sys_platform=="win32"
|
||||||
python_requires = >=3.6.1
|
python_requires = >=3.6.1
|
||||||
|
|||||||
75
tests/features/comment_test.py
Normal file
75
tests/features/comment_test.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
from testing.runner import and_exit
|
||||||
|
from testing.runner import trigger_command_mode
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment_some_code(run, ten_lines):
|
||||||
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
|
h.press('S-Down')
|
||||||
|
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':comment')
|
||||||
|
|
||||||
|
h.await_text('# line_0\n# line_1\nline_2\n')
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment_some_code_with_alternate_comment_character(run, ten_lines):
|
||||||
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
|
h.press('S-Down')
|
||||||
|
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':comment //')
|
||||||
|
|
||||||
|
h.await_text('// line_0\n// line_1\nline_2\n')
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment_partially_commented(run, ten_lines):
|
||||||
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
|
h.press('#')
|
||||||
|
h.press('S-Down')
|
||||||
|
h.await_text('#line_0\nline_1\nline_2')
|
||||||
|
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':comment')
|
||||||
|
|
||||||
|
h.await_text('line_0\nline_1\nline_2\n')
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment_partially_uncommented(run, ten_lines):
|
||||||
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
|
h.press('Down')
|
||||||
|
h.press('#')
|
||||||
|
h.press('Up')
|
||||||
|
h.press('S-Down')
|
||||||
|
h.await_text('line_0\n#line_1\nline_2')
|
||||||
|
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':comment')
|
||||||
|
|
||||||
|
h.await_text('# line_0\n# #line_1\nline_2\n')
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment_single_line(run, ten_lines):
|
||||||
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':comment')
|
||||||
|
|
||||||
|
h.await_text('# line_0\nline_1\n')
|
||||||
|
|
||||||
|
|
||||||
|
def test_uncomment_single_line(run, ten_lines):
|
||||||
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
|
h.press('#')
|
||||||
|
h.await_text('#line_0\nline_1\n')
|
||||||
|
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':comment')
|
||||||
|
|
||||||
|
h.await_text('line_0\nline_1\n')
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment_with_trailing_whitespace(run, ten_lines):
|
||||||
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
|
trigger_command_mode(h)
|
||||||
|
h.press_and_enter(':comment // ')
|
||||||
|
|
||||||
|
h.await_text('// line_0\nline_1\n')
|
||||||
@@ -637,3 +637,25 @@ def test_backslash_z(compiler_state):
|
|||||||
assert regions2 == (
|
assert regions2 == (
|
||||||
Region(0, 6, ('test', 'comment')),
|
Region(0, 6, ('test', 'comment')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_buggy_begin_end_grammar(compiler_state):
|
||||||
|
# before this would result in an infinite loop of start / end
|
||||||
|
compiler, state = compiler_state({
|
||||||
|
'scopeName': 'test',
|
||||||
|
'patterns': [
|
||||||
|
{
|
||||||
|
'begin': '(?=</style)',
|
||||||
|
'end': '(?=</style)',
|
||||||
|
'name': 'css',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
state, regions = highlight_line(compiler, state, 'test </style', True)
|
||||||
|
|
||||||
|
assert regions == (
|
||||||
|
Region(0, 5, ('test',)),
|
||||||
|
Region(5, 6, ('test', 'css')),
|
||||||
|
Region(6, 12, ('test',)),
|
||||||
|
)
|
||||||
|
|||||||
@@ -35,9 +35,8 @@ def test_reg_other_escapes_left_untouched():
|
|||||||
def test_reg_not_out_of_bounds_at_end():
|
def test_reg_not_out_of_bounds_at_end():
|
||||||
# the only way this is triggerable is with an illegal regex, we'd rather
|
# the only way this is triggerable is with an illegal regex, we'd rather
|
||||||
# produce an error about the regex being wrong than an IndexError
|
# produce an error about the regex being wrong than an IndexError
|
||||||
reg = _Reg('\\A\\')
|
|
||||||
with pytest.raises(onigurumacffi.OnigError) as excinfo:
|
with pytest.raises(onigurumacffi.OnigError) as excinfo:
|
||||||
reg.search('\\', 0, first_line=False, boundary=False)
|
_Reg('\\A\\')
|
||||||
msg, = excinfo.value.args
|
msg, = excinfo.value.args
|
||||||
assert msg == 'end pattern at escape'
|
assert msg == 'end pattern at escape'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user