vimniscient 0.2.1__tar.gz → 0.3.0__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.1 → vimniscient-0.3.0}/PKG-INFO +24 -7
- {vimniscient-0.2.1 → vimniscient-0.3.0}/README.md +23 -6
- {vimniscient-0.2.1 → vimniscient-0.3.0}/pyproject.toml +1 -1
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/app.py +87 -10
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/cli.py +24 -0
- vimniscient-0.3.0/src/vimniscient/diagnose.py +113 -0
- vimniscient-0.3.0/src/vimniscient/doctor.py +70 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/endless.py +1 -1
- vimniscient-0.3.0/src/vimniscient/eval.yaml +136 -0
- vimniscient-0.3.0/src/vimniscient/evaluation.py +58 -0
- vimniscient-0.3.0/src/vimniscient/generate.py +305 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w1d1-words.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w1d2-pairs.yaml +6 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w1d3-sentences.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w1d4-find.yaml +2 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w1d5-lines.yaml +4 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w1d6-counts.yaml +4 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w1d7-review.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w2d1-dot.yaml +3 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w2d2-search.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w2d3-cgn.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w2d4-case-numbers.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w2d5-insert.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w2d6-visual.yaml +4 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w2d7-review.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w3d1-macros.yaml +3 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w3d2-macros-more.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w3d3-registers.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w3d4-marks.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w3d5-visual-block.yaml +3 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w3d6-substitute.yaml +2 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w3d7-review.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w4d1-ranges.yaml +5 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w4d2-sort-filter.yaml +3 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w4d3-regex.yaml +3 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w4d4-norm.yaml +2 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w4d5-motions.yaml +2 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w4d6-indent.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w4d7-review.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w5d1-arguments.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w5d2-any-quote-bracket.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w5d3-functions.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w5d4-small-objects.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w5d5-comments.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w5d6-review.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w6d1-code.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w6d2-notes.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/w6d3-data.yaml +1 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons.py +10 -0
- vimniscient-0.3.0/src/vimniscient/missions.py +44 -0
- vimniscient-0.3.0/src/vimniscient/missions.yaml +121 -0
- vimniscient-0.3.0/src/vimniscient/nvim/mission.lua +84 -0
- vimniscient-0.3.0/src/vimniscient/placement.py +63 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/progress.py +29 -4
- vimniscient-0.3.0/src/vimniscient/quick.py +49 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/session.py +33 -3
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/today.py +4 -1
- vimniscient-0.3.0/src/vimniscient/transfer.py +56 -0
- vimniscient-0.3.0/src/vimniscient/transfer.yaml +184 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/ui/builtin_lessons.py +29 -8
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/ui/lesson.py +17 -2
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/ui/main.py +7 -1
- vimniscient-0.3.0/src/vimniscient/ui/quick.py +56 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/check_app.py +25 -0
- vimniscient-0.3.0/tests/check_diagnose.py +59 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/check_generate.py +1 -1
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/check_lessons.py +4 -1
- vimniscient-0.3.0/tests/check_missions.py +49 -0
- vimniscient-0.3.0/tests/check_placement.py +78 -0
- vimniscient-0.3.0/tests/check_quick.py +47 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/check_today.py +12 -0
- vimniscient-0.3.0/tests/check_transfer.py +65 -0
- vimniscient-0.3.0/tests/live_mission.py +45 -0
- vimniscient-0.2.1/src/vimniscient/generate.py +0 -82
- {vimniscient-0.2.1 → vimniscient-0.3.0}/.gitignore +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/LICENSE +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/extras/nvim/vimniscient.lua +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/__init__.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/__main__.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/answer.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/cards/foot.yaml +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/cards/lazyvim.yaml +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/cards/shell.yaml +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/cards/vim.yaml +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/cards/zathura.yaml +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/cards.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/drills/omarchy.yaml +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/drills.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/habits.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/keys.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/lessons/sources.md +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/mnemonics.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/mnemonics.yaml +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/nvim/keymaps.lua +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/nvim/runner.lua +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/reminders.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/settings.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/srs.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/ui/__init__.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/ui/cards.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/ui/common.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/ui/drills.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/ui/stats.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/src/vimniscient/vimgolf.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/check_answers.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/check_drills.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/check_lessons.lua +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/check_timing.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/live_drills.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/live_endless.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/live_lessons.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/tests/live_retry.py +0 -0
- {vimniscient-0.2.1 → vimniscient-0.3.0}/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.0
|
|
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
|
|
|
@@ -301,6 +312,12 @@ uv run python tests/check_lessons.py --lazyvim # LazyVim lessons, in your own c
|
|
|
301
312
|
uv run python tests/check_answers.py # card answers written different ways
|
|
302
313
|
uv run python tests/check_timing.py # task times: old progress databases upgrade safely
|
|
303
314
|
uv run python tests/check_generate.py # generated texts: reproducible, and they solve in Neovim
|
|
315
|
+
uv run python tests/check_diagnose.py # strategy feedback: no reference solution sets it off
|
|
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/live_mission.py # a mission in your real Neovim, in a hidden tmux session
|
|
304
321
|
uv run python tests/check_today.py # the daily budget, SRS scheduling, card introductions, hardtime
|
|
305
322
|
uv run python tests/check_app.py # every screen, driven headless
|
|
306
323
|
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
|
|
|
@@ -278,6 +289,12 @@ uv run python tests/check_lessons.py --lazyvim # LazyVim lessons, in your own c
|
|
|
278
289
|
uv run python tests/check_answers.py # card answers written different ways
|
|
279
290
|
uv run python tests/check_timing.py # task times: old progress databases upgrade safely
|
|
280
291
|
uv run python tests/check_generate.py # generated texts: reproducible, and they solve in Neovim
|
|
292
|
+
uv run python tests/check_diagnose.py # strategy feedback: no reference solution sets it off
|
|
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/live_mission.py # a mission in your real Neovim, in a hidden tmux session
|
|
281
298
|
uv run python tests/check_today.py # the daily budget, SRS scheduling, card introductions, hardtime
|
|
282
299
|
uv run python tests/check_app.py # every screen, driven headless
|
|
283
300
|
uv run python tests/live_lessons.py --all # types every solution into the real runner (needs tmux)
|
|
@@ -8,21 +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
|
|
15
|
+
from textual.content import Content
|
|
14
16
|
|
|
15
|
-
from vimniscient import drills, endless, reminders, today, vimgolf
|
|
17
|
+
from vimniscient import drills, endless, missions, placement, quick, reminders, today, transfer, vimgolf
|
|
16
18
|
from vimniscient.cards import has_lazyvim, load_decks
|
|
17
19
|
from vimniscient.lessons import Lesson, load_lessons, two_rounds
|
|
18
20
|
from vimniscient.progress import Progress
|
|
19
|
-
from vimniscient.session import COLORS, TaskResult, mouse_wheel, run_endless, run_lesson
|
|
20
|
-
from vimniscient.ui.builtin_lessons import endless_lesson, placement_lesson, review_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
|
|
21
23
|
from vimniscient.ui.cards import CardScreen
|
|
22
24
|
from vimniscient.ui.common import CATPPUCCIN, DEFAULT_THEME, omarchy_theme, plural
|
|
23
25
|
from vimniscient.ui.drills import DrillScreen
|
|
24
26
|
from vimniscient.ui.lesson import LessonScreen, ResultsScreen
|
|
25
27
|
from vimniscient.ui.main import MainScreen
|
|
28
|
+
from vimniscient.ui.quick import QuickScreen
|
|
26
29
|
from vimniscient.ui.stats import StatsScreen
|
|
27
30
|
|
|
28
31
|
|
|
@@ -44,7 +47,7 @@ class VimniscientApp(App):
|
|
|
44
47
|
|
|
45
48
|
#main { height: 1fr; }
|
|
46
49
|
#left { width: 46; max-width: 45%; }
|
|
47
|
-
#plan { height:
|
|
50
|
+
#plan { height: 10; }
|
|
48
51
|
#lesson-list { height: 1fr; }
|
|
49
52
|
#right { width: 1fr; }
|
|
50
53
|
#detail-pane { height: 1fr; }
|
|
@@ -74,7 +77,7 @@ class VimniscientApp(App):
|
|
|
74
77
|
#results-bar { height: 1; }
|
|
75
78
|
#note { padding: 0 0 1 0; color: $success; height: auto; }
|
|
76
79
|
#results-list { height: auto; padding-right: 1; }
|
|
77
|
-
#results-why { height: auto; padding: 1 0 0 0; }
|
|
80
|
+
#results-why, #results-advice { height: auto; padding: 1 0 0 0; }
|
|
78
81
|
|
|
79
82
|
#stats-view { height: 1fr; padding: 0 2; }
|
|
80
83
|
.section-label { color: $text-muted; text-style: bold; height: 1; margin-top: 1; }
|
|
@@ -121,7 +124,7 @@ class VimniscientApp(App):
|
|
|
121
124
|
def start_lesson(
|
|
122
125
|
self,
|
|
123
126
|
lesson: Lesson,
|
|
124
|
-
on_results: Callable[[list[TaskResult]], str] | None = None,
|
|
127
|
+
on_results: Callable[[list[TaskResult]], str | Content] | None = None,
|
|
125
128
|
done: Callable[[], None] = lambda: None,
|
|
126
129
|
) -> None:
|
|
127
130
|
"""Run a lesson in Neovim, then show the results; `done` runs when the user moves on.
|
|
@@ -188,6 +191,12 @@ class VimniscientApp(App):
|
|
|
188
191
|
elif step_id == "vimgolf":
|
|
189
192
|
if not progress.vimgolf_done_today():
|
|
190
193
|
return self.start_vimgolf
|
|
194
|
+
elif step_id == "quick":
|
|
195
|
+
return self.start_quick
|
|
196
|
+
elif step_id == "transfer":
|
|
197
|
+
picks = transfer.pick(self.lessons, progress)
|
|
198
|
+
if picks:
|
|
199
|
+
return lambda done: self.start_transfer(picks, done)
|
|
191
200
|
elif step_id == "endless":
|
|
192
201
|
start = progress.endless_start(endless.RESTART_BELOW)
|
|
193
202
|
if direct:
|
|
@@ -240,18 +249,86 @@ class VimniscientApp(App):
|
|
|
240
249
|
self.notify("No cards due. Come back tomorrow.")
|
|
241
250
|
|
|
242
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
|
+
|
|
243
258
|
def on_results(results: list[TaskResult]) -> str:
|
|
244
259
|
self.progress.record(results)
|
|
245
|
-
|
|
246
|
-
|
|
260
|
+
search.update(results)
|
|
261
|
+
search.seen |= {r.task.item_id for r in results}
|
|
262
|
+
if not search.finished:
|
|
263
|
+
return "That round is done. The next one follows from how it went."
|
|
264
|
+
known = search.known()
|
|
265
|
+
self.progress.mark_known(known, search.seen)
|
|
266
|
+
if not known:
|
|
247
267
|
return "No lessons skipped. Start from Week 1 · Day 1."
|
|
248
|
-
return "Known already: " + ", ".join(f"{p.label} ({p.title})" for p in
|
|
268
|
+
return "Known already: " + ", ".join(f"{p.label} ({p.title})" for p in known)
|
|
269
|
+
|
|
270
|
+
def after() -> None:
|
|
271
|
+
self.placement_round(search, done) if not search.finished else done()
|
|
249
272
|
|
|
250
|
-
self.
|
|
273
|
+
self.start_lesson(placement_lesson(tasks), on_results, after)
|
|
251
274
|
|
|
252
275
|
def action_endless(self) -> None:
|
|
253
276
|
self.run_step("endless", lambda: None)
|
|
254
277
|
|
|
278
|
+
def action_quick(self) -> None:
|
|
279
|
+
self.run_step("quick", lambda: None)
|
|
280
|
+
|
|
281
|
+
def start_quick(self, done: Callable[[], None]) -> None:
|
|
282
|
+
finished = {lesson.id for lesson in self.lessons if self.progress.is_done(lesson)}
|
|
283
|
+
pools = quick.pools(self.lessons, finished)
|
|
284
|
+
mission = missions.pick(self.lessons, self.progress)
|
|
285
|
+
|
|
286
|
+
def chosen(choice: tuple[str, int] | None) -> None:
|
|
287
|
+
if choice is None:
|
|
288
|
+
done()
|
|
289
|
+
return
|
|
290
|
+
skill, minutes = choice
|
|
291
|
+
if skill == "mission":
|
|
292
|
+
self.start_mission(mission, done)
|
|
293
|
+
return
|
|
294
|
+
count, seed = quick.how_many(minutes, self.progress.typical_task_seconds()), datetime.now().isoformat()
|
|
295
|
+
tasks = (quick.navigation(count, self.lessons[0].tasks[0], seed) if skill == quick.NAVIGATION
|
|
296
|
+
else quick.tasks(pools[skill], count, seed))
|
|
297
|
+
|
|
298
|
+
def on_results(results: list[TaskResult]) -> str:
|
|
299
|
+
self.progress.record_ungraded(results, "quick")
|
|
300
|
+
return ""
|
|
301
|
+
|
|
302
|
+
self.start_lesson(quick_lesson(quick.title(skill), tasks), on_results, done)
|
|
303
|
+
|
|
304
|
+
self.push_screen(QuickScreen({skill: len(tasks) for skill, tasks in pools.items()}, mission.title if mission else ""), chosen)
|
|
305
|
+
|
|
306
|
+
def start_mission(self, mission: missions.Mission, done: Callable[[], None]) -> None:
|
|
307
|
+
with self.suspend():
|
|
308
|
+
result = run_mission(mission)
|
|
309
|
+
mouse_wheel(False)
|
|
310
|
+
self.progress.record_ungraded([result], "mission")
|
|
311
|
+
ways = "\n".join(mission.ways)
|
|
312
|
+
note = Content(f"{'Another way' if result.status == 'solved' else 'Ways to do it'}\n{ways}")
|
|
313
|
+
|
|
314
|
+
def after(again: bool) -> None:
|
|
315
|
+
self.start_mission(mission, done) if again else done()
|
|
316
|
+
|
|
317
|
+
self.push_screen(ResultsScreen(mission_lesson(mission), [result], note), after)
|
|
318
|
+
|
|
319
|
+
def action_transfer(self) -> None:
|
|
320
|
+
self.run_step("transfer", lambda: None)
|
|
321
|
+
|
|
322
|
+
def start_transfer(self, picks: list[transfer.Challenge], done: Callable[[], None]) -> None:
|
|
323
|
+
def on_results(results: list[TaskResult]) -> Content:
|
|
324
|
+
self.progress.record_ungraded(results, "transfer")
|
|
325
|
+
by_goal = {c.task.goal: c for c in picks}
|
|
326
|
+
lines = [f"{r.task.goal} {' or '.join(ways)}" for r in results
|
|
327
|
+
if (ways := transfer.another_way(by_goal[r.task.goal], r.keys, r.status == "solved"))]
|
|
328
|
+
return Content("Another way\n" + "\n".join(lines)) if lines else Content("")
|
|
329
|
+
|
|
330
|
+
self.start_lesson(transfer_lesson([c.task for c in picks]), on_results, done)
|
|
331
|
+
|
|
255
332
|
def start_endless(self, start: int, done: Callable[[], None]) -> None:
|
|
256
333
|
"""Climb from `start` until the user quits, then show how it went."""
|
|
257
334
|
best = self.progress.endless_best()
|
|
@@ -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
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""What your own keys say about your strategy, on the results screen.
|
|
2
|
+
|
|
3
|
+
A few rules, each one certain from the keys alone, so that a task with nothing
|
|
4
|
+
clear to say says nothing. They only speak when you went over the reference,
|
|
5
|
+
and never about text you typed in insert mode (tests/check_diagnose.py checks
|
|
6
|
+
that no reference solution sets one off).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from vimniscient.keys import split_keys
|
|
12
|
+
|
|
13
|
+
MOTION_RUN = 4 # jjjj; Vjj in a reference is fine, and three presses aren't a habit
|
|
14
|
+
X_RUN = 3
|
|
15
|
+
MOTIONS = {"h", "j", "k", "l", "w", "b", "e"}
|
|
16
|
+
OPERATORS = {"c", "d", "y", ">", "<", "="}
|
|
17
|
+
TAKES_CHAR = {"f", "t", "F", "T", "r", "m", "'", "`", "@", '"'}
|
|
18
|
+
INSERTS = {"i", "a", "A", "I", "o", "O", "s", "S", "C", "R"}
|
|
19
|
+
_REPEAT = re.compile(r"((?:<[^<>\s]+>|.){3,}?)\1\1", re.S)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _skip_line(tokens: list[str], i: int) -> int:
|
|
23
|
+
while i < len(tokens) and tokens[i] != "<CR>":
|
|
24
|
+
i += 1
|
|
25
|
+
return i + 1
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _skip_insert(tokens: list[str], i: int) -> int:
|
|
29
|
+
while i < len(tokens) and tokens[i] != "<Esc>":
|
|
30
|
+
i += 1
|
|
31
|
+
return i + 1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _skip_motion(tokens: list[str], i: int) -> int:
|
|
35
|
+
"""Past what follows an operator: a count, then a motion or a text object."""
|
|
36
|
+
while i < len(tokens) and tokens[i].isdigit():
|
|
37
|
+
i += 1
|
|
38
|
+
if i >= len(tokens):
|
|
39
|
+
return i
|
|
40
|
+
key = tokens[i]
|
|
41
|
+
if key in ("/", "?"):
|
|
42
|
+
return _skip_line(tokens, i + 1)
|
|
43
|
+
return i + (2 if key in {"i", "a", "g", "f", "t", "F", "T", "'", "`"} else 1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def normal_keys(keys: str) -> list[list[str]]:
|
|
47
|
+
"""The runs of keys pressed as commands: not typed text, search or `:` lines."""
|
|
48
|
+
tokens, runs, run = split_keys(keys), [], []
|
|
49
|
+
i, visual, recording = 0, False, False
|
|
50
|
+
while i < len(tokens):
|
|
51
|
+
key = tokens[i]
|
|
52
|
+
i += 1
|
|
53
|
+
if key in (":", "/", "?"):
|
|
54
|
+
i, visual = _skip_line(tokens, i), False
|
|
55
|
+
elif visual and key in {"c", "s", "I", "A"}:
|
|
56
|
+
i, visual = _skip_insert(tokens, i), False
|
|
57
|
+
elif visual and key in OPERATORS | {"x", "J", "U", "u", "<Esc>"}:
|
|
58
|
+
visual = False
|
|
59
|
+
elif key in OPERATORS:
|
|
60
|
+
if tokens[i:i + 1] == [key]: # dd, cc, yy
|
|
61
|
+
i += 1
|
|
62
|
+
else:
|
|
63
|
+
i = _skip_motion(tokens, i)
|
|
64
|
+
if key == "c":
|
|
65
|
+
i = _skip_insert(tokens, i)
|
|
66
|
+
elif key == "g" and tokens[i:i + 1] and tokens[i] in {"U", "u", "~"}:
|
|
67
|
+
i = _skip_motion(tokens, i + 1)
|
|
68
|
+
elif key == "g":
|
|
69
|
+
i += 1
|
|
70
|
+
elif key in TAKES_CHAR:
|
|
71
|
+
i += 1
|
|
72
|
+
elif key == "q":
|
|
73
|
+
i += 0 if recording else 1
|
|
74
|
+
recording = not recording
|
|
75
|
+
elif key in INSERTS:
|
|
76
|
+
i = _skip_insert(tokens, i)
|
|
77
|
+
else:
|
|
78
|
+
visual = visual or key in {"v", "V", "<C-v>"}
|
|
79
|
+
run.append(key)
|
|
80
|
+
continue
|
|
81
|
+
if run:
|
|
82
|
+
runs.append(run)
|
|
83
|
+
run = []
|
|
84
|
+
return [*runs, run] if run else runs
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _longest_run(runs: list[list[str]], wanted: set[str]) -> tuple[str, int]:
|
|
88
|
+
best, length = "", 0
|
|
89
|
+
for tokens in runs:
|
|
90
|
+
last, n = "", 0
|
|
91
|
+
for key in tokens:
|
|
92
|
+
n = n + 1 if key == last else 1
|
|
93
|
+
last = key
|
|
94
|
+
if key in wanted and n > length:
|
|
95
|
+
best, length = key, n
|
|
96
|
+
return best, length
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def diagnose(keys: str, count: int, ref: int) -> str:
|
|
100
|
+
"""One thing worth saying about how `keys` went about it, or nothing."""
|
|
101
|
+
if count <= ref:
|
|
102
|
+
return ""
|
|
103
|
+
runs = normal_keys(keys)
|
|
104
|
+
motion, length = _longest_run(runs, MOTIONS)
|
|
105
|
+
if length >= MOTION_RUN:
|
|
106
|
+
better = "f, t, / or a count" if motion in "hl" else "a count"
|
|
107
|
+
return f"{motion} {length} times in a row: {better} gets there in one go."
|
|
108
|
+
if _longest_run(runs, {"x"})[1] >= X_RUN:
|
|
109
|
+
return "x over and over: a count, dw or diw takes it all at once."
|
|
110
|
+
repeat = _REPEAT.search(keys)
|
|
111
|
+
if repeat and not any("." in run for run in runs) and "<Esc>" in repeat.group(1):
|
|
112
|
+
return f"You typed {repeat.group(1)} three times: . repeats the last change."
|
|
113
|
+
return ""
|
|
@@ -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]
|
|
@@ -45,7 +45,7 @@ def ladder(lessons: list[Lesson], start: int, length: int = RUNGS, seed: object
|
|
|
45
45
|
lesson = mix[-1] if rng.random() < 0.5 else rng.choice(mix)
|
|
46
46
|
task = rng.choice([t for t in lesson.tasks if t.item_id not in recent] or lesson.tasks)
|
|
47
47
|
recent.append(task.item_id)
|
|
48
|
-
rungs.append((task, rng.randrange(task.
|
|
48
|
+
rungs.append((task, rng.randrange(task.choices)))
|
|
49
49
|
return rungs
|
|
50
50
|
|
|
51
51
|
|
|
@@ -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']
|