(Re)generate: (find-eepitch-intro) Source code: (find-eev "eev-intro.el" "find-eepitch-intro") More intros: (find-eev-quick-intro) (find-eval-intro) (find-wrap-intro) This buffer is _temporary_ and _editable_. It is meant as both a tutorial (for eepitch) and a sandbox. This intro _complements_ the material in: (find-eev-quick-intro "6. Controlling shell-like programs") For a good visual introduction to eepitch, see this page: http://anggtwu.net/eepitch.html My video for the EmacsConf2019 has a simple demo of eepitch: https://www.youtube.com/watch?v=86yiRG8YJD0&t=956 http://anggtwu.net/emacsconf2019.html This (old) video shows a demo like the one in section 1.3: https://www.youtube.com/watch?v=Lj_zKC5BR64&t=16s The relevant part is from t=16s to t=25s. In this intro we suppose that the reader knows what is a terminal and what is a shell. In Unix-like systems the terminal and the shell are clearly different programs, and it's easy to understand how a terminal can be used to run other programs that are not shells (e.g., a Python interpreter; see "REPL" below); in Windows most people don't know that the "DOS window" is in fact a Windows console running cmd.exe. Some links: https://en.wikipedia.org/wiki/Pipeline_(Unix) https://en.wikipedia.org/wiki/Unix_philosophy https://en.wikipedia.org/wiki/Unix-like https://en.wikipedia.org/wiki/Shell_(computing) https://en.wikipedia.org/wiki/Shell_(computing)#Text_(CLI)_shells https://en.wikipedia.org/wiki/Shell_script https://en.wikipedia.org/wiki/Command-line_interface https://en.wikipedia.org/wiki/Command-line_interface#Command-line_interpreter https://en.wikipedia.org/wiki/Read-eval-print_loop ("REPL") https://en.wikipedia.org/wiki/Terminal_emulator https://en.wikipedia.org/wiki/Text_terminal https://en.wikipedia.org/wiki/MS-DOS#Windows_command-line_interface https://en.wikipedia.org/wiki/Windows_Console https://en.wikipedia.org/wiki/Cmd.exe
1. Some demosLet's start with the simplest case. If you put the cursor on the first line that starts with a red star below and type the key <f8> six times, * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) echo "We are at: $PWD" cd /tmp/ echo "We changed to: $(pwd)" you will notice that each <f8> does something with the current line and move the cursor to the next line; first three <f8>s - on the lines that start with red stars - create a window setting like this, ________________________________ | | | | notes | target | | buffer | buffer | | (this intro) | ("*shell*") | | | | |________________|_______________| and the last three <f8>s - on "non-red star lines" - send the lines echo "We are at: $PWD" cd /tmp/ echo "We changed to: $(pwd)" to the "target buffer", that in this case is the buffer with a terminal running a shell; the shell behaves exactly is if the the user had typed those three lines at its prompt.
1.1. Another targetIf you put the cursor at the first red star line below and type <f8> six times you will get something very similar to the example above, * (eepitch-python) * (eepitch-kill) * (eepitch-python) 1 + 2 print("Hello " + "world") but now the window setting will be like this: ________________________________ | | | | notes | target | | buffer | buffer | | (this intro) | ("*python*") | | | | |________________|_______________| and the target buffer will be called "*python*", and it contains a terminal running a Python interpreter.
1.2. Two targetsThe demo below uses an advanced feature - the function `find-3EE', explained at: (find-multiwindow-intro "find-3EE") to create a 3-window setup like this: _______________________ | | | | | *shell* | | notes |____________| | buffer | | | | *python* | |__________|____________| Some non-red star lines in it send the current line to the "*shell*" buffer, and some send the current line to the "*python*" buffer. The red star lines with "(eepitch-shell)" set the target to "*shell*", and the red star lines with with "(eepitch-python)" set the target to "*python*". Try it! Put the cursor on the first red star line below, then type <f8> twelve times: * (find-3EE '(eepitch-shell) '(eepitch-python)) * (eepitch-shell) echo Hello... > /tmp/o * (eepitch-python) print(open("/tmp/o").read()) * (eepitch-shell) echo ...and bye >> /tmp/o * (eepitch-python) print(open("/tmp/o").read())
1.3. Two targets, two windowsThe demo below is similar to the one with three windows in the previous section, but it uses just two windows - and the window at the right alternates between "*shell*" and "*python*": * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) echo Hello... > /tmp/o * (eepitch-python) * (eepitch-kill) * (eepitch-python) print(open("/tmp/o").read()) * (eepitch-shell) echo ...and bye >> /tmp/o * (eepitch-python) print(open("/tmp/o").read())
2. How <f8> worksThe key <f8> works in one way when the cursor is on a line that starts with a red star - it executes everything at the right of the "*" as Lisp code, and then moves down - and in a totally different way on non-red star lines: on non-red star lines it makes sure that the target buffer is being displayed, then sends the current line to the target buffer "as if the user had typed it", then moves down.
2.1. Eepitch blocksA block of three red star lines like * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) or * (eepitch-python) * (eepitch-kill) * (eepitch-python) is called an "eepitch block". The _final effect_ of typing <f8> thrice on an eepitch block like this * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) is easy to describe: after the third <f8> we get a window setting like this, ________________________ | | | | notes | target | | buffer | buffer | | | ("*shell*") | | | | |__________|_____________| where the target buffer is running a _new_ shell...
2.2. `(eepitch-kill)'The effect of running <f8> on a line like * (eepitch-kill) is to kill the current target. More precisely, `(eepitch-kill)' kills a buffer with the name stored in the variable `eepitch-buffer-name', if a buffer with that name exists; in the examples above the target buffer names are always either "*shell*" or "*python*". If we are in a window setting like this and the target is "*shell*" ________________________ | | | | notes | target | | buffer | buffer | | | ("*shell*") | | | | |__________|_____________| and we run `(eepitch-kill)' the window setting becomes this: _____________________ | | | | notes | some | | buffer | other | | | buffer | | | | |__________|__________| which may be confusing...
2.2. `(eepitch-shell)'The effect of running <f8> on a line like * (eepitch-shell) can be *roughly* described as: a) Set the name of the target buffer to "*shell*". b) If the target buffer does not exist, create it - by running `(shell)'. c) If the target buffer is not being displayed then display it - by creating a two-window setting with the target buffer at the right. This is a simplification, though... the sexp (eepitch-shell) runs this, (eepitch '(shell)) and the name of the target buffer is obtained from the sexp `(shell)' by running it in a certain way.
2.3. `eepitch'The documentation for `eepitch' says: (eepitch CODE) Set up a target for eepitch and make sure it is displayed in another window. The argument CODE must be a "shell-like sexp", i.e., one that when evaluated always switches to a buffer with a fixed name, and when that buffer does not exists it creates it. For example, running `(shell)' switches to a buffer whose name is "*shell*"; the name of the target buffer can obtained from the sexp `(shell)' by running this: (save-window-excursion (shell) (setq eepitch-buffer-name (buffer-name (current-buffer))))
2.4. `(eepitch-python)'The effect of running <f8> on a line like * (eepitch-python) is very similar to `(eepitch-shell)', but it uses "*python*" as the name of the target buffer. `(eepitch-python)' is defined as: (eepitch '(find-comintprocess "python" "python"))
2.5. `find-comintprocess'The sexp (find-comintprocess "buffer name" "program and args") switches to a buffer called "*buffer name*" and if that buffer does not have an associated process then it runs "program and args" there in comint mode. Comint is explained here: (find-enode "Shell Mode") The sexp (eepitch-comint "buffer name" "program and args") works as an abbreviation for: (eepitch '(find-comintprocess "buffer name" "program and args")) Most `eepitch-<lang>' functions are defined using `eepitch-comint'. See: (find-eev "eepitch.el" "eepitch-langs") (find-eev "eepitch.el" "find-comintprocess") (find-eev "eepitch.el" "find-comintprocess" "defun eepitch-comint ")
2.6. `find-vtermprocess'Some programs don't run well inside comint buffers, but run well inside other terminal emulators that are harder to set up but that handle more escape sequences, like vterm: https://github.com/akermu/emacs-libvterm Most `eepitch-<lang>' functions are defined using `eepitch-comint' and `find-comintprocess'; the `eepitch-<lang>' functions that need vterm are defined using `eepitch-vterm' and `find-vtermprocess'. See: (find-eev "eepitch.el" "other-terms") (find-eev "eepitch.el" "eepitch-langs-vterm")
3. Test blocksSuppose that we have a file "foo.py" containing this (without the indentation): def square (x): return x*x """ * (eepitch-python) * (eepitch-kill) * (eepitch-python) execfile("foo.py", globals()) print(square(5)) """ Python treats everything between the first and the second `"""'s as a multiline comment, and ignores it - but for us this multiline comment contains an eepitch block that starts a Python interpreter, then a line that loads "foo.py" in it, then a line that tests the function "square" defined in foo.py. We call the block between the `"""'s a "test block". A "test block" is a multiline comment in a Python script, a Lua script, or in a script in one of the other supported languages - we call them the "ambient script" and the "ambient language" - that contains at least: 1) an eepitch block that runs an interpreter for the ambient language, 2) a line that loads the ambient script in that interpreter, 3) code that tests functions defined in the ambient script. We can insert a test block in the current buffer by running `M-x ee-insert-test', or `M-x eeit'. The current implementation of M-x ee-insert-test' uses the name of the major mode to decide which other function to call. If you are in a buffer in which the value of the variable major-mode is `FooBar-mode' then `M-x eeit' tries to run the function `ee-insert-test-FooBar-mode', and yields an error if that function does not exist. To add support for `FooBar-mode' to `M-x eeit', just define a function with the right name. See the source for examples: (find-eev "eev-testblocks.el" "examples") My presentation at the EmacsConf2021 was about test blocks. Links: Pages: http://anggtwu.net/emacsconf2021.html https://emacsconf.org/2021/talks/test/ Slides: http://anggtwu.net/LATEX/2021emacsconf.pdf Video: https://emacsconf.org/2021/talks/test/ (find-eev2021video "0:00")
3.1. `find-eeit-links'If you run this, (find-eeit-links 'lua-mode) you will get a buffer with: a) links to inspect the current definition of `ee-insert-test-lua-mode', b) links that let you compare that with the `ee-insert-test-'s for other major modes, c) a barebones `(defun ...)' that lets you redefine `ee-insert-test-lua-mode'. If you run `find-eeit-links' interactively with `M-x' then it will run as: (find-eeit-links <current-major-mode>) and you can use that to inspect the `ee-insert-test-' support for the current major mode, or to implement it yourself.
3.2. Test blocks as documentationI found that test blocks are a really good way to document my programs. Most people think that they look very alien at first, but they understand them immediately when they see a demo - so here are some demos. You need to have lua5.1 in your path to run them; they use eepitch-lua51, that calls lua5.1. So try this first: * (eepitch-lua51) * (eepitch-kill) * (eepitch-lua51) print("Hello!") for k,v in pairs(os) do print(k, v) end os.exit() If it works then try the demo below. Note that eepitch treats the lines with two red stars as comments; the sexps in "**"-lines are hyperlinks, and the ones in "*"-lines are not. * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) rm -Rv /tmp/dednat6/ mkdir /tmp/dednat6/ cd /tmp/dednat6/ wget http://anggtwu.net/dednat6-minimal.zip unzip dednat6-minimal.zip * (code-c-d "dn6lua" "/tmp/dednat6/dednat6/" :anchor) * (setenv "LUA_INIT" "@/tmp/dednat6/dednat6/edrxlib.lua") ** (find-dn6lua "edrxlib.lua") ** (find-dn6lua "treetex.lua" "TreeNode-tests" 3) ** (find-dn6lua "rect.lua" "dedtorect-tests" 3)
3.3. `eepitch-preprocess-line'The key <f8> is bound to `eepitch-this-line'. You can see the source code of that function by following these hyperlinks: (find-eev "eepitch.el" "eepitch-this-line") (find-efunction 'eepitch-this-line) The source of `eepitch-this-line' contains this mysterious setq: (let ((line (buffer-substring (ee-bol) (ee-eol)))) (setq line (eepitch-preprocess-line line)) ... ) By default `eepitch-preprocess-line' is a no-op that simply returns this argument unchanged. Its definition is just this: ;; See: ;; (find-eepitch-intro "3.3. `eepitch-preprocess-line'") (defun eepitch-preprocess-line (line) line) Remember that the behavior of <f8> is usually described in human-friendly terms as: "lines starting with two red stars are treated as comments, lines starting with a red star are executed as lisp, and other lines are sent to the target buffer." The function `eepitch-preprocess-line' is a stub that lets us change in arbitrary ways what is the "line" that is processed in the sense above. Let's see a simple example. Try: (replace-regexp-in-string "^abc" "" "foo") (replace-regexp-in-string "^abc" "" "abcfoo") (replace-regexp-in-string "^abc" "" "abcfooabc") (replace-regexp-in-string "^abc" "" "fooabc") A `(replace-regexp-in-string "^abc" "" ...)' deletes an initial "abc" from a string if that string starts with "abc", but returns other strings unchanged. So, if we redefine `eepitch-preprocess-line' in this way, (setq eepitch-preprocess-regexp "^#: ") (defun eepitch-preprocess-line (line) (replace-regexp-in-string eepitch-preprocess-regexp "" line)) then the (let ((line (buffer-substring (ee-bol) (ee-eol)))) (setq line (eepitch-preprocess-line line)) ... ) in the source of `eepitch-this-line' will first set `line' to the string in the current line between the beginning-of-line and the end-of-line, and then if `line' starts with "#: " that prefix is deleted from it; and it is this "line after removing the prefix" that is processed according the the rules of two red stars/one red star/no red stars. Now let's see a practical example. Gnuplot does not support multiline comments, and using exactly the hack above I can make <f8> ignore the prefix "#: ". Then a block like this in a Gnuplot file #: * (eepitch-shell) #: * (eepitch-kill) #: * (eepitch-shell) #: gnuplot #: load "foo.plt" #: plot sin(x) works as a test block. Running (setq eepitch-preprocess-regexp "^") or (defun eepitch-preprocess-line (line) line) disables the hack. A similar technique for using test blocks in makefiles is explained here: http://anggtwu.net/eev-make.html (find-2022eevmake0video) Running `M-x eeit' in a makefile runs `ee-insert-test-makefile-mode', that inserts a test block like this: # See: (find-eepitch-intro "3.3. `eepitch-preprocess-line'") # (setq eepitch-preprocess-regexp "^") # (setq eepitch-preprocess-regexp "^#T ") # #T * (eepitch-shell) #T * (eepitch-kill) #T * (eepitch-shell) #T make -f nameofthismakefile TARGET The lines that start with just "# " serve as a reminder that you need a special setup to make that test block work. Some languages have syntaxes for comments that are much more eepitch-unfriendly and test-blocks-unfriendly than this. An extreme example is SmallTalk, in which comments are delimited by double quotes and can't contain double quotes. It should be possible to use `eepitch-preprocess-line' to add support for test blocks in SmallTalk source files - but I haven't tried that yet. -=-=-=-=- Old stuff:
1. MotivationSuppose that we have to do some reasonably complex task using a shell, and that we want to take notes of what we do because we might have to do something similar later. The two usual ways to interact with a shell are: 1) through a _script_, that is, by preparing in advance all commands to be executed, putting them in a script file, and then running that file, 2) _interactively_, by typing the commands one by one on a shell prompt. Suppose that we have to discover which commands to run as we go; that rules out preparing a script beforehand, so we need to use the shell interactively. After issuing the right commands, the two usual ways to retrieve what we did are: a) through the _shell history_, which records the last commands that the shell received, b) by looking at the full _transcript_ of our interaction with the shell. The way (a) gets a list of commands, without comments, that can be then saved into a text editor; the way (b) may require some tricky editing to isolate the commands from their outputs. Eepitch.el implements a simple alternative way of interacting with shells (and other shell-like programs) while keeping notes. It has only one essential key binding, <F8>, which is better explained through the executable example in the next section, and two unessential features, `M-T' and "*", which will be explained later.
2. The main key: <F8>Emacs can run a shell in a buffer, and it can split its frame into windows, like this: ___________________ | | | | our | a | | notes | shell | | | buffer | |_________|_________| The usual way to use a shell buffer is to move the cursor there and type commands into its prompt; the eepitch-y way is to leave the cursor at the "notes" buffer, write the commands for the shell there, and send these commands to the shell with <F8>. Here's what <F8> does: When we type <F8> on a line that starts with a red star ("*"), it executes the rest of the line as Lisp, and moves down; when we type <F8> on a line that does not start with a "*", it makes sure that the "target buffer" is being displayed (the "target" is usually the buffer called "*shell*"), it "send"s the current line to the target buffer, and moves down. "Sending the current line to the target buffer" means copying the contents of the current line to the target - as if the user had typed that line there by hand -, then "typing" a <RET> at the target buffet. Please try that in the example after this paragraph, by typing <F8> six times starting at the first line that says "* (eepitch-shell)". The three red star lines at the top will create a target buffer, destroy it, and create it again; the other three lines will send commands to the target shell. * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) echo "We are at: $PWD" cd /tmp/ echo "We changed to: $(pwd)"
3. Other targetsJust like `(eepitch-shell)' creates a shell buffer and sets the eepitch target to it, `(eepitch-python)' creates a buffer with a Python interpreter and uses it as the eepitch target. Try: * (eepitch-python) * (eepitch-kill) * (eepitch-python) def square (x): return x*x print(square(5)) We can use several targets at the time, alternating between them. For example: * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) echo Hello... > /tmp/o * (eepitch-python) * (eepitch-kill) * (eepitch-python) print(open("/tmp/o").read()) * (eepitch-shell) echo ...and bye >> /tmp/o * (eepitch-python) print(open("/tmp/o").read()) There is a (much) more advanced example of working with several targets here: (find-prepared-intro "An `ee' for Python")
4. More on eepitch-killNote that `(eepitch-kill)' kills the _current_ target, that may or may not be a shell buffer, a Python interaction buffer, etc... That explains the first line in blocks like: * (eepitch-python) * (eepitch-kill) * (eepitch-python) and: * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) by running the first `(eepitch-python)' we can be sure that the following `(eepitch-kill)' will kill the Python buffer, not the shell buffer! And the last `(eepitch-python)' in the block of three lines will then create a new Python interaction buffer, erasing all definitions done in previous sessions.
5. Creating eepitch blocks: `M-T'Write just "shell" or "python" in a line, then type `M-T' (i.e., meta-shift-t) there. The line will be turned into three - an "* (eepitch-xxx)", an "* (eepitch-kill)", and an "* (eepitch-xxx)". We call these blocks of three lines "eepitch blocks". Try this below, converting the "shell" into an eepitch block for starting a shell. shell pwd cd /tmp/ pwd
6. Red starsEepitch.el sets the glyph for the char 15 to a red star in the standard display table. In layman's terms: eepitch.el tells Emacs that the character 15 should be displayed as a red star. The character 15 corresponds to control-O, whose default representation on screen would be "^O". You can enter a literal ^O in a buffer by typing `C-q C-o'.
7. For more informationOn hyperlinks: (find-eval-intro) On keys similar to `M-T': (find-wrap-intro) An older text about eepitch: (find-eev "eepitch.readme") (find-eev "eepitch.readme" "the-trivial-case") (find-eev "eepitch.readme" "red-stars") (find-eev "eepitch.readme" "eepitch-blocks") (find-eev "eepitch.readme" "eepitch-blocks") Many functions like `eepitch-shell': (find-efunction 'eepitch-bash) What functions can generate target buffers: (find-eevfile "eepitch.el" "shell-like sexp") (find-efunction 'eepitch)