vimniscient 0.3.0__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.3.0 → vimniscient-0.3.1}/PKG-INFO +2 -1
- {vimniscient-0.3.0 → vimniscient-0.3.1}/README.md +1 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/pyproject.toml +1 -1
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/app.py +8 -18
- vimniscient-0.3.1/src/vimniscient/card_session.py +81 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/diagnose.py +11 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/endless.py +6 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d2-pairs.yaml +6 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d3-sentences.yaml +51 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d4-find.yaml +31 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d5-lines.yaml +23 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d6-counts.yaml +40 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d7-review.yaml +29 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d1-dot.yaml +20 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d2-search.yaml +21 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d3-cgn.yaml +36 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d4-case-numbers.yaml +38 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d5-insert.yaml +42 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d6-visual.yaml +13 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w2d7-review.yaml +37 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d1-macros.yaml +37 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d2-macros-more.yaml +35 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d3-registers.yaml +49 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d4-marks.yaml +35 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d5-visual-block.yaml +26 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d6-substitute.yaml +48 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w3d7-review.yaml +45 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d1-ranges.yaml +12 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d2-sort-filter.yaml +36 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d3-regex.yaml +5 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d4-norm.yaml +16 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d5-motions.yaml +25 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d6-indent.yaml +29 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w4d7-review.yaml +39 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d1-arguments.yaml +27 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d2-any-quote-bracket.yaml +26 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d3-functions.yaml +40 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d4-small-objects.yaml +41 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d5-comments.yaml +30 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/missions.py +3 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/placement.py +9 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/quick.py +4 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/today.py +6 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/transfer.py +8 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/ui/cards.py +24 -59
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/ui/lesson.py +2 -7
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/ui/main.py +1 -4
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_answers.py +8 -7
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_app.py +4 -4
- 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/make_golden.py +145 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/.gitignore +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/LICENSE +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/extras/nvim/vimniscient.lua +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/__init__.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/__main__.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/answer.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/cards/foot.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/cards/lazyvim.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/cards/shell.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/cards/vim.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/cards/zathura.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/cards.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/cli.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/doctor.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/drills/omarchy.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/drills.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/eval.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/evaluation.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/generate.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/habits.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/keys.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/sources.md +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w1d1-words.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w5d6-review.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w6d1-code.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w6d2-notes.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons/w6d3-data.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/lessons.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/missions.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/mnemonics.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/mnemonics.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/nvim/keymaps.lua +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/nvim/mission.lua +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/nvim/runner.lua +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/progress.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/reminders.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/session.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/settings.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/srs.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/transfer.yaml +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/ui/__init__.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/ui/builtin_lessons.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/ui/common.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/ui/drills.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/ui/quick.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/ui/stats.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/src/vimniscient/vimgolf.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_diagnose.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_drills.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_generate.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_lessons.lua +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_lessons.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_missions.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_placement.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_quick.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_timing.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_today.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/check_transfer.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/live_drills.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/live_endless.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/live_lessons.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/live_mission.py +0 -0
- {vimniscient-0.3.0 → vimniscient-0.3.1}/tests/live_retry.py +0 -0
- {vimniscient-0.3.0 → 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.
|
|
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
|
|
@@ -317,6 +317,7 @@ uv run python tests/check_transfer.py # Choose: every way solves it in
|
|
|
317
317
|
uv run python tests/check_quick.py # quick practice: the right tasks for the time, nothing graded
|
|
318
318
|
uv run python tests/check_missions.py # missions: every way does the whole job on real files
|
|
319
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
|
|
320
321
|
uv run python tests/live_mission.py # a mission in your real Neovim, in a hidden tmux session
|
|
321
322
|
uv run python tests/check_today.py # the daily budget, SRS scheduling, card introductions, hardtime
|
|
322
323
|
uv run python tests/check_app.py # every screen, driven headless
|
|
@@ -294,6 +294,7 @@ uv run python tests/check_transfer.py # Choose: every way solves it in
|
|
|
294
294
|
uv run python tests/check_quick.py # quick practice: the right tasks for the time, nothing graded
|
|
295
295
|
uv run python tests/check_missions.py # missions: every way does the whole job on real files
|
|
296
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
|
|
297
298
|
uv run python tests/live_mission.py # a mission in your real Neovim, in a hidden tmux session
|
|
298
299
|
uv run python tests/check_today.py # the daily budget, SRS scheduling, card introductions, hardtime
|
|
299
300
|
uv run python tests/check_app.py # every screen, driven headless
|
|
@@ -21,7 +21,7 @@ from vimniscient.progress import Progress
|
|
|
21
21
|
from vimniscient.session import COLORS, TaskResult, mouse_wheel, run_endless, run_lesson, run_mission
|
|
22
22
|
from vimniscient.ui.builtin_lessons import endless_lesson, mission_lesson, placement_lesson, quick_lesson, review_lesson, transfer_lesson, vimgolf_lesson
|
|
23
23
|
from vimniscient.ui.cards import CardScreen
|
|
24
|
-
from vimniscient.ui.common import CATPPUCCIN, DEFAULT_THEME, omarchy_theme
|
|
24
|
+
from vimniscient.ui.common import CATPPUCCIN, DEFAULT_THEME, omarchy_theme
|
|
25
25
|
from vimniscient.ui.drills import DrillScreen
|
|
26
26
|
from vimniscient.ui.lesson import LessonScreen, ResultsScreen
|
|
27
27
|
from vimniscient.ui.main import MainScreen
|
|
@@ -260,12 +260,10 @@ class VimniscientApp(App):
|
|
|
260
260
|
search.update(results)
|
|
261
261
|
search.seen |= {r.task.item_id for r in results}
|
|
262
262
|
if not search.finished:
|
|
263
|
-
return
|
|
263
|
+
return placement.NEXT_ROUND
|
|
264
264
|
known = search.known()
|
|
265
265
|
self.progress.mark_known(known, search.seen)
|
|
266
|
-
|
|
267
|
-
return "No lessons skipped. Start from Week 1 · Day 1."
|
|
268
|
-
return "Known already: " + ", ".join(f"{p.label} ({p.title})" for p in known)
|
|
266
|
+
return placement.note(known)
|
|
269
267
|
|
|
270
268
|
def after() -> None:
|
|
271
269
|
self.placement_round(search, done) if not search.finished else done()
|
|
@@ -291,9 +289,8 @@ class VimniscientApp(App):
|
|
|
291
289
|
if skill == "mission":
|
|
292
290
|
self.start_mission(mission, done)
|
|
293
291
|
return
|
|
294
|
-
count
|
|
295
|
-
tasks =
|
|
296
|
-
else quick.tasks(pools[skill], count, seed))
|
|
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())
|
|
297
294
|
|
|
298
295
|
def on_results(results: list[TaskResult]) -> str:
|
|
299
296
|
self.progress.record_ungraded(results, "quick")
|
|
@@ -308,8 +305,7 @@ class VimniscientApp(App):
|
|
|
308
305
|
result = run_mission(mission)
|
|
309
306
|
mouse_wheel(False)
|
|
310
307
|
self.progress.record_ungraded([result], "mission")
|
|
311
|
-
|
|
312
|
-
note = Content(f"{'Another way' if result.status == 'solved' else 'Ways to do it'}\n{ways}")
|
|
308
|
+
note = Content(mission.note(result.status == "solved"))
|
|
313
309
|
|
|
314
310
|
def after(again: bool) -> None:
|
|
315
311
|
self.start_mission(mission, done) if again else done()
|
|
@@ -322,10 +318,7 @@ class VimniscientApp(App):
|
|
|
322
318
|
def start_transfer(self, picks: list[transfer.Challenge], done: Callable[[], None]) -> None:
|
|
323
319
|
def on_results(results: list[TaskResult]) -> Content:
|
|
324
320
|
self.progress.record_ungraded(results, "transfer")
|
|
325
|
-
|
|
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("")
|
|
321
|
+
return Content(transfer.note(picks, results))
|
|
329
322
|
|
|
330
323
|
self.start_lesson(transfer_lesson([c.task for c in picks]), on_results, done)
|
|
331
324
|
|
|
@@ -339,10 +332,7 @@ class VimniscientApp(App):
|
|
|
339
332
|
mouse_wheel(False)
|
|
340
333
|
run = endless.summary(results, start)
|
|
341
334
|
self.progress.record_endless(results, run)
|
|
342
|
-
note = (
|
|
343
|
-
f"{run['fixed']} recalled afterwards")
|
|
344
|
-
if run["level"] > best and run["climbed"]:
|
|
345
|
-
note += " · a new best!"
|
|
335
|
+
note = endless.note(run, best)
|
|
346
336
|
|
|
347
337
|
def after(again: bool) -> None:
|
|
348
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)
|
|
@@ -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:
|
|
@@ -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 {
|
|
@@ -105,3 +105,9 @@ tasks:
|
|
|
105
105
|
target: |-
|
|
106
106
|
<b>the tags stay</b>
|
|
107
107
|
solution: 'citthe tags stay<Esc>'
|
|
108
|
+
- goal: 'Change the text inside the tag to "it is inside".'
|
|
109
|
+
start: |-
|
|
110
|
+
<i>only the ▮text goes</i>
|
|
111
|
+
target: |-
|
|
112
|
+
<i>it is inside</i>
|
|
113
|
+
solution: 'citit is inside<Esc>'
|
|
@@ -35,6 +35,19 @@ tasks:
|
|
|
35
35
|
a is around: the paragraph and the blank line after it.
|
|
36
36
|
|
|
37
37
|
One gap stays.
|
|
38
|
+
- goal: 'Delete the middle paragraph.'
|
|
39
|
+
start: |-
|
|
40
|
+
a paragraph runs to the blank line.
|
|
41
|
+
|
|
42
|
+
This block goes▮.
|
|
43
|
+
All of it.
|
|
44
|
+
|
|
45
|
+
It leaves no double gap behind.
|
|
46
|
+
target: |-
|
|
47
|
+
a paragraph runs to the blank line.
|
|
48
|
+
|
|
49
|
+
It leaves no double gap behind.
|
|
50
|
+
solution: 'dap'
|
|
38
51
|
- goal: 'Duplicate the first paragraph.'
|
|
39
52
|
start: |-
|
|
40
53
|
▮yap yanks a paragraph; P puts it above.
|
|
@@ -59,6 +72,18 @@ tasks:
|
|
|
59
72
|
P puts above, p puts below.
|
|
60
73
|
|
|
61
74
|
Done.
|
|
75
|
+
- goal: 'Duplicate the first paragraph.'
|
|
76
|
+
start: |-
|
|
77
|
+
▮y yanks, a is around, p is paragraph.
|
|
78
|
+
|
|
79
|
+
Done.
|
|
80
|
+
target: |-
|
|
81
|
+
y yanks, a is around, p is paragraph.
|
|
82
|
+
|
|
83
|
+
y yanks, a is around, p is paragraph.
|
|
84
|
+
|
|
85
|
+
Done.
|
|
86
|
+
solution: 'yapP'
|
|
62
87
|
- goal: 'Fix the wrong sentence: it should say "A sentence ends at a full stop."'
|
|
63
88
|
start: |-
|
|
64
89
|
s is for sentence. A sentence ends at a ▮comma. cis changes just one.
|
|
@@ -73,6 +98,12 @@ tasks:
|
|
|
73
98
|
target: |-
|
|
74
99
|
cis keeps the spaces. The i means inner. das takes one along.
|
|
75
100
|
solution: 'cisThe i means inner.<Esc>'
|
|
101
|
+
- goal: 'Fix the wrong sentence: it should say "A sentence ends at a full stop."'
|
|
102
|
+
start: |-
|
|
103
|
+
c changes. A sentence ends at a ▮comma. The next one stays.
|
|
104
|
+
target: |-
|
|
105
|
+
c changes. A sentence ends at a full stop. The next one stays.
|
|
106
|
+
solution: 'cisA sentence ends at a full stop.<Esc>'
|
|
76
107
|
- goal: 'Delete the middle sentence.'
|
|
77
108
|
start: |-
|
|
78
109
|
das deletes a sentence. This one ▮is wrong. Its space goes too.
|
|
@@ -85,6 +116,12 @@ tasks:
|
|
|
85
116
|
a is around: a sentence and its space. This sentence ▮is spare. The next one moves up.
|
|
86
117
|
target: |-
|
|
87
118
|
a is around: a sentence and its space. The next one moves up.
|
|
119
|
+
- goal: 'Delete the middle sentence.'
|
|
120
|
+
start: |-
|
|
121
|
+
d deletes. This sent▮ence goes. a takes the space.
|
|
122
|
+
target: |-
|
|
123
|
+
d deletes. a takes the space.
|
|
124
|
+
solution: 'das'
|
|
88
125
|
- goal: 'Indent the three lines.'
|
|
89
126
|
start: |-
|
|
90
127
|
What ip covers:
|
|
@@ -114,3 +151,17 @@ tasks:
|
|
|
114
151
|
one
|
|
115
152
|
two
|
|
116
153
|
three
|
|
154
|
+
- goal: 'Indent the three lines.'
|
|
155
|
+
start: |-
|
|
156
|
+
i is inner, p is paragraph:
|
|
157
|
+
|
|
158
|
+
▮the block
|
|
159
|
+
the block
|
|
160
|
+
then > indents
|
|
161
|
+
target: |-
|
|
162
|
+
i is inner, p is paragraph:
|
|
163
|
+
|
|
164
|
+
the block
|
|
165
|
+
the block
|
|
166
|
+
then > indents
|
|
167
|
+
solution: '>ip'
|
|
@@ -39,6 +39,12 @@ tasks:
|
|
|
39
39
|
target: |-
|
|
40
40
|
let f = "find"; let t = "till";
|
|
41
41
|
solution: 'ct;"find"<Esc>'
|
|
42
|
+
- goal: 'Change the value to "till" (keep the semicolon).'
|
|
43
|
+
start: |-
|
|
44
|
+
let dt = ▮"lands"; let df = "find";
|
|
45
|
+
target: |-
|
|
46
|
+
let dt = "till"; let df = "find";
|
|
47
|
+
solution: 'ct;"till"<Esc>'
|
|
42
48
|
- goal: 'Keep only the folder path: delete the file name.'
|
|
43
49
|
start: |-
|
|
44
50
|
lessons/week-1/till/backwards.ya▮ml
|
|
@@ -51,6 +57,12 @@ tasks:
|
|
|
51
57
|
vim/motions/capital-T/goes-back.tx▮t
|
|
52
58
|
target: |-
|
|
53
59
|
vim/motions/capital-T/
|
|
60
|
+
- goal: 'Keep only the folder path: delete the file name.'
|
|
61
|
+
start: |-
|
|
62
|
+
keys/capital/T/looks-back.m▮d
|
|
63
|
+
target: |-
|
|
64
|
+
keys/capital/T/
|
|
65
|
+
solution: 'T/D'
|
|
54
66
|
- goal: 'Turn the third comma into a semicolon.'
|
|
55
67
|
start: |-
|
|
56
68
|
▮count, then f, then the comma, done
|
|
@@ -63,6 +75,12 @@ tasks:
|
|
|
63
75
|
▮f, t, F, T
|
|
64
76
|
target: |-
|
|
65
77
|
f, t, F; T
|
|
78
|
+
- goal: 'Turn the third comma into a semicolon.'
|
|
79
|
+
start: |-
|
|
80
|
+
▮one, two, three, then r
|
|
81
|
+
target: |-
|
|
82
|
+
one, two, three; then r
|
|
83
|
+
solution: '3f,r;'
|
|
66
84
|
- goal: 'Replace the whole before(char) call with on_it.'
|
|
67
85
|
ft: python
|
|
68
86
|
start: |-
|
|
@@ -78,6 +96,13 @@ tasks:
|
|
|
78
96
|
target: |-
|
|
79
97
|
t_stops = till # one short of it
|
|
80
98
|
solution: 'ct till<Esc>'
|
|
99
|
+
- goal: 'Replace the whole past(char) call with till_it.'
|
|
100
|
+
ft: python
|
|
101
|
+
start: |-
|
|
102
|
+
ct_stops = ▮past(char) # ct stops at the space
|
|
103
|
+
target: |-
|
|
104
|
+
ct_stops = till_it # ct stops at the space
|
|
105
|
+
solution: 'ct till_it<Esc>'
|
|
81
106
|
- goal: 'Delete the part in brackets: from the cursor up to and including the ")".'
|
|
82
107
|
start: |-
|
|
83
108
|
f lands on▮ (not before) the letter.
|
|
@@ -90,3 +115,9 @@ tasks:
|
|
|
90
115
|
F finds backwards▮ (to the left) on the line.
|
|
91
116
|
target: |-
|
|
92
117
|
F finds backwards on the line.
|
|
118
|
+
- goal: 'Delete the part in brackets: from the cursor up to and including the ")".'
|
|
119
|
+
start: |-
|
|
120
|
+
df deletes▮ (through) the letter.
|
|
121
|
+
target: |-
|
|
122
|
+
df deletes the letter.
|
|
123
|
+
solution: 'df)'
|
|
@@ -41,6 +41,12 @@ tasks:
|
|
|
41
41
|
target: |-
|
|
42
42
|
C is a big c: it changes the rest of the line
|
|
43
43
|
solution: 'Cthe rest of the line<Esc>'
|
|
44
|
+
- goal: 'Change the rest of the line to "the end of the line".'
|
|
45
|
+
start: |-
|
|
46
|
+
C goes big: ▮a little
|
|
47
|
+
target: |-
|
|
48
|
+
C goes big: the end of the line
|
|
49
|
+
solution: 'Cthe end of the line<Esc>'
|
|
44
50
|
- goal: 'Replace the middle line with "cc changes a line".'
|
|
45
51
|
start: |-
|
|
46
52
|
c changes
|
|
@@ -63,6 +69,16 @@ tasks:
|
|
|
63
69
|
cc changes a line
|
|
64
70
|
yy yanks a line
|
|
65
71
|
solution: 'cccc changes a line<Esc>'
|
|
72
|
+
- goal: 'Replace the middle line with "cc changes a line".'
|
|
73
|
+
start: |-
|
|
74
|
+
p puts a line
|
|
75
|
+
▮ww changes a line
|
|
76
|
+
o opens a line
|
|
77
|
+
target: |-
|
|
78
|
+
p puts a line
|
|
79
|
+
cc changes a line
|
|
80
|
+
o opens a line
|
|
81
|
+
solution: 'cccc changes a line<Esc>'
|
|
66
82
|
- goal: 'Join the two lines.'
|
|
67
83
|
generate: J-split
|
|
68
84
|
start: |-
|
|
@@ -134,3 +150,10 @@ tasks:
|
|
|
134
150
|
start = ▮"I goes to the first letter"
|
|
135
151
|
target: |-
|
|
136
152
|
# start = "I goes to the first letter"
|
|
153
|
+
- goal: 'Comment the line out with "# ".'
|
|
154
|
+
ft: python
|
|
155
|
+
start: |-
|
|
156
|
+
big_i = ▮"capital I starts the line"
|
|
157
|
+
target: |-
|
|
158
|
+
# big_i = "capital I starts the line"
|
|
159
|
+
solution: 'I# <Esc>'
|
|
@@ -63,6 +63,12 @@ tasks:
|
|
|
63
63
|
target: |-
|
|
64
64
|
c2w: change two words.
|
|
65
65
|
solution: 'c2wtwo words<Esc>'
|
|
66
|
+
- goal: 'Change "three words" to "two words".'
|
|
67
|
+
start: |-
|
|
68
|
+
2 on cw changes ▮three words.
|
|
69
|
+
target: |-
|
|
70
|
+
2 on cw changes two words.
|
|
71
|
+
solution: 'c2wtwo words<Esc>'
|
|
66
72
|
- goal: 'Fix the number: 5, not 500000.'
|
|
67
73
|
generate: count-x
|
|
68
74
|
start: |-
|
|
@@ -90,6 +96,11 @@ tasks:
|
|
|
90
96
|
▮c changes, d deletes, y yanks, z naps
|
|
91
97
|
target: |-
|
|
92
98
|
c changes, d deletes, y yanks
|
|
99
|
+
- goal: 'Delete from the third comma to the end (z is not an operator).'
|
|
100
|
+
start: |-
|
|
101
|
+
▮p puts, x cuts, u undoes, z naps
|
|
102
|
+
target: |-
|
|
103
|
+
p puts, x cuts, u undoes
|
|
93
104
|
- goal: 'Join the three lines into one.'
|
|
94
105
|
start: |-
|
|
95
106
|
▮3J
|
|
@@ -146,6 +157,23 @@ tasks:
|
|
|
146
157
|
four
|
|
147
158
|
six
|
|
148
159
|
solution: '5jdd'
|
|
160
|
+
- goal: 'Delete the line five lines below the cursor.'
|
|
161
|
+
start: |-
|
|
162
|
+
▮the count is relative:
|
|
163
|
+
one
|
|
164
|
+
two
|
|
165
|
+
three
|
|
166
|
+
four
|
|
167
|
+
this line goes
|
|
168
|
+
six
|
|
169
|
+
target: |-
|
|
170
|
+
the count is relative:
|
|
171
|
+
one
|
|
172
|
+
two
|
|
173
|
+
three
|
|
174
|
+
four
|
|
175
|
+
six
|
|
176
|
+
solution: '5jdd'
|
|
149
177
|
|
|
150
178
|
- goal: 'Fix "Teh" three lines up.'
|
|
151
179
|
start: |-
|
|
@@ -173,3 +201,15 @@ tasks:
|
|
|
173
201
|
so 3k goes
|
|
174
202
|
three lines up.
|
|
175
203
|
solution: '3kciwRelative<Esc>'
|
|
204
|
+
- goal: 'Fix "Cuont" three lines up.'
|
|
205
|
+
start: |-
|
|
206
|
+
Cuont first
|
|
207
|
+
then k,
|
|
208
|
+
so 3k goes
|
|
209
|
+
▮three lines up.
|
|
210
|
+
target: |-
|
|
211
|
+
Count first
|
|
212
|
+
then k,
|
|
213
|
+
so 3k goes
|
|
214
|
+
three lines up.
|
|
215
|
+
solution: '3kciwCount<Esc>'
|
|
@@ -58,6 +58,13 @@ tasks:
|
|
|
58
58
|
target: |-
|
|
59
59
|
ci_paren(inside)
|
|
60
60
|
solution: 'ci(inside<Esc>'
|
|
61
|
+
- goal: 'Replace all three arguments with new.'
|
|
62
|
+
ft: python
|
|
63
|
+
start: |-
|
|
64
|
+
args(▮old, old, old)
|
|
65
|
+
target: |-
|
|
66
|
+
args(new)
|
|
67
|
+
solution: 'ci(new<Esc>'
|
|
61
68
|
- goal: 'Delete the wrong sentence.'
|
|
62
69
|
start: |-
|
|
63
70
|
d deletes. ▮y means yell. c changes.
|
|
@@ -96,6 +103,17 @@ tasks:
|
|
|
96
103
|
target: |-
|
|
97
104
|
import yank
|
|
98
105
|
import put
|
|
106
|
+
- goal: 'Delete the cut_one and cut_two imports.'
|
|
107
|
+
ft: python
|
|
108
|
+
start: |-
|
|
109
|
+
import two
|
|
110
|
+
▮import cut_one
|
|
111
|
+
import cut_two
|
|
112
|
+
import dd
|
|
113
|
+
target: |-
|
|
114
|
+
import two
|
|
115
|
+
import dd
|
|
116
|
+
solution: '2dd'
|
|
99
117
|
- goal: 'Change the subdomain to www.'
|
|
100
118
|
start: |-
|
|
101
119
|
url = 'https://▮old.vimhelp.org'
|
|
@@ -133,3 +151,14 @@ tasks:
|
|
|
133
151
|
def put():
|
|
134
152
|
text = yanked
|
|
135
153
|
return text
|
|
154
|
+
- goal: 'Indent the body of the function.'
|
|
155
|
+
ft: python
|
|
156
|
+
start: |-
|
|
157
|
+
def indent():
|
|
158
|
+
▮push = ">"
|
|
159
|
+
return push
|
|
160
|
+
target: |-
|
|
161
|
+
def indent():
|
|
162
|
+
push = ">"
|
|
163
|
+
return push
|
|
164
|
+
solution: '>j'
|
|
@@ -23,6 +23,12 @@ tasks:
|
|
|
23
23
|
target: |-
|
|
24
24
|
dot = again + again
|
|
25
25
|
solution: 'ciwagain<Esc>ww.'
|
|
26
|
+
- goal: 'Change both "change" to "repeat".'
|
|
27
|
+
start: |-
|
|
28
|
+
keys = ▮change + change
|
|
29
|
+
target: |-
|
|
30
|
+
keys = repeat + repeat
|
|
31
|
+
solution: 'ciwrepeat<Esc>ww.'
|
|
26
32
|
- goal: 'Add a semicolon to every line.'
|
|
27
33
|
generate: dot-append
|
|
28
34
|
start: |-
|
|
@@ -60,6 +66,14 @@ tasks:
|
|
|
60
66
|
target: |-
|
|
61
67
|
␣ repeat
|
|
62
68
|
the change
|
|
69
|
+
- goal: 'Indent both lines.'
|
|
70
|
+
start: |-
|
|
71
|
+
▮> then j
|
|
72
|
+
then dot
|
|
73
|
+
target: |2-
|
|
74
|
+
> then j
|
|
75
|
+
then dot
|
|
76
|
+
solution: '>>j.'
|
|
63
77
|
- goal: 'Delete all three "again".'
|
|
64
78
|
start: |-
|
|
65
79
|
The dot ▮again again again repeats the last change.
|
|
@@ -73,6 +87,12 @@ tasks:
|
|
|
73
87
|
The . key ▮dot dot dot repeats your last change.
|
|
74
88
|
target: |-
|
|
75
89
|
The . key repeats your last change.
|
|
90
|
+
- goal: 'Delete all three "repeat".'
|
|
91
|
+
start: |-
|
|
92
|
+
The dot ▮repeat repeat repeat repeats the last change.
|
|
93
|
+
target: |-
|
|
94
|
+
The dot repeats the last change.
|
|
95
|
+
solution: 'dw..'
|
|
76
96
|
- goal: 'Turn the lines into a list with "- ".'
|
|
77
97
|
generate: dot-prefix
|
|
78
98
|
start: |-
|
|
@@ -51,6 +51,12 @@ tasks:
|
|
|
51
51
|
target: |-
|
|
52
52
|
Slash leans forward
|
|
53
53
|
solution: 'd/S<CR>'
|
|
54
|
+
- goal: 'Delete the hedging before "So".'
|
|
55
|
+
start: |-
|
|
56
|
+
▮um, well, so, So d/ goes till it
|
|
57
|
+
target: |-
|
|
58
|
+
So d/ goes till it
|
|
59
|
+
solution: 'd/S<CR>'
|
|
54
60
|
- goal: 'Rename the second "starred" to found.'
|
|
55
61
|
ft: python
|
|
56
62
|
start: |-
|
|
@@ -70,6 +76,15 @@ tasks:
|
|
|
70
76
|
star = "*"
|
|
71
77
|
found = match
|
|
72
78
|
solution: '*ciwmatch<Esc>'
|
|
79
|
+
- goal: 'Rename the second "marked" to found.'
|
|
80
|
+
ft: python
|
|
81
|
+
start: |-
|
|
82
|
+
▮marked = "this word"
|
|
83
|
+
then = marked
|
|
84
|
+
target: |-
|
|
85
|
+
marked = "this word"
|
|
86
|
+
then = found
|
|
87
|
+
solution: '*ciwfound<Esc>'
|
|
73
88
|
- goal: 'Delete the third "next".'
|
|
74
89
|
start: |-
|
|
75
90
|
n means ▮next, and N means the next next one back.
|
|
@@ -102,3 +117,9 @@ tasks:
|
|
|
102
117
|
target: |-
|
|
103
118
|
? asks backward; / leans forward; ? looks backward from the end.
|
|
104
119
|
solution: '?fo<CR>cwbackward<Esc>'
|
|
120
|
+
- goal: 'Change the last "forward" to "backward", searching backwards.'
|
|
121
|
+
start: |-
|
|
122
|
+
the slash goes forward; ? asks forward, from the en▮d.
|
|
123
|
+
target: |-
|
|
124
|
+
the slash goes forward; ? asks backward, from the end.
|
|
125
|
+
solution: '?fo<CR>cwbackward<Esc>'
|
|
@@ -33,6 +33,16 @@ tasks:
|
|
|
33
33
|
c(match)
|
|
34
34
|
dot(match)
|
|
35
35
|
solution: '*cgnmatch<Esc>..'
|
|
36
|
+
- goal: 'Rename every x to match.'
|
|
37
|
+
start: |-
|
|
38
|
+
▮x = star()
|
|
39
|
+
next(x)
|
|
40
|
+
dot(x)
|
|
41
|
+
target: |-
|
|
42
|
+
match = star()
|
|
43
|
+
next(match)
|
|
44
|
+
dot(match)
|
|
45
|
+
solution: '*cgnmatch<Esc>..'
|
|
36
46
|
- goal: 'Change the first and the last "the" to "a", keep the middle one.'
|
|
37
47
|
start: |-
|
|
38
48
|
▮the change, the skip and the repeat
|
|
@@ -45,6 +55,12 @@ tasks:
|
|
|
45
55
|
▮the star, the next and the dot
|
|
46
56
|
target: |-
|
|
47
57
|
a star, the next and a dot
|
|
58
|
+
- goal: 'Change the first and the last "the" to "a", keep the middle one.'
|
|
59
|
+
start: |-
|
|
60
|
+
▮the find, the pass and the dot
|
|
61
|
+
target: |-
|
|
62
|
+
a find, the pass and a dot
|
|
63
|
+
solution: '*Ncgna<Esc>nn.'
|
|
48
64
|
- goal: 'Replace "the last" with "the next" on both lines.'
|
|
49
65
|
start: |-
|
|
50
66
|
▮gn goes to the last match.
|
|
@@ -63,6 +79,14 @@ tasks:
|
|
|
63
79
|
cgn changes the next match.
|
|
64
80
|
The dot changes the next match.
|
|
65
81
|
solution: '/one match<CR>cgnthe next match<Esc>.'
|
|
82
|
+
- goal: 'Replace "one match" with "the next match" on both lines.'
|
|
83
|
+
start: |-
|
|
84
|
+
▮gn finds one match.
|
|
85
|
+
Dot repeats on one match.
|
|
86
|
+
target: |-
|
|
87
|
+
gn finds the next match.
|
|
88
|
+
Dot repeats on the next match.
|
|
89
|
+
solution: '/one match<CR>cgnthe next match<Esc>.'
|
|
66
90
|
- goal: 'Change all four match to dot.'
|
|
67
91
|
start: |-
|
|
68
92
|
cgn ▮match; cgn match; cgn match; cgn match
|
|
@@ -77,6 +101,12 @@ tasks:
|
|
|
77
101
|
target: |-
|
|
78
102
|
say again; say again; say again; say again
|
|
79
103
|
solution: '*cgnagain<Esc>...'
|
|
104
|
+
- goal: 'Change all four it to again.'
|
|
105
|
+
start: |-
|
|
106
|
+
do i▮t; do it; do it; do it
|
|
107
|
+
target: |-
|
|
108
|
+
do again; do again; do again; do again
|
|
109
|
+
solution: '*cgnagain<Esc>...'
|
|
80
110
|
- goal: 'Delete every "um, ".'
|
|
81
111
|
start: |-
|
|
82
112
|
dgn ▮um, deletes um, the next match um, each time.
|
|
@@ -91,3 +121,9 @@ tasks:
|
|
|
91
121
|
target: |-
|
|
92
122
|
dgn deletes every match in turn.
|
|
93
123
|
solution: '/so, <CR>dgn..'
|
|
124
|
+
- goal: 'Delete every "er, ".'
|
|
125
|
+
start: |-
|
|
126
|
+
dgn ▮er, cuts er, the next match er, each time.
|
|
127
|
+
target: |-
|
|
128
|
+
dgn cuts the next match each time.
|
|
129
|
+
solution: '/er, <CR>dgn..'
|