33 Commits

Author SHA1 Message Date
Anthony Sottile
c7904fdf9b v0.0.21 2021-01-29 20:31:27 -08:00
Anthony Sottile
f6db46736c Merge pull request #124 from theendlessriver13/io_error_save
fix crashing on permission denied error
2021-01-29 20:30:34 -08:00
theendlessriver13
e059dc294b fix crashing on permission denied 2021-01-30 04:09:22 +01:00
Anthony Sottile
4dc42aa628 Merge pull request #123 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-01-25 12:10:22 -08:00
pre-commit-ci[bot]
25659ca5c7 [pre-commit.ci] pre-commit autoupdate 2021-01-25 16:48:35 +00:00
Anthony Sottile
bd8dc3beb0 v0.0.20 2021-01-18 15:08:22 -08:00
Anthony Sottile
324513c36a Merge pull request #122 from asottile/backspace_blank_line_at_eof
fix backspacing an extra blank line at end of file
2021-01-18 15:07:48 -08:00
Anthony Sottile
09643b0f80 fix backspacing an extra blank line at end of file 2021-01-18 14:57:34 -08:00
Anthony Sottile
8245ee56a5 Merge pull request #119 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-01-04 09:18:29 -08:00
pre-commit-ci[bot]
8aaee402e7 [pre-commit.ci] pre-commit autoupdate 2021-01-04 16:53:18 +00:00
Anthony Sottile
641eed65d5 Merge pull request #118 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2020-12-21 09:32:09 -08:00
pre-commit-ci[bot]
694853e341 [pre-commit.ci] pre-commit autoupdate 2020-12-21 16:51:45 +00:00
Anthony Sottile
1a4d15206c Merge pull request #113 from asottile/case_insensitive_short_answers
ignore case for quick prompt answers
2020-11-20 12:34:40 -08:00
Anthony Sottile
4ca3b0d1e5 ignore case for quick prompt answers 2020-11-20 12:22:55 -08:00
Anthony Sottile
47868e77a2 Merge pull request #112 from asottile/all-repos_autofix_gh-sponsors
Add link to GitHub Sponsors
2020-11-19 17:12:44 -08:00
Anthony Sottile
9906e223bd Add link to GitHub Sponsors
at the time of writing I am currently unemployed.  I'd love to make open
source a full time career.  if you or your company is deriving value from
this free software, please consider [sponsoring].

[sponsoring]: https://github.com/sponsors/asottile

