13 Commits

Author SHA1 Message Date
Anthony Sottile
d7622f38c6 v0.0.16 2020-08-29 13:07:01 -07:00
Anthony Sottile
e474396790 Merge pull request #92 from asottile/xterm_mono
do not crash if the terminal does not have color support
2020-08-29 13:06:34 -07:00
Anthony Sottile
e6a0353650 do not crash if the terminal does not have color support 2020-08-29 12:40:12 -07:00
Anthony Sottile
e0a59e3f9c v0.0.15 2020-08-28 21:17:51 -07:00
Anthony Sottile
787dc0d18f Merge pull request #91 from asottile/fix_cursor_position_comment
fix position changes when commenting and cursor is before comment
2020-08-28 21:17:27 -07:00
Anthony Sottile
fd9393c8b1 fix position changes when commenting and cursor is before comment 2020-08-28 21:09:16 -07:00
Anthony Sottile
eb26d93e03 Merge pull request #90 from asottile/fix_out_of_bounds_on_uncomment
Fix out of bounds on uncomment
2020-08-28 21:00:52 -07:00
Anthony Sottile
055d738142 Fix out of bounds on uncomment 2020-08-28 20:52:01 -07:00
Anthony Sottile
29062628f9 v0.0.14 2020-08-24 14:03:36 -07:00
Anthony Sottile
1fab2a4b71 Merge pull request #85 from asottile/comment
add :comment command for toggling comments
2020-08-24 14:02:23 -07:00
Anthony Sottile
9f5e8c02cb add :comment command for toggling comments 2020-08-24 13:52:35 -07:00
Anthony Sottile
31624856d2 Merge pull request #84 from asottile/asottile-patch-1
add logo to README
2020-08-11 23:27:25 -07:00
Anthony Sottile
97b3b4deef add logo to README 2020-08-11 23:20:48 -07:00
8 changed files with 193 additions and 12 deletions

View File

@@ -1,6 +1,8 @@
[![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/asottile.babi?branchName=master)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=29&branchName=master) [![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/asottile.babi?branchName=master)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=29&branchName=master)
[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/29/master.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=29&branchName=master) [![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/29/master.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=29&branchName=master)
![babi logo](https://user-images.githubusercontent.com/1810591/89981369-9ed84e80-dc28-11ea-9708-5f4c49c09632.png)
babi babi
==== ====

View File

@@ -33,14 +33,17 @@ class ColorManager(NamedTuple):
return self.raw_color_pair(fg_i, bg_i) return self.raw_color_pair(fg_i, bg_i)
def raw_color_pair(self, fg: int, bg: int) -> int: def raw_color_pair(self, fg: int, bg: int) -> int:
try: if curses.COLORS > 0:
return self.raw_pairs[(fg, bg)] try:
except KeyError: return self.raw_pairs[(fg, bg)]
pass except KeyError:
pass
n = self.raw_pairs[(fg, bg)] = len(self.raw_pairs) + 1 n = self.raw_pairs[(fg, bg)] = len(self.raw_pairs) + 1
curses.init_pair(n, fg, bg) curses.init_pair(n, fg, bg)
return n return n
else:
return 0
@classmethod @classmethod
def make(cls) -> 'ColorManager': def make(cls) -> 'ColorManager':

View File

@@ -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,51 @@ 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()}'
if self.buf.y == lineno and self.buf.x > ws_len:
self.buf.x -= len(line) - len(self.buf[lineno])
def _comment_add(self, lineno: int, prefix: str) -> None:
prefix = f'{prefix} '
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:]}'
if lineno == self.buf.y and self.buf.x > ws_len:
self.buf.x += len(prefix)
@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,

View File

@@ -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

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
name = babi name = babi
version = 0.0.13 version = 0.0.16
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

View File

@@ -0,0 +1,114 @@
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')
def test_comment_cursor_at_end_of_line(run, ten_lines):
with run(str(ten_lines)) as h, and_exit(h):
h.press('# ')
h.press('End')
h.await_cursor_position(x=8, y=1)
trigger_command_mode(h)
h.press_and_enter(':comment')
h.await_cursor_position(x=6, y=1)
def test_add_comment_moves_cursor(run, ten_lines):
with run(str(ten_lines)) as h, and_exit(h):
h.press('End')
h.await_cursor_position(x=6, y=1)
trigger_command_mode(h)
h.press_and_enter(':comment')
h.await_cursor_position(x=8, y=1)
def test_do_not_move_if_cursor_before_comment(run, tmpdir):
f = tmpdir.join('f')
f.write('\t\tfoo')
with run(str(f)) as h, and_exit(h):
h.press('Right')
h.await_cursor_position(x=4, y=1)
trigger_command_mode(h)
h.press_and_enter(':comment')
h.await_cursor_position(x=4, y=1)

View File

@@ -303,6 +303,7 @@ class DeferredRunner:
self.color_pairs = {0: (7, 0)} self.color_pairs = {0: (7, 0)}
self.screen = Screen(width, height) self.screen = Screen(width, height)
self._n_colors, self._can_change_color = { self._n_colors, self._can_change_color = {
'xterm-mono': (0, False),
'screen': (8, False), 'screen': (8, False),
'screen-256color': (256, False), 'screen-256color': (256, False),
'xterm-256color': (256, True), 'xterm-256color': (256, True),

View File

@@ -153,3 +153,8 @@ def test_syntax_highlighting_tabs_after_line_creation(run, tmpdir):
h.press('Enter') h.press('Enter')
h.await_text('foo\n x\nx\ny\n') h.await_text('foo\n x\nx\ny\n')
def test_does_not_crash_with_no_color_support(run):
with run(term='xterm-mono') as h, and_exit(h):
pass