vimniscient 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. vimniscient/__init__.py +3 -0
  2. vimniscient/__main__.py +3 -0
  3. vimniscient/answer.py +113 -0
  4. vimniscient/app.py +310 -0
  5. vimniscient/cards/foot.yaml +19 -0
  6. vimniscient/cards/lazyvim.yaml +80 -0
  7. vimniscient/cards/shell.yaml +22 -0
  8. vimniscient/cards/vim.yaml +95 -0
  9. vimniscient/cards/zathura.yaml +32 -0
  10. vimniscient/cards.py +200 -0
  11. vimniscient/cli.py +98 -0
  12. vimniscient/drills/omarchy.yaml +88 -0
  13. vimniscient/drills.py +198 -0
  14. vimniscient/endless.py +60 -0
  15. vimniscient/generate.py +102 -0
  16. vimniscient/habits.py +116 -0
  17. vimniscient/keys.py +14 -0
  18. vimniscient/lessons/sources.md +59 -0
  19. vimniscient/lessons/w1d1-words.yaml +223 -0
  20. vimniscient/lessons/w1d2-pairs.yaml +188 -0
  21. vimniscient/lessons/w1d3-sentences.yaml +247 -0
  22. vimniscient/lessons/w1d4-find.yaml +184 -0
  23. vimniscient/lessons/w1d5-lines.yaml +249 -0
  24. vimniscient/lessons/w1d6-counts.yaml +351 -0
  25. vimniscient/lessons/w1d7-review.yaml +222 -0
  26. vimniscient/lessons/w2d1-dot.yaml +195 -0
  27. vimniscient/lessons/w2d2-search.yaml +194 -0
  28. vimniscient/lessons/w2d3-cgn.yaml +196 -0
  29. vimniscient/lessons/w2d4-case-numbers.yaml +201 -0
  30. vimniscient/lessons/w2d5-insert.yaml +229 -0
  31. vimniscient/lessons/w2d6-visual.yaml +206 -0
  32. vimniscient/lessons/w2d7-review.yaml +197 -0
  33. vimniscient/lessons/w3d1-macros.yaml +240 -0
  34. vimniscient/lessons/w3d2-macros-more.yaml +170 -0
  35. vimniscient/lessons/w3d3-registers.yaml +230 -0
  36. vimniscient/lessons/w3d4-marks.yaml +188 -0
  37. vimniscient/lessons/w3d5-visual-block.yaml +201 -0
  38. vimniscient/lessons/w3d6-substitute.yaml +290 -0
  39. vimniscient/lessons/w3d7-review.yaml +204 -0
  40. vimniscient/lessons/w4d1-ranges.yaml +214 -0
  41. vimniscient/lessons/w4d2-sort-filter.yaml +243 -0
  42. vimniscient/lessons/w4d3-regex.yaml +167 -0
  43. vimniscient/lessons/w4d4-norm.yaml +158 -0
  44. vimniscient/lessons/w4d5-motions.yaml +149 -0
  45. vimniscient/lessons/w4d6-indent.yaml +143 -0
  46. vimniscient/lessons/w4d7-review.yaml +175 -0
  47. vimniscient/lessons/w5d1-arguments.yaml +133 -0
  48. vimniscient/lessons/w5d2-any-quote-bracket.yaml +136 -0
  49. vimniscient/lessons/w5d3-functions.yaml +193 -0
  50. vimniscient/lessons/w5d4-small-objects.yaml +185 -0
  51. vimniscient/lessons/w5d5-comments.yaml +161 -0
  52. vimniscient/lessons/w5d6-review.yaml +120 -0
  53. vimniscient/lessons/w6d1-code.yaml +157 -0
  54. vimniscient/lessons/w6d2-notes.yaml +163 -0
  55. vimniscient/lessons/w6d3-data.yaml +145 -0
  56. vimniscient/lessons.py +184 -0
  57. vimniscient/mnemonics.py +141 -0
  58. vimniscient/mnemonics.yaml +124 -0
  59. vimniscient/nvim/keymaps.lua +25 -0
  60. vimniscient/nvim/runner.lua +711 -0
  61. vimniscient/progress.py +496 -0
  62. vimniscient/reminders.py +220 -0
  63. vimniscient/session.py +152 -0
  64. vimniscient/settings.py +33 -0
  65. vimniscient/srs.py +56 -0
  66. vimniscient/today.py +189 -0
  67. vimniscient/ui/__init__.py +1 -0
  68. vimniscient/ui/builtin_lessons.py +99 -0
  69. vimniscient/ui/cards.py +160 -0
  70. vimniscient/ui/common.py +129 -0
  71. vimniscient/ui/drills.py +163 -0
  72. vimniscient/ui/lesson.py +193 -0
  73. vimniscient/ui/main.py +217 -0
  74. vimniscient/ui/stats.py +145 -0
  75. vimniscient/vimgolf.py +97 -0
  76. vimniscient-0.2.0.dist-info/METADATA +322 -0
  77. vimniscient-0.2.0.dist-info/RECORD +80 -0
  78. vimniscient-0.2.0.dist-info/WHEEL +4 -0
  79. vimniscient-0.2.0.dist-info/entry_points.txt +2 -0
  80. vimniscient-0.2.0.dist-info/licenses/LICENSE +661 -0
