vimniscient 0.2.2__tar.gz → 0.3.1__tar.gz
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.
- {vimniscient-0.2.2 → vimniscient-0.3.1}/PKG-INFO +23 -7
- {vimniscient-0.2.2 → vimniscient-0.3.1}/README.md +22 -6
- {vimniscient-0.2.2 → vimniscient-0.3.1}/pyproject.toml +1 -1
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/app.py +68 -19
- vimniscient-0.3.1/src/vimniscient/card_session.py +81 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/cli.py +24 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/diagnose.py +11 -0
- vimniscient-0.3.1/src/vimniscient/doctor.py +70 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/endless.py +6 -0
- vimniscient-0.3.1/src/vimniscient/eval.yaml +136 -0
- vimniscient-0.3.1/src/vimniscient/evaluation.py +58 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/generate.py +90 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d2-pairs.yaml +6 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d3-sentences.yaml +51 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d4-find.yaml +31 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d5-lines.yaml +23 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d6-counts.yaml +40 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d7-review.yaml +29 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d1-dot.yaml +22 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d2-search.yaml +21 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d3-cgn.yaml +36 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d4-case-numbers.yaml +38 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d5-insert.yaml +42 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d6-visual.yaml +16 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d7-review.yaml +37 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d1-macros.yaml +39 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d2-macros-more.yaml +35 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d3-registers.yaml +49 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d4-marks.yaml +35 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d5-visual-block.yaml +28 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d6-substitute.yaml +49 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d7-review.yaml +45 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d1-ranges.yaml +16 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d2-sort-filter.yaml +38 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d3-regex.yaml +7 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d4-norm.yaml +17 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d5-motions.yaml +26 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d6-indent.yaml +29 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d7-review.yaml +39 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d1-arguments.yaml +27 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d2-any-quote-bracket.yaml +26 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d3-functions.yaml +40 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d4-small-objects.yaml +41 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d5-comments.yaml +30 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons.py +5 -0
- vimniscient-0.3.1/src/vimniscient/missions.py +47 -0
- vimniscient-0.3.1/src/vimniscient/missions.yaml +121 -0
- vimniscient-0.3.1/src/vimniscient/nvim/mission.lua +84 -0
- vimniscient-0.3.1/src/vimniscient/placement.py +72 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/progress.py +18 -10
- vimniscient-0.3.1/src/vimniscient/quick.py +53 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/session.py +27 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/today.py +7 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/transfer.py +9 -1
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/ui/builtin_lessons.py +15 -8
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/ui/cards.py +24 -59
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/ui/lesson.py +2 -7
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/ui/main.py +5 -5
- vimniscient-0.3.1/src/vimniscient/ui/quick.py +56 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/check_answers.py +8 -7
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/check_app.py +11 -4
- vimniscient-0.3.1/tests/check_missions.py +49 -0
- vimniscient-0.3.1/tests/check_placement.py +78 -0
- vimniscient-0.3.1/tests/check_quick.py +47 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/check_transfer.py +1 -1
- vimniscient-0.3.1/tests/golden/answers.json +329 -0
- vimniscient-0.3.1/tests/golden/cards.json +2417 -0
- vimniscient-0.3.1/tests/golden/diagnose.json +108 -0
- vimniscient-0.3.1/tests/golden/keys.json +2221 -0
- vimniscient-0.3.1/tests/golden/lessons.json +12762 -0
- vimniscient-0.3.1/tests/golden/placement.json +232 -0
- vimniscient-0.3.1/tests/golden/schema.sql +57 -0
- vimniscient-0.3.1/tests/golden/srs.json +231 -0
- vimniscient-0.3.1/tests/golden/today.json +564 -0
- vimniscient-0.3.1/tests/live_mission.py +45 -0
- vimniscient-0.3.1/tests/make_golden.py +145 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/.gitignore +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/LICENSE +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/extras/nvim/vimniscient.lua +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/__init__.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/__main__.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/answer.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/cards/foot.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/cards/lazyvim.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/cards/shell.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/cards/vim.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/cards/zathura.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/cards.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/drills/omarchy.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/drills.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/habits.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/keys.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/sources.md +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d1-words.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d6-review.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w6d1-code.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w6d2-notes.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/lessons/w6d3-data.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/mnemonics.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/mnemonics.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/nvim/keymaps.lua +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/nvim/runner.lua +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/reminders.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/settings.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/srs.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/transfer.yaml +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/ui/__init__.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/ui/common.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/ui/drills.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/ui/stats.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/src/vimniscient/vimgolf.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/check_diagnose.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/check_drills.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/check_generate.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/check_lessons.lua +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/check_lessons.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/check_timing.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/check_today.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/live_drills.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/live_endless.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/live_lessons.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/live_retry.py +0 -0
- {vimniscient-0.2.2 → vimniscient-0.3.1}/tests/live_review.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vimniscient
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Get fluent in Vim in fifteen minutes a day, in your own Neovim.
|
|
5
5
|
Project-URL: Homepage, https://vimniscient.com
|
|
6
6
|
Project-URL: Source, https://github.com/dialectmaterial/Vimniscient
|
|
@@ -85,10 +85,8 @@ circles split by a line; `q` records a macro, a quill writing down what you
|
|
|
85
85
|
do. Some come with a bit of history: `:t` copies lines because the old `ed`
|
|
86
86
|
editor called a copy a *transfer*, and `grep` is named after `ed`'s `g/re/p`.
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Vimniscient itself is built on. Every one has a source in
|
|
91
|
-
[`lessons/sources.md`](https://github.com/dialectmaterial/Vimniscient/blob/main/src/vimniscient/lessons/sources.md).
|
|
88
|
+
Week 6 and a few code tasks are the exception: they're realistic on purpose,
|
|
89
|
+
so that you practise on the kind of text you'll really edit.
|
|
92
90
|
|
|
93
91
|
After you answer a card, and at the end of a lesson for the tasks you missed,
|
|
94
92
|
Vimniscient shows what each part of the keys stands for:
|
|
@@ -187,9 +185,22 @@ lesson, and your progress with a keystroke-efficiency graph.
|
|
|
187
185
|
| `Tab`, `j`/`k` | Move between panes and through lists |
|
|
188
186
|
| `e` | Endless mode |
|
|
189
187
|
| `c` / `g` / `p` | Cards / VimGolf / placement test |
|
|
188
|
+
| `z` | Quick practice: 1, 2 or 5 minutes on one skill, navigation drills, or a multi-file mission |
|
|
189
|
+
| `x` | Choose: edits with no hints, where you pick the strategy |
|
|
190
190
|
| `s` | Stats |
|
|
191
191
|
| `q` | Quit |
|
|
192
192
|
|
|
193
|
+
## Is it working?
|
|
194
|
+
|
|
195
|
+
`vimniscient eval pre` is a short test on texts from no lesson. Take it before
|
|
196
|
+
you've trained much, then `vimniscient eval post` after some weeks and
|
|
197
|
+
`vimniscient eval delayed` a few weeks after that. `vimniscient eval` compares
|
|
198
|
+
them: solved, median time, and keys against the reference. Results stay on your
|
|
199
|
+
machine.
|
|
200
|
+
|
|
201
|
+
`vimniscient doctor` checks that Neovim and your config work with the trainer.
|
|
202
|
+
`vimniscient placement reset` undoes a placement test.
|
|
203
|
+
|
|
193
204
|
## It adapts to your setup
|
|
194
205
|
|
|
195
206
|
| You have | You get |
|
|
@@ -288,8 +299,8 @@ key stands for, so that the edit leaves it true: `ciw is change inner word:
|
|
|
288
299
|
it swaps the wh▮ile word` (fix it with `ciw`). What each key officially means,
|
|
289
300
|
and the hook to use where it means nothing, is in
|
|
290
301
|
[`mnemonics.yaml`](https://github.com/dialectmaterial/Vimniscient/blob/main/src/vimniscient/mnemonics.yaml). The app reads the same
|
|
291
|
-
file to say what a key stands for after you answer.
|
|
292
|
-
|
|
302
|
+
file to say what a key stands for after you answer. Every task needs at least
|
|
303
|
+
two texts. Memory science belongs in lesson intros, with a source in
|
|
293
304
|
[`lessons/sources.md`](https://github.com/dialectmaterial/Vimniscient/blob/main/src/vimniscient/lessons/sources.md). Code tasks stay
|
|
294
305
|
code, with the meaning in names and strings.
|
|
295
306
|
|
|
@@ -303,6 +314,11 @@ uv run python tests/check_timing.py # task times: old progress datab
|
|
|
303
314
|
uv run python tests/check_generate.py # generated texts: reproducible, and they solve in Neovim
|
|
304
315
|
uv run python tests/check_diagnose.py # strategy feedback: no reference solution sets it off
|
|
305
316
|
uv run python tests/check_transfer.py # Choose: every way solves it in Neovim, nothing graded
|
|
317
|
+
uv run python tests/check_quick.py # quick practice: the right tasks for the time, nothing graded
|
|
318
|
+
uv run python tests/check_missions.py # missions: every way does the whole job on real files
|
|
319
|
+
uv run python tests/check_placement.py # placement, the evaluation and doctor
|
|
320
|
+
uv run python tests/make_golden.py --check # the reference files for the Rust port are up to date
|
|
321
|
+
uv run python tests/live_mission.py # a mission in your real Neovim, in a hidden tmux session
|
|
306
322
|
uv run python tests/check_today.py # the daily budget, SRS scheduling, card introductions, hardtime
|
|
307
323
|
uv run python tests/check_app.py # every screen, driven headless
|
|
308
324
|
uv run python tests/live_lessons.py --all # types every solution into the real runner (needs tmux)
|
|
@@ -62,10 +62,8 @@ circles split by a line; `q` records a macro, a quill writing down what you
|
|
|
62
62
|
do. Some come with a bit of history: `:t` copies lines because the old `ed`
|
|
63
63
|
editor called a copy a *transfer*, and `grep` is named after `ed`'s `g/re/p`.
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Vimniscient itself is built on. Every one has a source in
|
|
68
|
-
[`lessons/sources.md`](https://github.com/dialectmaterial/Vimniscient/blob/main/src/vimniscient/lessons/sources.md).
|
|
65
|
+
Week 6 and a few code tasks are the exception: they're realistic on purpose,
|
|
66
|
+
so that you practise on the kind of text you'll really edit.
|
|
69
67
|
|
|
70
68
|
After you answer a card, and at the end of a lesson for the tasks you missed,
|
|
71
69
|
Vimniscient shows what each part of the keys stands for:
|
|
@@ -164,9 +162,22 @@ lesson, and your progress with a keystroke-efficiency graph.
|
|
|
164
162
|
| `Tab`, `j`/`k` | Move between panes and through lists |
|
|
165
163
|
| `e` | Endless mode |
|
|
166
164
|
| `c` / `g` / `p` | Cards / VimGolf / placement test |
|
|
165
|
+
| `z` | Quick practice: 1, 2 or 5 minutes on one skill, navigation drills, or a multi-file mission |
|
|
166
|
+
| `x` | Choose: edits with no hints, where you pick the strategy |
|
|
167
167
|
| `s` | Stats |
|
|
168
168
|
| `q` | Quit |
|
|
169
169
|
|
|
170
|
+
## Is it working?
|
|
171
|
+
|
|
172
|
+
`vimniscient eval pre` is a short test on texts from no lesson. Take it before
|
|
173
|
+
you've trained much, then `vimniscient eval post` after some weeks and
|
|
174
|
+
`vimniscient eval delayed` a few weeks after that. `vimniscient eval` compares
|
|
175
|
+
them: solved, median time, and keys against the reference. Results stay on your
|
|
176
|
+
machine.
|
|
177
|
+
|
|
178
|
+
`vimniscient doctor` checks that Neovim and your config work with the trainer.
|
|
179
|
+
`vimniscient placement reset` undoes a placement test.
|
|
180
|
+
|
|
170
181
|
## It adapts to your setup
|
|
171
182
|
|
|
172
183
|
| You have | You get |
|
|
@@ -265,8 +276,8 @@ key stands for, so that the edit leaves it true: `ciw is change inner word:
|
|
|
265
276
|
it swaps the wh▮ile word` (fix it with `ciw`). What each key officially means,
|
|
266
277
|
and the hook to use where it means nothing, is in
|
|
267
278
|
[`mnemonics.yaml`](https://github.com/dialectmaterial/Vimniscient/blob/main/src/vimniscient/mnemonics.yaml). The app reads the same
|
|
268
|
-
file to say what a key stands for after you answer.
|
|
269
|
-
|
|
279
|
+
file to say what a key stands for after you answer. Every task needs at least
|
|
280
|
+
two texts. Memory science belongs in lesson intros, with a source in
|
|
270
281
|
[`lessons/sources.md`](https://github.com/dialectmaterial/Vimniscient/blob/main/src/vimniscient/lessons/sources.md). Code tasks stay
|
|
271
282
|
code, with the meaning in names and strings.
|
|
272
283
|
|
|
@@ -280,6 +291,11 @@ uv run python tests/check_timing.py # task times: old progress datab
|
|
|
280
291
|
uv run python tests/check_generate.py # generated texts: reproducible, and they solve in Neovim
|
|
281
292
|
uv run python tests/check_diagnose.py # strategy feedback: no reference solution sets it off
|
|
282
293
|
uv run python tests/check_transfer.py # Choose: every way solves it in Neovim, nothing graded
|
|
294
|
+
uv run python tests/check_quick.py # quick practice: the right tasks for the time, nothing graded
|
|
295
|
+
uv run python tests/check_missions.py # missions: every way does the whole job on real files
|
|
296
|
+
uv run python tests/check_placement.py # placement, the evaluation and doctor
|
|
297
|
+
uv run python tests/make_golden.py --check # the reference files for the Rust port are up to date
|
|
298
|
+
uv run python tests/live_mission.py # a mission in your real Neovim, in a hidden tmux session
|
|
283
299
|
uv run python tests/check_today.py # the daily budget, SRS scheduling, card introductions, hardtime
|
|
284
300
|
uv run python tests/check_app.py # every screen, driven headless
|
|
285
301
|
uv run python tests/live_lessons.py --all # types every solution into the real runner (needs tmux)
|
|
@@ -8,22 +8,24 @@ Omarchy theme in use (Catppuccin Mocha's own palette by default).
|
|
|
8
8
|
|
|
9
9
|
from collections.abc import Callable
|
|
10
10
|
from dataclasses import replace
|
|
11
|
+
from datetime import datetime
|
|
11
12
|
|
|
12
13
|
from textual.app import App
|
|
13
14
|
from textual.color import Color
|
|
14
15
|
from textual.content import Content
|
|
15
16
|
|
|
16
|
-
from vimniscient import drills, endless, reminders, today, transfer, vimgolf
|
|
17
|
+
from vimniscient import drills, endless, missions, placement, quick, reminders, today, transfer, vimgolf
|
|
17
18
|
from vimniscient.cards import has_lazyvim, load_decks
|
|
18
19
|
from vimniscient.lessons import Lesson, load_lessons, two_rounds
|
|
19
20
|
from vimniscient.progress import Progress
|
|
20
|
-
from vimniscient.session import COLORS, TaskResult, mouse_wheel, run_endless, run_lesson
|
|
21
|
-
from vimniscient.ui.builtin_lessons import endless_lesson, placement_lesson, review_lesson, transfer_lesson, vimgolf_lesson
|
|
21
|
+
from vimniscient.session import COLORS, TaskResult, mouse_wheel, run_endless, run_lesson, run_mission
|
|
22
|
+
from vimniscient.ui.builtin_lessons import endless_lesson, mission_lesson, placement_lesson, quick_lesson, review_lesson, transfer_lesson, vimgolf_lesson
|
|
22
23
|
from vimniscient.ui.cards import CardScreen
|
|
23
|
-
from vimniscient.ui.common import CATPPUCCIN, DEFAULT_THEME, omarchy_theme
|
|
24
|
+
from vimniscient.ui.common import CATPPUCCIN, DEFAULT_THEME, omarchy_theme
|
|
24
25
|
from vimniscient.ui.drills import DrillScreen
|
|
25
26
|
from vimniscient.ui.lesson import LessonScreen, ResultsScreen
|
|
26
27
|
from vimniscient.ui.main import MainScreen
|
|
28
|
+
from vimniscient.ui.quick import QuickScreen
|
|
27
29
|
from vimniscient.ui.stats import StatsScreen
|
|
28
30
|
|
|
29
31
|
|
|
@@ -45,7 +47,7 @@ class VimniscientApp(App):
|
|
|
45
47
|
|
|
46
48
|
#main { height: 1fr; }
|
|
47
49
|
#left { width: 46; max-width: 45%; }
|
|
48
|
-
#plan { height:
|
|
50
|
+
#plan { height: 10; }
|
|
49
51
|
#lesson-list { height: 1fr; }
|
|
50
52
|
#right { width: 1fr; }
|
|
51
53
|
#detail-pane { height: 1fr; }
|
|
@@ -189,6 +191,8 @@ class VimniscientApp(App):
|
|
|
189
191
|
elif step_id == "vimgolf":
|
|
190
192
|
if not progress.vimgolf_done_today():
|
|
191
193
|
return self.start_vimgolf
|
|
194
|
+
elif step_id == "quick":
|
|
195
|
+
return self.start_quick
|
|
192
196
|
elif step_id == "transfer":
|
|
193
197
|
picks = transfer.pick(self.lessons, progress)
|
|
194
198
|
if picks:
|
|
@@ -245,28 +249,76 @@ class VimniscientApp(App):
|
|
|
245
249
|
self.notify("No cards due. Come back tomorrow.")
|
|
246
250
|
|
|
247
251
|
def action_placement(self) -> None:
|
|
252
|
+
search = placement.Search(self.lessons)
|
|
253
|
+
self.push_screen(LessonScreen(placement_lesson(search.round()), start=lambda done: self.placement_round(search, done)))
|
|
254
|
+
|
|
255
|
+
def placement_round(self, search: placement.Search, done: Callable[[], None]) -> None:
|
|
256
|
+
tasks = search.round()
|
|
257
|
+
|
|
248
258
|
def on_results(results: list[TaskResult]) -> str:
|
|
249
259
|
self.progress.record(results)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
260
|
+
search.update(results)
|
|
261
|
+
search.seen |= {r.task.item_id for r in results}
|
|
262
|
+
if not search.finished:
|
|
263
|
+
return placement.NEXT_ROUND
|
|
264
|
+
known = search.known()
|
|
265
|
+
self.progress.mark_known(known, search.seen)
|
|
266
|
+
return placement.note(known)
|
|
254
267
|
|
|
255
|
-
|
|
268
|
+
def after() -> None:
|
|
269
|
+
self.placement_round(search, done) if not search.finished else done()
|
|
270
|
+
|
|
271
|
+
self.start_lesson(placement_lesson(tasks), on_results, after)
|
|
256
272
|
|
|
257
273
|
def action_endless(self) -> None:
|
|
258
274
|
self.run_step("endless", lambda: None)
|
|
259
275
|
|
|
276
|
+
def action_quick(self) -> None:
|
|
277
|
+
self.run_step("quick", lambda: None)
|
|
278
|
+
|
|
279
|
+
def start_quick(self, done: Callable[[], None]) -> None:
|
|
280
|
+
finished = {lesson.id for lesson in self.lessons if self.progress.is_done(lesson)}
|
|
281
|
+
pools = quick.pools(self.lessons, finished)
|
|
282
|
+
mission = missions.pick(self.lessons, self.progress)
|
|
283
|
+
|
|
284
|
+
def chosen(choice: tuple[str, int] | None) -> None:
|
|
285
|
+
if choice is None:
|
|
286
|
+
done()
|
|
287
|
+
return
|
|
288
|
+
skill, minutes = choice
|
|
289
|
+
if skill == "mission":
|
|
290
|
+
self.start_mission(mission, done)
|
|
291
|
+
return
|
|
292
|
+
count = quick.how_many(minutes, self.progress.typical_task_seconds())
|
|
293
|
+
tasks = quick.build(skill, count, pools, self.lessons[0].tasks[0], datetime.now().isoformat())
|
|
294
|
+
|
|
295
|
+
def on_results(results: list[TaskResult]) -> str:
|
|
296
|
+
self.progress.record_ungraded(results, "quick")
|
|
297
|
+
return ""
|
|
298
|
+
|
|
299
|
+
self.start_lesson(quick_lesson(quick.title(skill), tasks), on_results, done)
|
|
300
|
+
|
|
301
|
+
self.push_screen(QuickScreen({skill: len(tasks) for skill, tasks in pools.items()}, mission.title if mission else ""), chosen)
|
|
302
|
+
|
|
303
|
+
def start_mission(self, mission: missions.Mission, done: Callable[[], None]) -> None:
|
|
304
|
+
with self.suspend():
|
|
305
|
+
result = run_mission(mission)
|
|
306
|
+
mouse_wheel(False)
|
|
307
|
+
self.progress.record_ungraded([result], "mission")
|
|
308
|
+
note = Content(mission.note(result.status == "solved"))
|
|
309
|
+
|
|
310
|
+
def after(again: bool) -> None:
|
|
311
|
+
self.start_mission(mission, done) if again else done()
|
|
312
|
+
|
|
313
|
+
self.push_screen(ResultsScreen(mission_lesson(mission), [result], note), after)
|
|
314
|
+
|
|
260
315
|
def action_transfer(self) -> None:
|
|
261
316
|
self.run_step("transfer", lambda: None)
|
|
262
317
|
|
|
263
318
|
def start_transfer(self, picks: list[transfer.Challenge], done: Callable[[], None]) -> None:
|
|
264
319
|
def on_results(results: list[TaskResult]) -> Content:
|
|
265
|
-
self.progress.
|
|
266
|
-
|
|
267
|
-
lines = [f"{r.task.goal} {' or '.join(ways)}" for r in results
|
|
268
|
-
if (ways := transfer.another_way(by_goal[r.task.goal], r.keys, r.status == "solved"))]
|
|
269
|
-
return Content("Another way\n" + "\n".join(lines)) if lines else Content("")
|
|
320
|
+
self.progress.record_ungraded(results, "transfer")
|
|
321
|
+
return Content(transfer.note(picks, results))
|
|
270
322
|
|
|
271
323
|
self.start_lesson(transfer_lesson([c.task for c in picks]), on_results, done)
|
|
272
324
|
|
|
@@ -280,10 +332,7 @@ class VimniscientApp(App):
|
|
|
280
332
|
mouse_wheel(False)
|
|
281
333
|
run = endless.summary(results, start)
|
|
282
334
|
self.progress.record_endless(results, run)
|
|
283
|
-
note = (
|
|
284
|
-
f"{run['fixed']} recalled afterwards")
|
|
285
|
-
if run["level"] > best and run["climbed"]:
|
|
286
|
-
note += " · a new best!"
|
|
335
|
+
note = endless.note(run, best)
|
|
287
336
|
|
|
288
337
|
def after(again: bool) -> None:
|
|
289
338
|
if again:
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""One run through a queue of cards: what's asked next and how each answer grades.
|
|
2
|
+
|
|
3
|
+
A card you've never answered from memory is introduced: its answer is shown and
|
|
4
|
+
you type it once. A miss comes back at the end, but only the first try counts.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
|
|
9
|
+
from vimniscient import srs
|
|
10
|
+
from vimniscient.answer import is_correct
|
|
11
|
+
from vimniscient.cards import Card
|
|
12
|
+
from vimniscient.progress import Progress
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class CardSession:
|
|
17
|
+
progress: Progress
|
|
18
|
+
queue: list[Card]
|
|
19
|
+
total: int = 0
|
|
20
|
+
retried: set[str] = field(default_factory=set)
|
|
21
|
+
pending: tuple[Card, bool, str] | None = None
|
|
22
|
+
intro: bool = False # the card on screen is new to you
|
|
23
|
+
correct: int = 0
|
|
24
|
+
answered: int = 0
|
|
25
|
+
|
|
26
|
+
def __post_init__(self) -> None:
|
|
27
|
+
self.queue = list(self.queue)
|
|
28
|
+
self.total = len(self.queue)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def done(self) -> int:
|
|
32
|
+
return self.total - len([c for c in self.queue if c.id not in self.retried])
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def card(self) -> Card | None:
|
|
36
|
+
return self.queue[0] if self.queue else None
|
|
37
|
+
|
|
38
|
+
def begin(self) -> Card | None:
|
|
39
|
+
"""The next card, and whether to introduce it."""
|
|
40
|
+
card = self.card
|
|
41
|
+
if card:
|
|
42
|
+
item = self.progress.item(card.id)
|
|
43
|
+
# never answered from memory, and not introduced since the last miss
|
|
44
|
+
self.intro = card.id not in self.retried and not self.progress.ever_right(card.id) and (not item or item.reps == 0)
|
|
45
|
+
return card
|
|
46
|
+
|
|
47
|
+
def submit(self, answer: str) -> bool:
|
|
48
|
+
"""False when an introduced card was copied wrong, so it's asked again."""
|
|
49
|
+
card = self.queue[0]
|
|
50
|
+
correct = is_correct(answer, card.answers, card.style, card.case_sensitive)
|
|
51
|
+
if self.intro and not correct:
|
|
52
|
+
return False
|
|
53
|
+
self.pending = (card, correct, answer)
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
def override(self) -> None:
|
|
57
|
+
""""I was right": a miss the checker was too strict about."""
|
|
58
|
+
if self.pending and not self.pending[1]:
|
|
59
|
+
self.pending = (self.pending[0], True, self.pending[2])
|
|
60
|
+
|
|
61
|
+
def settle(self) -> None:
|
|
62
|
+
"""Grade the answered card (only the first try of a session counts)."""
|
|
63
|
+
if not self.pending:
|
|
64
|
+
return
|
|
65
|
+
card, correct, answer = self.pending
|
|
66
|
+
self.pending = None
|
|
67
|
+
self.queue.pop(0)
|
|
68
|
+
if self.intro: # typed with the answer shown: scheduled for tomorrow, not scored
|
|
69
|
+
self.progress.grade(card.id, "card", srs.GOOD)
|
|
70
|
+
self.progress.log_card(card, True, answer, intro=True) # still practice, for the streak
|
|
71
|
+
return
|
|
72
|
+
if card.id in self.retried:
|
|
73
|
+
return
|
|
74
|
+
self.answered += 1
|
|
75
|
+
self.correct += correct
|
|
76
|
+
self.progress.log_card(card, correct, answer)
|
|
77
|
+
known = self.progress.item(card.id) is not None
|
|
78
|
+
self.progress.grade(card.id, "card", (srs.GOOD if known else srs.EASY) if correct else srs.AGAIN)
|
|
79
|
+
if not correct:
|
|
80
|
+
self.retried.add(card.id)
|
|
81
|
+
self.queue.append(card)
|
|
@@ -18,6 +18,11 @@ def main() -> None:
|
|
|
18
18
|
remind.add_argument("--check", action="store_true", help="a follow-up reminder firing")
|
|
19
19
|
budget = sub.add_parser("minutes", help="how long today's routine is meant to take (15 by default)")
|
|
20
20
|
budget.add_argument("minutes", type=int, nargs="?", help="set it; leave it out to see it")
|
|
21
|
+
place = sub.add_parser("placement", help="undo the placement test")
|
|
22
|
+
place.add_argument("action", choices=["reset"])
|
|
23
|
+
sub.add_parser("doctor", help="check that Neovim and your setup work with the trainer")
|
|
24
|
+
evaluation = sub.add_parser("eval", help="measure your progress: a test now, one later, compared")
|
|
25
|
+
evaluation.add_argument("stage", choices=["pre", "post", "delayed", "report"], nargs="?", default="report")
|
|
21
26
|
setup = sub.add_parser("reminders", help="daily practice reminders through Omarchy")
|
|
22
27
|
setup.add_argument("action", choices=["install", "uninstall", "status"], nargs="?", default="status")
|
|
23
28
|
args = parser.parse_args()
|
|
@@ -46,6 +51,14 @@ def main() -> None:
|
|
|
46
51
|
settings.set_minutes(args.minutes)
|
|
47
52
|
print(f"Today's routine takes about {settings.minutes()} minutes. ({settings.CONFIG})")
|
|
48
53
|
return
|
|
54
|
+
if args.command == "doctor":
|
|
55
|
+
from vimniscient import doctor
|
|
56
|
+
|
|
57
|
+
ok = True
|
|
58
|
+
for check in doctor.checks():
|
|
59
|
+
print(f"{check.mark} {check.name}: {check.detail}")
|
|
60
|
+
ok = ok and (check.passed or not check.required)
|
|
61
|
+
raise SystemExit(0 if ok else 1)
|
|
49
62
|
if args.command == "reminders":
|
|
50
63
|
from vimniscient import reminders
|
|
51
64
|
|
|
@@ -69,6 +82,17 @@ def main() -> None:
|
|
|
69
82
|
from vimniscient.progress import Progress
|
|
70
83
|
from vimniscient.session import run_endless, run_lesson
|
|
71
84
|
|
|
85
|
+
if args.command == "placement":
|
|
86
|
+
print(f"Placement undone: {Progress().reset_placement()} lessons are back on the list.")
|
|
87
|
+
return
|
|
88
|
+
if args.command == "eval":
|
|
89
|
+
from vimniscient import evaluation as evaluate
|
|
90
|
+
|
|
91
|
+
progress = Progress()
|
|
92
|
+
if args.stage != "report":
|
|
93
|
+
progress.record_ungraded(run_lesson(evaluate.lesson(args.stage)), f"eval-{args.stage}")
|
|
94
|
+
print(evaluate.report(progress))
|
|
95
|
+
return
|
|
72
96
|
if args.command == "endless":
|
|
73
97
|
from vimniscient import endless
|
|
74
98
|
from vimniscient.cards import has_lazyvim
|
|
@@ -84,6 +84,17 @@ def normal_keys(keys: str) -> list[list[str]]:
|
|
|
84
84
|
return [*runs, run] if run else runs
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
def results_summary(results: list) -> str:
|
|
88
|
+
"""One line on how a run went: solved, beat the reference, the median time."""
|
|
89
|
+
recall = [r for r in results if r.task.phase != "learn"]
|
|
90
|
+
solved = [r for r in recall if r.status == "solved"]
|
|
91
|
+
beat = sum(r.count < r.task.ref for r in solved)
|
|
92
|
+
times = sorted(r.seconds for r in solved if r.seconds is not None)
|
|
93
|
+
verb = "recalled" if len(recall) < len(results) else "solved"
|
|
94
|
+
return (f"{len(solved)} of {len(recall)} {verb}" + (f" · {beat} beat the reference" if beat else "")
|
|
95
|
+
+ (f" · {times[len(times) // 2]:.1f}s a task" if times else ""))
|
|
96
|
+
|
|
97
|
+
|
|
87
98
|
def _longest_run(runs: list[list[str]], wanted: set[str]) -> tuple[str, int]:
|
|
88
99
|
best, length = "", 0
|
|
89
100
|
for tokens in runs:
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""`vimniscient doctor`: does this machine work with the trainer? Required
|
|
2
|
+
checks stop it working; the rest say what you'd be missing."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import sqlite3
|
|
8
|
+
import subprocess
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from vimniscient.cards import has_lazyvim, has_plugin
|
|
12
|
+
|
|
13
|
+
MIN_NVIM = (0, 10)
|
|
14
|
+
MIN_SIZE = (80, 24)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Check:
|
|
19
|
+
name: str
|
|
20
|
+
passed: bool
|
|
21
|
+
detail: str
|
|
22
|
+
required: bool = True
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def mark(self) -> str:
|
|
26
|
+
return "✓" if self.passed else "✗" if self.required else "·"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _nvim() -> list[Check]:
|
|
30
|
+
if not shutil.which("nvim"):
|
|
31
|
+
return [Check("Neovim", False, f"not found; install {MIN_NVIM[0]}.{MIN_NVIM[1]} or newer")]
|
|
32
|
+
out = subprocess.run(["nvim", "--version"], capture_output=True, text=True).stdout
|
|
33
|
+
found = re.search(r"v(\d+)\.(\d+)", out)
|
|
34
|
+
version = (int(found[1]), int(found[2])) if found else (0, 0)
|
|
35
|
+
good = version >= MIN_NVIM
|
|
36
|
+
checks = [Check("Neovim", good, f"{version[0]}.{version[1]}" + ("" if good else f"; {MIN_NVIM[0]}.{MIN_NVIM[1]} or newer needed"))]
|
|
37
|
+
try:
|
|
38
|
+
run = subprocess.run(["nvim", "--headless", "-i", "NONE", "+qa"], capture_output=True, text=True, timeout=30)
|
|
39
|
+
problem = next((line for line in run.stderr.splitlines() if re.search(r"(?i)error", line)), "")
|
|
40
|
+
checks.append(Check("Your config", run.returncode == 0 and not problem, problem[:90] or "starts cleanly"))
|
|
41
|
+
except subprocess.TimeoutExpired:
|
|
42
|
+
checks.append(Check("Your config", False, "Neovim didn't start within 30 seconds"))
|
|
43
|
+
return checks
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _terminal() -> Check:
|
|
47
|
+
size = shutil.get_terminal_size()
|
|
48
|
+
good = size.columns >= MIN_SIZE[0] and size.lines >= MIN_SIZE[1]
|
|
49
|
+
return Check("Terminal size", good, f"{size.columns}×{size.lines}" + ("" if good else f"; {MIN_SIZE[0]}×{MIN_SIZE[1]} or larger is needed"), required=False)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _data() -> Check:
|
|
53
|
+
from vimniscient.progress import DATA_DIR
|
|
54
|
+
|
|
55
|
+
path = DATA_DIR / "progress.db"
|
|
56
|
+
try:
|
|
57
|
+
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
sqlite3.connect(path).execute("SELECT 1")
|
|
59
|
+
return Check("Progress data", True, str(path))
|
|
60
|
+
except (OSError, sqlite3.Error) as error:
|
|
61
|
+
return Check("Progress data", False, f"{path}: {error}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def checks() -> list[Check]:
|
|
65
|
+
optional = [
|
|
66
|
+
Check("LazyVim", has_lazyvim(), "week 5 lessons and the LazyVim cards" + ("" if has_lazyvim() else " stay hidden"), required=False),
|
|
67
|
+
Check("hardtime.nvim", has_plugin("hardtime.nvim"), "habit cards and drills", required=False),
|
|
68
|
+
Check("Hyprland", bool(os.environ.get("HYPRLAND_INSTANCE_SIGNATURE")), "Omarchy drills", required=False),
|
|
69
|
+
]
|
|
70
|
+
return [*_nvim(), _data(), _terminal(), *optional]
|
|
@@ -49,6 +49,12 @@ def ladder(lessons: list[Lesson], start: int, length: int = RUNGS, seed: object
|
|
|
49
49
|
return rungs
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def note(run: dict[str, int], best: int) -> str:
|
|
53
|
+
misses = f"{run['missed']} miss" if run["missed"] == 1 else f"{run['missed']} misses"
|
|
54
|
+
text = f"Level {run['start']} → {run['level']} · {misses}, {run['fixed']} recalled afterwards"
|
|
55
|
+
return text + " · a new best!" if run["level"] > best and run["climbed"] else text
|
|
56
|
+
|
|
57
|
+
|
|
52
58
|
def summary(results: list, start: int) -> dict[str, int]:
|
|
53
59
|
climbed = sum(r.task.phase == "climb" and r.status == "solved" for r in results)
|
|
54
60
|
return {
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# The evaluation: forms a and b are the same eight skills on different texts,
|
|
2
|
+
# none of them from a lesson. `pre` uses a, `post` b, `delayed` a again, so a
|
|
3
|
+
# change can't just be remembering the test. Realistic on purpose. Never graded.
|
|
4
|
+
forms:
|
|
5
|
+
a:
|
|
6
|
+
- goal: 'Change the port to 9000.'
|
|
7
|
+
start: |-
|
|
8
|
+
▮server = connect("db.local", 5432)
|
|
9
|
+
target: |-
|
|
10
|
+
server = connect("db.local", 9000)
|
|
11
|
+
ways: ['f5ciw9000<Esc>']
|
|
12
|
+
- goal: 'Delete the line with the password.'
|
|
13
|
+
start: |-
|
|
14
|
+
▮user = "ana"
|
|
15
|
+
password = "hunter2"
|
|
16
|
+
host = "web1"
|
|
17
|
+
target: |-
|
|
18
|
+
user = "ana"
|
|
19
|
+
host = "web1"
|
|
20
|
+
ways: ['jdd']
|
|
21
|
+
- goal: 'Change "draft" to "final" in both places.'
|
|
22
|
+
start: |-
|
|
23
|
+
▮status: draft
|
|
24
|
+
note: this is a draft
|
|
25
|
+
target: |-
|
|
26
|
+
status: final
|
|
27
|
+
note: this is a final
|
|
28
|
+
ways: [':%s/draft/final<CR>']
|
|
29
|
+
- goal: 'Put each name in quotes.'
|
|
30
|
+
start: |-
|
|
31
|
+
▮anna
|
|
32
|
+
bert
|
|
33
|
+
carl
|
|
34
|
+
target: |-
|
|
35
|
+
"anna"
|
|
36
|
+
"bert"
|
|
37
|
+
"carl"
|
|
38
|
+
ways: [':%s/.*/"&"/<CR>']
|
|
39
|
+
- goal: 'Delete the arguments of the call.'
|
|
40
|
+
start: |-
|
|
41
|
+
▮result = process(data, retries, timeout)
|
|
42
|
+
target: |-
|
|
43
|
+
result = process()
|
|
44
|
+
ways: ['f(di(']
|
|
45
|
+
- goal: 'Move the last line to the top.'
|
|
46
|
+
start: |-
|
|
47
|
+
one
|
|
48
|
+
two
|
|
49
|
+
▮zero
|
|
50
|
+
target: |-
|
|
51
|
+
zero
|
|
52
|
+
one
|
|
53
|
+
two
|
|
54
|
+
ways: [':m0<CR>']
|
|
55
|
+
- goal: 'Sort the lines.'
|
|
56
|
+
start: |-
|
|
57
|
+
▮pear
|
|
58
|
+
apple
|
|
59
|
+
mango
|
|
60
|
+
target: |-
|
|
61
|
+
apple
|
|
62
|
+
mango
|
|
63
|
+
pear
|
|
64
|
+
ways: [':sort<CR>']
|
|
65
|
+
- goal: 'Delete from here to the end of the line.'
|
|
66
|
+
start: |-
|
|
67
|
+
keep this▮, drop all of this
|
|
68
|
+
target: |-
|
|
69
|
+
keep this
|
|
70
|
+
ways: ['D']
|
|
71
|
+
b:
|
|
72
|
+
- goal: 'Change the timeout to 60.'
|
|
73
|
+
start: |-
|
|
74
|
+
▮client = open("api.host", 30)
|
|
75
|
+
target: |-
|
|
76
|
+
client = open("api.host", 60)
|
|
77
|
+
ways: ['f3ciw60<Esc>']
|
|
78
|
+
- goal: 'Delete the line with the token.'
|
|
79
|
+
start: |-
|
|
80
|
+
▮name = "bo"
|
|
81
|
+
token = "abc123"
|
|
82
|
+
path = "/srv"
|
|
83
|
+
target: |-
|
|
84
|
+
name = "bo"
|
|
85
|
+
path = "/srv"
|
|
86
|
+
ways: ['jdd']
|
|
87
|
+
- goal: 'Change "todo" to "done" in both places.'
|
|
88
|
+
start: |-
|
|
89
|
+
▮state: todo
|
|
90
|
+
again: still todo
|
|
91
|
+
target: |-
|
|
92
|
+
state: done
|
|
93
|
+
again: still done
|
|
94
|
+
ways: [':%s/todo/done<CR>']
|
|
95
|
+
- goal: 'Put each word in quotes.'
|
|
96
|
+
start: |-
|
|
97
|
+
▮red
|
|
98
|
+
green
|
|
99
|
+
blue
|
|
100
|
+
target: |-
|
|
101
|
+
"red"
|
|
102
|
+
"green"
|
|
103
|
+
"blue"
|
|
104
|
+
ways: [':%s/.*/"&"/<CR>']
|
|
105
|
+
- goal: 'Delete the arguments of the call.'
|
|
106
|
+
start: |-
|
|
107
|
+
▮value = compute(left, right, scale)
|
|
108
|
+
target: |-
|
|
109
|
+
value = compute()
|
|
110
|
+
ways: ['f(di(']
|
|
111
|
+
- goal: 'Move the last line to the top.'
|
|
112
|
+
start: |-
|
|
113
|
+
beta
|
|
114
|
+
gamma
|
|
115
|
+
▮alpha
|
|
116
|
+
target: |-
|
|
117
|
+
alpha
|
|
118
|
+
beta
|
|
119
|
+
gamma
|
|
120
|
+
ways: [':m0<CR>']
|
|
121
|
+
- goal: 'Sort the lines.'
|
|
122
|
+
start: |-
|
|
123
|
+
▮walnut
|
|
124
|
+
almond
|
|
125
|
+
hazelnut
|
|
126
|
+
target: |-
|
|
127
|
+
almond
|
|
128
|
+
hazelnut
|
|
129
|
+
walnut
|
|
130
|
+
ways: [':sort<CR>']
|
|
131
|
+
- goal: 'Delete from here to the end of the line.'
|
|
132
|
+
start: |-
|
|
133
|
+
the good part▮, and the rest goes
|
|
134
|
+
target: |-
|
|
135
|
+
the good part
|
|
136
|
+
ways: ['D']
|