Skip to content
Major mode for editing SEML (S-Expression Markup Language) files
Branch: master
Clone or download
Latest commit 58eea4c May 31, 2019
Type Name Latest commit message Commit time
Failed to load latest commit information.
git-hooks Initial commit Jan 8, 2019
sample add encode/decode function from file path Jan 16, 2019
.gitignore fix decoded HTML format May 24, 2019
.travis.yml fix seml-htmlize function, dtop Emacs-24 Jan 19, 2019
LICENSE Initial commit Jan 8, 2019
Makefile fix decoded HTML format May 24, 2019 add dependdir variable Jan 18, 2019 Initial commit Jan 8, 2019
cort-test.el fix cort-test.el issue May 12, 2019
seml-mode-tests.el fix decoded HTML format May 24, 2019
seml-mode.el tagged v1.5.7 May 31, 2019


Below 2 files represent the same structure. With compare 2 files, SEML is short and easy to understand for Lisp hacker.

<!DOCTYPE html>
<html lang="en">
    <meta charset="utf-8"/>
    <title>sample page</title>
    <link rel="stylesheet" href="sample1.css"/>
      text sample
(html ((lang . "en"))
  (head nil
    (meta ((charset . "utf-8")))
    (title nil "sample page")
    (link ((rel . "stylesheet") (href . "sample1.css"))))
  (body nil
    (h1 nil "sample")
    (p nil "text sample")))

Since SEML can use Elisp freely in SEML file, I think that it has the same power as PHP. You can open files, use information that is only visible to Emacs, and use the results of Emacs hitting an external API.

seml-mode.el provides major mode for editing SEML (S-Expression Markup Language) files and utilities for you.

SEML Syntax

SEML syntax is very easy.

  • TAG is symbol of html tag name.
  • ATTRS is list of attribute.
    • attribute is dotted pair such as (ATTR . VALUE).
    • attribute is also allowed jade/pug like syntax sugar such as #id.class1.class2

      You do not need to specify id, but you need to put it at the beginning. And class must be separated by ..

  • VALUE is string.
  • VALUE is also allowed another SEML.
