The "elisp" Practice

Table of Contents

Turing Completeness Makes You Diving in Your Digital World.

Noting new, but quote for creation.

1. Introduction

Emacs Lisp is a programming language as "elisp" to write the most of GNU Emacs, as well as its extensions. The elisp is a full computer programming language and it has capabilities for scanning and parsing text through editing commands.

These editing commands are elisp functions that can conveniently be called from Lisp programs, and parameters for customisation are ordinary Lisp variables to handle files, buffers, displays, subprocesses, and so on. The elisp has Turing completeness to facilitate Emacs become a platform for coordinating the information flow.

2. Hacking Emacs with elisp

First things first. How to make a elisp program running in Emacs?

(message "Hello World ~ ")
            
  • In lispinteraction-mode
    LispInteractionMode is the mode of the buffer first selected when Emacs starts. The first buffer is called the ‘*scratch*’ buffer. It is useful for evaluating EmacsLisp expressions. Typing ‘C-j’ after an expression will print the result on the next line.

    Switch to the buffer \*scratch\* and set the mode as "lisp-interaction-mode" by M-x lispinteraction-mode.

    Put the cursor at the end of "(message "Hello World ~ ")" and press "C-j". Then, the results will be shown in the following.

    "C-x C-e" the make the code run and echo the results in the mini-buffer area.

  • ielm mode
    IELM (Interactive Emacs Lisp Mode) is a nice mode that gives you an interactive Emacs Lisp shell that you can use for exploratory programming. You can start it by pressing M-x ielm. You'll be taken to a buffer named "ielm", where Emacs Lisp expressions you've entered will be evaluated on pressing the Return key. IELM offers some nice features like tab autocompletion of symbol names and the ability to span an expression over several lines.

    * Welcome to IELM * Type (describe-mode) for help.
    ELISP> 1
    1
    ELISP> (+ 1 1)
    2
    ELISP> ;; comment here
    ELISP> (defun fac (n)
    (if (= 0 n)
    1
    (* n (fac (- n 1)))))
    fac
    ELISP> (fac 4)
    24
    ELISP>

    y default, IELM evaluates complete expressions automatically as soon you as you press Enter. So one thing to remember is that if you want to have multi-line expression (like above), you must make sure that after each line the expression is not complete (i.e., the brackets are not balanced) – otherwise the expression will be evaluated too early. That makes modes like autopair or paredit a bit inconvenient for this. If you don't like that behavior, you can do:

    (setq ielm-dynamic-return nil)
                    

    which will allow you to Enter as much as you want and only evaluate things when you press C-j. But then you might as well use scratch I suppose. Personally, I use IELM mostly as a calculator.

3. The practice of elisp

3.1. Basic

3.1.1. Simple elisp examples:

  • Hello world
  • wrap markup region in a block <b></b>
  • mark a word, a line, a paragraph, a region
  • replace a string in a region
  • Text Editing:
    • Insert:
      • insert text
      • insert around region
      • insert a random number
    • Delete text:
      • char: delete-char
      • region: delete-region
      • buffer: erase-buffer
      • kill-ring: delete-and-extract-region

3.1.2. nil and t \\

