diff --git a/extensions/lem-tutor/.gitignore b/extensions/lem-tutor/.gitignore new file mode 100644 index 000000000..a760b0d4d --- /dev/null +++ b/extensions/lem-tutor/.gitignore @@ -0,0 +1,5 @@ +*.fasl +*.fas +*.lib +*.o +lem-tutor-saves/ diff --git a/extensions/lem-tutor/lem-tutor.asd b/extensions/lem-tutor/lem-tutor.asd new file mode 100644 index 000000000..f8f4430fa --- /dev/null +++ b/extensions/lem-tutor/lem-tutor.asd @@ -0,0 +1,3 @@ +(defsystem "lem-tutor" + :depends-on ("lem/core") + :components ((:file "lem-tutor"))) \ No newline at end of file diff --git a/extensions/lem-tutor/lem-tutor.lisp b/extensions/lem-tutor/lem-tutor.lisp new file mode 100644 index 000000000..6bddcaa75 --- /dev/null +++ b/extensions/lem-tutor/lem-tutor.lisp @@ -0,0 +1,75 @@ +(defpackage :lem-tutor + (:use :cl :lem) + (:export #:tutorial :tutorial-rescan)) + +(in-package :lem-tutor) + +(defun tutorial-text () + "Set correct paths to core and save files" + (merge-pathnames "tutorial-basics.txt" (asdf:system-source-directory :lem-tutor))) + +(defun tutorial-save-file () + (merge-pathnames "lem-tutor-saves/lem-tutor-save.txt" (lem-home))) + +(defun tutorial-progress () + (merge-pathnames "lem-tutor-saves/lem-tutor-progress.lisp" (lem-home))) + +(define-command tutorial () () + "Learn Lem interactively with guided exercises and progress tracking." + (tutorial-mode t)) + +(defun tutorial-save-progress (buffer) + "Create or update a save file with the cursor position, for easy continuation of the tutorial" + (let* ((point (buffer-point buffer)) + (line (line-number-at-point point)) + (column (point-column point))) + (with-open-file (stream (tutorial-progress) + :direction :output + :if-exists :supersede + :if-does-not-exist :create) + (format stream "(:line ~D :column ~D)" line column)))) + +(defun tutorial-load-progress () + "Load cursor position from progress file and restore cursor position." + (handler-case + (when (probe-file (tutorial-progress)) + (with-open-file (stream (tutorial-progress) + :direction :input) + (let* ((plist (read stream)) + (line (getf plist :line)) + (column (getf plist :column)) + (point (buffer-point (find-file-buffer (tutorial-save-file))))) + (move-to-line point line) + (move-to-column point column)))) + (error (e) + (declare (ignore e)) + (editor-error "Could not restore latest savepoint. Starting at the top, previously made edits are preserved")))) + +(defun tutorial-enable () + "Enable tutorial mode: ensure save directory exists, initialize working copy if needed, + open the save file, store the buffer and hook into after-save for progress tracking." + (ensure-directories-exist (tutorial-save-file)) + (unless (probe-file (tutorial-save-file)) + (uiop:copy-file (tutorial-text) (tutorial-save-file))) + (let ((buffer (find-file-buffer (tutorial-save-file)))) + (switch-to-buffer buffer) + (tutorial-load-progress) + (add-hook (variable-value 'after-save-hook :buffer buffer) + #'tutorial-save-progress))) + +(defun tutorial-disable () + "Save progress when tutorial mode is disabled." + (tutorial-save-progress (find-file-buffer (tutorial-save-file)))) + +(define-command tutorial-rescan () () + "Force a syntax rescan of the tutorial buffer" + (let ((buffer (find-file-buffer (tutorial-save-file)))) + (lem-tutor/syntax-parser:scan-region + (buffer-start-point buffer) + (buffer-end-point buffer)))) + +(define-minor-mode tutorial-mode + (:name "Lem-tutor" + :description "A tutorial for the lem editor." + :enable-hook #'tutorial-enable + :disable-hook #'tutorial-disable)) diff --git a/extensions/lem-tutor/tutorial-basics.txt b/extensions/lem-tutor/tutorial-basics.txt new file mode 100644 index 000000000..ffefb7f22 --- /dev/null +++ b/extensions/lem-tutor/tutorial-basics.txt @@ -0,0 +1,637 @@ +Lem Tutor +========= +INTRODUCTION + +You are reading lem-tutor, an interactive tutorial for the Lem editor. +This is your working copy. Practice commands directly in this buffer. +For an on-line overview of lems usage look at: +https://lem-project.github.io/usage/usage/ + +NOTATION + + C-x Hold Control, press x + Alt-x Hold Alt, press x + C-x C-c Hold Control, press x; then hold Control, press c + +"C-" means Control. "M-" means Alt. +Lem uses "M-" notation internally - a remnant of the Meta key. +They mean the same thing. + +Key sequences like C-x C-c mean: first C-x, then C-c. + +CONVENTIONS + +Throughout this tutorial, commands are shown in this format: + key command-name Description of what it does + +For commands without a key binding, Alt-x takes the place of the key: + Alt-x command-name Description of what it does + +NAVIGATION + +Two keys that are useful right now: + + C-v Scroll down one screen + M-v Scroll up one screen + +----- Quick exercises +>> Press C-v to scroll down. +>> Press M-v to scroll back up here. +----- + +COMMANDS + +Every action in Lem is a named command. Keys are shortcuts to commands, +but there are many commands that have no key binding at all. You can call any command +by name with Alt-x. + +----- +>> Press Alt-x. Press Tab to see available commands. Notice that each + entry also shows its key binding, if it has one. Press Escape to close. +----- + +Escape and C-g both cancel and dismiss pop-ups throughout Lem. +When in doubt, press one or the other. + +Lem also has a vi-mode for those who prefer it. To enable it: + Alt-x vi-mode + +REFERENCE + +Two commands are useful references throughout this tutorial: +Use these any time you're unsure what a key does or what's available. + + C-x ? describe-key Describe any key - press the key when prompted. + Alt-x describe-bindings List all bindings active in the current buffer. + +QUITTING + +To leave Lem: + C-x C-c exit-lem Quit the Lem editor. + +----- +>> C-x C-c +----- +To return to this tutorial after quitting: Alt-x tutorial. + + +LESSON 1: MOVEMENT + +You have already used two movement commands: + + C-v Scroll down one screen + M-v Scroll up one screen + +This lesson covers the rest. + +CHARACTER MOVEMENT + + C-f forward-char Right arrow also works + C-b backward-char Left arrow also works + C-n next-line Down arrow also works + C-p previous-line Up arrow also works + +Arrow keys work, but C-f, C-b, C-n, C-p keep your hands on the home +row and compose with other keys in ways arrows do not. Worth learning. + +----- +>> Move through this line character by character with C-f, then back with C-b. +>> Move down a few lines with C-n, then back up with C-p. +----- + +LINE MOVEMENT + + C-a move-to-beginning-of-line Home also works + C-e move-to-end-of-line End also works + +----- +>> Press C-e to jump to the end of this line. Press C-a to return. +----- + +WORD MOVEMENT + +One character at a time is slow across long lines. +Move by word is available. + + M-f forward-word C-Right also works + M-b previous-word C-Left also works + M-d delete-word delete word from cursor to end of word + M-Backspace backward-delete-word delete word from cursor to beginning of word +----- +>> Use M-f to move through the words in this sentence, then M-b to return. +----- + +PARAGRAPH MOVEMENT + + M-} forward-paragraph + M-{ backward-paragraph + +Note: { and } require Shift. So M-} is Alt+Shift+] and M-{ is Alt+Shift+[. + +Here is the first practice paragraph. It exists so you can feel what +paragraph movement does. The cursor lands on the blank line between +paragraphs. + +Here is the second practice paragraph. From here, M-{ jumps back to +the paragraph above. M-} moves forward again. + +----- +>> Use M-} and M-{ to move between the two paragraphs above. +----- + +BUFFER MOVEMENT + +A buffer holds your text in memory. It may or may not be saved to a file - +but for navigation purposes they feel the same. + + M-< move-to-beginning-of-buffer + M-> move-to-end-of-buffer + +Note: < and > require Shift. So M-< is Alt+Shift+, and M-> is Alt+Shift+. + +----- +>> Press M-> to jump to the end of this file. Press M-< to return. +----- + +SEARCH + + C-s isearch-forward + C-r isearch-backward + +Type to search incrementally - the cursor jumps to the first match +as you type. Press C-s again to move to the next match. Press Escape +or C-g to cancel and return to where you started. + +----- +>> Press C-s and type a word from this buffer. Press C-s again to + find the next occurrence. Press Escape when done. +----- + +GOTO LINE + +Jump to a specific line number. + + M-g goto-line + +Lem will prompt you for a line number. Type it and press Enter. +Knowing your current line number helps here - see LINE NUMBERS at the +end of this file. + +----- +>> Press M-g, enter 1, press Enter. Use M-< or M-> and C-v or M-v + to orient yourself. +----- + +RECENTERING + + C-l recenter Recenter the window on the cursor + +----- +>> Use C-n to move the cursor near the bottom of the screen without + scrolling. Then press C-l to recenter the view on the cursor. +----- + +SUMMARY + + C-f forward-char Move forward one character + C-b backward-char Move backward one character + C-n next-line Move to next line + C-p previous-line Move to previous line + C-a move-to-beginning-of-line Move to beginning of line + C-e move-to-end-of-line Move to end of line + M-f forward-word Move forward one word + M-b previous-word Move backward one word + M-d delete-word del word from cursor to end of word + M-Backspace backward-delete-word del word from cursor to beginning of word + M-} forward-paragraph Move forward one paragraph + M-{ backward-paragraph Move backward one paragraph + C-v next-page Scroll down one screen + M-v previous-page Scroll up one screen + C-s isearch-forward Search forward + C-r isearch-backward Search backward + M-< move-to-beginning-of-buffer Move to beginning of buffer + M-> move-to-end-of-buffer Move to end of buffer + M-g goto-line Go to line number + C-l recenter Recenter view on cursor + +------------------------------------------------------------------------ + +LESSON 2: FILES AND CONFIGURATION + +In Lem, the text you work with lives in a buffer. A buffer may be +tied to a file on disk, or exist only in memory. When you open a +file, Lem loads it into a buffer. When you save, the buffer contents +are written to that file. + +This lesson covers opening and saving files, and introduces the one +file that shapes how Lem behaves at startup: your init file. + +OPENING FILES + + C-x C-f find-file Open a file + +Lem prompts you for a path. You can use tab completion. If the file does not +exist, Lem opens it in the buffer and creates it on disk when you save. + +----- +>> Press C-x C-f and open any file on your system. Press Escape to + cancel if you prefer. +----- + +SAVING FILES + + C-x C-s save-current-buffer Save the current buffer to its file + C-x C-w write-file Save to a different filename + C-x s save-some-buffers Save all modified buffers (prompts for each) + +----- +>> Make a small edit anywhere in this buffer. Press C-x C-s to save it. +----- + +The mode line runs along the bottom of the buffer. When a buffer has +unsaved changes, a small indicator appears there. It clears when you save. + +YOUR INIT FILE + +Lem reads your init file at startup. This is where you configure +keybindings, load extensions, and set preferences. It is a Common +Lisp file evaluated when Lem starts. + +Its location is: + ~/.config/lem/init.lisp + +----- +>> Press C-x C-f and open your init file. If it does not exist, + Lem will create it when you save. +----- + +ADDING CONFIGURATION + +Each setting is a Lisp form. For example, to enable line numbers at start-up. +Type the expression below in your init.lisp file. Save with C-x C-s. +Then close and re-open Lem. + + (lem/line-numbers:toggle-line-numbers) + +Line numbers do not appear in the special buffers like *tmp*, *dashboard* or +*messages*. To see if it worked open a text or code file. Your init.lisp file +itself should now have line-numbers. + +In your init file you can write normal Lisp code, including your own commands +with key-bindings. +For example, a command to open your init file quickly: + + (lem:define-command open-init-file () () + "Open the Lem init file for editing" + (lem:find-file + (merge-pathnames "init.lisp" (lem:lem-home)))) + + (define-keys *global-keymap* + ("M-C-e" 'open-init-file)) + +Here we've defined a command open-init-file and bound it to a combination of keys. +You can call it with Alt-x open-init-file or press Control+Alt+e. + +----- +>> Add both forms to your init file. Save it with C-x C-s. + Restart Lem. Try Alt-x open-init-file or the keybinding. +----- + +You do not need to understand the Lisp syntax fully yet. That comes +later. For now, treat init file forms as configuration entries you +can copy, paste, and modify. + +RECENT FILES + +Lem keeps track of files you have opened. Press C-x C-h and use tab +completion to pick from the list. + + C-x C-h find-recent-file Open a recently visited file + +----- +>> Press C-x C-h and reopen the file you just opened. +----- + +SUMMARY + + C-x C-f find-file Open a file + C-x C-s save-current-buffer Save current buffer + C-x C-w write-file Save to a new filename + C-x s save-some-buffers Save all modified buffers + C-x C-h find-recent-file Open a recent file +------------------------------------------------------------------------ + +LESSON 3: BUFFERS AND WINDOWS + +You opened files in Lesson 2. Each open file lives in a buffer. +Lem can display multiple buffers at once by splitting the screen +into windows. This lesson covers switching between buffers and +arranging windows. + +SWITCHING BUFFERS + + C-x b select-buffer Switch to a buffer by name + C-x Left previous-buffer + C-x Right next-buffer + C-x k kill-buffer Close any open buffer, you will get a select window + +----- +>> Press C-x Right and C-x Left to cycle through your open buffers. +>> Press C-x b, type a buffer name, press Enter to switch to it. +----- + +BUFFER LIST + + C-x C-b list-buffers Opens a buffer menu + +Shows all open buffers with their file paths. Buffers marked with a +lock icon are read-only. Special buffers like *dashboard* and *tmp* +appear here too - these start and end with * and are not tied to files. + + Space Mark a buffer + M-Space Mark a buffer (cursor moves up) + C-k Kill all marked buffers + Return Open the buffer at point + C-g / Esc Close the list + +----- +>> Press C-x C-b. Browse the list, mark a buffer with Space, + then press Escape. +----- + +SPLITTING WINDOWS + +A window is a view onto a buffer. Splitting the screen lets you +see two buffers at once. +The command names of the split window commands describe the way the windows stack, +not the orientation of the dividing bar. + + C-x 2 split-active-window-vertically windows appear above and below each other + C-x 3 split-active-window-horizontally windows appear side by side + C-x 1 delete-other-windows + C-x 0 delete-active-window + C-x 4 b select-buffer-next-window + +The most useful split for writing code is side by side -- C-x 3 -- +keeping a file on the left and a reference or REPL on the right. + +----- +>> Press C-x 3 to split the screen side by side. +>> Press C-x C-f to open a file in the new window. +>> Press C-x 1 to return to a single window. +----- + +SWITCHING WINDOWS + + C-x o next-window + M-o next-window faster, one key + +----- +>> Split with C-x 3. Use M-o to move between the two windows. +----- + +AUTOMATIC SPLITS + +Lem splits the window automatically in some situations. When you +start a REPL with Alt-x start-lisp-repl, it opens in a new window. +The debugger does the same when an error occurs. + +For now, know that C-x 1 always gets you back to a single window, +and M-o moves you between whatever windows are open. + +OTHER BUFFER COMMANDS + + Alt-x revert-buffer Reload the file from disk, discarding changes + Alt-x rename-buffer Rename the current buffer + M-~ unmark-buffer Mark the buffer as unmodified without saving. + Useful when you've made exploratory edits you want to discard. + +WORKSPACES (FRAME MULTIPLEXER) + +Lem supports multiple workspaces - separate window arrangements +within the same Lem instance. Each workspace has its own buffer +layout. + + C-z c frame-multiplexer-create-with-new-buffer-list Create a new workspace + C-z n frame-multiplexer-next Next workspace + C-z p frame-multiplexer-prev Previous workspace + C-z d frame-multiplexer-delete Delete current workspace + +Useful when you want to keep a separate layout for different tasks - +for example, one workspace for editing, another for the REPL and +debugger. + +----- +>> Press C-z c to create a new workspace. Press C-z n and C-z p + to switch between them. Press C-z d to delete it. +----- + +SUMMARY + + C-x b select-buffer Switch to buffer by name + C-x C-b list-buffers Open the buffer list + C-x Left previous-buffer Previous buffer + C-x Right next-buffer Next buffer + C-x k kill-buffer Kill current buffer + C-x 4 b select-buffer-next-window Select buffer in other window + M-~ unmark-buffer Clear the modified marker + C-x 2 split-active-window-vertically Windows appear above and below + C-x 3 split-active-window-horizontally Windows appear side by side + C-x 1 delete-other-windows Close other windows + C-x 0 delete-active-window Close current window + C-x o next-window Switch to next window + M-o next-window Switch to next window + C-z c frame-multiplexer-create-with-new-buffer-list Create new workspace + C-z n frame-multiplexer-next Next workspace + C-z p frame-multiplexer-prev Previous workspace + C-z d frame-multiplexer-delete Delete current workspace + + Alt-x revert-buffer Reload buffer from disk + Alt-x rename-buffer Rename the current buffer + + In buffer list (C-x C-b): + Space Mark buffer + M-Space Mark buffer (cursor up) + C-k Kill marked buffers + Return Open buffer at point + +------------------------------------------------------------------------ + +LESSON 4: EDITING + +Lessons 1 through 3 covered moving around and managing files, buffers, +and windows. This lesson covers changing text — selecting, cutting, +copying, pasting, undoing, and cleaning up whitespace. + +REGION + +A region is a selection of text between the mark and the cursor. +Set the mark with C-Space, then move the cursor — the region is +everything between them. + + C-Space mark-set Set the mark here + C-x h mark-set-whole-buffer Select the entire buffer + +Shift+arrow keys also set the mark and move simultaneously, like +selection in most editors. + +----- +>> Press C-Space at the start of the practice line below. + Move with C-f or C-n to select some text. The region highlights + as you move. Press C-g to cancel the selection. + +The quick brown fox jumps over the lazy dog. +----- + +KILL AND YANK + +In Lem, "kill" means cut and "yank" means paste. Killed text goes +onto the kill ring — a clipboard that remembers multiple entries. + + C-k kill-line Kill from cursor to end of line + C-w kill-region Kill the active region + M-w copy-region Copy the region without killing it + C-y yank Paste the most recently killed text + M-y yank-pop Cycle through earlier kill ring entries + +C-k on an empty line kills the newline itself. Press it twice on a +normal line to kill the line and its newline together. + +----- +>> Move to the kill line below. Press C-k to kill it. + Move down a few lines. Press C-y to yank it back. +>> Press C-Space, select a word, then M-w to copy it. + Move elsewhere and C-y to paste. +>> Press C-y again, then M-y to cycle to an earlier kill. + +Kill this: one two three four five +----- + +UNDO AND REDO + + C-\ undo + C-/ redo + C-_ redo + +Undo walks back through every change. Redo walks forward again. + +----- +>> Make a few edits on the line below. Press C-\ repeatedly to undo + them one by one. Press C-/ to redo. + +Undo test: delete or change the words on this line, then undo. +----- + +WHITESPACE + + M-Space just-one-space Collapse surrounding whitespace to one space + M-^ delete-indentation Join current line to the line above + + Alt-x delete-trailing-whitespace Remove trailing whitespace from the buffer + Alt-x format-current-buffer Reformat the whole buffer according to its mode rules + +----- +>> Put the cursor between two words in the whitespace line below. + Press M-Space to collapse the spaces to one. +>> Move to the indented line and press M-^ to join it to the line above. + +Whitespace test: collapse these extra spaces to one. +Joining test: + this line should join to the one above it. +----- +LINE EDITING + +Two commands that comein handy are those that create and destroy a blanck line. + + C-o open-line Creates a blank line above cursor and moves cursor to the start of that line + C-x C-o delete-blank-lines Collapses all blank lines around cursor if on empty line, else only below cursor + +COMMENTS + +Lem can toggle comments on a selected region using the comment +syntax of the current mode. + + Alt-x comment-or-uncomment-region + +This command is mode-aware — it uses the correct comment syntax for +the language you are editing. It has no effect in plain text buffers. +See Lesson 6 for a practical exercise in a Lisp buffer. + +SEARCH AND REPLACE + + M-% query-replace + +Lem prompts for a search string, then a replacement. For each match +it asks what to do: + + y replace this match and move to the next + n skip this match + ! replace all remaining matches without asking + q stop replacing + C-g exit query-replace + +Query-replace searches forward from your current cursor or point position. +Use M-< first to start from the beginning of buffer. + +----- +>> Press M-% and replace "cat" with "dog" in the line below. + Try y to replace one at a time, or ! to replace all at once. + Press q or Escape when done. + +Replace test: cat cat cat cat cat +----- + +SUMMARY + + C-Space mark-set Set the mark + C-x h mark-set-whole-buffer Select the whole buffer + C-k kill-line Kill line + C-w kill-region Kill region + M-w copy-region Copy region + C-y yank Yank + M-y yank-pop Cycle kill ring + C-\ undo Undo + C-/ redo Redo + M-Space just-one-space Collapse whitespace around cursor to one space + M-^ delete-indentation Join line to line above + M-% query-replace Query-replace + C-o open-line Creates a blank line above cursor and moves cursor to the start of that line + C-x C-o delete-blank-lines Collapses all blank lines around cursor if on empty line, else only below cursor + + Alt-x delete-trailing-whitespace Remove trailing whitespace from the buffer + Alt-x comment-or-uncomment-region Toggle comments on region + Alt-x format-current-buffer Reformat the buffer according to its mode rules + +------------------------------------------------------------------------ + +APPENDIX + +This section covers useful features that don't belong to a single +lesson. Return here any time. + +LINE NUMBERS + +Lem can display line numbers in the left margin. + +----- +>> Alt-x toggle-line-numbers + Press it again to turn them off. +----- + +This toggles line numbers on and off for the current buffer. Useful +when navigating with M-g or orienting yourself in a long file. + +For relative line numbers - showing the distance from the cursor rather +than the absolute line number - add this to your init file: + + (setf lem/line-numbers:*relative-line* t) + +Relative line numbers are useful when jumping by a known number of +lines. The current line always shows its absolute number. + +Resources: + +https://lem-project.github.io +https://lem-project.github.io/installation/webview/ +https://lem-project.github.io/usage/usage/ +https://github.com/lem-project/lem/blob/main/docs/default-keybindings.md