Committed via https://github.com/asottile/all-repos
2020-11-19 16:58:49 -08:00
Anthony Sottile
a5101007cd Merge pull request #111 from rmorshea/patch-1
Add video explaining babi vs nano
2020-11-18 12:08:01 -08:00
Ryan Morshead
572151197d Add video explaining babi vs nano
closes: #110
2020-11-18 11:52:50 -08:00
Anthony Sottile
a8a5afc6ed Merge pull request #108 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2020-11-16 09:46:42 -08:00
pre-commit-ci[bot]
e523a694b6 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2020-11-16 17:12:39 +00:00
pre-commit-ci[bot]
8161c9e34f [pre-commit.ci] pre-commit autoupdate 2020-11-16 17:11:07 +00:00
Anthony Sottile
0b5e187be5 v0.0.19 2020-11-07 12:04:31 -08:00
Anthony Sottile
ee13b7bb78 Merge pull request #106 from asottile/missing_escdelay_some_curses
fix set_escdelay when the curses library does not support it
2020-11-07 12:03:36 -08:00
Anthony Sottile
cad35f7b4d Merge pull request #107 from asottile/pci
use pre-commit.ci
2020-11-07 12:03:13 -08:00
Anthony Sottile
c1a9823894 use pre-commit.ci 2020-11-07 11:51:07 -08:00
Anthony Sottile
899eb6f879 fix set_escdelay when the curses library does not support it 2020-11-07 11:25:04 -08:00
Anthony Sottile
945e0b1620 Merge pull request #104 from villelaitila/feature/101-ctrl-d-to-forward-delete
Familiar forward deletion keyboard shortcut Ctrl-D (#101)
2020-11-03 10:43:38 -08:00
Ville Laitila
1f348882b8 Familiar forward deletion keyboard shortcut Ctrl-D
With help of the mapping rules, this is easily implemented and all
the unit tests will also test usage of Ctrl-D already as Del char
behaves similarly.
2020-11-03 10:32:17 -08:00
Anthony Sottile
604942306f v0.0.18 2020-10-24 12:56:04 -07:00
Anthony Sottile
00570f8eda Merge pull request #100 from theendlessriver13/fix_keys_for_win_terminal
fix keys for new windows terminal
2020-10-24 12:55:42 -07:00
Jonas Kittner
51a7b10192 fix keys for windows terminal when using xterm 2020-10-24 21:46:03 +02:00
Anthony Sottile
4d1101daf9 Merge pull request #96 from brynphillips/fix_comment_whitespace
fix whitespace on added comment
2020-09-04 22:19:59 -07:00
shazbot
08ec1874d1 fix whitespace on blank line with added comment 2020-09-04 22:11:04 -07:00
12 changed files with 92 additions and 22 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: asottile

View File

@@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.5.0 rev: v3.4.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
@@ -11,34 +11,34 @@ repos:
- id: name-tests-test - id: name-tests-test
- id: requirements-txt-fixer - id: requirements-txt-fixer
- repo: https://gitlab.com/pycqa/flake8 - repo: https://gitlab.com/pycqa/flake8
rev: 3.8.0 rev: 3.8.4
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: [flake8-typing-imports==1.7.0] additional_dependencies: [flake8-typing-imports==1.7.0]
- repo: https://github.com/pre-commit/mirrors-autopep8 - repo: https://github.com/pre-commit/mirrors-autopep8
rev: v1.5.2 rev: v1.5.4
hooks: hooks:
- id: autopep8 - id: autopep8
- repo: https://github.com/asottile/reorder_python_imports - repo: https://github.com/asottile/reorder_python_imports
rev: v2.3.0 rev: v2.3.6
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
args: [--py3-plus] args: [--py3-plus]
- repo: https://github.com/asottile/add-trailing-comma - repo: https://github.com/asottile/add-trailing-comma
rev: v2.0.1 rev: v2.1.0
hooks: hooks:
- id: add-trailing-comma - id: add-trailing-comma
args: [--py36-plus] args: [--py36-plus]
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v2.4.1 rev: v2.7.4
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py36-plus] args: [--py36-plus]
- repo: https://github.com/asottile/setup-cfg-fmt - repo: https://github.com/asottile/setup-cfg-fmt
rev: v1.9.0 rev: v1.16.0
hooks: hooks:
- id: setup-cfg-fmt - id: setup-cfg-fmt
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.770 rev: v0.800
hooks: hooks:
- id: mypy - id: mypy

View File