@@ -0,0 +1,102 @@
1
+ """Practice texts made from a seed, for skills where any sentence will do.
2
+
3
+ A lesson task can name a `generate` kind. Its Learn round keeps the authored
4
+ text; Recall and reviews can get a generated one instead, so a skill gets
5
+ practised on sentences you haven't seen. The same seed always gives the same
6
+ task, so a run can be replayed and every kind can be checked in Neovim
7
+ (tests/check_generate.py).
8
+
9
+ The sentences stay mnemonic: they're the hooks from mnemonics.yaml (what keys
10
+ stand for) and facts from lessons/sources.md.
11
+
12
+ Kinds, all word text objects (w1d1):
13
+ ciw-typo two letters of a word swapped; ciw and type it right
14
+ daw-double a word doubled; daw deletes one copy and its space
15
+ ciW-dotted a word written with dots between its letters; ciW rewrites it
16
+ """
17
+
18
+ import random
19
+ import re
20
+ from dataclasses import replace
21
+ from functools import cache
22
+
23
+ from vimniscient.lessons import Task
24
+ from vimniscient.mnemonics import _tables
25
+
26
+ KINDS = ("ciw-typo", "daw-double", "ciW-dotted")
27
+
28
+ # Facts from lessons/sources.md, one sentence each.
29
+ FACTS = [
30
+ "Ebbinghaus tested himself on nonsense syllables and published his results in 1885.",
31
+ "Forgetting is steepest right after learning, then the curve levels off.",
32
+ "Miller wrote about the magical number seven, plus or minus two.",
33
+ "Cowan later argued that short-term memory holds about four chunks.",
34
+ "Retrieval practice beat restudying on a test a week later.",
35
+ "Restudying looked better after five minutes, which is why it feels like it works.",
36
+ "Spacing reviews out is among the best replicated findings about memory.",
37
+ "Sleep after learning helps new memories stabilise.",
38
+ "Rereading makes text feel familiar, and people mistake that for knowing it.",
39
+ "Words read aloud are remembered better than words read silently.",
40
+ "Mixed practice feels harder than blocked practice but tests better later.",
41
+ "Practising with variation helps a skill carry over to new conditions.",
42
+ "Trying to recall an answer before seeing it helps you remember it later.",
43
+ "Fitts and Posner described three stages of skill: cognitive, associative and autonomous.",
44
+ "Desirable difficulties make learning feel harder but make it last.",
45
+ ]
46
+
47
+
48
+ @cache
49
+ def sentences() -> list[str]:
50
+ hooks = list(dict.fromkeys(hook for table in _tables()[:2] for hook in table.values()))
51
+ # as written: a capital would change the key ("u is down" is not "U is down")
52
+ return [h + ("" if h.endswith(".") else ".") for h in hooks] + FACTS
53
+
54
+
55
+ _WORD = re.compile(r"(?<![\w'.-])[A-Za-z]{4,}(?![\w'-])")
56
+
57
+
58
+ def _words(sentence: str, spaced: bool) -> list[re.Match]:
59
+ """Plain words of four letters or more; `spaced` wants a space on both sides (a WORD)."""
60
+ found = list(_WORD.finditer(sentence))
61
+ if spaced:
62
+ found = [m for m in found if m.start() > 0 and sentence[m.start() - 1] == " "
63
+ and m.end() < len(sentence) and sentence[m.end()] == " "]
64
+ else: # daw and ciw want the word to have a space after it, or anything but a letter
65
+ found = [m for m in found if m.end() < len(sentence)]
66
+ return found
67
+
68
+
69
+ def generate(kind: str, seed: str, parent: Task) -> Task:
70
+ """A `kind` task from `seed`, with the parent task's hint and place in its lesson."""
71
+ rng = random.Random(f"{kind}:{seed}")
72
+ while True:
73
+ sentence = rng.choice(sentences())
74
+ words = _words(sentence, spaced=kind == "ciW-dotted")
75
+ if kind == "daw-double":
76
+ words = [m for m in words if sentence[m.end()] == " "]
77
+ if kind == "ciw-typo":
78
+ words = [m for m in words if len(set(m.group()[1:-1])) > 1]
79
+ if words:
80
+ break
81
+ m = rng.choice(words)
82
+ word, before, after = m.group(), sentence[: m.start()], sentence[m.end():]
83
+ if kind == "ciw-typo":
84
+ while True:
85
+ i = rng.randrange(1, len(word) - 2)
86
+ if word[i] != word[i + 1]:
87
+ break
88
+ shown = word[:i] + word[i + 1] + word[i] + word[i + 2:]
89
+ start, cursor = before + shown + after, len(before) + rng.randrange(len(shown))
90
+ goal, solution = f'Fix "{shown}" to "{word}".', f"ciw{word}<Esc>"
91
+ elif kind == "daw-double":
92
+ start = before + word + " " + word + after
93
+ cursor = len(before) + rng.choice([0, len(word) + 1]) + rng.randrange(len(word))
94
+ goal, solution = f'Delete the doubled "{word}".', "daw"
95
+ elif kind == "ciW-dotted":
96
+ shown = ".".join(word)
97
+ start, cursor = before + shown + after, len(before) + rng.randrange(len(shown))
98
+ goal, solution = f'Write the dotted "{shown}" as "{word}".', f"ciW{word}<Esc>"
99
+ else:
100
+ raise ValueError(f"no generator called {kind}")
101
+ return replace(parent, goal=goal, start=[start], cursor=(1, cursor), target=[sentence],
102
+ solution=solution, ft="", variants=[], generated=seed)
vimniscient/habits.py ADDED
@@ -0,0 +1,116 @@
1
+ """Bad habits, read from hardtime.nvim's log (~/.local/state/nvim/hardtime.nvim.log).
2
+
3
+ hardtime writes three kinds of message:
4
+ Use dj instead of 2dd -> a card: what's shorter than 2dd?
5
+ You pressed the j key too soon! Use [count]j… -> drill the motion lessons
6
+ The <Up> key is disabled! -> same, for arrow keys
7
+ """
8
+
9
+ import os
10
+ import re
11
+ from collections import Counter
12
+ from dataclasses import dataclass
13
+ from datetime import date, datetime, timedelta
14
+ from pathlib import Path
15
+
16
+ from vimniscient.cards import Card, Deck
17
+ from vimniscient.keys import split_keys
18
+
19
+ LOG = Path(os.environ.get("XDG_STATE_HOME", Path.home() / ".local/state")) / "nvim" / "hardtime.nvim.log"
20
+
21
+ _LINE = re.compile(r"^\[\w+\s+\w{3} (\d{2} \w{3} \d{4}) [^\]]*\] (.+)$")
22
+ _USE = re.compile(r"^Use (.+) instead of (.+)$")
23
+ _TOO_SOON = re.compile(r"^You pressed the (\S+) key too soon!")
24
+ _DISABLED = re.compile(r"^The (\S+) key is disabled!")
25
+
26
+ # Keys pressed over and over -> lessons that teach the better motions.
27
+ DRILL_LESSONS = {
28
+ "j": ["w1d6-counts", "w2d2-search", "w1d3-sentences"],
29
+ "k": ["w1d6-counts", "w2d2-search", "w1d3-sentences"],
30
+ "h": ["w1d4-find", "w1d1-words"],
31
+ "l": ["w1d4-find", "w1d1-words"],
32
+ }
33
+ _ARROWS = {"<Down>": "j", "<Up>": "k", "<Left>": "h", "<Right>": "l"}
34
+
35
+
36
+ @dataclass
37
+ class Habit:
38
+ message: str
39
+ total: int
40
+ recent: int # in the last two weeks
41
+ last: date
42
+
43
+
44
+ def read_log() -> list[tuple[date, str]]:
45
+ if not LOG.exists():
46
+ return []
47
+ entries = []
48
+ for line in LOG.read_text(errors="replace").splitlines():
49
+ m = _LINE.match(line.strip())
50
+ if not m:
51
+ continue
52
+ try:
53
+ day = datetime.strptime(m.group(1), "%d %b %Y").date()
54
+ except ValueError:
55
+ continue
56
+ entries.append((day, m.group(2).strip()))
57
+ return entries
58
+
59
+
60
+ def habits(days: int = 14) -> list[Habit]:
61
+ """Every distinct message, most frequent recently first."""
62
+ since = date.today() - timedelta(days=days)
63
+ total: Counter[str] = Counter()
64
+ recent: Counter[str] = Counter()
65
+ last: dict[str, date] = {}
66
+ for day, message in read_log():
67
+ total[message] += 1
68
+ if day >= since:
69
+ recent[message] += 1
70
+ last[message] = max(day, last.get(message, day))
71
+ return sorted(
72
+ (Habit(m, total[m], recent[m], last[m]) for m in total),
73
+ key=lambda h: (-h.recent, -h.total, h.message),
74
+ )
75
+
76
+
77
+ def habit_deck() -> Deck:
78
+ """One card per "Use X instead of Y" hint, most frequent first."""
79
+ deck = Deck("habits", "Your habits")
80
+ for i, habit in enumerate(habits()):
81
+ m = _USE.match(habit.message)
82
+ if not m:
83
+ continue
84
+ better, worse = m.group(1), m.group(2)
85
+ n = len(split_keys(better))
86
+ deck.cards.append(
87
+ Card(
88
+ deck="habits",
89
+ id=f"habits:{habit.message}",
90
+ prompt=f"You typed {worse}. hardtime knows the same edit in {n} key{'' if n == 1 else 's'}: which?",
91
+ answers=[better],
92
+ style="keys",
93
+ case_sensitive=True,
94
+ note=f"hardtime caught this {habit.total}×.",
95
+ order=i,
96
+ )
97
+ )
98
+ return deck
99
+
100
+
101
+ def drill_lessons(days: int = 7) -> list[str]:
102
+ """Lessons worth repeating because of keys pressed over and over lately."""
103
+ since = date.today() - timedelta(days=days)
104
+ keys: Counter[str] = Counter()
105
+ for day, message in read_log():
106
+ if day < since:
107
+ continue
108
+ m = _TOO_SOON.match(message) or _DISABLED.match(message)
109
+ if m:
110
+ key = _ARROWS.get(m.group(1), m.group(1))
111
+ if key in DRILL_LESSONS:
112
+ keys[key] += 1
113
+ lessons: list[str] = []
114
+ for key, _ in keys.most_common():
115
+ lessons += [lesson for lesson in DRILL_LESSONS[key] if lesson not in lessons]
116
+ return lessons
vimniscient/keys.py ADDED
@@ -0,0 +1,14 @@
1
+ """Counting keystrokes written in Vim key notation (`ciwfoo<Esc>`)."""
2
+
3
+ import re
4
+
5
+ _TOKEN = re.compile(r"<[^<>\s]+>|.", re.S)
6
+
7
+
8
+ def split_keys(keys: str) -> list[str]:
9
+ """Split `ci"x<Esc>` into ['c', 'i', '"', 'x', '<Esc>']."""
10
+ return _TOKEN.findall(keys)
11
+
12
+
13
+ def count_keys(keys: str) -> int:
14
+ return len(split_keys(keys))
@@ -0,0 +1,59 @@
1
+ # Sources for the facts in the lesson texts
2
+
3
+ The lesson texts are mnemonic: they describe the key being practised (see
4
+ `../mnemonics.yaml`), and the rest is about the science of learning and memory.
5
+ Every factual claim in them should trace back to one of these.
6
+
7
+ - **Atkinson & Raugh (1975).** An application of the mnemonic keyword method to
8
+ the acquisition of a Russian vocabulary. *JEP: Human Learning and Memory*
9
+ 1(2). The keyword method: link a foreign word to a sound-alike word and an
10
+ image.
11
+ - **Bjork (1994).** Memory and metamemory considerations in the training of
12
+ human beings. In *Metacognition*. "Desirable difficulties": conditions that
13
+ make learning feel harder (spacing, testing, interleaving) often make it last.
14
+ - **Cowan (2001).** The magical number 4 in short-term memory. *Behavioral and
15
+ Brain Sciences* 24(1). About four chunks.
16
+ - **Dunlosky, Rawson, Marsh, Nathan & Willingham (2013).** Improving students'
17
+ learning with effective learning techniques. *Psychological Science in the
18
+ Public Interest* 14(1). High utility: practice testing, distributed practice.
19
+ Moderate: interleaved practice, elaborative interrogation, self-explanation.
20
+ Low: highlighting, rereading, summarization, keyword mnemonic, imagery for
21
+ text.
22
+ - **Ebbinghaus (1885).** *Über das Gedächtnis.* Nonsense syllables, tested on
23
+ himself; the forgetting curve, steepest right after learning.
24
+ - **Fitts & Posner (1967).** *Human Performance.* Three stages of skill
25
+ learning: cognitive, associative, autonomous.
26
+ - **Miller (1956).** The magical number seven, plus or minus two. *Psychological
27
+ Review* 63(2).
28
+ - **Roediger & Karpicke (2006).** Test-enhanced learning. *Psychological
29
+ Science* 17(3). Retrieval practice beat restudying on a test a week later,
30
+ though restudying looked better after five minutes.
31
+ - **Spacing effect.** Among the best-replicated findings in memory research;
32
+ see Cepeda, Pashler, Vul, Wixted & Rohrer (2006), Distributed practice in
33
+ verbal recall tasks, *Psychological Bulletin* 132(3).
34
+ - **Sleep and consolidation.** Sleep after learning helps memories stabilise;
35
+ see Diekelmann & Born (2010), The memory function of sleep, *Nature Reviews
36
+ Neuroscience* 11(2).
37
+ - **Fluency illusion.** Rereading makes text feel familiar, which people
38
+ mistake for knowing it; see Bjork, Dunlosky & Kornell (2013), Self-regulated
39
+ learning, *Annual Review of Psychology* 64.
40
+ - **Production effect.** Words read aloud are remembered better than words
41
+ read silently; MacLeod, Gopie, Hourihan, Neary & Ozubko (2010), The
42
+ production effect, *JEP: Learning, Memory, and Cognition* 36(3).
43
+ - **Interleaving.** Mixed practice feels harder than blocked practice but
44
+ tests better later; Rohrer & Taylor (2007), The shuffling of mathematics
45
+ problems improves learning, *Instructional Science* 35.
46
+ - **Variability of practice.** Practising with variation helps a skill carry
47
+ over to new conditions; Kerr & Booth (1978), Specific and varied practice
48
+ of motor skill, *Perceptual and Motor Skills* 46(2).
49
+ - **Lag effect.** Longer gaps between reviews tend to beat shorter ones;
50
+ covered in Cepeda et al. (2006) above.
51
+ - **Life dates and years used:** Ebbinghaus born 1850, *Über das Gedächtnis*
52
+ 1885; Miller 1956; Roediger & Karpicke 2006.
53
+ - **Recall before the answer.** Trying to retrieve an answer, even
54
+ unsuccessfully, before seeing it improves later memory of it; Kornell,
55
+ Hays & Bjork (2009), Unsuccessful retrieval attempts enhance subsequent
56
+ learning, *JEP: Learning, Memory, and Cognition* 35(4). This is why the
57
+ app shows what keys stand for only after you answer.
58
+ - **ed's `t`.** The ed manual: "(.,.)t(.) Copies (i.e., transfers) the
59
+ addressed lines". **grep** takes its name from ed's `g/re/p`.
@@ -0,0 +1,223 @@
1
+ week: 1
2
+ day: 1
3
+ title: Word text objects
4
+ intro: |
5
+ Read a Vim command as a small sentence: a verb, then a noun. The verbs are
6
+ `c` *change*, `d` *delete* and `y` *yank* (Vim's old word for copy). The noun
7
+ is a text object: `i` *inner* or `a` (*a word*, or *around* it), then what
8
+ kind of thing, `w` for *word*. So `ciw` reads *change inner word*, and it
9
+ works from anywhere inside the word. `iw` is the word alone, `aw` takes its
10
+ space too. Capital `W` is a big WORD: everything between two spaces, dots
11
+ included.
12
+ keys: [ciw, diw, daw, ciW, yiw, viwp]
13
+ tasks:
14
+ - goal: 'Fix "while" to "whole" (the cursor is in the middle of the word).'
15
+ start: |-
16
+ ciw is change inner word: it swaps the wh▮ile word, wherever the cursor sits.
17
+ target: |-
18
+ ciw is change inner word: it swaps the whole word, wherever the cursor sits.
19
+ solution: 'ciwwhole<Esc>'
20
+ hint: 'c changes, i keeps it inner, w names the word: ciw, from anywhere inside it.'
21
+ generate: ciw-typo # Recall and reviews can get a new text (generate.py)
22
+ variants:
23
+ - goal: 'Fix "spaec" to "space" (the cursor is in the middle of the word).'
24
+ start: |-
25
+ The i in ciw means inner: just the word, never the spa▮ec around it.
26
+ target: |-
27
+ The i in ciw means inner: just the word, never the space around it.
28
+ solution: 'ciwspace<Esc>'
29
+ - goal: 'Fix "anywhrere" to "anywhere" (the cursor is in the middle of the word).'
30
+ start: |-
31
+ Change inner word: c is the verb, iw the noun, and the cursor sits anywh▮rere in it.
32
+ target: |-
33
+ Change inner word: c is the verb, iw the noun, and the cursor sits anywhere in it.
34
+ solution: 'ciwanywhere<Esc>'
35
+ - goal: 'Fix "memroy" to "memory" (the cursor is in the middle of the word).'
36
+ start: |-
37
+ Testing yourself beats rereading: type ciw from mem▮roy, not from the page.
38
+ target: |-
39
+ Testing yourself beats rereading: type ciw from memory, not from the page.
40
+ solution: 'ciwmemory<Esc>'
41
+ - goal: 'Fix "undersocres" to "underscores" (the cursor is in the middle of the word).'
42
+ start: |-
43
+ In ciw, the w is a word: letters, digits and under▮socres, nothing else.
44
+ target: |-
45
+ In ciw, the w is a word: letters, digits and underscores, nothing else.
46
+ solution: 'ciwunderscores<Esc>'
47
+ - goal: 'Delete one "own", including its space.'
48
+ start: |-
49
+ The a in daw is around: the word and its own ▮own space.
50
+ target: |-
51
+ The a in daw is around: the word and its own space.
52
+ solution: 'daw'
53
+ hint: 'd deletes, a takes the word with its space, w is the word: daw leaves no double space.'
54
+ generate: daw-double # Recall and reviews can get a new text (generate.py)
55
+ variants:
56
+ - goal: 'Delete the second "behind".'
57
+ start: |-
58
+ daw leaves no double space behind be▮hind.
59
+ target: |-
60
+ daw leaves no double space behind.
61
+ solution: 'daw'
62
+ - goal: 'Delete one "its", including its space.'
63
+ start: |-
64
+ daw takes a word and what is around it, its ▮its trailing space.
65
+ target: |-
66
+ daw takes a word and what is around it, its trailing space.
67
+ - goal: 'Delete one "himself".'
68
+ start: |-
69
+ Ebbinghaus tested ▮himself himself on nonsense syllables; daw cuts the echo.
70
+ target: |-
71
+ Ebbinghaus tested himself on nonsense syllables; daw cuts the echo.
72
+ - goal: 'Delete one "so".'
73
+ start: |-
74
+ With daw, a means around: the word goes, and ▮so so does one space.
75
+ target: |-
76
+ With daw, a means around: the word goes, and so does one space.
77
+ - goal: 'Write the dotted "e.v.e.r.y.t.h.i.n.g" as "everything".'
78
+ start: |-
79
+ Big W, big WORD: ciW takes e.v.e.r.y▮.t.h.i.n.g between two spaces.
80
+ target: |-
81
+ Big W, big WORD: ciW takes everything between two spaces.
82
+ solution: 'ciWeverything<Esc>'
83
+ hint: 'A WORD (capital W) runs from space to space, dots included. Try ciW.'
84
+ generate: ciW-dotted # Recall and reviews can get a new text (generate.py)
85
+ variants:
86
+ - goal: 'Replace the whole URL with "a URL".'
87
+ start: |-
88
+ ciW sees www.wa▮ls.info as one WORD: dots do not split it.
89
+ target: |-
90
+ ciW sees a URL as one WORD: dots do not split it.
91
+ solution: 'ciWa URL<Esc>'
92
+ - goal: 'Replace "e.g." with "for example".'
93
+ start: |-
94
+ A WORD runs from space to space, e.▮g. a dotted abbreviation.
95
+ target: |-
96
+ A WORD runs from space to space, for example a dotted abbreviation.
97
+ solution: 'ciWfor example<Esc>'
98
+ - goal: 'Replace the path with "a path".'
99
+ start: |-
100
+ ciW sees ~/notes/vi▮m.md as one WORD, slashes and all.
101
+ target: |-
102
+ ciW sees a path as one WORD, slashes and all.
103
+ solution: 'ciWa path<Esc>'
104
+ - goal: 'Replace "Fitts&Posner(1967)" with "Fitts-and-Posner".'
105
+ start: |-
106
+ ciW: from space to space, Fitts&Pos▮ner(1967) counts as one WORD.
107
+ target: |-
108
+ ciW: from space to space, Fitts-and-Posner counts as one WORD.
109
+ solution: 'ciWFitts-and-Posner<Esc>'
110
+ - goal: 'Spell the function name out as change_inner_word.'
111
+ ft: python
112
+ start: |-
113
+ def ▮chg_in_wd(word, new):
114
+ """c = change, i = inner, w = word."""
115
+ return new
116
+ target: |-
117
+ def change_inner_word(word, new):
118
+ """c = change, i = inner, w = word."""
119
+ return new
120
+ solution: 'ciwchange_inner_word<Esc>'
121
+ hint: 'Underscores belong to a word, so ciw takes the whole name.'
122
+ variants:
123
+ - goal: 'Spell the function name out as delete_a_word.'
124
+ start: |-
125
+ def ▮del_a_wd(text):
126
+ """d = delete, a = a word and its space, w = word."""
127
+ return text
128
+ target: |-
129
+ def delete_a_word(text):
130
+ """d = delete, a = a word and its space, w = word."""
131
+ return text
132
+ solution: 'ciwdelete_a_word<Esc>'
133
+ - goal: 'Spell the function name out as change_inner_WORD.'
134
+ start: |-
135
+ def ▮chg_in_WD(text, new):
136
+ """c = change, i = inner, W = WORD, space to space."""
137
+ return new
138
+ target: |-
139
+ def change_inner_WORD(text, new):
140
+ """c = change, i = inner, W = WORD, space to space."""
141
+ return new
142
+ solution: 'ciwchange_inner_WORD<Esc>'
143
+ - goal: 'Spell the function name out as yank_inner_word.'
144
+ start: |-
145
+ def ▮ynk_in_wd(text):
146
+ """y = yank, Vim's older word for copy; i = inner; w = word."""
147
+ return text
148
+ target: |-
149
+ def yank_inner_word(text):
150
+ """y = yank, Vim's older word for copy; i = inner; w = word."""
151
+ return text
152
+ solution: 'ciwyank_inner_word<Esc>'
153
+ - goal: 'Spell the function name out as snake_case_name.'
154
+ start: |-
155
+ def ▮snk_cs_nm(name):
156
+ """ciw takes it all: underscores belong to the word."""
157
+ return name
158
+ target: |-
159
+ def snake_case_name(name):
160
+ """ciw takes it all: underscores belong to the word."""
161
+ return name
162
+ solution: 'ciwsnake_case_name<Esc>'
163
+ - goal: 'Copy "word" over "noun".'
164
+ start: |-
165
+ viwp puts the ▮word you yanked over the other noun
166
+ target: |-
167
+ viwp puts the word you yanked over the other word
168
+ solution: 'yiw$viwp'
169
+ hint: 'yiw yanks the inner word; $ jumps to the last one; viwp selects it and puts the yank over it.'
170
+ variants:
171
+ - goal: 'Copy "verb" over "noun".'
172
+ start: |-
173
+ Put is a ▮verb, and yank is a noun.
174
+ target: |-
175
+ Put is a verb, and yank is a verb.
176
+ solution: 'yiw$bviwp'
177
+ - goal: 'Copy "yank" over "paste".'
178
+ start: |-
179
+ viwp swaps a selection for the ▮yank, so select, then put the paste
180
+ target: |-
181
+ viwp swaps a selection for the yank, so select, then put the yank
182
+ solution: 'yiw$viwp'
183
+ - goal: 'Copy "spaced" over "massed".'
184
+ start: |-
185
+ ▮spaced reviews beat massed ones, so keep them massed
186
+ target: |-
187
+ spaced reviews beat massed ones, so keep them spaced
188
+ solution: 'yiw$viwp'
189
+ - goal: 'Copy "mixed" over the last "blocked".'
190
+ start: |-
191
+ Rohrer and Taylor: blocked practice feels better than ▮mixed, yet scores below blocked
192
+ target: |-
193
+ Rohrer and Taylor: blocked practice feels better than mixed, yet scores below mixed
194
+ solution: 'yiw$viwp'
195
+ - goal: 'Delete the doubled "the".'
196
+ start: |-
197
+ daw: delete a word, and ▮the the space after it.
198
+ target: |-
199
+ daw: delete a word, and the space after it.
200
+ solution: 'daw'
201
+ hint: 'daw takes the word and its trailing space.'
202
+ generate: daw-double # Recall and reviews can get a new text (generate.py)
203
+ variants:
204
+ - goal: 'Delete the doubled "like".'
205
+ start: |-
206
+ Doubled words ▮like like this one are what daw is for.
207
+ target: |-
208
+ Doubled words like this one are what daw is for.
209
+ - goal: 'Delete the doubled "four".'
210
+ start: |-
211
+ Cowan put short-term memory at about ▮four four chunks.
212
+ target: |-
213
+ Cowan put short-term memory at about four chunks.
214
+ - goal: 'Delete the doubled "familiar".'
215
+ start: |-
216
+ Rereading builds a fluency illusion: the text feels ▮familiar familiar, not known.
217
+ target: |-
218
+ Rereading builds a fluency illusion: the text feels familiar, not known.
219
+ - goal: 'Delete the doubled "and".'
220
+ start: |-
221
+ daw: d for delete, a for a word ▮and and its space.
222
+ target: |-
223
+ daw: d for delete, a for a word and its space.