Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

333 рядки
22KB

  1. """make-guide.py - build vim-user-guide.pdf (needs: pip install reportlab).
  2. python make-guide.py [out.pdf] [screenshot.png]
  3. Defaults: vim-user-guide.pdf and shots/run.png next to this script.
  4. The command reference is hand-written: update it when the editor's keys change.
  5. """
  6. import os, sys
  7. from reportlab.lib.pagesizes import letter
  8. from reportlab.lib import colors
  9. from reportlab.lib.units import inch
  10. from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
  11. from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table,
  12. TableStyle, Image, PageBreak, Preformatted, KeepTogether)
  13. HERE = os.path.dirname(os.path.abspath(__file__))
  14. OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(HERE, "vim-user-guide.pdf")
  15. SHOT = sys.argv[2] if len(sys.argv) > 2 else os.path.join(HERE, "shots", "run.png")
  16. ss = getSampleStyleSheet()
  17. BLUE = colors.HexColor("#352879") # C64 dark blue
  18. LIGHT = colors.HexColor("#6c5eb5") # C64 light blue
  19. H1 = ParagraphStyle("H1", parent=ss["Heading1"], textColor=BLUE, spaceBefore=14, spaceAfter=6)
  20. H2 = ParagraphStyle("H2", parent=ss["Heading2"], textColor=BLUE, spaceBefore=10, spaceAfter=4)
  21. B = ParagraphStyle("B", parent=ss["BodyText"], fontSize=10, leading=13.5, spaceAfter=6)
  22. CELL = ParagraphStyle("C", parent=B, fontSize=9, leading=11.5, spaceAfter=0)
  23. CELLB = ParagraphStyle("CB", parent=CELL, fontName="Helvetica-Bold", textColor=colors.white)
  24. CODE = ParagraphStyle("Code", fontName="Courier", fontSize=8.8, leading=11,
  25. backColor=colors.HexColor("#f2f1f8"), borderPadding=6,
  26. leftIndent=6, rightIndent=6, spaceBefore=4, spaceAfter=10)
  27. NOTE = ParagraphStyle("N", parent=B, backColor=colors.HexColor("#fff6dc"),
  28. borderColor=colors.HexColor("#e0b84a"), borderWidth=0.6,
  29. borderPadding=6, leftIndent=6, rightIndent=6, spaceBefore=4, spaceAfter=10)
  30. def P(t, s=B): return Paragraph(t, s)
  31. def k(t): return f'<font face="Courier-Bold">{t}</font>'
  32. def code(t): return Preformatted(t, CODE)
  33. def table(rows, widths, head=True):
  34. data = [[P(c, CELLB if (head and i == 0) else CELL) for c in r] for i, r in enumerate(rows)]
  35. t = Table(data, colWidths=widths, repeatRows=1 if head else 0)
  36. st = [("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#b8b4d6")),
  37. ("VALIGN", (0, 0), (-1, -1), "TOP"),
  38. ("TOPPADDING", (0, 0), (-1, -1), 3), ("BOTTOMPADDING", (0, 0), (-1, -1), 3)]
  39. if head:
  40. st.append(("BACKGROUND", (0, 0), (-1, 0), BLUE))
  41. for i in range(1 if head else 0, len(rows)):
  42. if i % 2 == 0:
  43. st.append(("BACKGROUND", (0, i), (-1, i), colors.HexColor("#f4f3fa")))
  44. t.setStyle(TableStyle(st))
  45. return t
  46. def footer(c, d):
  47. c.saveState()
  48. c.setFont("Helvetica", 8)
  49. c.setFillColor(colors.grey)
  50. c.drawString(inch * 0.75, inch * 0.5, "vim for the Commodore 64 - User Guide")
  51. c.drawRightString(letter[0] - inch * 0.75, inch * 0.5, f"Page {d.page}")
  52. c.restoreState()
  53. W = letter[0] - 1.5 * inch
  54. s = []
  55. # ---------------------------------------------------------------- title
  56. s += [Spacer(1, 0.6 * inch),
  57. P("vim for the Commodore 64", ParagraphStyle("T", parent=ss["Title"], fontSize=28, textColor=BLUE, leading=34)),
  58. P("User Guide and Command Reference", ParagraphStyle("ST", parent=ss["Title"], fontSize=15, textColor=LIGHT)),
  59. Spacer(1, 6),
  60. P("A small modal, vi-style text editor written in Power C (K&amp;R C) with machine-language "
  61. "helpers, running on a stock C64 with a 1541/1581-style disk drive on device 8.",
  62. ParagraphStyle("Sub", parent=B, alignment=1, fontSize=10.5, textColor=colors.HexColor("#444444"))),
  63. Spacer(1, 14)]
  64. if os.path.exists(SHOT):
  65. img = Image(SHOT, width=3.84 * inch * 1.05, height=2.72 * inch * 1.05)
  66. s += [img, P("<i>The editor running in VICE: 24 text rows, the reverse-video cursor block "
  67. "and the status row (file name on the left, line number / total on the right).</i>",
  68. ParagraphStyle("cap", parent=B, alignment=1, fontSize=8.5, textColor=colors.grey))]
  69. else:
  70. print("note: no screenshot at", SHOT, "- title page built without it")
  71. s += [P("Contents", H2)]
  72. for line in ["1. Overview and limits", "2. Building and running", "3. Starting the editor",
  73. "4. The screen", "5. Normal mode commands", "6. Insert mode",
  74. "7. Colon (:) commands", "8. Status messages", "9. Differences from real vi/vim",
  75. "10. Worked examples", "11. Source layout"]:
  76. s.append(P(line, ParagraphStyle("toc", parent=B, spaceAfter=1, leftIndent=12)))
  77. s.append(PageBreak())
  78. # ---------------------------------------------------------------- 1
  79. s += [P("1. Overview and limits", H1),
  80. P("The editor loads a PETSCII sequential (SEQ) file from disk into one memory buffer, "
  81. "shows it on the 40-column screen, lets you move around and edit it with vi-style "
  82. "keys, and writes it back. It is a deliberately small subset of vi: the commands "
  83. "listed in this guide are the complete set."),
  84. table([["Item", "Limit / behaviour"],
  85. ["Text buffer", "19,000 bytes (one <font face='Courier'>malloc</font> block). "
  86. "A file larger than about 18,999 bytes is truncated on load without warning."],
  87. ["Lines", "Up to 599 lines. Opening a new line beyond that is refused."],
  88. ["Line length", "80 characters. Longer lines are split at 80 on load (you are told "
  89. "<i>long lines split at 80</i>); typing past 80 shows <i>line full (80)</i>."],
  90. ["Screen", "24 text rows of 40 columns plus a status row. The view scrolls vertically "
  91. "and horizontally to follow the cursor."],
  92. ["File format", "PETSCII, lines separated by carriage return ($0D). CR LF and bare LF "
  93. "are converted to CR on load; a Ctrl-Z ($1A) byte ends the file. A final CR is "
  94. "always added on save."],
  95. ["Disk", "Device 8, drive 0. Files are read as <font face='Courier'>name,S,R</font> and "
  96. "written as <font face='Courier'>@0:name,S,W</font> (replace)."],
  97. ["File names", "Up to 20 characters, stored in lower case."],
  98. ["Undo / search / counts", "Not supported (see section 9)."]],
  99. [1.4 * inch, W - 1.4 * inch])]
  100. # ---------------------------------------------------------------- 2
  101. s += [P("2. Building and running", H1),
  102. P("Everything lives in the <font face='Courier'>vim/</font> folder of the repository. "
  103. "The build needs VICE 3.10 installed at "
  104. "<font face='Courier'>C:\\Program Files\\GTK3VICE-3.10-win64</font> and the Power C system "
  105. "disk at <font face='Courier'>reference\\POWERc.d81</font> (never modified; the build works "
  106. "on a copy called <font face='Courier'>work.d81</font>)."),
  107. P("Build", H2),
  108. code("build.bat build vim on work.d81\n"
  109. "build.bat vim -Full fresh disk, recompile every unit\n"
  110. "build.bat vim -Run -Keys \"vim.c\\njjj\" build, then run and type keys"),
  111. P("The build runs one VICE session that compiles each changed unit "
  112. "(<font face='Courier'>vim, vscreen, vview, vedit, vml</font>) with "
  113. "<font face='Courier'>cc</font>, links them with <font face='Courier'>link -s</font>, and "
  114. "produces the program <font face='Courier'>vim</font> on <font face='Courier'>work.d81</font>. "
  115. "Only units whose source changed since the last build (tracked in "
  116. "<font face='Courier'>work.stamp</font>) are recompiled. Screenshots of each step go to "
  117. "<font face='Courier'>shots\\</font>."),
  118. table([["build.ps1 option", "Meaning"],
  119. [k("-Prog name"), "Program to build: <font face='Courier'>vim</font> (default) or <font face='Courier'>mltest</font>."],
  120. [k("-Full"), "Ignore the stamps: start from a fresh work disk and recompile everything."],
  121. [k("-Run"), "After linking, leave the Power C shell, load the program and run it."],
  122. [k("-Keys text"), "Keystrokes typed into the running program (<font face='Courier'>\\n</font> = RETURN)."],
  123. [k("-Shell text"), "Extra commands for the Power C shell instead of running."],
  124. [k("-Delay ms"), "Wall-clock milliseconds per keystroke (default 900)."],
  125. [k("-Pad / -Tail n"), "Spare RETURNs while a compile / the linker is working (20 / 8)."],
  126. [k("-Settle s"), "Extra seconds to keep running after the last key."]],
  127. [1.4 * inch, W - 1.4 * inch]),
  128. P("Run", H2),
  129. P("Double-click or run <font face='Courier'>run.bat</font>: it autostarts "
  130. "<font face='Courier'>vim</font> from <font face='Courier'>work.d81</font> in "
  131. "<font face='Courier'>x64sc</font>. On a real C64 (or any emulator) with the disk in device 8:"),
  132. code("LOAD\"VIM\",8,1\nRUN"),
  133. P("<b>Keyboard tip for VICE:</b> with the default keymap the PC <b>Esc</b> key is the C64 "
  134. "<b>RUN/STOP</b> key and <b>Backspace</b> is <b>INST/DEL</b>, so the editor feels close to "
  135. "vim on a PC keyboard. Upper-case letters such as G, A, I and O are typed with Shift.", NOTE)]
  136. # ---------------------------------------------------------------- 3
  137. s += [P("3. Starting the editor", H1),
  138. P("The screen clears to white text on blue and the status row asks:"),
  139. code("file: _"),
  140. table([["You type", "What happens"],
  141. ["A name, then RETURN", "The file is loaded from device 8 and you are in normal mode at line 1."],
  142. ["Just RETURN (or RUN/STOP)", "You get an empty buffer named <i>[no name]</i>. Give it a name later with "
  143. + k(":w name") + "."],
  144. ["A name that is not on the disk", "You are asked <font face='Courier'>not found - create it? (y/n)</font>. "
  145. + k("y") + " starts an empty buffer that keeps the name (the status row says <i>new file</i>; "
  146. "the file is created on the first " + k(":w") + "). " + k("n") + " or RUN/STOP asks for a name again."]],
  147. [1.9 * inch, W - 1.9 * inch]),
  148. P("While typing at the <i>file:</i> prompt (and on the colon command line) DEL erases the last "
  149. "character and RUN/STOP cancels. Shifted letters are folded to lower case.")]
  150. # ---------------------------------------------------------------- 4
  151. s += [P("4. The screen", H1),
  152. P("Rows 0-23 show the text. The character under the cursor is drawn in reverse video. "
  153. "Row 24 is the status row, also in reverse video:"),
  154. code("left part (26 chars) + line/total\n"
  155. "vim.c + 12/240"),
  156. table([["Part", "Shows"],
  157. ["Left", "A one-shot message if there is one (section 8); otherwise "
  158. "<font face='Courier'>-- insert --</font> in insert mode; otherwise the file name, or "
  159. "<font face='Courier'>[no name]</font>."],
  160. ["Column 28", "<font face='Courier'>+</font> when the buffer has unsaved changes."],
  161. ["Right", "Current line number / total lines."]],
  162. [1.2 * inch, W - 1.2 * inch]),
  163. P("The editor switches the VIC-II to the upper/lower-case character set, so unshifted "
  164. "letters show as lower case and shifted letters as capitals.")]
  165. # ---------------------------------------------------------------- 5
  166. s += [P("5. Normal mode commands", H1),
  167. P("Normal mode is where the editor starts and where RUN/STOP returns you. Commands are "
  168. "single keys (or the two-key " + k("dd") + " and " + k("gg") + "); they take no count prefix.")]
  169. s += [KeepTogether([P("Moving", H2), table([["Key", "Action"],
  170. [k("h") + " or CRSR left", "Left one character."],
  171. [k("l") + " or CRSR right", "Right one character (stops at end of line)."],
  172. [k("j") + " or CRSR down", "Down one line."],
  173. [k("k") + " or CRSR up", "Up one line."],
  174. [k("0"), "Start of the line."],
  175. [k("$") + " (Shift+4)", "End of the line."],
  176. [k("gg"), "First line of the file."],
  177. [k("G") + " (Shift+g)", "Last line of the file."]],
  178. [1.6 * inch, W - 1.6 * inch])]),
  179. P("When moving up or down onto a shorter line the cursor is pulled back to that line's end. "
  180. "The screen scrolls so the cursor is always visible, including sideways for lines "
  181. "longer than 40 columns.")]
  182. s += [P("Entering insert mode", H2),
  183. table([["Key", "Inserts"],
  184. [k("i"), "Before the cursor."],
  185. [k("a"), "After the cursor."],
  186. [k("I") + " (Shift+i)", "At the start of the line."],
  187. [k("A") + " (Shift+a)", "At the end of the line."],
  188. [k("o"), "On a new, empty line opened below the current one."],
  189. [k("O") + " (Shift+o)", "On a new, empty line opened above the current one."]],
  190. [1.6 * inch, W - 1.6 * inch])]
  191. s += [P("Changing text", H2),
  192. table([["Key", "Action"],
  193. [k("x"), "Delete the character under the cursor."],
  194. [k("dd"), "Delete the current line and keep a copy of it (up to 80 characters) in the yank buffer. "
  195. "Pressing " + k("d") + " followed by any other key cancels."],
  196. [k("p"), "Put the last line deleted with " + k("dd") + " on a new line below the cursor. "
  197. "The copy stays, so " + k("p") + " can be repeated; " + k("dd") + " then " + k("p") + " moves a line down."],
  198. [k(":"), "Open the colon command line (section 7)."]],
  199. [1.6 * inch, W - 1.6 * inch]),
  200. P("Any other key in normal mode is ignored.")]
  201. # ---------------------------------------------------------------- 6
  202. s += [P("6. Insert mode", H1),
  203. P("The status row shows <font face='Courier'>-- insert --</font>. Every printable key is "
  204. "inserted at the cursor, including shifted letters and the C64 graphics characters."),
  205. table([["Key", "Action"],
  206. ["RUN/STOP (Esc in VICE)", "Back to normal mode; the cursor steps one left, as in vi."],
  207. ["RETURN", "Split the line at the cursor; the rest moves to a new line below."],
  208. ["INST/DEL (Backspace in VICE)", "Delete the character before the cursor. At the start of a line, "
  209. "join it onto the previous line (refused with <i>joined line would pass 80</i> if the "
  210. "result would be too long)."],
  211. ["Cursor keys", "Move without leaving insert mode."]],
  212. [1.9 * inch, W - 1.9 * inch]),
  213. P("A line cannot grow past 80 characters: further typing is refused with <i>line full (80)</i>.")]
  214. # ---------------------------------------------------------------- 7
  215. s += [P("7. Colon (:) commands", H1),
  216. P("Press " + k(":") + " in normal mode, type the command on the status row and press RETURN. "
  217. "DEL edits; RUN/STOP or an empty line cancels."),
  218. table([["Command", "Action"],
  219. [k(":w"), "Write the buffer to its file name, replacing the old copy."],
  220. [k(":w name"), "Write to <i>name</i>; from now on the buffer is called <i>name</i>."],
  221. [k(":wq") + " / " + k(":wq name"), "Write, then quit."],
  222. [k(":x"), "Write (always, even if nothing changed), then quit."],
  223. [k(":q"), "Quit. Refused with <i>unsaved - :q! quits</i> if there are unsaved changes."],
  224. [k(":q!"), "Quit and throw away unsaved changes."],
  225. [k(":dir"), "List the disk directory (device 8) over the editor, one page at a time. "
  226. "Any key shows the next page, RUN/STOP stops; the editing screen then comes back unchanged."]],
  227. [1.6 * inch, W - 1.6 * inch]),
  228. P("<b>Quitting restarts the C64.</b> Power C uses zero-page locations BASIC depends on, so "
  229. "instead of returning to a damaged BASIC the editor jumps to the KERNAL reset routine "
  230. "($FCE2). You see the normal power-on banner and READY. Anything in memory is gone; only "
  231. "what you wrote to disk survives.", NOTE),
  232. P("Commands are matched on their letters only, so " + k(":Q") + " works like " + k(":q") +
  233. ". Anything else gives <i>unknown command</i>.")]
  234. # ---------------------------------------------------------------- 8
  235. s += [P("8. Status messages", H1),
  236. P("Messages appear on the left of the status row and disappear at the next key."),
  237. table([["Message", "Meaning"],
  238. ["new file", "The name you gave is not on the disk yet; " + k(":w") + " will create it."],
  239. ["long lines split at 80", "The file had lines over 80 characters; they were broken into several lines."],
  240. ["written", "The save succeeded."],
  241. ["cannot write", "The file could not be opened for writing (no disk, drive not present, ...)."],
  242. ["no file name", k(":w") + " on a buffer with no name: use " + k(":w name") + "."],
  243. ["unsaved - :q! quits", k(":q") + " with unsaved changes."],
  244. ["unknown command", "The colon command was not one of w, wq, x, q, q!, dir."],
  245. ["cannot read directory", k(":dir") + " could not open the drive's directory."],
  246. ["line full (80)", "Insert refused: the line is already 80 characters long."],
  247. ["joined line would pass 80", "Backspace at the start of a line refused: the joined line would be too long."],
  248. ["out of memory", "The 19,000-byte buffer could not be allocated (shown at start-up)."]],
  249. [1.9 * inch, W - 1.9 * inch])]
  250. # ---------------------------------------------------------------- 9
  251. s += [P("9. Differences from real vi/vim", H1),
  252. table([["Area", "This editor"],
  253. ["Escape", "RUN/STOP (the C64 has no Esc key; VICE maps PC Esc to it)."],
  254. ["Counts", "No numeric prefixes: " + k("3j") + " or " + k("5dd") + " do not work (digits other than "
  255. + k("0") + " are ignored)."],
  256. ["Undo / redo", "None - save often."],
  257. ["Yank / registers", "One unnamed register filled only by " + k("dd") + "; no " + k("yy") + ", "
  258. "no named registers, no " + k("P") + "."],
  259. ["Motions", "No word motions (w, b, e), no search (/, ?, n), no " + k(":number") + "."],
  260. ["Other", "No visual mode, no " + k(".") + " repeat, no " + k("r") + ", " + k("c") + ", " + k("J") +
  261. ", no " + k(":e") + " (quit and run again to open another file)."]],
  262. [1.4 * inch, W - 1.4 * inch])]
  263. # ---------------------------------------------------------------- 10
  264. s += [P("10. Worked examples", H1),
  265. P("Create a new file", H2),
  266. code("RUN start the editor\n"
  267. "notes RETURN file: notes\n"
  268. "y not found - create it? (y/n)\n"
  269. "i Hello, C64! RETURN type some text\n"
  270. " second line\n"
  271. "RUN/STOP back to normal mode\n"
  272. ":wq RETURN write and quit (the C64 restarts)"),
  273. P("Fix a typo and move a line", H2),
  274. code("j j j down to the line\n"
  275. "l l l l x move right, delete one character\n"
  276. "dd cut the line\n"
  277. "p put it back one line lower\n"
  278. ":w RETURN save; status row says \"written\""),
  279. P("Save under a new name and abandon edits", H2),
  280. code(":w backup RETURN write a copy as \"backup\"\n"
  281. "x x x more edits ...\n"
  282. ":q RETURN \"unsaved - :q! quits\"\n"
  283. ":q! RETURN quit without saving"),
  284. P("Automated test run in VICE", H2),
  285. code("build.bat vim -Run -Keys \"vim.c\\njjj\""),
  286. P("Builds, starts the editor, opens <font face='Courier'>vim.c</font> from the work disk and "
  287. "moves down three lines; the final screen is saved to <font face='Courier'>shots\\run.png</font>.")]
  288. # ---------------------------------------------------------------- 11
  289. s += [P("11. Source layout", H1),
  290. table([["File", "Contents"],
  291. ["vim.c", "<font face='Courier'>main()</font>, the key dispatcher for both modes, the file prompt, loading and the colon commands."],
  292. ["vedit.c", "Buffer editing: insert, delete, newline, backspace/join, " + k("dd") + "/" + k("p") + ", cursor keys, saving."],
  293. ["vview.c", "Scrolling, partial/full repaint and the status row."],
  294. ["vscreen.c", "Screen set-up and tear-down, low-level drawing, keyboard read from the KERNAL buffer ($C6/$0277)."],
  295. ["vml.s / vmlwrap.c", "6510 helpers copied to $C000: full-screen paint, row paint, block move, line-index adjust, key pop. "
  296. "<font face='Courier'>build-ml.ps1</font> assembles them into the generated <font face='Courier'>vml.c</font>."],
  297. ["*.pet", "PETSCII copies of the sources made by the build; the <font face='Courier'>.c</font> files are authoritative."],
  298. ["build.bat / build.ps1", "One-session build on <font face='Courier'>work.d81</font>."],
  299. ["run.bat", "Start the built editor in VICE."],
  300. ["NOTES.md", "Hard-won notes on Power C, sys(), the keyboard and VICE automation."]],
  301. [1.5 * inch, W - 1.5 * inch]),
  302. Spacer(1, 6),
  303. P("Memory use: Power C program and heap below $C000 (the text buffer is a 19,000-byte heap "
  304. "block), machine code at $C000, its parameter block at $C400, zero-page save area at $C410, "
  305. "and the C stack just below $D000. Screen at $0400, colour RAM at $D800.")]
  306. doc = SimpleDocTemplate(OUT, pagesize=letter, leftMargin=0.75 * inch, rightMargin=0.75 * inch,
  307. topMargin=0.75 * inch, bottomMargin=0.8 * inch,
  308. title="vim for the Commodore 64 - User Guide", author="Daniel Covington")
  309. doc.build(s, onFirstPage=footer, onLaterPages=footer)
  310. print("ok", OUT)

Powered by TurnKey Linux.