@@ -1,5 +1,6 @@
[![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)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/babi/master.svg)](https://results.pre-commit.ci/latest/github/asottile/babi/master)
![babi logo](https://user-images.githubusercontent.com/1810591/89981369-9ed84e80-dc28-11ea-9708-5f4c49c09632.png) ![babi logo](https://user-images.githubusercontent.com/1810591/89981369-9ed84e80-dc28-11ea-9708-5f4c49c09632.png)
@@ -17,6 +18,13 @@ a text editor, eventually...
I used to 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`.
### babi vs. nano
here is a youtube video where I discuss the motivation for creating and using
`babi` instead of `nano`:
[![youtube video about babi](https://img.youtube.com/vi/WyR1hAGmR3g/mqdefault.jpg)](https://youtu.be/WyR1hAGmR3g)
### quitting babi ### quitting babi
currently you can quit `babi` by using <kbd>^X</kbd> (or via <kbd>esc</kbd> + currently you can quit `babi` by using <kbd>^X</kbd> (or via <kbd>esc</kbd> +

View File

@@ -13,7 +13,6 @@ resources:
ref: refs/tags/v2.0.0 ref: refs/tags/v2.0.0
jobs: jobs:
- template: job--pre-commit.yml@asottile
- template: job--python-tox.yml@asottile - template: job--python-tox.yml@asottile
parameters: parameters:
toxenvs: [pypy3, py36, py37, py38, py39] toxenvs: [pypy3, py36, py37, py38, py39]

View File

@@ -476,7 +476,11 @@ class File:
if self.buf.y == 0 and self.buf.x == 0: if self.buf.y == 0 and self.buf.x == 0:
pass pass
# backspace at the end of the file does not change the contents # backspace at the end of the file does not change the contents
elif self.buf.y == len(self.buf) - 1: elif (
self.buf.y == len(self.buf) - 1 and
# still allow backspace if there are 2+ blank lines
self.buf[self.buf.y - 1] != ''
):
self.buf.left(margin) self.buf.left(margin)
# at the beginning of the line, we join the current line and # at the beginning of the line, we join the current line and
# the previous line # the previous line
@@ -707,7 +711,10 @@ class File:
def _comment_add(self, lineno: int, prefix: str, s_offset: int) -> None: def _comment_add(self, lineno: int, prefix: str, s_offset: int) -> None:
line = self.buf[lineno] line = self.buf[lineno]
self.buf[lineno] = f'{line[:s_offset]}{prefix} {line[s_offset:]}' if not line:
self.buf[lineno] = f'{prefix}'
else:
self.buf[lineno] = f'{line[:s_offset]}{prefix} {line[s_offset:]}'
if lineno == self.buf.y and self.buf.x > s_offset: if lineno == self.buf.y and self.buf.x > s_offset:
self.buf.x += len(self.buf[lineno]) - len(line) self.buf.x += len(self.buf[lineno]) - len(line)

View File

@@ -38,6 +38,8 @@ EditResult = enum.Enum('EditResult', 'EXIT NEXT PREV OPEN')
SEQUENCE_KEYNAME = { SEQUENCE_KEYNAME = {
'\x1bOH': b'KEY_HOME', '\x1bOH': b'KEY_HOME',
'\x1bOF': b'KEY_END', '\x1bOF': b'KEY_END',
'\x1b[1~': b'KEY_HOME',
'\x1b[4~': b'KEY_END',
'\x1b[1;2A': b'KEY_SR', '\x1b[1;2A': b'KEY_SR',
'\x1b[1;2B': b'KEY_SF', '\x1b[1;2B': b'KEY_SF',
'\x1b[1;2C': b'KEY_SRIGHT', '\x1b[1;2C': b'KEY_SRIGHT',
@@ -60,6 +62,7 @@ SEQUENCE_KEYNAME = {
'\x1b[1;6D': b'kLFT6', # Shift + ^Left '\x1b[1;6D': b'kLFT6', # Shift + ^Left
'\x1b[1;6H': b'kHOM6', # Shift + ^Home '\x1b[1;6H': b'kHOM6', # Shift + ^Home
'\x1b[1;6F': b'kEND6', # Shift + ^End '\x1b[1;6F': b'kEND6', # Shift + ^End
'\x1b[~': b'KEY_BTAB', # Shift + Tab
} }
KEYNAME_REWRITE = { KEYNAME_REWRITE = {
# windows-curses: numeric pad arrow keys # windows-curses: numeric pad arrow keys
@@ -93,6 +96,7 @@ KEYNAME_REWRITE = {
b'^?': b'KEY_BACKSPACE', b'^?': b'KEY_BACKSPACE',
# linux, perhaps others # linux, perhaps others
b'^H': b'KEY_BACKSPACE', # ^Backspace on my keyboard b'^H': b'KEY_BACKSPACE', # ^Backspace on my keyboard
b'^D': b'KEY_DC',
b'PADENTER': b'^M', # Enter on numpad b'PADENTER': b'^M', # Enter on numpad
} }
@@ -269,7 +273,7 @@ class Screen:
prompt: str, prompt: str,
opt_strs: Tuple[str, ...], opt_strs: Tuple[str, ...],
) -> Union[str, PromptResult]: ) -> Union[str, PromptResult]:
opts = [opt[0] for opt in opt_strs] opts = {opt[0] for opt in opt_strs}
while True: while True:
x = 0 x = 0
prompt_line = self.margin.lines - 1 prompt_line = self.margin.lines - 1
@@ -306,8 +310,8 @@ class Screen:
self.resize() self.resize()
elif key.keyname == b'^C': elif key.keyname == b'^C':
return self.status.cancelled() return self.status.cancelled()
elif isinstance(key.wch, str) and key.wch in opts: elif isinstance(key.wch, str) and key.wch.lower() in opts:
return key.wch return key.wch.lower()
def prompt( def prompt(
self, self,
@@ -504,8 +508,14 @@ class Screen:
self.status.update('(file changed on disk, not implemented)') self.status.update('(file changed on disk, not implemented)')
return PromptResult.CANCELLED return PromptResult.CANCELLED
with open(self.file.filename, 'w', encoding='UTF-8', newline='') as f: try:
f.write(contents) with open(
self.file.filename, 'w', encoding='UTF-8', newline='',
) as f:
f.write(contents)
except OSError as e:
self.status.update(f'cannot save file: {e}')
return PromptResult.CANCELLED
self.file.modified = False self.file.modified = False
self.file.sha256 = sha256_to_save self.file.sha256 = sha256_to_save
@@ -589,7 +599,10 @@ class Screen:
def _init_screen() -> 'curses._CursesWindow': def _init_screen() -> 'curses._CursesWindow':
# set the escape delay so curses does not pause waiting for sequences # set the escape delay so curses does not pause waiting for sequences
if sys.version_info >= (3, 9): # pragma: no cover if (
sys.version_info >= (3, 9) and
hasattr(curses, 'set_escdelay')
): # pragma: no cover
curses.set_escdelay(25) curses.set_escdelay(25)
else: # pragma: no cover else: # pragma: no cover
os.environ.setdefault('ESCDELAY', '25') os.environ.setdefault('ESCDELAY', '25')

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
name = babi name = babi
version = 0.0.17 version = 0.0.21
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
@@ -16,6 +16,7 @@ classifiers =
Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: Implementation :: PyPy

View File

@@ -21,6 +21,20 @@ def test_comment_some_code(run, ten_lines):
h.await_text('# line_0\n# line_1\nline_2\n') h.await_text('# line_0\n# line_1\nline_2\n')
def test_comment_empty_line_trailing_whitespace(run, tmpdir):
f = tmpdir.join('f')
f.write('1\n\n2\n')
with run(str(f)) as h, and_exit(h):
h.press('S-Down')
h.press('S-Down')
trigger_command_mode(h)
h.press_and_enter(':comment')
h.await_text('# 1\n#\n# 2')
def test_comment_some_code_with_alternate_comment_character(run, ten_lines): def test_comment_some_code_with_alternate_comment_character(run, ten_lines):
with run(str(ten_lines)) as h, and_exit(h): with run(str(ten_lines)) as h, and_exit(h):
h.press('S-Down') h.press('S-Down')

View File

@@ -290,6 +290,7 @@ KEYS = [
Key('^_', b'^_', '\x1f'), Key('^_', b'^_', '\x1f'),
Key('^\\', b'^\\', '\x1c'), Key('^\\', b'^\\', '\x1c'),
Key('!resize', b'KEY_RESIZE', curses.KEY_RESIZE), Key('!resize', b'KEY_RESIZE', curses.KEY_RESIZE),
Key('^D', b'^D', '\x04'),
] ]
KEYS_TMUX = {k.tmux: k.wch for k in KEYS} KEYS_TMUX = {k.tmux: k.wch for k in KEYS}
KEYS_CURSES = {k.value: k.curses for k in KEYS} KEYS_CURSES = {k.value: k.curses for k in KEYS}

View File

@@ -30,7 +30,8 @@ def test_replace_cancel_at_replace_string(run):
h.await_text('cancelled') h.await_text('cancelled')
def test_replace_actual_contents(run, ten_lines): @pytest.mark.parametrize('key', ('y', 'Y'))
def test_replace_actual_contents(run, ten_lines, key):
with run(str(ten_lines)) as h, and_exit(h): with run(str(ten_lines)) as h, and_exit(h):
h.press('^\\') h.press('^\\')
h.await_text('search (to replace):') h.await_text('search (to replace):')
@@ -38,7 +39,7 @@ def test_replace_actual_contents(run, ten_lines):
h.await_text('replace with:') h.await_text('replace with:')
h.press_and_enter('ohai') h.press_and_enter('ohai')
h.await_text('replace [yes, no, all]?') h.await_text('replace [yes, no, all]?')
h.press('y') h.press(key)
h.await_text_missing('line_0') h.await_text_missing('line_0')
h.await_text('ohai') h.await_text('ohai')
h.await_text(' *') h.await_text(' *')

View File

@@ -128,6 +128,18 @@ def test_save_file_when_it_did_not_exist(run, tmpdir):
assert f.read() == 'hello world\n' assert f.read() == 'hello world\n'
def test_saving_file_permission_denied(run, tmpdir):
f = tmpdir.join('f').ensure()
f.chmod(0o400)
with run(str(f)) as h, and_exit(h):
h.press('hello world')
h.press('^S')
# the filename message is missing as it is too long to be captured
h.await_text('cannot save file: [Errno 13] Permission denied:')
h.await_text(' *')
def test_save_via_ctrl_o(run, tmpdir): def test_save_via_ctrl_o(run, tmpdir):
f = tmpdir.join('f') f = tmpdir.join('f')
with run(str(f)) as h, and_exit(h): with run(str(f)) as h, and_exit(h):

View File

@@ -50,6 +50,18 @@ def test_backspace_at_end_of_file_still_allows_scrolling_down(run, tmpdir):
h.await_text_missing('*') h.await_text_missing('*')
def test_backspace_deletes_newline_at_end_of_file(run, tmpdir):
f = tmpdir.join('f')
f.write('foo\n\n')
with run(str(f)) as h, and_exit(h):
h.press('^End')
h.press('BSpace')
h.press('^S')
assert f.read() == 'foo\n'
@pytest.mark.parametrize('key', ('BSpace', '^H')) @pytest.mark.parametrize('key', ('BSpace', '^H'))
def test_backspace_deletes_text(run, tmpdir, key): def test_backspace_deletes_text(run, tmpdir, key):
f = tmpdir.join('f') f = tmpdir.join('f')
@@ -72,14 +84,15 @@ def test_delete_at_end_of_file(run, tmpdir):
h.await_text_missing('*') h.await_text_missing('*')
def test_delete_removes_character_afterwards(run, tmpdir): @pytest.mark.parametrize('key', ('DC', '^D'))
def test_delete_removes_character_afterwards(run, tmpdir, key):
f = tmpdir.join('f') f = tmpdir.join('f')
f.write('hello world') f.write('hello world')
with run(str(f)) as h, and_exit(h): with run(str(f)) as h, and_exit(h):
h.await_text('hello world') h.await_text('hello world')
h.press('Right') h.press('Right')
h.press('DC') h.press(key)
h.await_text('hllo world') h.await_text('hllo world')
h.await_text('f *') h.await_text('f *')