90 Commits

Author SHA1 Message Date
Anthony Sottile
c4cba4a884 Merge pull request #172 from asottile/all-repos_autofix_gh-funding-default
Use org-default .github/FUNDING.yml
2021-11-23 11:53:51 -05:00
Anthony Sottile
38faec4519 Use org-default .github/FUNDING.yml
Committed via https://github.com/asottile/all-repos
2021-11-23 11:16:13 -05:00
Anthony Sottile
b10afd5bd8 Merge pull request #171 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-11-22 17:06:47 -05:00
pre-commit-ci[bot]
b1080319ae [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/add-trailing-comma: v2.2.0 → v2.2.1](https://github.com/asottile/add-trailing-comma/compare/v2.2.0...v2.2.1)
- [github.com/asottile/pyupgrade: v2.29.0 → v2.29.1](https://github.com/asottile/pyupgrade/compare/v2.29.0...v2.29.1)
- [github.com/asottile/setup-cfg-fmt: v1.19.0 → v1.20.0](https://github.com/asottile/setup-cfg-fmt/compare/v1.19.0...v1.20.0)
2021-11-22 21:43:03 +00:00
Anthony Sottile
89dee66711 Merge pull request #170 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-11-01 17:24:53 -04:00
pre-commit-ci[bot]
3fb588bba9 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2021-11-01 21:16:08 +00:00
pre-commit-ci[bot]
99be8b59c9 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/setup-cfg-fmt: v1.18.0 → v1.19.0](https://github.com/asottile/setup-cfg-fmt/compare/v1.18.0...v1.19.0)
2021-11-01 21:15:36 +00:00
Anthony Sottile
d4da5eb800 Merge pull request #169 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-10-25 13:48:37 -07:00
pre-commit-ci[bot]
0c3abfc727 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/add-trailing-comma: v2.1.0 → v2.2.0](https://github.com/asottile/add-trailing-comma/compare/v2.1.0...v2.2.0)
2021-10-25 20:21:08 +00:00
Anthony Sottile
c3a34c6262 Merge pull request #168 from asottile/all-repos_autofix_exit-main-2
exit(main()) -> raise SystemExit(main()) pt2
2021-10-24 08:11:16 -07:00
Anthony Sottile
ae30b5763c exit(main()) -> raise SystemExit(main()) pt2
Committed via https://github.com/asottile/all-repos
2021-10-24 07:15:30 -07:00
Anthony Sottile
1cad38d632 Merge pull request #167 from asottile/all-repos_autofix_systemexit-main
replace exit(main()) with raise SystemExit(main())
2021-10-23 11:00:46 -07:00
Anthony Sottile
d2a7014925 replace exit(main()) with raise SystemExit(main())
Committed via https://github.com/asottile/all-repos
2021-10-23 13:22:47 -04:00
Anthony Sottile
9eae3da253 Merge pull request #166 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-10-11 13:54:46 -07:00
pre-commit-ci[bot]
075338563e [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2021-10-11 20:29:12 +00:00
pre-commit-ci[bot]
a7e83ef089 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 3.9.2 → 4.0.1](https://github.com/PyCQA/flake8/compare/3.9.2...4.0.1)
- [github.com/asottile/setup-cfg-fmt: v1.17.0 → v1.18.0](https://github.com/asottile/setup-cfg-fmt/compare/v1.17.0...v1.18.0)
- [github.com/pre-commit/mirrors-mypy: v0.910 → v0.910-1](https://github.com/pre-commit/mirrors-mypy/compare/v0.910...v0.910-1)
2021-10-11 20:24:11 +00:00
Anthony Sottile
03656c04bb Merge pull request #165 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-10-04 13:28:38 -07:00
pre-commit-ci[bot]
4be3cbff35 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.28.0 → v2.29.0](https://github.com/asottile/pyupgrade/compare/v2.28.0...v2.29.0)
2021-10-04 20:19:23 +00:00
Anthony Sottile
99b3371739 v0.0.24 2021-10-03 18:55:37 -04:00
Anthony Sottile
3c9c6173c3 Merge pull request #164 from AndrewLaneX/replace-errors
Handle errors when replacing
2021-10-03 15:53:22 -07:00
Andrew Lane
84015d3ac4 Handle errors when replacing 2021-10-03 23:45:29 +03:00
Anthony Sottile
d7ffdd1db8 Merge pull request #163 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-09-27 13:26:18 -07:00
pre-commit-ci[bot]
4e0c02eaa2 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.26.0 → v2.28.0](https://github.com/asottile/pyupgrade/compare/v2.26.0...v2.28.0)
2021-09-27 20:16:08 +00:00
Anthony Sottile
79919ece2a Merge pull request #162 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-09-13 13:30:33 -07:00
pre-commit-ci[bot]
f0ad0e4977 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.25.0 → v2.26.0](https://github.com/asottile/pyupgrade/compare/v2.25.0...v2.26.0)
2021-09-13 20:11:28 +00:00
Anthony Sottile
f18796b78d Merge pull request #161 from asottile/a-italic
A_ITALIC is not always present unlike what's documented
2021-09-12 06:47:05 -07:00
Anthony Sottile
1698533787 A_ITALIC is not always present unlike what's documented 2021-09-12 06:42:28 -07:00
Anthony Sottile
10fca36ea3 v0.0.22 2021-09-11 16:30:27 -04:00
Anthony Sottile
e6aab391f6 Merge pull request #158 from asottile/drop_py36
drop python 3.6
2021-09-11 12:27:26 -07:00
Anthony Sottile
ebee8fe6ff drop python 3.6 2021-09-11 15:21:27 -04:00
Anthony Sottile
04fc97a8f9 Merge pull request #157 from asottile/save-file-new-name
allow saving a file with a new name
2021-09-11 11:24:48 -07:00
Anthony Sottile
c49e722498 allow saving a file with a new name 2021-09-11 14:02:02 -04:00
Anthony Sottile
396d0e3a93 Merge pull request #156 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-08-30 16:17:14 -04:00
pre-commit-ci[bot]
660bf9bac0 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.24.0 → v2.25.0](https://github.com/asottile/pyupgrade/compare/v2.24.0...v2.25.0)
2021-08-30 19:56:51 +00:00
Anthony Sottile
0b3918f26f Merge pull request #155 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-08-23 16:18:43 -04:00
pre-commit-ci[bot]
9dffc276dc [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.23.3 → v2.24.0](https://github.com/asottile/pyupgrade/compare/v2.23.3...v2.24.0)
2021-08-23 19:21:33 +00:00
Anthony Sottile
ea6dbb69a6 Merge pull request #154 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-08-09 13:22:08 -07:00
pre-commit-ci[bot]
eeeba9e11d [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.23.1 → v2.23.3](https://github.com/asottile/pyupgrade/compare/v2.23.1...v2.23.3)
2021-08-09 19:16:56 +00:00
Anthony Sottile
b5538b3818 Merge pull request #153 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-08-02 15:32:10 -04:00
pre-commit-ci[bot]
16c60d68ad [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.23.0 → v2.23.1](https://github.com/asottile/pyupgrade/compare/v2.23.0...v2.23.1)
2021-08-02 19:16:02 +00:00
Anthony Sottile
27aa865989 Merge pull request #152 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-07-26 14:41:32 -04:00
pre-commit-ci[bot]
4b13488e8f [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/reorder_python_imports: v2.5.0 → v2.6.0](https://github.com/asottile/reorder_python_imports/compare/v2.5.0...v2.6.0)
- [github.com/asottile/pyupgrade: v2.21.2 → v2.23.0](https://github.com/asottile/pyupgrade/compare/v2.21.2...v2.23.0)
2021-07-26 18:25:12 +00:00
Anthony Sottile
b4593d281a Merge pull request #150 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-07-19 18:53:22 -04:00
pre-commit-ci[bot]
3ddf1c72f8 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.21.0 → v2.21.2](https://github.com/asottile/pyupgrade/compare/v2.21.0...v2.21.2)
2021-07-19 22:33:22 +00:00
Anthony Sottile
8f91b8c9ff Merge pull request #149 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-07-12 20:16:41 -04:00
pre-commit-ci[bot]
c48d3ed741 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.20.0 → v2.21.0](https://github.com/asottile/pyupgrade/compare/v2.20.0...v2.21.0)
2021-07-12 23:50:32 +00:00
Anthony Sottile
43a650925b Merge pull request #148 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-07-05 18:43:23 -04:00
pre-commit-ci[bot]
3116828e44 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.19.4 → v2.20.0](https://github.com/asottile/pyupgrade/compare/v2.19.4...v2.20.0)
2021-07-05 22:26:18 +00:00
Anthony Sottile
9bc43d58e4 Merge pull request #146 from asottile/all-repos_autofix_mypy-settings
stricter mypy settings
2021-06-29 19:41:08 -05:00
Anthony Sottile
bbc9647eb3 Merge pull request #147 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-06-28 18:14:01 -07:00
pre-commit-ci[bot]
e36ed09f1f [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v0.902 → v0.910](https://github.com/pre-commit/mirrors-mypy/compare/v0.902...v0.910)
2021-06-28 18:01:19 +00:00
Anthony Sottile
fb52b5f71c remove unused type ignore 2021-06-21 19:22:12 -07:00
Anthony Sottile
1916b49a06 stricter mypy settings
Committed via https://github.com/asottile/all-repos
2021-06-21 19:11:43 -07:00
Anthony Sottile
66c178b0b7 Merge pull request #145 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-06-14 11:16:52 -07:00
pre-commit-ci[bot]
ea4058549f [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.19.1 → v2.19.4](https://github.com/asottile/pyupgrade/compare/v2.19.1...v2.19.4)
- [github.com/pre-commit/mirrors-mypy: v0.812 → v0.902](https://github.com/pre-commit/mirrors-mypy/compare/v0.812...v0.902)
2021-06-14 17:57:33 +00:00
Anthony Sottile
cb6b431308 Merge pull request #144 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-06-07 19:21:23 -07:00
pre-commit-ci[bot]
a8283adb54 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.19.0 → v2.19.1](https://github.com/asottile/pyupgrade/compare/v2.19.0...v2.19.1)
2021-06-08 02:05:58 +00:00
Anthony Sottile
40bf9969fb Merge pull request #143 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-05-31 11:08:51 -07:00
pre-commit-ci[bot]
a04b8fdca6 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.18.2 → v2.19.0](https://github.com/asottile/pyupgrade/compare/v2.18.2...v2.19.0)
2021-05-31 17:49:36 +00:00
Anthony Sottile
76eef9adb6 Merge pull request #142 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-05-24 11:23:34 -07:00
pre-commit-ci[bot]
fa962d6cb9 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.16.0 → v2.18.2](https://github.com/asottile/pyupgrade/compare/v2.16.0...v2.18.2)
2021-05-24 17:36:32 +00:00
Anthony Sottile
f40f93b983 Merge pull request #141 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-05-17 11:19:19 -07:00
pre-commit-ci[bot]
af01959a48 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v3.4.0 → v4.0.1](https://github.com/pre-commit/pre-commit-hooks/compare/v3.4.0...v4.0.1)
- [github.com/asottile/pyupgrade: v2.15.0 → v2.16.0](https://github.com/asottile/pyupgrade/compare/v2.15.0...v2.16.0)
2021-05-17 17:43:01 +00:00
Anthony Sottile
16f4ec3681 Merge pull request #140 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-05-10 14:10:55 -07:00
pre-commit-ci[bot]
291d34028a [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 3.9.1 → 3.9.2](https://github.com/PyCQA/flake8/compare/3.9.1...3.9.2)
- [github.com/asottile/pyupgrade: v2.14.0 → v2.15.0](https://github.com/asottile/pyupgrade/compare/v2.14.0...v2.15.0)
2021-05-10 20:47:33 +00:00
Anthony Sottile
2c200b97ed Merge pull request #138 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-05-03 10:58:17 -07:00
pre-commit-ci[bot]
fc185b0eef [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-autopep8: v1.5.6 → v1.5.7](https://github.com/pre-commit/mirrors-autopep8/compare/v1.5.6...v1.5.7)
- [github.com/asottile/pyupgrade: v2.13.0 → v2.14.0](https://github.com/asottile/pyupgrade/compare/v2.13.0...v2.14.0)
2021-05-03 17:39:51 +00:00
Anthony Sottile
21357ed235 Merge pull request #137 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-04-26 11:07:57 -07:00
pre-commit-ci[bot]
1a023d3830 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/reorder_python_imports: v2.4.0 → v2.5.0](https://github.com/asottile/reorder_python_imports/compare/v2.4.0...v2.5.0)
- [github.com/asottile/pyupgrade: v2.12.0 → v2.13.0](https://github.com/asottile/pyupgrade/compare/v2.12.0...v2.13.0)
2021-04-26 17:34:09 +00:00
Anthony Sottile
61063b306c Merge pull request #136 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-04-19 12:57:00 -07:00
pre-commit-ci[bot]
628d3ced55 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 3.9.0 → 3.9.1](https://github.com/PyCQA/flake8/compare/3.9.0...3.9.1)
2021-04-19 17:19:18 +00:00
Anthony Sottile
b6dc975143 Merge pull request #135 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-04-12 11:27:43 -07:00
pre-commit-ci[bot]
4537df6aa1 [pre-commit.ci] pre-commit autoupdate 2021-04-12 17:22:50 +00:00
Anthony Sottile
1d1307aa1c Merge pull request #134 from asottile/all-repos_autofix_azure-pipelines-autoupdate
Update azure-pipelines template repositories
2021-04-08 19:39:56 -07:00
Anthony Sottile
194d1c5b9b Update azure-pipelines template repositories
Committed via https://github.com/asottile/all-repos
2021-04-08 19:19:17 -07:00
Anthony Sottile
e59c860097 Merge pull request #133 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-04-05 11:28:42 -07:00
pre-commit-ci[bot]
0a39d73959 [pre-commit.ci] pre-commit autoupdate 2021-04-05 17:19:35 +00:00
Anthony Sottile
f8bf24482e Merge pull request #131 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-03-22 12:09:30 -07:00
pre-commit-ci[bot]
50079514fd [pre-commit.ci] pre-commit autoupdate 2021-03-22 17:13:52 +00:00
Anthony Sottile
96402e30cf Merge pull request #130 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-03-15 12:12:32 -07:00
pre-commit-ci[bot]
048ed590ff [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2021-03-15 17:13:12 +00:00
pre-commit-ci[bot]
ab6cfcc8c9 [pre-commit.ci] pre-commit autoupdate 2021-03-15 17:11:31 +00:00
Anthony Sottile
6348313071 Merge pull request #129 from asottile/document_macos
document how to get more keyboard shortcuts working on macos
2021-03-05 10:03:21 -08:00
Anthony Sottile
c59e00975b document how to get more keyboard shortcuts working on macos 2021-03-05 09:21:22 -08:00
Anthony Sottile
e3ba08a331 Merge pull request #127 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-02-22 09:31:19 -08:00
pre-commit-ci[bot]
6bdf0ff2ea [pre-commit.ci] pre-commit autoupdate 2021-02-22 17:12:30 +00:00
Anthony Sottile
bc699e60e1 Merge pull request #126 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-02-08 09:18:34 -08:00
pre-commit-ci[bot]
151a56c7f8 [pre-commit.ci] pre-commit autoupdate 2021-02-08 16:58:08 +00:00
Anthony Sottile
d958934fdd Merge pull request #125 from asottile/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-02-01 12:46:03 -08:00
pre-commit-ci[bot]
9a8bda1f15 [pre-commit.ci] pre-commit autoupdate 2021-02-01 16:57:52 +00:00
77 changed files with 461 additions and 326 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1 +0,0 @@
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: v3.4.0 rev: v4.0.1
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
@@ -10,35 +10,35 @@ repos:
- id: double-quote-string-fixer - id: double-quote-string-fixer
- id: name-tests-test - id: name-tests-test
- id: requirements-txt-fixer - id: requirements-txt-fixer
- repo: https://gitlab.com/pycqa/flake8 - repo: https://github.com/PyCQA/flake8
rev: 3.8.4 rev: 4.0.1
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.4 rev: v1.5.7
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.6 rev: v2.6.0
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
args: [--py3-plus] args: [--py3-plus, --add-import, 'from __future__ import annotations']
- repo: https://github.com/asottile/add-trailing-comma - repo: https://github.com/asottile/add-trailing-comma
rev: v2.1.0 rev: v2.2.1
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.7.4 rev: v2.29.1
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py36-plus] args: [--py37-plus]
- repo: https://github.com/asottile/setup-cfg-fmt - repo: https://github.com/asottile/setup-cfg-fmt
rev: v1.16.0 rev: v1.20.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.800 rev: v0.910-1
hooks: hooks:
- id: mypy - id: mypy

View File

@@ -87,6 +87,25 @@ here's a modified vs dark plus theme that works:
./bin/download-theme vs-dark-asottile https://gist.github.com/asottile/b465856c82b1aaa4ba8c7c6314a72e13/raw/22d602fb355fb12b04f176a733941ba5713bc36c/vs_dark_asottile.json ./bin/download-theme vs-dark-asottile https://gist.github.com/asottile/b465856c82b1aaa4ba8c7c6314a72e13/raw/22d602fb355fb12b04f176a733941ba5713bc36c/vs_dark_asottile.json
``` ```
### keyboard shortcuts on macos
to get the most out of babi's built in keyboard shortcuts, a few settings must
be changed on macos with Terminal.app:
- in **System Preferences**: **Keyboard** > **Shortcuts** >
**Mission Control**: disable or rebind "Move left a space" and
"Move right a space" (the defaults `⌃ →` and `⌃ ←` conflict)
- in **Terminal.app**: **Terminal** > **Preferences** > **Profiles** >
**Keyboard**:
- check **Use Option as Meta key**
- ensure the following keys are enabled:
- `⌃ →`: `\033[1;5C`
- `⌃ ←`: `\033[1;5D`
- `⇧ ↑`: `\033[1;2A`
- `⇧ ↓`: `\033[1;2B`
- `⇧ →`: `\033[1;2C`
- `⇧ ←`: `\033[1;2D`
## demos ## demos
most things work! here's a few screenshots most things work! here's a few screenshots

View File

@@ -10,10 +10,10 @@ resources:
type: github type: github
endpoint: github endpoint: github
name: asottile/azure-pipeline-templates name: asottile/azure-pipeline-templates
ref: refs/tags/v2.0.0 ref: refs/tags/v2.1.0
jobs: jobs:
- template: job--python-tox.yml@asottile - template: job--python-tox.yml@asottile
parameters: parameters:
toxenvs: [pypy3, py36, py37, py38, py39] toxenvs: [py37, py38, py39]
os: linux os: linux

View File

@@ -1,4 +1,6 @@
from __future__ import annotations
from babi.main import main from babi.main import main
if __name__ == '__main__': if __name__ == '__main__':
exit(main()) raise SystemExit(main())

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@@ -1,12 +1,11 @@
from __future__ import annotations
import bisect import bisect
import contextlib import contextlib
from typing import Callable from typing import Callable
from typing import Generator from typing import Generator
from typing import Iterator from typing import Iterator
from typing import List
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Tuple
from babi._types import Protocol from babi._types import Protocol
from babi.horizontal_scrolling import line_x from babi.horizontal_scrolling import line_x
@@ -19,7 +18,7 @@ DelCallback = Callable[['Buf', int, str], None]
InsCallback = Callable[['Buf', int], None] InsCallback = Callable[['Buf', int], None]
def _offsets(s: str, tab_size: int) -> Tuple[int, ...]: def _offsets(s: str, tab_size: int) -> tuple[int, ...]:
ret = [0] ret = [0]
for c in s: for c in s:
if c == '\t': if c == '\t':
@@ -30,14 +29,14 @@ def _offsets(s: str, tab_size: int) -> Tuple[int, ...]:
class Modification(Protocol): class Modification(Protocol):
def __call__(self, buf: 'Buf') -> None: ... def __call__(self, buf: Buf) -> None: ...
class SetModification(NamedTuple): class SetModification(NamedTuple):
idx: int idx: int
s: str s: str
def __call__(self, buf: 'Buf') -> None: def __call__(self, buf: Buf) -> None:
buf[self.idx] = self.s buf[self.idx] = self.s
@@ -45,29 +44,29 @@ class InsModification(NamedTuple):
idx: int idx: int
s: str s: str
def __call__(self, buf: 'Buf') -> None: def __call__(self, buf: Buf) -> None:
buf.insert(self.idx, self.s) buf.insert(self.idx, self.s)
class DelModification(NamedTuple): class DelModification(NamedTuple):
idx: int idx: int
def __call__(self, buf: 'Buf') -> None: def __call__(self, buf: Buf) -> None:
del buf[self.idx] del buf[self.idx]
class Buf: class Buf:
def __init__(self, lines: List[str], tab_size: int = 4) -> None: def __init__(self, lines: list[str], tab_size: int = 4) -> None:
self._lines = lines self._lines = lines
self.expandtabs = True self.expandtabs = True
self.tab_size = tab_size self.tab_size = tab_size
self.file_y = self.y = self._x = self._x_hint = 0 self.file_y = self.y = self._x = self._x_hint = 0
self._set_callbacks: List[SetCallback] = [self._set_cb] self._set_callbacks: list[SetCallback] = [self._set_cb]
self._del_callbacks: List[DelCallback] = [self._del_cb] self._del_callbacks: list[DelCallback] = [self._del_cb]
self._ins_callbacks: List[InsCallback] = [self._ins_cb] self._ins_callbacks: list[InsCallback] = [self._ins_cb]
self._positions: List[Optional[Tuple[int, ...]]] = [] self._positions: list[tuple[int, ...] | None] = []
# read only interface # read only interface
@@ -163,16 +162,16 @@ class Buf:
self._ins_callbacks.remove(cb) self._ins_callbacks.remove(cb)
@contextlib.contextmanager @contextlib.contextmanager
def record(self) -> Generator[List[Modification], None, None]: def record(self) -> Generator[list[Modification], None, None]:
modifications: List[Modification] = [] modifications: list[Modification] = []
def set_cb(buf: 'Buf', idx: int, victim: str) -> None: def set_cb(buf: Buf, idx: int, victim: str) -> None:
modifications.append(SetModification(idx, victim)) modifications.append(SetModification(idx, victim))
def del_cb(buf: 'Buf', idx: int, victim: str) -> None: def del_cb(buf: Buf, idx: int, victim: str) -> None:
modifications.append(InsModification(idx, victim)) modifications.append(InsModification(idx, victim))
def ins_cb(buf: 'Buf', idx: int) -> None: def ins_cb(buf: Buf, idx: int) -> None:
modifications.append(DelModification(idx)) modifications.append(DelModification(idx))
self.add_set_callback(set_cb) self.add_set_callback(set_cb)
@@ -185,7 +184,7 @@ class Buf:
self.remove_del_callback(del_cb) self.remove_del_callback(del_cb)
self.remove_set_callback(set_cb) self.remove_set_callback(set_cb)
def apply(self, modifications: List[Modification]) -> List[Modification]: def apply(self, modifications: list[Modification]) -> list[Modification]:
with self.record() as ret_modifications: with self.record() as ret_modifications:
for modification in reversed(modifications): for modification in reversed(modifications):
modification(self) modification(self)
@@ -209,19 +208,19 @@ class Buf:
def _extend_positions(self, idx: int) -> None: def _extend_positions(self, idx: int) -> None:
self._positions.extend([None] * (1 + idx - len(self._positions))) self._positions.extend([None] * (1 + idx - len(self._positions)))
def _set_cb(self, buf: 'Buf', idx: int, victim: str) -> None: def _set_cb(self, buf: Buf, idx: int, victim: str) -> None:
self._extend_positions(idx) self._extend_positions(idx)
self._positions[idx] = None self._positions[idx] = None
def _del_cb(self, buf: 'Buf', idx: int, victim: str) -> None: def _del_cb(self, buf: Buf, idx: int, victim: str) -> None:
self._extend_positions(idx) self._extend_positions(idx)
del self._positions[idx] del self._positions[idx]
def _ins_cb(self, buf: 'Buf', idx: int) -> None: def _ins_cb(self, buf: Buf, idx: int) -> None:
self._extend_positions(idx) self._extend_positions(idx)
self._positions.insert(idx, None) self._positions.insert(idx, None)
def line_positions(self, idx: int) -> Tuple[int, ...]: def line_positions(self, idx: int) -> tuple[int, ...]:
self._extend_positions(idx) self._extend_positions(idx)
value = self._positions[idx] value = self._positions[idx]
if value is None: if value is None:
@@ -236,7 +235,7 @@ class Buf:
def _cursor_x(self) -> int: def _cursor_x(self) -> int:
return self.line_positions(self.y)[self.x] return self.line_positions(self.y)[self.x]
def cursor_position(self, margin: Margin) -> Tuple[int, int]: def cursor_position(self, margin: Margin) -> tuple[int, int]:
y = self.y - self.file_y + margin.header y = self.y - self.file_y + margin.header
x = self._cursor_x - self.line_x(margin) x = self._cursor_x - self.line_x(margin)
return y, x return y, x

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import sys import sys
if sys.version_info >= (3, 8): # pragma: no cover (>=py38) if sys.version_info >= (3, 8): # pragma: no cover (>=py38)
@@ -5,8 +7,6 @@ if sys.version_info >= (3, 8): # pragma: no cover (>=py38)
else: # pragma: no cover (<py38) else: # pragma: no cover (<py38)
from typing import Callable from typing import Callable
from typing import Generic from typing import Generic
from typing import Optional
from typing import Type
from typing import TypeVar from typing import TypeVar
TSelf = TypeVar('TSelf') TSelf = TypeVar('TSelf')
@@ -18,8 +18,8 @@ else: # pragma: no cover (<py38)
def __get__( def __get__(
self, self,
instance: Optional[TSelf], instance: TSelf | None,
owner: Optional[Type[TSelf]] = None, owner: type[TSelf] | None = None,
) -> TRet: ) -> TRet:
assert instance is not None assert instance is not None
ret = instance.__dict__[self._func.__name__] = self._func(instance) ret = instance.__dict__[self._func.__name__] = self._func(instance)

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from typing import NamedTuple from typing import NamedTuple
# TODO: find a standard which defines these # TODO: find a standard which defines these
@@ -11,7 +13,7 @@ class Color(NamedTuple):
b: int b: int
@classmethod @classmethod
def parse(cls, s: str) -> 'Color': def parse(cls, s: str) -> Color:
if s.startswith('#') and len(s) >= 7: if s.startswith('#') and len(s) >= 7:
return cls(r=int(s[1:3], 16), g=int(s[3:5], 16), b=int(s[5:7], 16)) return cls(r=int(s[1:3], 16), g=int(s[3:5], 16), b=int(s[5:7], 16))
elif s.startswith('#'): elif s.startswith('#'):

View File

@@ -1,9 +1,8 @@
from __future__ import annotations
import functools import functools
import itertools import itertools
from typing import List
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Tuple
from babi._types import Protocol from babi._types import Protocol
from babi.color import Color from babi.color import Color
@@ -19,19 +18,19 @@ class KD(Protocol):
@property @property
def n(self) -> int: ... def n(self) -> int: ...
@property @property
def left(self) -> Optional['KD']: ... def left(self) -> KD | None: ...
@property @property
def right(self) -> Optional['KD']: ... def right(self) -> KD | None: ...
class _KD(NamedTuple): class _KD(NamedTuple):
color: Color color: Color
n: int n: int
left: Optional[KD] left: KD | None
right: Optional[KD] right: KD | None
def _build(colors: List[Tuple[Color, int]], depth: int = 0) -> Optional[KD]: def _build(colors: list[tuple[Color, int]], depth: int = 0) -> KD | None:
if not colors: if not colors:
return None return None
@@ -46,11 +45,11 @@ def _build(colors: List[Tuple[Color, int]], depth: int = 0) -> Optional[KD]:
) )
def nearest(color: Color, colors: Optional[KD]) -> int: def nearest(color: Color, colors: KD | None) -> int:
best = 0 best = 0
dist = 2 ** 32 dist = 2 ** 32
def _search(kd: Optional[KD], *, depth: int) -> None: def _search(kd: KD | None, *, depth: int) -> None:
nonlocal best nonlocal best
nonlocal dist nonlocal dist
@@ -77,7 +76,7 @@ def nearest(color: Color, colors: Optional[KD]) -> int:
@functools.lru_cache(maxsize=1) @functools.lru_cache(maxsize=1)
def make_256() -> Optional[KD]: def make_256() -> KD | None:
vals = (0, 95, 135, 175, 215, 255) vals = (0, 95, 135, 175, 215, 255)
colors = [ colors = [
(Color(r, g, b), i) (Color(r, g, b), i)

View File

@@ -1,21 +1,20 @@
from __future__ import annotations
import curses import curses
from typing import Dict
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Tuple
from babi import color_kd from babi import color_kd
from babi.color import Color from babi.color import Color
def _color_to_curses(color: Color) -> Tuple[int, int, int]: def _color_to_curses(color: Color) -> tuple[int, int, int]:
factor = 1000 / 255 factor = 1000 / 255
return int(color.r * factor), int(color.g * factor), int(color.b * factor) return int(color.r * factor), int(color.g * factor), int(color.b * factor)
class ColorManager(NamedTuple): class ColorManager(NamedTuple):
colors: Dict[Color, int] colors: dict[Color, int]
raw_pairs: Dict[Tuple[int, int], int] raw_pairs: dict[tuple[int, int], int]
def init_color(self, color: Color) -> None: def init_color(self, color: Color) -> None:
if curses.can_change_color(): if curses.can_change_color():
@@ -27,7 +26,7 @@ class ColorManager(NamedTuple):
else: else:
self.colors[color] = -1 self.colors[color] = -1
def color_pair(self, fg: Optional[Color], bg: Optional[Color]) -> int: def color_pair(self, fg: Color | None, bg: Color | None) -> int:
fg_i = self.colors[fg] if fg is not None else -1 fg_i = self.colors[fg] if fg is not None else -1
bg_i = self.colors[bg] if bg is not None else -1 bg_i = self.colors[bg] if bg is not None else -1
return self.raw_color_pair(fg_i, bg_i) return self.raw_color_pair(fg_i, bg_i)
@@ -46,5 +45,5 @@ class ColorManager(NamedTuple):
return 0 return 0
@classmethod @classmethod
def make(cls) -> 'ColorManager': def make(cls) -> ColorManager:
return cls({}, {}) return cls({}, {})

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Generic from typing import Generic
from typing import Iterable from typing import Iterable
from typing import Mapping from typing import Mapping

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import collections import collections
import contextlib import contextlib
import curses import curses
@@ -12,15 +14,11 @@ from typing import Callable
from typing import cast from typing import cast
from typing import Generator from typing import Generator
from typing import IO from typing import IO
from typing import List
from typing import Match from typing import Match
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Pattern from typing import Pattern
from typing import Tuple
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import TypeVar from typing import TypeVar
from typing import Union
from babi.buf import Buf from babi.buf import Buf
from babi.buf import Modification from babi.buf import Modification
@@ -42,7 +40,7 @@ TCallable = TypeVar('TCallable', bound=Callable[..., Any])
WS_RE = re.compile(r'^\s*') 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()
lines = [] lines = []
newlines = collections.Counter({'\n': 0}) # default to `\n` newlines = collections.Counter({'\n': 0}) # default to `\n`
@@ -64,7 +62,7 @@ def get_lines(sio: IO[str]) -> Tuple[List[str], str, bool, str]:
class Action: class Action:
def __init__( def __init__(
self, *, name: str, modifications: List[Modification], self, *, name: str, modifications: list[Modification],
start_x: int, start_y: int, start_modified: bool, start_x: int, start_y: int, start_modified: bool,
end_x: int, end_y: int, end_modified: bool, end_x: int, end_y: int, end_modified: bool,
final: bool, final: bool,
@@ -79,7 +77,7 @@ class Action:
self.end_modified = end_modified self.end_modified = end_modified
self.final = final self.final = final
def apply(self, file: 'File') -> 'Action': def apply(self, file: File) -> Action:
action = Action( action = Action(
name=self.name, modifications=file.buf.apply(self.modifications), name=self.name, modifications=file.buf.apply(self.modifications),
start_x=self.end_x, start_y=self.end_y, start_x=self.end_x, start_y=self.end_y,
@@ -98,7 +96,7 @@ class Action:
def action(func: TCallable) -> TCallable: def action(func: TCallable) -> TCallable:
@functools.wraps(func) @functools.wraps(func)
def action_inner(self: 'File', *args: Any, **kwargs: Any) -> Any: def action_inner(self: File, *args: Any, **kwargs: Any) -> Any:
self.finalize_previous_action() self.finalize_previous_action()
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
return cast(TCallable, action_inner) return cast(TCallable, action_inner)
@@ -111,7 +109,7 @@ def edit_action(
) -> Callable[[TCallable], TCallable]: ) -> Callable[[TCallable], TCallable]:
def edit_action_decorator(func: TCallable) -> TCallable: def edit_action_decorator(func: TCallable) -> TCallable:
@functools.wraps(func) @functools.wraps(func)
def edit_action_inner(self: 'File', *args: Any, **kwargs: Any) -> Any: def edit_action_inner(self: File, *args: Any, **kwargs: Any) -> Any:
with self.edit_action_context(name, final=final): with self.edit_action_context(name, final=final):
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
return cast(TCallable, edit_action_inner) return cast(TCallable, edit_action_inner)
@@ -120,7 +118,7 @@ def edit_action(
def keep_selection(func: TCallable) -> TCallable: def keep_selection(func: TCallable) -> TCallable:
@functools.wraps(func) @functools.wraps(func)
def keep_selection_inner(self: 'File', *args: Any, **kwargs: Any) -> Any: def keep_selection_inner(self: File, *args: Any, **kwargs: Any) -> Any:
with self.select(): with self.select():
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
return cast(TCallable, keep_selection_inner) return cast(TCallable, keep_selection_inner)
@@ -128,7 +126,7 @@ def keep_selection(func: TCallable) -> TCallable:
def clear_selection(func: TCallable) -> TCallable: def clear_selection(func: TCallable) -> TCallable:
@functools.wraps(func) @functools.wraps(func)
def clear_selection_inner(self: 'File', *args: Any, **kwargs: Any) -> Any: def clear_selection_inner(self: File, *args: Any, **kwargs: Any) -> Any:
ret = func(self, *args, **kwargs) ret = func(self, *args, **kwargs)
self.selection.clear() self.selection.clear()
return ret return ret
@@ -143,7 +141,7 @@ class Found(NamedTuple):
class _SearchIter: class _SearchIter:
def __init__( def __init__(
self, self,
file: 'File', file: File,
reg: Pattern[str], reg: Pattern[str],
*, *,
offset: int, offset: int,
@@ -155,7 +153,7 @@ class _SearchIter:
self._start_x = file.buf.x + offset self._start_x = file.buf.x + offset
self._start_y = file.buf.y self._start_y = file.buf.y
def __iter__(self) -> '_SearchIter': def __iter__(self) -> _SearchIter:
return self return self
def _stop_if_past_original(self, y: int, match: Match[str]) -> Found: def _stop_if_past_original(self, y: int, match: Match[str]) -> Found:
@@ -168,7 +166,7 @@ class _SearchIter:
raise StopIteration() raise StopIteration()
return Found(y, match) return Found(y, match)
def __next__(self) -> Tuple[int, Match[str]]: def __next__(self) -> tuple[int, Match[str]]:
x = self.file.buf.x + self.offset x = self.file.buf.x + self.offset
y = self.file.buf.y y = self.file.buf.y
@@ -200,25 +198,25 @@ class _SearchIter:
class File: class File:
def __init__( def __init__(
self, self,
filename: Optional[str], filename: str | None,
initial_line: int, initial_line: int,
color_manager: ColorManager, color_manager: ColorManager,
hl_factories: Tuple[HLFactory, ...], hl_factories: tuple[HLFactory, ...],
) -> None: ) -> None:
self.filename = filename self.filename = filename
self.initial_line = initial_line self.initial_line = initial_line
self.modified = False self.modified = False
self.buf = Buf([]) self.buf = Buf([])
self.nl = '\n' self.nl = '\n'
self.sha256: Optional[str] = None self.sha256: str | None = None
self._in_edit_action = False self._in_edit_action = False
self.undo_stack: List[Action] = [] self.undo_stack: list[Action] = []
self.redo_stack: List[Action] = [] self.redo_stack: list[Action] = []
self._hl_factories = hl_factories self._hl_factories = hl_factories
self._trailing_whitespace = TrailingWhitespace(color_manager) self._trailing_whitespace = TrailingWhitespace(color_manager)
self._replace_hl = Replace() self._replace_hl = Replace()
self.selection = Selection() self.selection = Selection()
self._file_hls: Tuple[FileHL, ...] = () self._file_hls: tuple[FileHL, ...] = ()
def ensure_loaded( def ensure_loaded(
self, self,
@@ -395,14 +393,14 @@ class File:
@clear_selection @clear_selection
def replace( def replace(
self, self,
screen: 'Screen', screen: Screen,
reg: Pattern[str], reg: Pattern[str],
replace: str, replace: str,
) -> None: ) -> None:
self.finalize_previous_action() self.finalize_previous_action()
count = 0 count = 0
res: Union[str, PromptResult] = '' res: str | PromptResult = ''
search = _SearchIter(self, reg, offset=0) search = _SearchIter(self, reg, offset=0)
for line_y, match in search: for line_y, match in search:
end = match.end() end = match.end()
@@ -597,7 +595,7 @@ class File:
@edit_action('cut selection', final=True) @edit_action('cut selection', final=True)
@clear_selection @clear_selection
def cut_selection(self, margin: Margin) -> Tuple[str, ...]: def cut_selection(self, margin: Margin) -> tuple[str, ...]:
ret = [] ret = []
(s_y, s_x), (e_y, e_x) = self.selection.get() (s_y, s_x), (e_y, e_x) = self.selection.get()
if s_y == e_y: if s_y == e_y:
@@ -617,7 +615,7 @@ class File:
self.buf.scroll_screen_if_needed(margin) self.buf.scroll_screen_if_needed(margin)
return tuple(ret) return tuple(ret)
def cut(self, cut_buffer: Tuple[str, ...]) -> Tuple[str, ...]: def cut(self, cut_buffer: tuple[str, ...]) -> tuple[str, ...]:
# only continue a cut if the last action is a non-final cut # only continue a cut if the last action is a non-final cut
if not self._continue_last_action('cut'): if not self._continue_last_action('cut'):
cut_buffer = () cut_buffer = ()
@@ -630,7 +628,7 @@ class File:
self.buf.x = 0 self.buf.x = 0
return cut_buffer + (victim,) return cut_buffer + (victim,)
def _uncut(self, cut_buffer: Tuple[str, ...], margin: Margin) -> None: def _uncut(self, cut_buffer: tuple[str, ...], margin: Margin) -> None:
for cut_line in cut_buffer: for cut_line in cut_buffer:
line = self.buf[self.buf.y] line = self.buf[self.buf.y]
before, after = line[:self.buf.x], line[self.buf.x:] before, after = line[:self.buf.x], line[self.buf.x:]
@@ -641,14 +639,14 @@ class File:
@edit_action('uncut', final=True) @edit_action('uncut', final=True)
@clear_selection @clear_selection
def uncut(self, cut_buffer: Tuple[str, ...], margin: Margin) -> None: def uncut(self, cut_buffer: tuple[str, ...], margin: Margin) -> None:
self._uncut(cut_buffer, margin) self._uncut(cut_buffer, margin)
@edit_action('uncut selection', final=True) @edit_action('uncut selection', final=True)
@clear_selection @clear_selection
def uncut_selection( def uncut_selection(
self, self,
cut_buffer: Tuple[str, ...], margin: Margin, cut_buffer: tuple[str, ...], margin: Margin,
) -> None: ) -> None:
self._uncut(cut_buffer, margin) self._uncut(cut_buffer, margin)
self.buf.up(margin) self.buf.up(margin)
@@ -666,7 +664,7 @@ 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]: def _selection_lines(self) -> tuple[int, int]:
(s_y, _), (e_y, _) = self.selection.get() (s_y, _), (e_y, _) = self.selection.get()
e_y = min(e_y + 1, len(self.buf) - 1) e_y = min(e_y + 1, len(self.buf) - 1)
if self.buf[e_y - 1] == '': if self.buf[e_y - 1] == '':
@@ -852,12 +850,12 @@ class File:
def move_cursor( def move_cursor(
self, self,
stdscr: 'curses._CursesWindow', stdscr: curses._CursesWindow,
margin: Margin, margin: Margin,
) -> None: ) -> None:
stdscr.move(*self.buf.cursor_position(margin)) stdscr.move(*self.buf.cursor_position(margin))
def draw(self, stdscr: 'curses._CursesWindow', margin: Margin) -> None: def draw(self, stdscr: curses._CursesWindow, margin: Margin) -> None:
to_display = min(self.buf.displayable_count, margin.body_lines) to_display = min(self.buf.displayable_count, margin.body_lines)
for file_hl in self._file_hls: for file_hl in self._file_hls:

View File

@@ -1,13 +1,11 @@
from __future__ import annotations
import functools import functools
import json import json
import os.path import os.path
from typing import Any from typing import Any
from typing import Dict
from typing import FrozenSet
from typing import List
from typing import Match from typing import Match
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Tuple from typing import Tuple
from typing import TypeVar from typing import TypeVar
@@ -34,7 +32,7 @@ def uniquely_constructed(t: T) -> T:
return t return t
def _split_name(s: Optional[str]) -> Tuple[str, ...]: def _split_name(s: str | None) -> tuple[str, ...]:
if s is None: if s is None:
return () return ()
else: else:
@@ -44,17 +42,17 @@ def _split_name(s: Optional[str]) -> Tuple[str, ...]:
class _Rule(Protocol): class _Rule(Protocol):
"""hax for recursive types python/mypy#731""" """hax for recursive types python/mypy#731"""
@property @property
def name(self) -> Tuple[str, ...]: ... def name(self) -> tuple[str, ...]: ...
@property @property
def match(self) -> Optional[str]: ... def match(self) -> str | None: ...
@property @property
def begin(self) -> Optional[str]: ... def begin(self) -> str | None: ...
@property @property
def end(self) -> Optional[str]: ... def end(self) -> str | None: ...
@property @property
def while_(self) -> Optional[str]: ... def while_(self) -> str | None: ...
@property @property
def content_name(self) -> Tuple[str, ...]: ... def content_name(self) -> tuple[str, ...]: ...
@property @property
def captures(self) -> Captures: ... def captures(self) -> Captures: ...
@property @property
@@ -64,39 +62,39 @@ class _Rule(Protocol):
@property @property
def while_captures(self) -> Captures: ... def while_captures(self) -> Captures: ...
@property @property
def include(self) -> Optional[str]: ... def include(self) -> str | None: ...
@property @property
def patterns(self) -> 'Tuple[_Rule, ...]': ... def patterns(self) -> tuple[_Rule, ...]: ...
@property @property
def repository(self) -> 'FChainMap[str, _Rule]': ... def repository(self) -> FChainMap[str, _Rule]: ...
@uniquely_constructed @uniquely_constructed
class Rule(NamedTuple): class Rule(NamedTuple):
name: Tuple[str, ...] name: tuple[str, ...]
match: Optional[str] match: str | None
begin: Optional[str] begin: str | None
end: Optional[str] end: str | None
while_: Optional[str] while_: str | None
content_name: Tuple[str, ...] content_name: tuple[str, ...]
captures: Captures captures: Captures
begin_captures: Captures begin_captures: Captures
end_captures: Captures end_captures: Captures
while_captures: Captures while_captures: Captures
include: Optional[str] include: str | None
patterns: Tuple[_Rule, ...] patterns: tuple[_Rule, ...]
repository: FChainMap[str, _Rule] repository: FChainMap[str, _Rule]
@classmethod @classmethod
def make( def make(
cls, cls,
dct: Dict[str, Any], dct: dict[str, Any],
parent_repository: FChainMap[str, _Rule], parent_repository: FChainMap[str, _Rule],
) -> _Rule: ) -> _Rule:
if 'repository' in dct: if 'repository' in dct:
# this looks odd, but it's so we can have a self-referential # this looks odd, but it's so we can have a self-referential
# immutable-after-construction chain map # immutable-after-construction chain map
repository_dct: Dict[str, _Rule] = {} repository_dct: dict[str, _Rule] = {}
repository = FChainMap(parent_repository, repository_dct) repository = FChainMap(parent_repository, repository_dct)
for k, sub_dct in dct['repository'].items(): for k, sub_dct in dct['repository'].items():
repository_dct[k] = Rule.make(sub_dct, repository) repository_dct[k] = Rule.make(sub_dct, repository)
@@ -183,15 +181,15 @@ class Rule(NamedTuple):
class Grammar(NamedTuple): class Grammar(NamedTuple):
scope_name: str scope_name: str
repository: FChainMap[str, _Rule] repository: FChainMap[str, _Rule]
patterns: Tuple[_Rule, ...] patterns: tuple[_Rule, ...]
@classmethod @classmethod
def make(cls, data: Dict[str, Any]) -> 'Grammar': def make(cls, data: dict[str, Any]) -> Grammar:
scope_name = data['scopeName'] scope_name = data['scopeName']
if 'repository' in data: if 'repository' in data:
# this looks odd, but it's so we can have a self-referential # this looks odd, but it's so we can have a self-referential
# immutable-after-construction chain map # immutable-after-construction chain map
repository_dct: Dict[str, _Rule] = {} repository_dct: dict[str, _Rule] = {}
repository = FChainMap(repository_dct) repository = FChainMap(repository_dct)
for k, dct in data['repository'].items(): for k, dct in data['repository'].items():
repository_dct[k] = Rule.make(dct, repository) repository_dct[k] = Rule.make(dct, repository)
@@ -212,54 +210,54 @@ class Region(NamedTuple):
class State(NamedTuple): class State(NamedTuple):
entries: Tuple['Entry', ...] entries: tuple[Entry, ...]
while_stack: Tuple[Tuple['WhileRule', int], ...] while_stack: tuple[tuple[WhileRule, int], ...]
@classmethod @classmethod
def root(cls, entry: 'Entry') -> 'State': def root(cls, entry: Entry) -> State:
return cls((entry,), ()) return cls((entry,), ())
@property @property
def cur(self) -> 'Entry': def cur(self) -> Entry:
return self.entries[-1] return self.entries[-1]
def push(self, entry: 'Entry') -> 'State': def push(self, entry: Entry) -> State:
return self._replace(entries=(*self.entries, entry)) return self._replace(entries=(*self.entries, entry))
def pop(self) -> 'State': def pop(self) -> State:
return self._replace(entries=self.entries[:-1]) return self._replace(entries=self.entries[:-1])
def push_while(self, rule: 'WhileRule', entry: 'Entry') -> 'State': def push_while(self, rule: WhileRule, entry: Entry) -> State:
entries = (*self.entries, entry) entries = (*self.entries, entry)
while_stack = (*self.while_stack, (rule, len(entries))) while_stack = (*self.while_stack, (rule, len(entries)))
return self._replace(entries=entries, while_stack=while_stack) return self._replace(entries=entries, while_stack=while_stack)
def pop_while(self) -> 'State': def pop_while(self) -> State:
entries, while_stack = self.entries[:-1], self.while_stack[:-1] entries, while_stack = self.entries[:-1], self.while_stack[:-1]
return self._replace(entries=entries, while_stack=while_stack) return self._replace(entries=entries, while_stack=while_stack)
class CompiledRule(Protocol): class CompiledRule(Protocol):
@property @property
def name(self) -> Tuple[str, ...]: ... def name(self) -> tuple[str, ...]: ...
def start( def start(
self, self,
compiler: 'Compiler', compiler: Compiler,
match: Match[str], match: Match[str],
state: State, state: State,
) -> Tuple[State, bool, Regions]: ) -> tuple[State, bool, Regions]:
... ...
def search( def search(
self, self,
compiler: 'Compiler', compiler: Compiler,
state: State, state: State,
line: str, line: str,
pos: int, pos: int,
first_line: bool, first_line: bool,
boundary: bool, boundary: bool,
) -> Optional[Tuple[State, int, bool, Regions]]: ) -> tuple[State, int, bool, Regions] | None:
... ...
@@ -267,19 +265,19 @@ class CompiledRegsetRule(CompiledRule, Protocol):
@property @property
def regset(self) -> _RegSet: ... def regset(self) -> _RegSet: ...
@property @property
def u_rules(self) -> Tuple[_Rule, ...]: ... def u_rules(self) -> tuple[_Rule, ...]: ...
class Entry(NamedTuple): class Entry(NamedTuple):
scope: Tuple[str, ...] scope: tuple[str, ...]
rule: CompiledRule rule: CompiledRule
start: Tuple[str, int] start: tuple[str, int]
reg: _Reg = ERR_REG reg: _Reg = ERR_REG
boundary: bool = False boundary: bool = False
def _inner_capture_parse( def _inner_capture_parse(
compiler: 'Compiler', compiler: Compiler,
start: int, start: int,
s: str, s: str,
scope: Scope, scope: Scope,
@@ -293,12 +291,12 @@ def _inner_capture_parse(
def _captures( def _captures(
compiler: 'Compiler', compiler: Compiler,
scope: Scope, scope: Scope,
match: Match[str], match: Match[str],
captures: Captures, captures: Captures,
) -> Regions: ) -> Regions:
ret: List[Region] = [] ret: list[Region] = []
pos, pos_end = match.span() pos, pos_end = match.span()
for i, u_rule in captures: for i, u_rule in captures:
try: try:
@@ -347,12 +345,12 @@ def _captures(
def _do_regset( def _do_regset(
idx: int, idx: int,
match: Optional[Match[str]], match: Match[str] | None,
rule: CompiledRegsetRule, rule: CompiledRegsetRule,
compiler: 'Compiler', compiler: Compiler,
state: State, state: State,
pos: int, pos: int,
) -> Optional[Tuple[State, int, bool, Regions]]: ) -> tuple[State, int, bool, Regions] | None:
if match is None: if match is None:
return None return None
@@ -369,73 +367,73 @@ def _do_regset(
@uniquely_constructed @uniquely_constructed
class PatternRule(NamedTuple): class PatternRule(NamedTuple):
name: Tuple[str, ...] name: tuple[str, ...]
regset: _RegSet regset: _RegSet
u_rules: Tuple[_Rule, ...] u_rules: tuple[_Rule, ...]
def start( def start(
self, self,
compiler: 'Compiler', compiler: Compiler,
match: Match[str], match: Match[str],
state: State, state: State,
) -> Tuple[State, bool, Regions]: ) -> tuple[State, bool, Regions]:
raise AssertionError(f'unreachable {self}') raise AssertionError(f'unreachable {self}')
def search( def search(
self, self,
compiler: 'Compiler', compiler: Compiler,
state: State, state: State,
line: str, line: str,
pos: int, pos: int,
first_line: bool, first_line: bool,
boundary: bool, boundary: bool,
) -> Optional[Tuple[State, int, bool, Regions]]: ) -> tuple[State, int, bool, Regions] | None:
idx, match = self.regset.search(line, pos, first_line, boundary) idx, match = self.regset.search(line, pos, first_line, boundary)
return _do_regset(idx, match, self, compiler, state, pos) return _do_regset(idx, match, self, compiler, state, pos)
@uniquely_constructed @uniquely_constructed
class MatchRule(NamedTuple): class MatchRule(NamedTuple):
name: Tuple[str, ...] name: tuple[str, ...]
captures: Captures captures: Captures
def start( def start(
self, self,
compiler: 'Compiler', compiler: Compiler,
match: Match[str], match: Match[str],
state: State, state: State,
) -> Tuple[State, bool, Regions]: ) -> tuple[State, bool, Regions]:
scope = state.cur.scope + self.name scope = state.cur.scope + self.name
return state, False, _captures(compiler, scope, match, self.captures) return state, False, _captures(compiler, scope, match, self.captures)
def search( def search(
self, self,
compiler: 'Compiler', compiler: Compiler,
state: State, state: State,
line: str, line: str,
pos: int, pos: int,
first_line: bool, first_line: bool,
boundary: bool, boundary: bool,
) -> Optional[Tuple[State, int, bool, Regions]]: ) -> tuple[State, int, bool, Regions] | None:
raise AssertionError(f'unreachable {self}') raise AssertionError(f'unreachable {self}')
@uniquely_constructed @uniquely_constructed
class EndRule(NamedTuple): class EndRule(NamedTuple):
name: Tuple[str, ...] name: tuple[str, ...]
content_name: Tuple[str, ...] content_name: tuple[str, ...]
begin_captures: Captures begin_captures: Captures
end_captures: Captures end_captures: Captures
end: str end: str
regset: _RegSet regset: _RegSet
u_rules: Tuple[_Rule, ...] u_rules: tuple[_Rule, ...]
def start( def start(
self, self,
compiler: 'Compiler', compiler: Compiler,
match: Match[str], match: Match[str],
state: State, state: State,
) -> Tuple[State, bool, Regions]: ) -> tuple[State, bool, Regions]:
scope = state.cur.scope + self.name scope = state.cur.scope + self.name
next_scope = scope + self.content_name next_scope = scope + self.content_name
@@ -448,11 +446,11 @@ class EndRule(NamedTuple):
def _end_ret( def _end_ret(
self, self,
compiler: 'Compiler', compiler: Compiler,
state: State, state: State,
pos: int, pos: int,
m: Match[str], m: Match[str],
) -> Tuple[State, int, bool, Regions]: ) -> tuple[State, int, bool, Regions]:
ret = [] ret = []
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))
@@ -470,13 +468,13 @@ class EndRule(NamedTuple):
def search( def search(
self, self,
compiler: 'Compiler', compiler: Compiler,
state: State, state: State,
line: str, line: str,
pos: int, pos: int,
first_line: bool, first_line: bool,
boundary: bool, boundary: bool,
) -> Optional[Tuple[State, int, bool, Regions]]: ) -> tuple[State, int, bool, Regions] | None:
end_match = state.cur.reg.search(line, pos, first_line, boundary) end_match = state.cur.reg.search(line, pos, first_line, boundary)
if end_match is not None and end_match.start() == pos: if end_match is not None and end_match.start() == pos:
return self._end_ret(compiler, state, pos, end_match) return self._end_ret(compiler, state, pos, end_match)
@@ -493,20 +491,20 @@ class EndRule(NamedTuple):
@uniquely_constructed @uniquely_constructed
class WhileRule(NamedTuple): class WhileRule(NamedTuple):
name: Tuple[str, ...] name: tuple[str, ...]
content_name: Tuple[str, ...] content_name: tuple[str, ...]
begin_captures: Captures begin_captures: Captures
while_captures: Captures while_captures: Captures
while_: str while_: str
regset: _RegSet regset: _RegSet
u_rules: Tuple[_Rule, ...] u_rules: tuple[_Rule, ...]
def start( def start(
self, self,
compiler: 'Compiler', compiler: Compiler,
match: Match[str], match: Match[str],
state: State, state: State,
) -> Tuple[State, bool, Regions]: ) -> tuple[State, bool, Regions]:
scope = state.cur.scope + self.name scope = state.cur.scope + self.name
next_scope = scope + self.content_name next_scope = scope + self.content_name
@@ -520,13 +518,13 @@ class WhileRule(NamedTuple):
def continues( def continues(
self, self,
compiler: 'Compiler', compiler: Compiler,
state: State, state: State,
line: str, line: str,
pos: int, pos: int,
first_line: bool, first_line: bool,
boundary: bool, boundary: bool,
) -> Optional[Tuple[int, bool, Regions]]: ) -> tuple[int, bool, Regions] | None:
match = state.cur.reg.match(line, pos, first_line, boundary) match = state.cur.reg.match(line, pos, first_line, boundary)
if match is None: if match is None:
return None return None
@@ -536,23 +534,23 @@ class WhileRule(NamedTuple):
def search( def search(
self, self,
compiler: 'Compiler', compiler: Compiler,
state: State, state: State,
line: str, line: str,
pos: int, pos: int,
first_line: bool, first_line: bool,
boundary: bool, boundary: bool,
) -> Optional[Tuple[State, int, bool, Regions]]: ) -> tuple[State, int, bool, Regions] | None:
idx, match = self.regset.search(line, pos, first_line, boundary) idx, match = self.regset.search(line, pos, first_line, boundary)
return _do_regset(idx, match, self, compiler, state, pos) return _do_regset(idx, match, self, compiler, state, pos)
class Compiler: class Compiler:
def __init__(self, grammar: Grammar, grammars: 'Grammars') -> None: def __init__(self, grammar: Grammar, grammars: Grammars) -> None:
self._root_scope = grammar.scope_name self._root_scope = grammar.scope_name
self._grammars = grammars self._grammars = grammars
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, ('', 0))) self.root_state = State.root(Entry(root.name, root, ('', 0)))
@@ -566,7 +564,7 @@ class Compiler:
grammar: Grammar, grammar: Grammar,
repository: FChainMap[str, _Rule], repository: FChainMap[str, _Rule],
s: str, s: str,
) -> Tuple[List[str], Tuple[_Rule, ...]]: ) -> tuple[list[str], tuple[_Rule, ...]]:
if s == '$self': if s == '$self':
return self._patterns(grammar, grammar.patterns) return self._patterns(grammar, grammar.patterns)
elif s == '$base': elif s == '$base':
@@ -586,10 +584,10 @@ class Compiler:
def _patterns( def _patterns(
self, self,
grammar: Grammar, grammar: Grammar,
rules: Tuple[_Rule, ...], rules: tuple[_Rule, ...],
) -> Tuple[List[str], Tuple[_Rule, ...]]: ) -> tuple[list[str], tuple[_Rule, ...]]:
ret_regs = [] ret_regs = []
ret_rules: List[_Rule] = [] ret_rules: list[_Rule] = []
for rule in rules: for rule in rules:
if rule.include is not None: if rule.include is not None:
tmp_regs, tmp_rules = self._include( tmp_regs, tmp_rules = self._include(
@@ -676,12 +674,12 @@ class Grammars:
unknown_grammar = {'scopeName': 'source.unknown', 'patterns': []} unknown_grammar = {'scopeName': 'source.unknown', 'patterns': []}
self._raw = {'source.unknown': unknown_grammar} self._raw = {'source.unknown': unknown_grammar}
self._file_types: List[Tuple[FrozenSet[str], str]] = [] self._file_types: list[tuple[frozenset[str], str]] = []
self._first_line: List[Tuple[_Reg, str]] = [] self._first_line: list[tuple[_Reg, str]] = []
self._parsed: Dict[str, Grammar] = {} self._parsed: dict[str, Grammar] = {}
self._compiled: Dict[str, Compiler] = {} self._compiled: dict[str, Compiler] = {}
def _raw_for_scope(self, scope: str) -> Dict[str, Any]: def _raw_for_scope(self, scope: str) -> dict[str, Any]:
try: try:
return self._raw[scope] return self._raw[scope]
except KeyError: except KeyError:
@@ -747,12 +745,12 @@ class Grammars:
def highlight_line( def highlight_line(
compiler: 'Compiler', compiler: Compiler,
state: State, state: State,
line: str, line: str,
first_line: bool, first_line: bool,
) -> Tuple[State, Regions]: ) -> tuple[State, Regions]:
ret: List[Region] = [] ret: list[Region] = []
pos = 0 pos = 0
boundary = state.cur.boundary boundary = state.cur.boundary

View File

@@ -1,18 +1,18 @@
from __future__ import annotations
import collections import collections
import contextlib import contextlib
import os.path import os.path
from typing import Dict
from typing import Generator from typing import Generator
from typing import List
from babi.user_data import xdg_data from babi.user_data import xdg_data
class History: class History:
def __init__(self) -> None: def __init__(self) -> None:
self._orig_len: Dict[str, int] = collections.defaultdict(int) self._orig_len: dict[str, int] = collections.defaultdict(int)
self.data: Dict[str, List[str]] = collections.defaultdict(list) self.data: dict[str, list[str]] = collections.defaultdict(list)
self.prev: Dict[str, str] = {} self.prev: dict[str, str] = {}
@contextlib.contextmanager @contextlib.contextmanager
def save(self) -> Generator[None, None, None]: def save(self) -> Generator[None, None, None]:

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from typing import NamedTuple from typing import NamedTuple
from typing import Tuple from typing import Tuple

View File

@@ -1,7 +1,8 @@
from __future__ import annotations
import collections import collections
import contextlib import contextlib
import curses import curses
from typing import Dict
from typing import Generator from typing import Generator
from babi.buf import Buf from babi.buf import Buf
@@ -13,7 +14,7 @@ class Replace:
include_edge = True include_edge = True
def __init__(self) -> None: def __init__(self) -> None:
self.regions: Dict[int, HLs] = collections.defaultdict(tuple) self.regions: dict[int, HLs] = collections.defaultdict(tuple)
def highlight_until(self, lines: Buf, idx: int) -> None: def highlight_until(self, lines: Buf, idx: int) -> None:
"""our highlight regions are populated in other ways""" """our highlight regions are populated in other ways"""

View File

@@ -1,8 +1,7 @@
from __future__ import annotations
import collections import collections
import curses import curses
from typing import Dict
from typing import Optional
from typing import Tuple
from babi.buf import Buf from babi.buf import Buf
from babi.hl.interface import HL from babi.hl.interface import HL
@@ -13,9 +12,9 @@ class Selection:
include_edge = True include_edge = True
def __init__(self) -> None: def __init__(self) -> None:
self.regions: Dict[int, HLs] = collections.defaultdict(tuple) self.regions: dict[int, HLs] = collections.defaultdict(tuple)
self.start: Optional[Tuple[int, int]] = None self.start: tuple[int, int] | None = None
self.end: Optional[Tuple[int, int]] = None self.end: tuple[int, int] | None = None
def register_callbacks(self, buf: Buf) -> None: def register_callbacks(self, buf: Buf) -> None:
"""our highlight regions are populated in other ways""" """our highlight regions are populated in other ways"""
@@ -39,7 +38,7 @@ class Selection:
) )
self.regions[e_y] = (HL(x=0, end=e_x, attr=attr),) self.regions[e_y] = (HL(x=0, end=e_x, attr=attr),)
def get(self) -> Tuple[Tuple[int, int], Tuple[int, int]]: def get(self) -> tuple[tuple[int, int], tuple[int, int]]:
assert self.start is not None and self.end is not None assert self.start is not None and self.end is not None
if self.start < self.end: if self.start < self.end:
return self.start, self.end return self.start, self.end

View File

@@ -1,11 +1,10 @@
from __future__ import annotations
import curses import curses
import functools import functools
import math import math
from typing import Callable from typing import Callable
from typing import List
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Tuple
from babi.buf import Buf from babi.buf import Buf
from babi.color_manager import ColorManager from babi.color_manager import ColorManager
@@ -21,7 +20,7 @@ from babi.user_data import prefix_data
from babi.user_data import xdg_config from babi.user_data import xdg_config
from babi.user_data import xdg_data from babi.user_data import xdg_data
A_ITALIC = getattr(curses, 'A_ITALIC', 0x80000000) # new in py37 A_ITALIC = getattr(curses, 'A_ITALIC', 0x80000000) # not always present
class FileSyntax: class FileSyntax:
@@ -37,12 +36,12 @@ class FileSyntax:
self._theme = theme self._theme = theme
self._color_manager = color_manager self._color_manager = color_manager
self.regions: List[HLs] = [] self.regions: list[HLs] = []
self._states: List[State] = [] self._states: list[State] = []
# this will be assigned a functools.lru_cache per instance for # this will be assigned a functools.lru_cache per instance for
# better hit rate and memory usage # better hit rate and memory usage
self._hl: Optional[Callable[[State, str, bool], Tuple[State, HLs]]] self._hl: Callable[[State, str, bool], tuple[State, HLs]] | None
self._hl = None self._hl = None
def attr(self, style: Style) -> int: def attr(self, style: Style) -> int:
@@ -59,7 +58,7 @@ class FileSyntax:
state: State, state: State,
line: str, line: str,
first_line: bool, first_line: bool,
) -> Tuple[State, HLs]: ) -> tuple[State, HLs]:
new_state, regions = highlight_line( new_state, regions = highlight_line(
self._compiler, state, f'{line}\n', first_line=first_line, self._compiler, state, f'{line}\n', first_line=first_line,
) )
@@ -68,7 +67,7 @@ class FileSyntax:
new_end = regions[-1]._replace(end=regions[-1].end - 1) new_end = regions[-1]._replace(end=regions[-1].end - 1)
regions = regions[:-1] + (new_end,) regions = regions[:-1] + (new_end,)
regs: List[HL] = [] regs: list[HL] = []
for r in regions: for r in regions:
style = self._theme.select(r.scope) style = self._theme.select(r.scope)
if style == self._theme.default: if style == self._theme.default:
@@ -115,8 +114,7 @@ class FileSyntax:
state = self._states[-1] state = self._states[-1]
for i in range(len(self._states), idx): for i in range(len(self._states), idx):
# https://github.com/python/mypy/issues/8579 state, regions = self._hl(state, lines[i], i == 0)
state, regions = self._hl(state, lines[i], i == 0) # type: ignore
self._states.append(state) self._states.append(state)
self.regions.append(regions) self.regions.append(regions)
@@ -134,7 +132,7 @@ class Syntax(NamedTuple):
compiler = self.grammars.blank_compiler() compiler = self.grammars.blank_compiler()
return FileSyntax(compiler, self.theme, self.color_manager) return FileSyntax(compiler, self.theme, self.color_manager)
def _init_screen(self, stdscr: 'curses._CursesWindow') -> None: def _init_screen(self, stdscr: curses._CursesWindow) -> None:
default_fg, default_bg = self.theme.default.fg, self.theme.default.bg default_fg, default_bg = self.theme.default.fg, self.theme.default.bg
all_colors = {c for c in (default_fg, default_bg) if c is not None} all_colors = {c for c in (default_fg, default_bg) if c is not None}
todo = list(self.theme.rules.children.values()) todo = list(self.theme.rules.children.values())
@@ -155,9 +153,9 @@ class Syntax(NamedTuple):
@classmethod @classmethod
def from_screen( def from_screen(
cls, cls,
stdscr: 'curses._CursesWindow', stdscr: curses._CursesWindow,
color_manager: ColorManager, color_manager: ColorManager,
) -> 'Syntax': ) -> Syntax:
grammars = Grammars(prefix_data('grammar_v1'), xdg_data('grammar_v1')) grammars = Grammars(prefix_data('grammar_v1'), xdg_data('grammar_v1'))
theme = Theme.from_filename(xdg_config('theme.json')) theme = Theme.from_filename(xdg_config('theme.json'))
ret = cls(grammars, theme, color_manager) ret = cls(grammars, theme, color_manager)

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import curses import curses
from typing import List
from babi.buf import Buf from babi.buf import Buf
from babi.color_manager import ColorManager from babi.color_manager import ColorManager
@@ -13,7 +14,7 @@ class TrailingWhitespace:
def __init__(self, color_manager: ColorManager) -> None: def __init__(self, color_manager: ColorManager) -> None:
self._color_manager = color_manager self._color_manager = color_manager
self.regions: List[HLs] = [] self.regions: list[HLs] = []
def _trailing_ws(self, line: str) -> HLs: def _trailing_ws(self, line: str) -> HLs:
if not line: if not line:

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import curses import curses
from babi.cached_property import cached_property from babi.cached_property import cached_property
@@ -34,7 +36,7 @@ def scrolled_line(s: str, x: int, width: int) -> str:
class _CalcWidth: class _CalcWidth:
@cached_property @cached_property
def _window(self) -> 'curses._CursesWindow': def _window(self) -> curses._CursesWindow:
return curses.newwin(1, 10) return curses.newwin(1, 10)
def wcwidth(self, c: str) -> int: def wcwidth(self, c: str) -> int:

View File

@@ -1,13 +1,12 @@
from __future__ import annotations
import argparse import argparse
import curses import curses
import os import os
import re import re
import signal import signal
import sys import sys
from typing import List
from typing import Optional
from typing import Sequence from typing import Sequence
from typing import Tuple
from babi.buf import Buf from babi.buf import Buf
from babi.file import File from babi.file import File
@@ -44,9 +43,9 @@ def _edit(screen: Screen, stdin: str) -> EditResult:
def c_main( def c_main(
stdscr: 'curses._CursesWindow', stdscr: curses._CursesWindow,
filenames: List[Optional[str]], filenames: list[str | None],
positions: List[int], positions: list[int],
stdin: str, stdin: str,
perf: Perf, perf: Perf,
) -> int: ) -> int:
@@ -73,7 +72,7 @@ def c_main(
return 0 return 0
def _key_debug(stdscr: 'curses._CursesWindow', perf: Perf) -> int: def _key_debug(stdscr: curses._CursesWindow, perf: Perf) -> int:
screen = Screen(stdscr, ['<<key debug>>'], [0], perf) screen = Screen(stdscr, ['<<key debug>>'], [0], perf)
screen.file.buf = Buf(['']) screen.file.buf = Buf([''])
@@ -91,11 +90,11 @@ def _key_debug(stdscr: 'curses._CursesWindow', perf: Perf) -> int:
return 0 return 0
def _filenames(filenames: List[str]) -> Tuple[List[Optional[str]], List[int]]: def _filenames(filenames: list[str]) -> tuple[list[str | None], list[int]]:
if not filenames: if not filenames:
return [None], [0] return [None], [0]
ret_filenames: List[Optional[str]] = [] ret_filenames: list[str | None] = []
ret_positions = [] ret_positions = []
filenames_iter = iter(filenames) filenames_iter = iter(filenames)
@@ -122,7 +121,7 @@ def _filenames(filenames: List[str]) -> Tuple[List[Optional[str]], List[int]]:
return ret_filenames, ret_positions return ret_filenames, ret_positions
def main(argv: Optional[Sequence[str]] = None) -> int: def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('filenames', metavar='filename', nargs='*') parser.add_argument('filenames', metavar='filename', nargs='*')
parser.add_argument('--perf-log') parser.add_argument('--perf-log')
@@ -153,4 +152,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
if __name__ == '__main__': if __name__ == '__main__':
exit(main()) raise SystemExit(main())

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import curses import curses
from typing import NamedTuple from typing import NamedTuple
@@ -31,5 +33,5 @@ class Margin(NamedTuple):
return int(self.lines / 2 + .5) return int(self.lines / 2 + .5)
@classmethod @classmethod
def from_current_screen(cls) -> 'Margin': def from_current_screen(cls) -> Margin:
return cls(curses.LINES, curses.COLS) return cls(curses.LINES, curses.COLS)

View File

@@ -1,18 +1,17 @@
from __future__ import annotations
import contextlib import contextlib
import cProfile import cProfile
import time import time
from typing import Generator from typing import Generator
from typing import List
from typing import Optional
from typing import Tuple
class Perf: class Perf:
def __init__(self) -> None: def __init__(self) -> None:
self._prof: Optional[cProfile.Profile] = None self._prof: cProfile.Profile | None = None
self._records: List[Tuple[str, float]] = [] self._records: list[tuple[str, float]] = []
self._name: Optional[str] = None self._name: str | None = None
self._time: Optional[float] = None self._time: float | None = None
def start(self, name: str) -> None: def start(self, name: str) -> None:
if self._prof: if self._prof:
@@ -43,7 +42,7 @@ class Perf:
@contextlib.contextmanager @contextlib.contextmanager
def perf_log(filename: Optional[str]) -> Generator[Perf, None, None]: def perf_log(filename: str | None) -> Generator[Perf, None, None]:
perf = Perf() perf = Perf()
if filename is None: if filename is None:
yield perf yield perf

View File

@@ -1,10 +1,8 @@
from __future__ import annotations
import curses import curses
import enum import enum
from typing import List
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union
from babi.horizontal_scrolling import line_x from babi.horizontal_scrolling import line_x
from babi.horizontal_scrolling import scrolled_line from babi.horizontal_scrolling import scrolled_line
@@ -16,7 +14,7 @@ PromptResult = enum.Enum('PromptResult', 'CANCELLED')
class Prompt: class Prompt:
def __init__(self, screen: 'Screen', prompt: str, lst: List[str]) -> None: def __init__(self, screen: Screen, prompt: str, lst: list[str]) -> None:
self._screen = screen self._screen = screen
self._prompt = prompt self._prompt = prompt
self._lst = lst self._lst = lst
@@ -31,7 +29,7 @@ class Prompt:
def _s(self, s: str) -> None: def _s(self, s: str) -> None:
self._lst[self._y] = s self._lst[self._y] = s
def _render_prompt(self, *, base: Optional[str] = None) -> None: def _render_prompt(self, *, base: str | None = None) -> None:
base = base or self._prompt base = base or self._prompt
if not base or self._screen.margin.cols < 7: if not base or self._screen.margin.cols < 7:
prompt_s = '' prompt_s = ''
@@ -100,7 +98,7 @@ class Prompt:
def _resize(self) -> None: def _resize(self) -> None:
self._screen.resize() self._screen.resize()
def _check_failed(self, idx: int, s: str) -> Tuple[bool, int]: def _check_failed(self, idx: int, s: str) -> tuple[bool, int]:
failed = False failed = False
for search_idx in range(idx, -1, -1): for search_idx in range(idx, -1, -1):
if s in self._lst[search_idx]: if s in self._lst[search_idx]:
@@ -111,7 +109,7 @@ class Prompt:
failed = True failed = True
return failed, idx return failed, idx
def _reverse_search(self) -> Union[None, str, PromptResult]: def _reverse_search(self) -> None | str | PromptResult:
reverse_s = '' reverse_s = ''
idx = self._y idx = self._y
while True: while True:
@@ -177,7 +175,7 @@ class Prompt:
self._s = self._s[:self._x] + c + self._s[self._x:] self._s = self._s[:self._x] + c + self._s[self._x:]
self._x += len(c) self._x += len(c)
def run(self) -> Union[PromptResult, str]: def run(self) -> PromptResult | str:
while True: while True:
self._render_prompt() self._render_prompt()

View File

@@ -1,8 +1,8 @@
from __future__ import annotations
import functools import functools
import re import re
from typing import Match from typing import Match
from typing import Optional
from typing import Tuple
import onigurumacffi import onigurumacffi
@@ -42,7 +42,7 @@ class _Reg:
pos: int, pos: int,
first_line: bool, first_line: bool,
boundary: bool, boundary: bool,
) -> Optional[Match[str]]: ) -> Match[str] | None:
return self._reg.search(line, pos, flags=_FLAGS[first_line, boundary]) return self._reg.search(line, pos, flags=_FLAGS[first_line, boundary])
def match( def match(
@@ -51,7 +51,7 @@ class _Reg:
pos: int, pos: int,
first_line: bool, first_line: bool,
boundary: bool, boundary: bool,
) -> Optional[Match[str]]: ) -> Match[str] | None:
return self._reg.match(line, pos, flags=_FLAGS[first_line, boundary]) return self._reg.match(line, pos, flags=_FLAGS[first_line, boundary])
@@ -70,7 +70,7 @@ class _RegSet:
pos: int, pos: int,
first_line: bool, first_line: bool,
boundary: bool, boundary: bool,
) -> Tuple[int, Optional[Match[str]]]: ) -> tuple[int, Match[str] | None]:
return self._set.search(line, pos, flags=_FLAGS[first_line, boundary]) return self._set.search(line, pos, flags=_FLAGS[first_line, boundary])

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib import contextlib
import curses import curses
import enum import enum
@@ -5,14 +7,11 @@ import hashlib
import os import os
import re import re
import signal import signal
import sre_parse
import sys import sys
from typing import Generator from typing import Generator
from typing import List
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Pattern from typing import Pattern
from typing import Tuple
from typing import Union
from babi.color_manager import ColorManager from babi.color_manager import ColorManager
from babi.file import Action from babi.file import Action
@@ -102,16 +101,16 @@ KEYNAME_REWRITE = {
class Key(NamedTuple): class Key(NamedTuple):
wch: Union[int, str] wch: int | str
keyname: bytes keyname: bytes
class Screen: class Screen:
def __init__( def __init__(
self, self,
stdscr: 'curses._CursesWindow', stdscr: curses._CursesWindow,
filenames: List[Optional[str]], filenames: list[str | None],
initial_lines: List[int], initial_lines: list[int],
perf: Perf, perf: Perf,
) -> None: ) -> None:
self.stdscr = stdscr self.stdscr = stdscr
@@ -126,9 +125,9 @@ class Screen:
self.perf = perf self.perf = perf
self.status = Status() self.status = Status()
self.margin = Margin.from_current_screen() self.margin = Margin.from_current_screen()
self.cut_buffer: Tuple[str, ...] = () self.cut_buffer: tuple[str, ...] = ()
self.cut_selection = False self.cut_selection = False
self._buffered_input: Union[int, str, None] = None self._buffered_input: int | str | None = None
@property @property
def file(self) -> File: def file(self) -> File:
@@ -271,8 +270,8 @@ class Screen:
def quick_prompt( def quick_prompt(
self, self,
prompt: str, prompt: str,
opt_strs: Tuple[str, ...], opt_strs: tuple[str, ...],
) -> Union[str, PromptResult]: ) -> 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
@@ -318,10 +317,10 @@ class Screen:
prompt: str, prompt: str,
*, *,
allow_empty: bool = False, allow_empty: bool = False,
history: Optional[str] = None, history: str | None = None,
default_prev: bool = False, default_prev: bool = False,
default: Optional[str] = None, default: str | None = None,
) -> Union[str, PromptResult]: ) -> str | PromptResult:
default = default or '' default = default or ''
self.status.clear() self.status.clear()
if history is not None: if history is not None:
@@ -378,7 +377,7 @@ class Screen:
else: else:
self.file.uncut(self.cut_buffer, self.margin) self.file.uncut(self.cut_buffer, self.margin)
def _get_search_re(self, prompt: str) -> Union[Pattern[str], PromptResult]: def _get_search_re(self, prompt: str) -> Pattern[str] | PromptResult:
response = self.prompt(prompt, history='search', default_prev=True) response = self.prompt(prompt, history='search', default_prev=True)
if response is PromptResult.CANCELLED: if response is PromptResult.CANCELLED:
return response return response
@@ -391,8 +390,8 @@ class Screen:
def _undo_redo( def _undo_redo(
self, self,
op: str, op: str,
from_stack: List[Action], from_stack: list[Action],
to_stack: List[Action], to_stack: list[Action],
) -> None: ) -> None:
if not from_stack: if not from_stack:
self.status.update(f'nothing to {op}!') self.status.update(f'nothing to {op}!')
@@ -421,9 +420,14 @@ class Screen:
'replace with', history='replace', allow_empty=True, 'replace with', history='replace', allow_empty=True,
) )
if response is not PromptResult.CANCELLED: if response is not PromptResult.CANCELLED:
self.file.replace(self, search_response, response) try:
sre_parse.parse_template(response, search_response)
except re.error:
self.status.update('invalid replacement string')
else:
self.file.replace(self, search_response, response)
def command(self) -> Optional[EditResult]: def command(self) -> EditResult | None:
response = self.prompt('', history='command') response = self.prompt('', history='command')
if response is PromptResult.CANCELLED: if response is PromptResult.CANCELLED:
pass pass
@@ -480,7 +484,7 @@ class Screen:
self.status.update(f'invalid command: {response}') self.status.update(f'invalid command: {response}')
return None return None
def save(self) -> Optional[PromptResult]: def save(self) -> PromptResult | None:
self.file.finalize_previous_action() self.file.finalize_previous_action()
# TODO: make directories if they don't exist # TODO: make directories if they don't exist
@@ -494,17 +498,17 @@ class Screen:
else: else:
self.file.filename = filename self.file.filename = filename
if os.path.isfile(self.file.filename): if not os.path.isfile(self.file.filename):
sha256: str | None = None
else:
with open(self.file.filename, encoding='UTF-8', newline='') as f: with open(self.file.filename, encoding='UTF-8', newline='') as f:
*_, sha256 = get_lines(f) *_, sha256 = get_lines(f)
else:
sha256 = hashlib.sha256(b'').hexdigest()
contents = self.file.nl.join(self.file.buf) contents = self.file.nl.join(self.file.buf)
sha256_to_save = hashlib.sha256(contents.encode()).hexdigest() sha256_to_save = hashlib.sha256(contents.encode()).hexdigest()
# the file on disk is the same as when we opened it # the file on disk is the same as when we opened it
if sha256 not in (self.file.sha256, sha256_to_save): if sha256 not in (None, self.file.sha256, sha256_to_save):
self.status.update('(file changed on disk, not implemented)') self.status.update('(file changed on disk, not implemented)')
return PromptResult.CANCELLED return PromptResult.CANCELLED
@@ -532,7 +536,7 @@ class Screen:
first = False first = False
return None return None
def save_filename(self) -> Optional[PromptResult]: def save_filename(self) -> PromptResult | None:
response = self.prompt('enter filename', default=self.file.filename) response = self.prompt('enter filename', default=self.file.filename)
if response is PromptResult.CANCELLED: if response is PromptResult.CANCELLED:
return PromptResult.CANCELLED return PromptResult.CANCELLED
@@ -540,7 +544,7 @@ class Screen:
self.file.filename = response self.file.filename = response
return self.save() return self.save()
def open_file(self) -> Optional[EditResult]: def open_file(self) -> EditResult | None:
response = self.prompt('enter filename', history='open') response = self.prompt('enter filename', history='open')
if response is not PromptResult.CANCELLED: if response is not PromptResult.CANCELLED:
opened = File(response, 0, self.color_manager, self.hl_factories) opened = File(response, 0, self.color_manager, self.hl_factories)
@@ -549,7 +553,7 @@ class Screen:
else: else:
return None return None
def quit_save_modified(self) -> Optional[EditResult]: def quit_save_modified(self) -> EditResult | None:
if self.file.modified: if self.file.modified:
response = self.quick_prompt( response = self.quick_prompt(
'file is modified - save', ('yes', 'no'), 'file is modified - save', ('yes', 'no'),
@@ -597,7 +601,7 @@ 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 ( if (
sys.version_info >= (3, 9) and sys.version_info >= (3, 9) and
@@ -623,7 +627,7 @@ def _init_screen() -> 'curses._CursesWindow':
@contextlib.contextmanager @contextlib.contextmanager
def make_stdscr() -> Generator['curses._CursesWindow', None, None]: def make_stdscr() -> Generator[curses._CursesWindow, None, None]:
"""essentially `curses.wrapper` but split out to implement ^Z""" """essentially `curses.wrapper` but split out to implement ^Z"""
try: try:
yield _init_screen() yield _init_screen()

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import curses import curses
from babi.margin import Margin from babi.margin import Margin
@@ -16,7 +18,7 @@ class Status:
def clear(self) -> None: def clear(self) -> None:
self._status = '' self._status = ''
def draw(self, stdscr: 'curses._CursesWindow', margin: Margin) -> None: def draw(self, stdscr: curses._CursesWindow, margin: Margin) -> None:
if margin.footer or self._status: if margin.footer or self._status:
stdscr.insstr(margin.lines - 1, 0, ' ' * margin.cols) stdscr.insstr(margin.lines - 1, 0, ' ' * margin.cols)
if self._status: if self._status:

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import argparse import argparse
from typing import Optional
from typing import Sequence from typing import Sequence
from babi.highlight import Compiler from babi.highlight import Compiler
@@ -47,7 +48,7 @@ def _highlight_output(theme: Theme, compiler: Compiler, filename: str) -> int:
return 0 return 0
def main(argv: Optional[Sequence[str]] = None) -> int: def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--theme', default=xdg_config('theme.json')) parser.add_argument('--theme', default=xdg_config('theme.json'))
parser.add_argument('--grammar-dir', default=prefix_data('grammar_v1')) parser.add_argument('--grammar-dir', default=prefix_data('grammar_v1'))
@@ -66,4 +67,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
if __name__ == '__main__': if __name__ == '__main__':
exit(main()) raise SystemExit(main())

View File

@@ -1,11 +1,10 @@
from __future__ import annotations
import functools import functools
import json import json
import os.path import os.path
from typing import Any from typing import Any
from typing import Dict
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Tuple
from babi._types import Protocol from babi._types import Protocol
from babi.color import Color from babi.color import Color
@@ -13,32 +12,32 @@ from babi.fdict import FDict
class Style(NamedTuple): class Style(NamedTuple):
fg: Optional[Color] fg: Color | None
bg: Optional[Color] bg: Color | None
b: bool b: bool
i: bool i: bool
u: bool u: bool
@classmethod @classmethod
def blank(cls) -> 'Style': def blank(cls) -> Style:
return cls(fg=None, bg=None, b=False, i=False, u=False) return cls(fg=None, bg=None, b=False, i=False, u=False)
class PartialStyle(NamedTuple): class PartialStyle(NamedTuple):
fg: Optional[Color] = None fg: Color | None = None
bg: Optional[Color] = None bg: Color | None = None
b: Optional[bool] = None b: bool | None = None
i: Optional[bool] = None i: bool | None = None
u: Optional[bool] = None u: bool | None = None
def overlay_on(self, dct: Dict[str, Any]) -> None: def overlay_on(self, dct: dict[str, Any]) -> None:
for attr in self._fields: for attr in self._fields:
value = getattr(self, attr) value = getattr(self, attr)
if value is not None: if value is not None:
dct[attr] = value dct[attr] = value
@classmethod @classmethod
def from_dct(cls, dct: Dict[str, Any]) -> 'PartialStyle': def from_dct(cls, dct: dict[str, Any]) -> PartialStyle:
kv = cls()._asdict() kv = cls()._asdict()
if 'foreground' in dct: if 'foreground' in dct:
kv['fg'] = Color.parse(dct['foreground']) kv['fg'] = Color.parse(dct['foreground'])
@@ -57,7 +56,7 @@ class _TrieNode(Protocol):
@property @property
def style(self) -> PartialStyle: ... def style(self) -> PartialStyle: ...
@property @property
def children(self) -> FDict[str, '_TrieNode']: ... def children(self) -> FDict[str, _TrieNode]: ...
class TrieNode(NamedTuple): class TrieNode(NamedTuple):
@@ -65,7 +64,7 @@ class TrieNode(NamedTuple):
children: FDict[str, _TrieNode] children: FDict[str, _TrieNode]
@classmethod @classmethod
def from_dct(cls, dct: Dict[str, Any]) -> _TrieNode: def from_dct(cls, dct: dict[str, Any]) -> _TrieNode:
children = FDict({ children = FDict({
k: TrieNode.from_dct(v) for k, v in dct['children'].items() k: TrieNode.from_dct(v) for k, v in dct['children'].items()
}) })
@@ -77,7 +76,7 @@ class Theme(NamedTuple):
rules: _TrieNode rules: _TrieNode
@functools.lru_cache(maxsize=None) @functools.lru_cache(maxsize=None)
def select(self, scope: Tuple[str, ...]) -> Style: def select(self, scope: tuple[str, ...]) -> Style:
if not scope: if not scope:
return self.default return self.default
else: else:
@@ -92,7 +91,7 @@ class Theme(NamedTuple):
return Style(**style) return Style(**style)
@classmethod @classmethod
def from_dct(cls, data: Dict[str, Any]) -> 'Theme': def from_dct(cls, data: dict[str, Any]) -> Theme:
default = Style.blank()._asdict() default = Style.blank()._asdict()
for k in ('foreground', 'editor.foreground'): for k in ('foreground', 'editor.foreground'):
@@ -105,7 +104,7 @@ class Theme(NamedTuple):
default['bg'] = Color.parse(data['colors'][k]) default['bg'] = Color.parse(data['colors'][k])
break break
root: Dict[str, Any] = {'children': {}} root: dict[str, Any] = {'children': {}}
rules = data.get('tokenColors', []) + data.get('settings', []) rules = data.get('tokenColors', []) + data.get('settings', [])
for rule in rules: for rule in rules:
if 'scope' not in rule: if 'scope' not in rule:
@@ -139,11 +138,11 @@ class Theme(NamedTuple):
return cls(Style(**default), TrieNode.from_dct(root)) return cls(Style(**default), TrieNode.from_dct(root))
@classmethod @classmethod
def blank(cls) -> 'Theme': def blank(cls) -> Theme:
return cls(Style.blank(), TrieNode.from_dct({'children': {}})) return cls(Style.blank(), TrieNode.from_dct({'children': {}}))
@classmethod @classmethod
def from_filename(cls, filename: str) -> 'Theme': def from_filename(cls, filename: str) -> Theme:
if not os.path.exists(filename): if not os.path.exists(filename):
return cls.blank() return cls.blank()
else: else:

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import os.path import os.path
import sys import sys

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import argparse import argparse
import io import io
import json import json
@@ -85,4 +87,4 @@ def main() -> int:
if __name__ == '__main__': if __name__ == '__main__':
exit(main()) raise SystemExit(main())

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
name = babi name = babi
version = 0.0.21 version = 0.0.24
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
@@ -13,10 +13,10 @@ classifiers =
License :: OSI Approved :: MIT License License :: OSI Approved :: MIT License
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 :: Only
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 :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: Implementation :: PyPy
@@ -26,20 +26,20 @@ install_requires =
babi-grammars babi-grammars
identify identify
onigurumacffi>=0.0.18 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.7
[options.entry_points]
console_scripts =
babi = babi.main:main
babi-textmate-demo = babi.textmate_demo:main
[options.packages.find] [options.packages.find]
exclude = exclude =
tests* tests*
testing* testing*
[options.entry_points]
console_scripts =
babi = babi.main:main
babi-textmate-demo = babi.textmate_demo:main
[bdist_wheel] [bdist_wheel]
universal = True universal = True
@@ -53,6 +53,8 @@ disallow_any_generics = true
disallow_incomplete_defs = true disallow_incomplete_defs = true
disallow_untyped_defs = true disallow_untyped_defs = true
no_implicit_optional = true no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
[mypy-testing.*] [mypy-testing.*]
disallow_untyped_defs = false disallow_untyped_defs = false

View File

@@ -1,2 +1,4 @@
from __future__ import annotations
from setuptools import setup from setuptools import setup
setup() setup()

View File

@@ -1,9 +1,9 @@
from __future__ import annotations
import contextlib import contextlib
import curses import curses
import enum import enum
import re import re
from typing import List
from typing import Tuple
from hecate import Runner from hecate import Runner
@@ -34,7 +34,7 @@ def to_attrs(screen, width):
fg = bg = -1 fg = bg = -1
attr = 0 attr = 0
idx = 0 idx = 0
ret: List[List[Tuple[int, int, int]]] ret: list[list[tuple[int, int, int]]]
ret = [[] for _ in range(len(screen.splitlines()))] ret = [[] for _ in range(len(screen.splitlines()))]
for tp, match in tokenize_colors(screen): for tp, match in tokenize_colors(screen):

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from babi.buf import Buf from babi.buf import Buf

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from babi import color_kd from babi import color_kd
from babi.color import Color from babi.color import Color

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from babi.color import Color from babi.color import Color

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from babi.color import Color from babi.color import Color

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import json import json
import pytest import pytest

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from babi.fdict import FChainMap from babi.fdict import FChainMap

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,11 +1,10 @@
from __future__ import annotations
import contextlib import contextlib
import curses import curses
import os import os
import sys import sys
from typing import List
from typing import NamedTuple from typing import NamedTuple
from typing import Tuple
from typing import Union
from unittest import mock from unittest import mock
import pytest import pytest
@@ -148,7 +147,7 @@ class AssertScreenLineEquals(NamedTuple):
class AssertScreenAttrEquals(NamedTuple): class AssertScreenAttrEquals(NamedTuple):
n: int n: int
attr: List[Tuple[int, int, int]] attr: list[tuple[int, int, int]]
def __call__(self, screen: Screen) -> None: def __call__(self, screen: Screen) -> None:
assert screen.attrs[self.n] == self.attr assert screen.attrs[self.n] == self.attr
@@ -170,7 +169,7 @@ class Resize(NamedTuple):
class KeyPress(NamedTuple): class KeyPress(NamedTuple):
wch: Union[int, str] wch: int | str
def __call__(self, screen: Screen) -> None: def __call__(self, screen: Screen) -> None:
raise AssertionError('unreachable') raise AssertionError('unreachable')
@@ -236,7 +235,7 @@ class CursesScreen:
class Key(NamedTuple): class Key(NamedTuple):
tmux: str tmux: str
curses: bytes curses: bytes
wch: Union[int, str] wch: int | str
@property @property
def value(self) -> int: def value(self) -> int:
@@ -300,7 +299,7 @@ class DeferredRunner:
def __init__(self, command, width=80, height=24, term='screen'): def __init__(self, command, width=80, height=24, term='screen'):
self.command = command self.command = command
self._i = 0 self._i = 0
self._ops: List[Op] = [] self._ops: list[Op] = []
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 = {

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit from testing.runner import and_exit
from testing.runner import trigger_command_mode from testing.runner import trigger_command_mode

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit from testing.runner import and_exit
from testing.runner import trigger_command_mode from testing.runner import trigger_command_mode

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import curses import curses
from babi.screen import VERSION_STR from babi.screen import VERSION_STR

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit
@@ -20,6 +22,16 @@ def test_replace_invalid_regex(run):
h.await_text("invalid regex: '('") h.await_text("invalid regex: '('")
def test_replace_invalid_replacement(run, ten_lines):
with run(str(ten_lines)) as h, and_exit(h):
h.press('^\\')
h.await_text('search (to replace):')
h.press_and_enter('line_0')
h.await_text('replace with:')
h.press_and_enter('\\')
h.await_text('invalid replacement string')
def test_replace_cancel_at_replace_string(run): def test_replace_cancel_at_replace_string(run):
with run() as h, and_exit(h): with run() as h, and_exit(h):
h.press('^\\') h.press('^\\')

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from babi.screen import VERSION_STR from babi.screen import VERSION_STR
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit
@@ -162,6 +164,18 @@ def test_save_via_ctrl_o_set_filename(run, tmpdir):
assert f.read() == 'hello world\n' assert f.read() == 'hello world\n'
def test_save_via_ctrl_o_new_filename(run, tmpdir):
f = tmpdir.join('f')
f.write('wat\n')
with run(str(f)) as h, and_exit(h):
h.press('^O')
h.await_text('enter filename: ')
h.press_and_enter('new')
h.await_text('saved! (1 line written)')
assert f.read() == 'wat\n'
assert tmpdir.join('fnew').read() == 'wat\n'
@pytest.mark.parametrize('key', ('^C', 'Enter')) @pytest.mark.parametrize('key', ('^C', 'Enter'))
def test_save_via_ctrl_o_cancelled(run, key): def test_save_via_ctrl_o_cancelled(run, key):
with run() as h, and_exit(h): with run() as h, and_exit(h):

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import shlex import shlex
import sys import sys

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import shlex import shlex
import sys import sys

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import curses import curses
import json import json

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import curses import curses
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from testing.runner import and_exit from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import io import io
import pytest import pytest

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from babi.highlight import highlight_line from babi.highlight import highlight_line

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib import contextlib
import curses import curses
from unittest import mock from unittest import mock

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from babi import main from babi import main

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import onigurumacffi import onigurumacffi
import pytest import pytest

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import json import json
import pytest import pytest

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from babi.color import Color from babi.color import Color

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import os import os
from unittest import mock from unittest import mock

View File

@@ -1,5 +1,5 @@
[tox] [tox]
envlist = py36,py37,pre-commit envlist = py37,pre-commit
[testenv] [testenv]
deps = -rrequirements-dev.txt deps = -rrequirements-dev.txt