In Emacs Lisp, the symbol nil has three separate meanings: it is a symbol with the name ‘nil’; it is the logical truth value false; and it is the empty list—the list of zero elements. When used as a variable, nil always has the value nil. As far as the Lisp reader is concerned, ‘()’ and ‘nil’ are identical: they stand for the same object, the symbol nil. The different ways of writing the symbol are intended entirely for human readers. After the Lisp reader has read either ‘()’ or ‘nil’, there is no way to determine which representation was actually written by the programmer. In this manual, we write () when we wish to emphasize that it means the empty list, and we write nil when we wish to emphasize that it means the truth value false. That is a good convention to use in Lisp programs also. (cons 'foo ()) ;Emphasize the empty list (setq foo-flag nil) ; Emphasize the truth value false In contexts where a truth value is expected, any non-nil value is considered to be true. However, t is the preferred way to represent the truth value true. When you need to choose a value that represents true, and there is no other basis for choosing, use t. The symbol t always has the value t. In Emacs Lisp, nil and t are special symbols that always evaluate to themselves. This is so that you do not need to quote them to use them as constants in a program. An attempt to change their values results in a setting-constant error.

3.1.3. List \\

  1. Cons Cell \\

    A cons cell, such as (1 . 2), is an object that consists of two slots, called the CAR slot and the CDR slot. Each slot can hold any Lisp object. We also say that the CAR of this cons cell is whatever object its CAR slot currently holds, and likewise for the CDR.

    • How to create a Cons pair (cons val1 val2)
    • A list is made of cons (list "a" "b" "c") which is equal to: (cons "a" (cons "b" (cons "c" nil)))
  2. list \\

    A list is a series of cons cells, linked together so that the CDR slot of each cons cell holds either the next cons cell or the empty list. The empty list is actually the symbol nil. Because most cons cells are used as part of lists, we refer to any structure made out of cons cells as a list structure.

    • How to create a list
      • Create a list
      • Create a list of number sequence
    1. association list
    2. Property list
      • set it as symbol
      • get from a symbl
      • add an element pair
      • get a key and value
      • get a key's value
      • change a key's value
      • check whether a key exists
  3. TODO Operate lists \\
    • Test whether a list
    • Get the length of a list
      (length (list 1 2 3 4 5))
    • Get an specific element from a list (car (list 1 2 3 4 5)) #+endsrc

      (cdr (list 1 2 3 4 5))
                              
    • Get a sublist from a list
    • Add an elements to a list
    • Append lists
    • Modify a list
    • Convert a list to a string
  4. Processing a list
    • mapc
    • dolist
    • do times

3.1.4. programming

  • sequence
  • selection
  • looping while

3.2. medium

3.2.1. Data Structure

  1. Sequence
    1. Vector
      • How to create a vector
        • Nested a vector
      • Get the length of a vector
      • To fill a vector
      • Get an element(s)
      • Modify a vector
      • concat vectors
      • From a vector(s) to a list
    2. Type and Functions
    3. Apply mapcar to derive a list
      • by functions

        (mapcar '1+ '(1 2 3)
                                    
      • with lambda

        (mapcar
                                      (lambda (x) (+ x 1))
                                      (list 1 2 3 )
                                      )
                                    
    4. dolist or mapc
      (setq alphabets '(a b c d e))
                                (dolist (element alphabets)
                                (print element))
                              
    5. dotimes: as for to loop with fixed times
      (let (s) (-dotimes 3 (lambda (n) (!cons n s))) s)
                              
    6. loop

      we could find the constructs available in the loop.el library: https://github.com/Wilfred/loop.el

      (let ((x 0)
                                (sum 0))
                                ;; sum of 0..5
                                (loop-while (< x 5)
                                (setq sum (+ sum x))
                                (setq x (1+ x)))
                                sum)
                              
  2. Hash Table
    • Create a hash table

    The printed representation for a hash table consists of ‘#s’ followed by a list beginning with ‘hash-table’.

    #s(hash-table size 30 data (key1 val1 key2 300))
                        
    (setq ahashtable #s(hash-table size 30 data (key1 val1 key2 300)))
                        
    • add a key&value
    • remove a key&value
    • get a key value
    • count the number of entries
    • Get all keys
    • operate all keys with a function
    • Clear all entries

3.2.2. Function

  1. Define: Hello-world
    • define a function

      (defun function-name (arguments-list)
                                "document string"
                                body)
                              

      example:

      (defun hello-world (name)
                                "Say hello to user whose name is NAME."
                                (message "Hello, %s" name))
                              
  2. With Parameters
    • with optional parameters

                                (defun ops (aa bb &optional cc dd)
                                "with optional parameters"
                                (message "The first two default parameter: %s, %s; the other two optional parameters: %s, %s." aa bb cc dd))
                              
    • with rest parameters

      (defun rp (aa bb &rest cc)
                                "with rest arguments"
                                (message "%s" cc) ; This "cc" is a list
                                )
                              
  3. As a command
  4. Types
    • Types
      • primitive
      • Lambda
      • Special Form
      • Macro
      • Command how to write a command?

        (defun hello-elisp-world()
                                      "Insert "Hello elisp World" at cursor position."
                                      (interactive)
                                      (insert "Hello elisp World"))
                                    

        You could also specify parameters and local variables, see the following template from Xah Lee:

        (defun my-command (p1, p2, $optional, p3, p4, $rest, p5)
                                      "One sentence summary of what this command do. Less than 70 chars, try.
        
                                      More details here. Be sure to mention the return value if relevant.
                                      Lines here should not be longer than 70 chars,
                                      and don't indent them."
                                      (interactive)
                                      (let (var1 var2 etc)
                                      ;; do something here
                                      ;; last expression is returned
                                      ))
                                    
  5. Advanced topics:
    1. Lambda
      • lambda

        (funcall (lambda (name)
                                      (message "Hello, %s!" name)) "Emacser")
                                    

        It is equal to the following:

        (setq foo (lambda (name)
                                      (message "Hello, %s!" name)))
                                      (funcall foo "Emacser")
                                    
    2. Doc String
    3. variables & Scops
    4. mapcar & mapc

3.2.3. Error Handling in Emacs lisp

How does Emacs handle errors in elisp?

  • throwing the exception
  • catch the exception
  • handling clean-up

(error …) throws the most basic exception as signals. If an error were popping up, emacs will enter the debugger.

  • unwind-protect unwind-protect is the emacs way of implementing a finally clause. In the following code, when you exit from the debugger, "Hello" will be inserted in the buffer:

    (unwind-protect
                          (error "error popping up")
                          (insert "Hello, we are handling errors"))
                        
  • condition-case Try/Catch is spelled "condition-case". there is one handler per signal we want to handle. All error signals are derived from the popup error, so catching error catches any remaining signals.

    (condition-case var bodyform &rest handlers)
                        
    (unwind-protect
                          (let (retval)
                          (condition-case ex
                          (setq retval (error "Hello"))
                          ('error (message (format "Caught exception: [%s]" ex))))
                          retval)
                          (message "Cleaning up..."))
                        

    It can be wrapped in a macro:

    (defmacro safe-wrap (fn &rest clean-up)
                          `(unwind-protect
                          (let (retval)
                          (condition-case ex
                          (setq retval (progn ,fn))
                          ('error
                          (message (format "Caught exception: [%s]" ex))
                          (setq retval (cons 'exception (list ex)))))
                          retval)
                          ,@clean-up))
                        

    An example as follows:

    (safe-wrap (error "Hello") (message "Unwinding..."))
                          Caught exception: [(error Hello)]
                          Unwinding...
                          (exception (error "Hello")) ;; return value
    
                          ;; (safe-wrap (message "Hello") (message "Unwinding..."))
                          Hello
                          Unwinding...
                          "Hello" ;; return value
    
                          (safe-wrap (/ 1 0))
                          Caught exception: [(arith-error)]
                          (exception (arith-error)) ;; return value
                        

To be added. https://www.gnu.org/software/emacs/manual/html_node/elisp/Handling-Errors.html

3.2.4. Text-Processing Overview

  • Cursor Position
    • (point), (point-min) and (max)
    • (region-beginning/end)
    • (line-beginning/end-position)
  • Move Cursor, Search Text
    • goto-char
    • forward-char
    • backward-char
    • search-forward
    • search-backward
    • re-search-forward
    • re-search-backward
    • skip-chars-forward
    • skip-chars-backward
  • Delete, Insert and Change Text
    • delete-char
    • delete-region
    • buffer-string
    • caitalize-region
  • String
    • length
    • substring
    • replace-regexp-in-string
  • Buffer
    • buffer-name
    • buffer-file-name
    • set-buffer
    • save-buffer
    • kill-buffer
    • with-current-buffer
  • File
    • find-file
    • write-file
    • insert-file-contents
    • append-to-file
    • rename-file
    • copy-file
    • delete-file
    • file-name-directory
    • file-name-nondirectory
    • file-name-extension
    • file-name-sans-extension

example:

(defun insert-p-tag ()
                  "Insert <p></p> at cursor point."
                  (interactive)
                  (insert "<p></p>")
                  (backward-char 4))
                

3.2.5. Define local variables

(defun circle-area-1 (radix)
                  (let (pi area)
                  (setq pi 3.14)
                  (setq area (* pi radix radix))
                  (message "The radix is %s and the area is %s." radix area)))
                
(defun circle-area-2 (radix)
                  (let ((pi 3.14) area)
                  (setq area (* pi radix radix))
                  (message "The radix is %s and the area is %s." radix area)))
                
(defun circle-area-3 (radix)
                  (let ((pi 3.14)
                  (area (* pi radix radix)))
                  (message "The radix is %s and the area is %s." radix area)))
                

3.2.6. Get User input

  • file name: read-file-name
  • directory: read-directory-name
  • string: read-string
  • regex: read-regexp
  • number: read-number
  • from a list: completing-read

3.2.7. Mark and Region

  • What is a mark or region
    • region-beginning
    • region-end
    • set/push-mark
  • an active region
    • mark-active
  • transient-mark-mode
  • Text Selection
    • use-region-p
      1. transient-mark-mode is on
      2. mark-active is true
      3. region isn't empty by checking use-empty-active-region
  • To create an active region
  • To get an active region or current word, line, Text-block, file-path, buffer, etc.

3.2.8. Get Strings from buffer or region

  • get text from a specific range: begin/end positions: buffer-substring-no-properties
  • Get selected text:
    $itemblue-red (include $ _ -) itemblue-red (include _ -) itemblue (include _) item (not include any $ _ -)
  • Get a line
  • Get Thing at Point word 'symbol 'list 'sexp 'defun 'filename 'url 'email 'sentence 'whitespace 'line 'number 'page

    ;; grab the current filename
                          (setq str (thing-at-point 'filename))
                        
    • Get text between brackets

3.2.9. Operations on lines

  • get positions of a line:
    • beginning of a line
    • end of a line
  • move cursor to beginning/end of a line
  • mvoe to next line
  • open a new line above/below the current line
  • duplicate the current line below

3.2.10. Cut Copy Paste to/from kill-ring

  • copy region to kill-ring
  • kill region to kill ring
  • string to kill ring
  • Append string to kill ring
  • paste from kill ring
  • mark a region

3.2.11. Thing-at-point

  • word
  • line
  • text block
  • file path
  • buffer, etc

3.2.12. Get Text Block (not clear about the part)

By Xah Lee. Date: 2020-06-10. Last updated: 2022-01-20. Often, you want a interactive command to work on a text block, without user having to manually select it. (text block is group of lines separated by empty lines, similar to a paragraph.)

Commands working on text block is especially useful in programing language source code.

3.2.13. narrow-to-region

Emacs has a narrow-to-region command. It lets users make the buffer show only the selected region, as if the entire buffer is just that text. This is convenient when you want to do some editing only in this region. Alt+x widen will make buffer show whole buffer again.

The narrow-to-region is also super useful in elisp code. However, when you command is done, you should return to the narrow region state of the user before the command was called. Emacs lisp provides save-restriction for this purpose.

You could select a region and M-x narrow-to-region (C-x n n) to open the region in a new buffer. After editing, you could use save-restriction to return to the narrow region state before the command was called. M-x widen or C-x n w will work to reture to the previous buffer.

3.2.14. Find and Replace operations

  • search-forward
  • search-backward
  • re-search-forward
  • re-search-backward
  • goto-char
  • replace-match
  • case-fold-search
  • match-string (get match string, which could be counted.)

3.2.15. Get universal-argument

  • current-prefix-arg: hold the value of universal argument Key Input Value of current-prefix-arg Numerical Value No universal arg called. nil 1 Ctrl+u - Symbol - -1 Ctrl+u - 2 Number -2 -2 Ctrl+u 1 Number 1 1 Ctrl+u 4 Number 4 4 Ctrl+u List '(4) 4 Ctrl+u Ctrl+u List '(16) 16

3.3. Advance

3.3.1. Regular Expression

3.3.2. How to write script

  • run elisp scrip in shell

    emacs --script file.el
                        
  • get command line arguments

    emacs --script do.el arg1 arg2
                        
    (message "argv 0: %s" (elt argv 0)) ; %s is for string
                          (message "argv 1: %s" (elt argv 1))
                          (message "argv 2: %s" (elt argv 2))
                          (message "argv 3: %s" (elt argv 3))
                        
  • file and directory functions Functions on files

    • file-exists-p
    • rename-file
    • copy-file
    • delet-file
    • set-file-modes

    Functions on directory

    • derectory-files
    • make-directory
    • delete-directory
    • copy-directory

    File Path Functions

    • file-name-directory
    • file-name-nondirectory
    • file-name-extension
    • file-name-sans-extension
    • file-relative-name
    • expand-file-name
    • default-directory
  • buffer functions
    • buffer-name
    • buffer-file-name
    • with-current-buffer: create a temporarily buffer to use it.

      (with-current-buffer temBuf
                                ;; the code to process text here)
                              
    • set-buffer: switch to a given buffer, which is not visible.
    • with-temp-buffer: to create a temporary buffer
    • buffer-string: return buffer content as string
    • generate-new-buffer
    • get-buffer-create

      ;; create new buffer, without undo info. make sure the string passed is unique and has space in front
                                (setq newBuf (get-buffer-create " xyz"))
      
                                ;; make it current (but does not make it visible), so all insert etc operations works on it.
                                (set-buffer newBuf)
                              
    • kill buffer
  • read files
    • To process thousands of files, read only, use with-temp-buffer.

      (defun my-process-file (fPath)
                                "Process the file at path FPATH …"
                                (with-temp-buffer
                                (insert-file-contents fPath)
                                ;; do something
                                ))
                              
    • To write to file, us write-region in the body.
    • find-file

      (find-file "~/test.txt")
                              
    • kill-bufer: close file

      ;; close a buffer
                                (kill-buffer myBuffName)
                              
  • write files
    • write-region
    • save-buffer
    • write-file
    • append-to-file
    • with-temp-file: to create a new file
    • open, read, write when change

      (defun my-process-file (fPath)
                                "Process the file at path FPATH …"
                                (let ((fileChanged-p nil))
                                (with-temp-buffer
                                (insert-file-contents fPath)
      
                                ;; process text
                                ;; set fileChanged-p to t or nil
      
                                (when fileChanged-p (write-region (point-min) (point-max) fPath)))))
                              
  • Walk Directory List File Names
    • list files in the given directory (directory-files DIRECTORY &optional FULL MATCH NOSORT)
    • list direcorty and all subdir (directory-files-recursively DIR REGEXP &optional INCLUDE-DIRECTORIES)
    • Fliter by Dir Depth Level (review)
    • Skipping Sbudir (review)
  • Get Dired Marked Files

    ;; apply a function to dired's marked files
    
                          (require 'dired)
    
                          (defun xah-open-dired-marked ()
                          "Open marked files in dired."
                          (interactive)
                          (mapc 'find-file (dired-get-marked-files))
                          )
    
                          ;; test
                          ;; M-x dired, then mark some files, then M-x xah-open-dired-marked
    
                          ;; replace find-file to your own function
                        
  • Call Shell Command
    • shell-command
    • shell-command-to-string
    • start-process
    • start-process-shell-command
  • call PowerShell

    ;; on Microsoft Windows 10, call PowerShell Core to generate UUID
                          (shell-command "pwsh.exe -Command [guid]::NewGuid().toString()" t)
                        
    ;; on macOS, call PowerShell Core to generate UUID
                          (shell-command "pwsh -Command '[guid]::NewGuid().toString()'" t)
                        
  • A good way to get the script name

    ;; get the current elisp script's name at run time
                          (or load-file-name buffer-file-name)
                        
    • Get Full Path from Relative Path at Run Time file A calls file B, file B calls file C.

      (defun xah-get-fullpath (@file-relative-path)
                                (concat (file-name-directory (or load-file-name buffer-file-name)) @file-relative-path))
                              

3.4. Master elisp

4. Create your own applications

4.1. Interaction with external commands, software, platform and frameworks.

4.1.1. Excute the external shell commands

You can execute an external shell command from within Emacs using `M-!’ (‘shell-command’). The output from the shell command is displayed in the minibuffer or in a separate buffer, depending on the output size. When used with a prefix argument (e.g, `C-u M-!’), the shell-command output is inserted in the current buffer at point.

  • `M-!’ (‘shell-command’)

    (defun call-ls-command()
                          "This is a function to call ls command in the terminal and insert it in the buffer."
                          (interactive)
                          (shell-command "ls -al"))
                        

    Then, excute the command: "(call-ls-command)".

  • `C-u M-!’: the shell-command output is inserted in the current buffer at point.
  • `M-|’ (‘shell-command-on-region’), You can switch to that buffer using ‘C-x b’
  1. Using the whole buffer

    If you are programming using an interpreted language, such as Perl or Python, or a shell script, such as BASH, you might want to run your entire program through the interpreter. To do that, use ‘C-x h’ (‘mark-whole-buffer’), then use `M-|’ to send all of the buffer text to the interpreter.

    This function runs a command with the whole buffer as input:

    (defun shell-command-on-buffer ()
                          "Asks for a command and executes it in inferior shell with current buffer
                          as input."
                          (interactive)
                          (shell-command-on-region
                          (point-min) (point-max)
                          (read-shell-command "Shell command on buffer: ")))
                        

    You can also bind this function with a key:

    (global-set-key (kbd "M-\"") 'shell-command-on-buffer)
                        
  2. Running external programs from Emacs (Rofi - Dmenu)

    Running external programs from emacs as Windows Manager programs (Dmenu/Rofi):

    (defun e-run-command ()
                          "Run external system programs. Dmenu/Rofi-like.  Tab/C-M-i to completion
                          n-[b/p] for walk backward/forward early commands history."
                          (interactive)
                          (require 'subr-x) 
                          (start-process "RUN" "RUN" (string-trim-right (read-shell-command "RUN: "))))
                        
  3. A wrapper with shell-command or python-scrip
    (defun do-something-region (startPos endPos)
                          "Do some text processing on region.
                          This command calls the external script “wc”."
                          (interactive "r")
                          (let (cmdStr)
                          (setq cmdStr "/usr/bin/wc") ; full path to your script
                          (shell-command-on-region startPos endPos cmdStr nil t nil t)))
                        
    (defun do-something-region (startPos endPos)
                          "Do something for processing the selected region."
                          (interactive "r")
                          (let ((cmdStr))
                          (setq cmdStr "/usr/bin/wc")
                          (shell-command-on-region startPos endPos cmdStr nil t nil t)))
                        
    (defun my-call-script-xyz ()
                          "example of calling a external command.
                          passing text of region to its stdin.
                          and passing current file name to the script as arg.
                          replace region by its stdout."
                          (interactive)
                          (let ((cmdStr
                          (format
                          "/usr/bin/python /home/joe/pythonscriptxyz %s"
                          (buffer-file-name))))
                          (shell-command-on-region (region-beginning) (region-end) cmdStr nil "REPLACE" nil t)))
                        

    You could be clear the arguments by "C-h f shell-command-on-region"

4.2. How to change the title with needed cases

4.3. Semicolon to underscore trick

(defun gopar/easy-underscore (arg)
                "Convert all inputs of semicolon to an underscore.
                If given ARG, then it will insert an actual semicolon."
                (interactive "P")
                (if arg
                (insert ";")
                (insert "_")))

                (global-set-key (kbd ";") 'gopar/easy-underscore)
              
(defun easy-camelcase (arg)
                (interactive "c")
                ;; arg is between a-z
                (cond ((and (>= arg 97) (<= arg 122))
                (insert (capitalize (char-to-string arg))))
                ;; If it's a new line
                ((= arg 13)
                (newline-and-indent))
                ((= arg 59)
                (insert ";"))
                ;; We probably meant a key command, so lets execute that
                (t (call-interactively
                (lookup-key (current-global-map) (char-to-string arg))))))
              

5. TODO Learn how to use Emacs Debugger

6. Reference

Date: 2022-10-14 Fri 00:00

Author: Dr YF Lin

Created: 2023-03-02 Thu 09:23

ThingsEngine