This manual provides comprehensive guidance for using ETAF (Emacs Text-based Application Framework).
本手册提供 ETAF(Emacs Text-based Application Framework)的完整使用指南。
- Installation / 安装
- Basic Concepts / 基础概念
- TML Syntax / TML 语法
- CSS Styling / CSS 样式
- Template Directives / 模板指令
- Component System / 组件系统
- Reactive System / 响应式系统
- Tailwind CSS / Tailwind CSS 支持
- Layout System / 布局系统
- ECSS Expressions / ECSS 表达式
- API Reference / API 参考
- Emacs 27.1 or later
cl-lib(built-in)dom(built-in)
- Clone the repository:
git clone https://github.com/Kinneyzhang/ETML.git- Add to your Emacs configuration:
(add-to-list 'load-path "/path/to/ETML")
(require 'etaf)- Verify installation:
(etaf-paint-to-buffer "*test*"
'(div "Hello ETAF!"))ETAF follows a browser-like rendering pipeline:
TML → DOM → CSSOM → Render Tree → Layout Tree → Buffer String
- TML - Template Markup Language (S-expression based HTML-like syntax)
- DOM - Document Object Model (alist-based tree structure)
- CSSOM - CSS Object Model (parsed CSS rules and computed styles)
- Render Tree - Visible elements with computed styles
- Layout Tree - Elements with box model and position information
- Buffer String - Final text with properties for display in Emacs buffer
;; Render TML to a buffer
(etaf-paint-to-buffer BUFFER-NAME TML &optional DATA ECSS WIDTH HEIGHT)
;; Convert TML to styled string
(etaf-paint-string TML &optional DATA ECSS WIDTH HEIGHT)TML (Template Markup Language) uses S-expressions to represent HTML-like structures.
(tag :attr1 value1 :attr2 value2 child1 child2 ...);; Simple element
(div "Hello World")
;; With attributes
(div :class "container" :id "main"
"Content")
;; Nested elements
(div :class "card"
(h1 "Title")
(p "Description")
(button :class "btn" "Click"))
;; With inline styles
(div :style "color: red; padding: 10px"
"Styled content")
;; List format for styles
(div :style ((color . "red") (padding . "10px"))
"Also styled")Strings are rendered as text content:
(p "This is paragraph text.")
(div
"Multiple "
"strings "
"are concatenated.");; String format
(div :style "background: blue; color: white; padding: 10px"
"Styled box")
;; Alist format
(div :style ((background . "blue")
(color . "white")
(padding . "10px"))
"Styled box")(html
(head
(style "
.container { width: 800px; margin: 0 auto; }
.card { background: white; padding: 20px; border: 1px solid #ccc; }
.card h1 { color: #333; }
"))
(body
(div :class "container"
(div :class "card"
(h1 "Card Title")
(p "Card content")))))- Selectors: tag, class, ID, attribute, pseudo-class, combinators
- Properties: color, background, padding, margin, border, font, display, etc.
- Cascade: specificity calculation, !important, source order
- Inheritance: inheritable properties (color, font-*, etc.)
- Media Queries: @media rules with type and feature conditions
ETAF supports Vue-style template directives for dynamic content.
(setq data '(:name "Alice" :count 42))
(etaf-paint-to-buffer "*demo*"
'(div
(p "Hello, {{ name }}!")
(p "Count: {{ count }}"))
data)(setq data '(:loggedIn t :role "admin"))
;; e-if
(p :e-if "loggedIn" "Welcome back!")
;; e-else-if and e-else
(div
(p :e-if "role == 'admin'" "Admin Panel")
(p :e-else-if "role == 'user'" "User Dashboard")
(p :e-else "Please login"))(setq data '(:items ("Apple" "Banana" "Cherry")))
;; Basic e-for
(ul
(li :e-for "item in items" "{{ item }}"))
;; With index
(ul
(li :e-for "(item, index) in items"
"{{ index }}: {{ item }}"));; e-show adds display:none when false
(div :e-show "isVisible" "Conditionally visible");; e-text sets text content from expression
(span :e-text "message")ETAF provides a Vue3-style component system.
;; Simple component
(etaf-define-component my-button
:props '(:label :type)
:template '(button :class "btn btn-{{ type }}"
"{{ label }}"))
;; Component with setup function
(etaf-define-component counter
:props '(:initial)
:setup (lambda (props)
(let* ((count (etaf-ref
(or (plist-get props :initial) 0)))
(increment (lambda ()
(etaf-ref-update count #'1+))))
(list :count count
:increment increment)))
:template (lambda (data)
`(div :class "counter"
(span ,(format "Count: %s"
(etaf-ref-get
(plist-get data :count))))
(button :on-click ,(plist-get data :increment)
"+"))));; Use component with props
(my-button :label "Submit" :type "primary")
;; With slot content
(my-card :title "Card Title"
(p "This goes into the slot"))| Function | Description |
|---|---|
etaf-define-component |
Define a new component |
etaf-component-get |
Get component definition |
etaf-component-defined-p |
Check if component exists |
etaf-component-list-all |
List all components |
ETAF implements Vue3-style reactivity.
;; Create ref
(setq count (etaf-ref 0))
;; Get value
(etaf-ref-get count) ;; => 0
;; Set value
(etaf-ref-set count 5)
;; Update based on current value
(etaf-ref-update count #'1+) ;; => 6(let* ((count (etaf-ref 3))
(doubled (etaf-computed
(lambda ()
(* 2 (etaf-ref-get count))))))
(etaf-computed-get doubled) ;; => 6
(etaf-ref-set count 5)
(etaf-computed-get doubled)) ;; => 10 (auto recomputed);; Watch a reactive source
(let* ((count (etaf-ref 0))
(stop (etaf-watch count
(lambda (new old)
(message "Changed: %s -> %s" old new)))))
(etaf-ref-set count 1) ;; triggers callback
(funcall stop) ;; stop watching
(etaf-ref-set count 2)) ;; no callback(let* ((count (etaf-ref 0))
(stop (etaf-etml-watch-effect
(lambda ()
;; Auto-tracks dependencies
(message "Count is: %s"
(etaf-ref-get count))))))
(etaf-ref-set count 1) ;; re-runs effect
(funcall stop)) ;; cleanup(let ((state (etaf-reactive '(:name "Alice" :age 30))))
(etaf-reactive-get state :name) ;; => "Alice"
(etaf-reactive-set state :age 31)
(etaf-reactive-to-plist state)) ;; => (:name "Alice" :age 31)ETAF has built-in support for Tailwind CSS utility classes.
(div :class "flex items-center justify-between p-4 bg-blue-500"
(span :class "text-white font-bold" "Title")
(button :class "bg-white text-blue-500 px-4 py-2 rounded" "Action"))| Feature | Examples |
|---|---|
| Colors | bg-red-500, text-gray-700, border-blue-300 |
| Spacing | p-4, mx-auto, mt-2, gap-4 |
| Typography | text-lg, font-bold, text-center |
| Flexbox | flex, items-center, justify-between |
| Display | block, inline-block, hidden |
| Border | border, rounded-lg, border-2 |
| Shadow | shadow-md, shadow-lg |
(div :class "w-full md:w-1/2 lg:w-1/3"
"Responsive width")Prefixes: sm:, md:, lg:, xl:, 2xl:
(button :class "bg-blue-500 hover:bg-blue-700 focus:ring-2"
"Hover me")Variants: hover:, focus:, active:, disabled:
(div :class "bg-[#1da1f2] w-[200px]"
"Custom values")ETAF implements the CSS box model:
┌─────────────────────────────────────┐
│ margin (外边距) │
│ ┌───────────────────────────────┐ │
│ │ border (边框) │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ padding (内边距) │ │ │
│ │ │ ┌───────────────────┐ │ │ │
│ │ │ │ content (内容) │ │ │ │
│ │ │ └───────────────────┘ │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
;; Block element
(div :style "display: block" "Block")
;; Inline element
(span :style "display: inline" "Inline")
;; Flex container
(div :style "display: flex; gap: 10px"
(div "Item 1")
(div "Item 2"))(div :style "display: flex; flex-direction: row; justify-content: space-between"
(div "Left")
(div "Right"))
;; Or with Tailwind
(div :class "flex flex-row justify-between"
(div "Left")
(div "Right"))ECSS provides Lisp-style CSS expressions, similar to rx for regex.
(require 'etaf-ecss)
;; Single rule
(etaf-ecss ".card"
'(background "white")
'(padding 20)
'(border 1 solid "#ccc"))
;; => ".card { background: white; padding: 20px; border: 1px solid #ccc; }"
;; Stylesheet
(etaf-ecss
'(".container" (width 800) (margin 0 auto))
'(".card" (background "white") (padding 20))
'(".btn" (padding 10 20) (background "blue") (color "white")))(etaf-ecss-selector 'div) ;; => "div"
(etaf-ecss-selector '(class "box")) ;; => ".box"
(etaf-ecss-selector '(id "main")) ;; => "#main"
(etaf-ecss-selector '(descendant "nav" "a")) ;; => "nav a"
(etaf-ecss-selector '(child "ul" "li")) ;; => "ul > li"
(etaf-ecss-selector '(and (tag "div") (class "box"))) ;; => "div.box"(etaf-ecss-property 'padding 10) ;; => "padding: 10px"
(etaf-ecss-property 'margin 0 'auto) ;; => "margin: 0 auto"
(etaf-ecss-property 'height 5) ;; => "height: 5lh" (vertical uses lh)ETAF supports the following CSS units:
| Unit | Direction | Description |
|---|---|---|
px |
Horizontal | Pixel value |
cw |
Horizontal | Character width (uses frame-char-width as base value) |
lh |
Vertical | Line height (number of lines) |
% |
Both | Percentage (relative to parent) |
em |
Both | Relative unit (1em = 16px for width, 1 line for height) |
;; Horizontal dimension examples
"100px" ;; 100 pixels
"10cw" ;; 10 character widths (10 * frame-char-width)
"50%" ;; 50% of parent width
;; Vertical dimension examples (use line count)
"5lh" ;; 5 lines
"3" ;; 3 lines (number without unit);; Generate :style value (alist format)
(div :style (etaf-ecss-props '(background "red") '(padding 10))
"content")
;; Generate :style value (string format)
(div :style (etaf-ecss-style '(color "red") '(padding 10))
"content")| Function | Description |
|---|---|
etaf-paint-to-buffer |
Render TML to a buffer |
| `etaf-paint-string | Convert TML to styled string |
etaf-etml-to-dom |
Convert TML to DOM |
etaf-etml-render |
Render template with data |
| Function | Description |
|---|---|
etaf-css-build-cssom |
Build CSSOM from DOM |
etaf-css-get-computed-style |
Get computed style for node |
etaf-css-add-stylesheet |
Add external CSS to CSSOM |
| Function | Description |
|---|---|
etaf-render-build-tree |
Build render tree from DOM |
etaf-render-get-style |
Get style property from render node |
| Function | Description |
|---|---|
etaf-layout-build-tree |
Build layout tree from render tree |
etaf-layout-to-string |
Convert layout tree to buffer string |
etaf-layout-get-box-model |
Get box model from layout node |
| Function | Description |
|---|---|
etaf-define-component |
Define a component |
etaf-component-get |
Get component definition |
etaf-component-defined-p |
Check if component exists |
| Function | Description |
|---|---|
etaf-ref |
Create reactive reference |
etaf-ref-get |
Get ref value |
etaf-ref-set |
Set ref value |
etaf-computed |
Create computed value |
etaf-watch |
Watch reactive source |
etaf-etml-watch-effect |
Create auto-tracking effect |
etaf-reactive |
Create reactive object |
- Check the examples directory for working code examples
- Read the Architecture documentation for system design details
- Read the Data Structures documentation for detailed data structure reference
- File issues on GitHub for bugs or feature requests