(cort-deftest seml-mode:/simple-jade
  (:string= (seml-decode-seml-from-sexp '(h1 ("#header.class1.class2") "sample"))
            "<h1 id=\"header\" class=\"class1 class2\">sample</h1>"))

(cort-deftest seml-mode:/simple-jade2
  (:string= (seml-decode-seml-from-sexp '(h1 ("#header.class1") "sample"))
            "<h1 id=\"header\" class=\"class1\">sample</h1>"))

(cort-deftest seml-mode:/simple-jade3
  (:string= (seml-decode-seml-from-sexp '(h1 ("class1") "sample"))
            "<h1 class=\"class1\">sample</h1>"))

Complex SEML example

(cort-deftest seml-mode:/simple-ul
  (:string= (seml-decode-seml-from-sexp
             `(ul nil
                  ,@(mapcar (lambda (x)
                             `(li nil ,(format "item-%s" x)))
                           (number-sequence 1 5))))

You only need to think about passing SEML list to SEML decoder. So you can use any function returning list.



seml-mode.el can install with package.el from MELPA, so sample instration code is below using leaf.el.

(prog1 "Load leaf.el"
  (add-to-list 'load-path (locate-user-emacs-file "site-lisp/leaf.el"))
  (require 'leaf)
  (leaf leaf
    :doc "Symplify your init.el configuration"
    :doc "Initialize leaf dependent packages"
    :url ""
    :custom ((leaf-backend-ensure . 'package))
    (leaf package
      :custom ((package-archives . '(("org"   . "")
                                     ("melpa" . "")
                                     ("gnu"   . ""))))

(leaf seml-mode
  :when (version<= "25.1" emacs-version)
  :ensure t)

Manual install

Put this package in your load-path, require it.

(add-to-list 'load-path
             (locate-user-emacs-file (format "site-lisp/seml-mode.el")))
(require 'seml-mode)


It is already set to automatically enable seml-mode for typical seml file extensions as follows, but if you want to enable seml-mode for special extensions or for other reasons, you need to set it to an optional setting.

(add-to-list 'auto-mode-alist '("\\.seml\\'" . seml-mode))
(add-to-list 'interpreter-mode-alist '("seml" . seml-mode))

Auto-refresh (With save file)

Live-refresh (Without save file)

(Without saving file. Use buffer string instead of read file contents. As Depends on AppleScript, it works on macOS with Google Chrome only)

seml-mode.el provides the function of live refresh like other modern editors.

If seml-mode.el read buffer string and eval temporally no error, send refresh message to Google Chrome.

Sample settings with leaf.el.

(leaf real-auto-save
  :ensure t
  :custom ((real-auto-save-interval . 0.3))
  :hook (find-file-hook . real-auto-save-mode))

(leaf seml-mode
  :config (require 'seml-mode)
  :custom ((seml-live-refresh-interval . 0.35)))

Then, with activating target SEML buffer, M-x seml-live-refresh-start to register servelet buffer (see simple-httpd) and set timer function.

If you stop monitor SEML buffer, M-x seml-live-refresh-stop.

seml-mode.el send refresh message to Google Chrome…

  1. When no error read and eval register buffer string,
  2. And when the evaled sexp differs from last time,
  3. And when open seml-mode.el live-refresh page (http://localhost:8080/seml-mode/live-refresh).


Customizable Variables

  • seml-mode-hook
  • seml-import-dir
  • seml-live-refresh-interval
  • seml-live-refresh-url-variable
  • seml-live-refresh-url-quety


  • seml-mode-keywords

    Support HTML5 tags.

    (defconst seml-mode-keywords
        head title base link meta style
        script noscript
        body section nav article aside hgroup header footer address
        h1 h2 h3 h4 h5 h6
        p hr pre backquote ol ul li
        dl dt dd figure figcaption div main
        a em strong small s cite q dfn addr time code var
        samp kbd sub sup i b mark ruby rt rpbdo span br wbr
        ins del
        img iframe embed object param
        video audio source canvas map area
        table caption colgroup col tbody thead tfoot tr td th
        form fieldset legend label input button select
        datalist optgroup option textarea keygen output progress meter
        details summary command menu
        ;; libxml-parse keywords
        comment top))
  • seml-html-single-tags

    Define single tag (without closing tag)

    (defconst seml-html-single-tags
      '(base link meta img br area param hr col option input wbr))


  • with-seml-elisp

    Provide environment to eval Elisp. Use ~,@(with-seml-elisp (sexp) (sexp) …)~

    (seml-mode misunderstands Elisp’s return value as a component of SEML. If you want to freely execute Elisp that does not need a return value, you need to use this macro to remove the return value from seml.)


Encode functions (HTML to SEML)

  • (seml-encode-html-from-region pointmin pointmax)
  • (seml-encode-html-from-string str)
  • (seml-encode-html-from-buffer &optional buf)
  • (seml-encode-html-from-file filepath)

Decode functions (SEML to HTML)

  • (seml-decode-seml-from-region start end &optional doctype)
  • (seml-decode-seml-from-sexp sexp &optional doctype)
  • (seml-decode-seml-from-string str &optional doctype)
  • (seml-decode-seml-from-buffer &optional buf doctype)
  • (seml-decode-seml-from-file filepath &optional doctype)

Buffer replace functions

  • (seml-replace-buffer-from-html)
  • (seml-replace-buffer-from-seml)

Live refresh functions

  • (seml-live-refresh-start)
  • (seml-live-refresh-stop)

Utility functions

  • (seml-indent-function indent-point state)
  • (seml-to-string sexp)
  • (seml-pp sexp &optional stream return-p)
  • (seml-xpath xpath sexp &optional without-top)
    (cort-deftest seml-test:simple-xpath
       (seml-xpath '(html head link)
                   '(html ((lang . "en"))
                          (head nil
                                (meta ((charset . "utf-8")))
                                (title nil
                                       "sample page")
                                (link ((rel . "stylesheet") (href . "sample1.css")))
                                (link ((rel . "stylesheet") (href . "sample2.css"))))
                          (body nil
                                (h1 nil
                                (p nil
                                   "text sample"))))
          ((rel . "stylesheet")
           (href . "sample1.css")))
          ((rel . "stylesheet")
           (href . "sample2.css"))))))
  • (seml-xpath-single xpath sexp &optional without-top)
    (cort-deftest seml-test:/simple-xpath-single
       (seml-xpath-single '(html body)
         '(html ((lang . "en"))
                (head nil
                      (meta ((charset . "utf-8")))
                      (title nil
                             "sample page")
                      (link ((rel . "stylesheet") (href . "sample1.css")))
                      (link ((rel . "stylesheet") (href . "sample2.css"))))
                (body nil
                      (h2 nil "sample-1")
                      (h2 nil "sample-2")
                      (h2 nil "sample-3")
                      (p nil
                         "text sample"))))
       '(body nil
              (h2 nil "sample-1")
              (h2 nil "sample-2")
              (h2 nil "sample-3")
              (p nil
                 "text sample"))))
  • (seml-xpath-without-top xpath sexp)
    (cort-deftest seml-test:/simple-xpath-without-top
       (seml-xpath '(html body h2)
         '(html ((lang . "en"))
                (head nil
                      (meta ((charset . "utf-8")))
                      (title nil
                             "sample page")
                      (link ((rel . "stylesheet") (href . "sample1.css")))
                      (link ((rel . "stylesheet") (href . "sample2.css"))))
                (body nil
                      (h2 nil "sample-1")
                      (h2 nil "sample-2")
                      (h2 nil "sample-3")
                      (p nil
                         "text sample")))
  • (seml-xpath-single-without-top xpath sexp)
  • (seml-htmlize majormode codestr &optional noindentp formatfn)

    Get SEML expression of any code in syntax highlight as specify major-mode.

    (cort-deftest seml-mode:/simple-htmlize
      (:equal (seml-htmlize 'emacs-lisp-mode "(leaf real-auto-save
      :ensure t
      :custom ((real-auto-save-interval . 0.3))
      :commands real-auto-save-mode
      :hook (find-file-hook . real-auto-save-mode))")
              '(pre nil "
                    (span ((class . "keyword")) "leaf")
                    " real-auto-save
                    (span ((class . "builtin")) ":ensure")
                    " t
                    (span ((class . "builtin")) ":custom")
                    " ((real-auto-save-interval . 0.3))
                    (span ((class . "builtin")) ":commands")
                    " real-auto-save-mode
                    (span ((class . "builtin")) ":hook")
                    " (find-file-hook . real-auto-save-mode))")))
  • (seml-import path)
  • (seml-expand-url path baseurl)

Major mode

  • (seml-mode)


Packages build on seml-mode.



I love OSS and I am dreaming of working on it as full-time job.

With your support, I will be able to spend more time at OSS!


All feedback and suggestions are welcome!

You can use github issues, but you can also use Slack if you want a more casual conversation.


Feel free to send PR!


Affero General Public License Version 3 (AGPLv3)
Copyright (c) Naoya Yamashita - https://seml-mode.el



  • Not yet… Now send PR and add your name!!
You can’t perform that action at this time.