side-vim 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,212 @@
1
+ Metadata-Version: 2.4
2
+ Name: side-vim
3
+ Version: 0.1.0
4
+ Summary: Vim mode for PySide6's QPlainTextEdit
5
+ Author: sprytnyk.dev
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Dist: pyside6-essentials>=6.8
9
+ Requires-Python: >=3.12
10
+ Project-URL: Repository, https://github.com/sprytnyk/side-vim
11
+ Description-Content-Type: text/markdown
12
+
13
+ # side-vim
14
+
15
+ A Vim mode for PySide6's `QPlainTextEdit`. Attach it to an existing editor to add
16
+ Normal, Insert, Visual and Visual Line modes.
17
+
18
+ ![The demo in Visual mode, with text selected and the mode in the status bar](https://raw.githubusercontent.com/sprytnyk/side-vim/main/docs/demo.png)
19
+
20
+ ## Install
21
+
22
+ Requires Python 3.12 or newer and PySide6 6.8 or newer. Installing side-vim also
23
+ installs PySide6-Essentials.
24
+
25
+ ```sh
26
+ uv add side-vim
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ```python
32
+ import sys
33
+
34
+ from PySide6.QtWidgets import QApplication, QLabel, QPlainTextEdit, QVBoxLayout, QWidget
35
+
36
+ from side_vim import VimMode
37
+
38
+ app = QApplication(sys.argv)
39
+ window = QWidget()
40
+ editor = QPlainTextEdit("Try w, i, Esc, v and y.")
41
+ status = QLabel()
42
+ layout = QVBoxLayout(window)
43
+ layout.addWidget(editor)
44
+ layout.addWidget(status)
45
+
46
+ vim = VimMode(editor) # Starts in Normal mode. Qt deletes it with the editor.
47
+ status.setText(vim.mode.value)
48
+ vim.mode_changed.connect(lambda mode: status.setText(mode.value))
49
+
50
+ window.resize(800, 500)
51
+ window.show()
52
+ editor.setFocus()
53
+ sys.exit(app.exec())
54
+ ```
55
+
56
+ Attach one `VimMode` per editor. Import `Mode` to compare `vim.mode` with
57
+ `Mode.NORMAL`, `Mode.INSERT`, `Mode.VISUAL` or `Mode.VISUAL_LINE`.
58
+
59
+ | API | Behavior |
60
+ | --- | --- |
61
+ | `vim.mode_changed.connect(callback)` | Calls `callback(mode)` when the mode changes. |
62
+ | `vim.pending_changed.connect(callback)` | Calls `callback(text)` with pending keys such as `2d`, or `""` when done. |
63
+ | `vim.escape_hook = callback` | Calls `callback()` before handling Esc. Return `True` if the app used it, for example to close a popup. |
64
+ | `vim.search_hook = callback` | `/` calls `callback(True)`; `?` calls `callback(False)`. The app opens its search UI. |
65
+ | `vim.set_search("rent", forward=True)` | Saves the search text and jumps to the next match. `n` repeats in that direction; `N` reverses it. |
66
+ | `vim.normal()` | Goes to Normal mode as Esc does, from Insert or Visual mode. |
67
+ | `vim.clipboard = True` | Uses the system clipboard for yank, delete, change and paste. Defaults to `False`. |
68
+ | `vim.layout_map = UKRAINIAN` | Maps Ukrainian letters to Vim keys. This is the default; import `UKRAINIAN` from `side_vim`. Set `None` to disable it, or pass your own dictionary. |
69
+ | `vim.set_enabled(False)` | Restores the plain editor and its cursor. `True` enables Vim in Normal mode. Read the setting through `vim.enabled`. |
70
+ | `vim.detach()` | Removes Vim from the editor. To turn it off for a while, use `set_enabled(False)`. |
71
+
72
+ When the app accepts a search, call `set_search` with its text and direction,
73
+ after `normal()` if the search was opened from Insert mode.
74
+ Search is case-sensitive, wraps around the document, and uses plain text rather
75
+ than regular expressions. Each editor keeps its own search. An empty string
76
+ clears it. In Insert mode or while disabled, `set_search` saves the search without
77
+ moving the cursor. The [demo](https://github.com/sprytnyk/side-vim/blob/main/side_vim/__main__.py)
78
+ shows a find bar and an Esc hook together.
79
+
80
+ The unnamed register is shared between editors in the same process. With
81
+ `clipboard=False`, yank in one editor and paste in another. With `clipboard=True`,
82
+ text copied in another app pastes as whole lines if it ends in a newline.
83
+
84
+ ## Supported keys
85
+
86
+ Counts work with motions and edits: `3w`, `5G`, `2dd`, `y3j`. A counted insert
87
+ repeats the typed text: `3ix<Esc>` inserts `xxx`.
88
+
89
+ | Action | Keys |
90
+ | --- | --- |
91
+ | Move | `h j k l`, `w b e`, `W B E`, `0 ^ $`, `gg G` |
92
+ | Insert | `i a I A o O` |
93
+ | Delete, change, yank | `d c y` followed by a motion; `dd cc yy`, `x D C` |
94
+ | Paste, join | `p P`, `J` |
95
+ | Undo, redo | `u`, `Ctrl+R` |
96
+ | Select | `v` for characters, `V` for lines; motions extend the selection |
97
+ | Edit a selection | `d c y x p`; `o` switches its ends |
98
+ | Search | `/ ?` through the app hook, then `n N` |
99
+ | Scroll a line | `Ctrl+E`, `Ctrl+Y` |
100
+ | Scroll half a screen | `Ctrl+D`, `Ctrl+U` |
101
+ | Scroll a screen | `Ctrl+F`, `Ctrl+B` |
102
+ | Return to Normal | `Esc`, `Ctrl+[` |
103
+
104
+ In Normal and Visual modes, the Ctrl keys above take priority over app shortcuts.
105
+ `j` and `k` keep the screen column across tabs and wide characters, and a wrapped
106
+ line counts as one line, as in Vim.
107
+
108
+ This release has only the keys above. It has no repeat (`.`), text objects,
109
+ `f t r ~`, colon commands, macros, marks, named registers or block selection.
110
+
111
+ ## Differences from Vim
112
+
113
+ side-vim follows Vim 9.1 with its default settings, except for these:
114
+
115
+ - Joined emoji and flags, such as 👨‍👩‍👧 and 🇺🇦, are one character. Vim splits
116
+ them into their parts. Accents, CJK and single emoji work as in Vim.
117
+ - Search uses plain text, not regular expressions. There is no command line.
118
+ `/` and `?` call `search_hook`, then the app shows its own search box and
119
+ passes the text to `set_search`.
120
+ - `J` puts one space after a period, like Vim with `nojoinspaces`. Vim's default
121
+ puts two.
122
+ - Qt stops scrolling when the last line reaches the bottom of the editor. Vim
123
+ scrolls on until the last line is at the top, so `Ctrl+F` and `Ctrl+E` stop
124
+ sooner near the end. The cursor still goes to the line Vim picks.
125
+ - In Insert mode the editor handles every key except Esc and `Ctrl+[`. Arrows,
126
+ Backspace and app shortcuts work as in any Qt editor. Vim's Insert mode keys,
127
+ such as `Ctrl+W`, `Ctrl+U` and `Ctrl+O`, are not there.
128
+ - In Normal and Visual modes, Ctrl keys that side-vim doesn't use go to the
129
+ editor or the app, and so do PageUp, PageDown and the function keys. `Ctrl+A`
130
+ selects all, for example. Qt's keys that change the text do nothing in these
131
+ modes: paste, cut, `Ctrl+Backspace` and `Ctrl+Delete`. In Vim, `Ctrl+V` starts
132
+ a block selection, which side-vim doesn't have.
133
+ - Other keys without a command do nothing in Normal mode. They never type text.
134
+ - Esc in Normal mode with nothing pending goes to the app, for example to close a
135
+ dialog. Vim beeps.
136
+ - A read-only editor allows movement, scrolling, selection, yank and search, and
137
+ ignores edits. Vim only warns when you change a read-only file.
138
+ - Dragging with the mouse in Normal mode starts Visual mode.
139
+ - Outside Insert mode, Ukrainian letters work as the Latin keys in the same
140
+ place, so `ц` is `w`. Vim needs `langmap` for that. Punctuation stays as typed,
141
+ `ґ` isn't mapped because its key moves between layout variants, and `/` needs
142
+ the English layout.
143
+ - On macOS, Ctrl means the physical Control key. Command shortcuts stay with the
144
+ app.
145
+ - The tab width comes from the editor's tab stop, not from a `tabstop` setting.
146
+ - `u` and the app's undo share the editor's undo stack, so they undo the same
147
+ steps.
148
+
149
+ ## Develop
150
+
151
+ ```sh
152
+ uv sync --locked
153
+ make run # Demo window
154
+ make check # Ruff and pytest
155
+ make format # Format Python
156
+ make record # Record expected results using local Vim
157
+ make unicode # Regenerate Vim character classes and widths
158
+ make mac-keys # Key checks on a Mac
159
+ ```
160
+
161
+ Tests run Qt offscreen. CI runs for release tags and can also be started from
162
+ **Actions → CI → Run workflow**. It tests Linux, macOS and Windows on Python 3.12
163
+ and 3.14, and the lowest allowed PySide6 on Python 3.12.
164
+ The tests replay saved [Vim cases](https://github.com/sprytnyk/side-vim/tree/main/tests/cases),
165
+ so CI doesn't need Vim. Recording them needs Vim, and the scrolling cases need a
166
+ Unix terminal.
167
+
168
+ On a Mac, `make mac-keys` opens the demo and presses keys through macOS. It checks
169
+ Ctrl and Cmd keys, search, read-only mode and the Ukrainian layout, and prints PASS
170
+ or FAIL for each. Start it on the English (ABC) layout with Ukrainian-PC installed,
171
+ and don't type until it finishes.
172
+
173
+ ## Release
174
+
175
+ Set these up by hand before the first tag:
176
+
177
+ 1. In the GitHub repository, open **Settings**, then **Environments**, and create
178
+ `pypi`. Add yourself as a required reviewer. Leave **Prevent self-review** off,
179
+ so you can approve runs you start. Limit deployment tags to `v*`.
180
+ 2. In your PyPI account's **Publishing** page, add a pending trusted publisher:
181
+
182
+ | Field | Value |
183
+ | --- | --- |
184
+ | PyPI project | `side-vim` |
185
+ | GitHub owner | `sprytnyk` |
186
+ | Repository | `side-vim` |
187
+ | Workflow | `release.yml` |
188
+ | Environment | `pypi` |
189
+
190
+ A pending publisher creates the project on its first upload. No PyPI token
191
+ goes into GitHub. See the [PyPI setup guide](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/)
192
+ and [GitHub approval guide](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/review-deployments).
193
+
194
+ For each release:
195
+
196
+ 1. On `main`, check the README and run `make check`. If key handling changed,
197
+ run `make mac-keys` on a Mac.
198
+ 2. Run `uv version X.Y.Z`. Write the dated `CHANGELOG.md` section from the commits
199
+ since the previous release, using only sections with entries: Breaking,
200
+ Highlights, Fixes, Changes and Removed. Review it before committing.
201
+ 3. Build and test the wheel in a temporary environment. Make the signed
202
+ `chore(release): vX.Y.Z` commit and signed `vX.Y.Z` tag with key
203
+ `0xC00C0F0D647BD3C6`. Push `main` and that tag to `origin`.
204
+ 4. The release workflow runs CI, checks that the tag matches the package version,
205
+ and builds the wheel and source distribution. Review the run and approve the
206
+ `pypi` job. It publishes those files using
207
+ [uv trusted publishing](https://docs.astral.sh/uv/guides/integration/github/#publishing-to-pypi).
208
+ 5. Confirm the version is on PyPI, then create a GitHub release using just the
209
+ changelog section as its notes. Attach no files.
210
+
211
+ The project [release skill](https://github.com/sprytnyk/side-vim/blob/main/.claude/skills/release/SKILL.md)
212
+ has the command sequence and approval points.
@@ -0,0 +1,20 @@
1
+ side_vim/__init__.py,sha256=6gWh57DZlBg6EPLZ4WMvBCx1m3HR1iNVw0EZ1K2uaHQ,195
2
+ side_vim/__main__.py,sha256=Ig6fb0AQW3RzIhuj-2TaGZlJhwnSqO0uA3ZKMQ7r6ug,5103
3
+ side_vim/_unicode_data.py,sha256=d8nQS-3YrKVj1nagX15AYVD6xYOKA1fpWgTb6mjtZCE,13359
4
+ side_vim/caret.py,sha256=WmMN9CZNdKqC0R-MNkaz7QgL9bFJ4zp0bmdM1xI_M-U,5081
5
+ side_vim/edits.py,sha256=r9m57uAZgwOYft3i4YW1_tyBtQmY4R3jgwbImryAMSw,3264
6
+ side_vim/keys.py,sha256=PnlskG8PB7IgoYusNV96ANdY7cYCQg4O6bn1qfN2qIw,4528
7
+ side_vim/layouts.py,sha256=CRnCFJRdfRSnPIVksPPCHkLP7euoDoLGbDX30R_IwKk,681
8
+ side_vim/mode.py,sha256=z58TQG14X8jNNdQ71nI3DpYv3BAanDBD4ltnf68Guxs,162
9
+ side_vim/motions.py,sha256=T1eAivp9zGAciCWE_OH-FM4p-FNCoNa1wGe2iGBH2io,15486
10
+ side_vim/notation.py,sha256=h7KxOQtTRSCNHg8tRIjUcc6YFV0cUjbiN7AVlF5-q0g,233
11
+ side_vim/parser.py,sha256=8874c884Deczw1Q_iIQTdtBJ5kNLzHFkzRztZd2HEZo,5706
12
+ side_vim/scroll.py,sha256=eFN5q8pY1uhGvwlxJ-ZUDEv4_ispAWZxRRZCRpzwuBs,3580
13
+ side_vim/search.py,sha256=6JTILcpPZCnTpYMb4JNAjnTV9-2fyuFTg8fG6RWMwb8,4064
14
+ side_vim/text.py,sha256=_zQEq9iDrTykh3UF7PDjsTNYNW6SuTcyvSuxCPEQOmI,5687
15
+ side_vim/unicode.py,sha256=IBgjmvTUfDEKoWt9_3hdhapHE84xc2K03vfTwRkT7ac,1229
16
+ side_vim/vim.py,sha256=px4pN-FBgRqozszHwqfocd0oN2qf5N4BnC2nTG2aP6Q,59102
17
+ side_vim-0.1.0.dist-info/licenses/LICENSE,sha256=7lr8JM-qhrsBax3u9brlqAkVX3KqOvgfPhB17xbG5o8,1069
18
+ side_vim-0.1.0.dist-info/WHEEL,sha256=3OmCxFA0ffBIsX4x0o8CdfSvZAOvO3mzEG8U_z5TvV4,80
19
+ side_vim-0.1.0.dist-info/METADATA,sha256=o2bCqOwo6ylRBVg4F4jpdo8eZFwIae9nPmyTh7TzpYo,9972
20
+ side_vim-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.13.8
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sprytnyk.dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.