22 KiB
tp.el - Text Properties Library for Emacs
A powerful text properties manipulation library with an innovative layer system
Features • Installation • Quick Start • API Reference • Layer System • 中文文档
tp.el provides a convenient and unified API for manipulating Emacs text properties. Inspired by ov.el for overlays, tp.el offers:
- Unified API: All property-setting functions work on both strings and buffers
- Layer System: Stack multiple property sets on the same text region
- Pattern Matching: Apply properties to text matching strings or regexps
Features
- ✅ Unified Object Support: Functions like
tp-set,tp-match,tp-regexpwork on both strings and buffers - ✅ Clear Semantics:
tp-reset(replace all),tp-set(replace specified),tp-add(deep merge) - ✅ Nested Property Access: Get/set/remove nested sub-properties with path syntax
- ✅ Innovative Layer System: Stack, rotate, and manage multiple layers of properties
- ✅ Layer Groups: Define reusable sets of related layers
- ✅ Search & Navigation: Find and navigate through propertized text
- ✅ Pattern Matching: Apply properties to string/regexp matches with reset/add variants
- ✅ Clean API: Consistent naming and calling conventions
Requirements
- Emacs 28.1+ (uses
object-intervalsfunction) - dash.el (list manipulation utilities)
Installation
;; Add to your load-path
(add-to-list 'load-path "/path/to/tp")
(require 'tp)
Or with use-package:
(use-package tp
:load-path "/path/to/tp")
Quick Start
Setting Properties
tp.el provides three main functions for setting properties, each with different semantics:
;; tp-set: Replace only specified properties, preserve others
(tp-set 1 10 '(face bold help-echo "Hello!"))
;; tp-reset: Completely replace ALL properties
(tp-reset 1 10 '(face bold)) ; Any other properties are removed
;; tp-add: Deep merge nested properties
(tp-add 1 10 '(face (:underline t))) ; Merges with existing face
All three functions support four calling conventions:
;; On current buffer (properties as a list)
(tp-set 1 10 '(face bold help-echo "Hello!"))
;; On a specific buffer
(tp-set 1 10 '(face bold) some-buffer)
;; On a string with range (0-indexed)
(tp-set 0 5 '(face bold) "Hello World")
;; => #("Hello World" 0 5 (face bold))
;; On entire string (flat properties)
(tp-set "Hello World" 'face 'bold 'help-echo "test")
;; => #("Hello World" 0 11 (face bold help-echo "test"))
Single-Property Setters
;; Set only face property
(tp-set-face 1 10 'bold)
(tp-set-face "Hello" 'italic) ; entire string
;; Set only display property
(tp-set-display 1 10 '(space :width 10))
Getting Properties
;; Get specific property at position
(tp-get 5 'face) ; => bold
;; Get nested sub-property
(tp-get 5 'face :foreground) ; => "red"
(tp-get 5 'face :box :color) ; => "blue" (deeply nested)
;; Get specific property from range
(tp-get 1 10 'face) ; => bold
;; Get all properties from range
(tp-get 1 10) ; => (face bold help-echo "Hello!")
;; Get all properties at point
(tp-at 5) ; => (face bold help-echo "Hello!")
Removing Properties
;; Remove entire property
(tp-remove 1 10 'face)
;; Remove sub-property
(tp-remove 1 10 '(face :underline))
;; Remove nested sub-properties (keep others)
(tp-remove 1 10 '(face :underline (:style :position)))
;; Removes :style and :position from :underline, keeps :color if present
Pattern Matching
;; Apply properties to all occurrences of "TODO" in buffer
(tp-match "TODO" '(face warning))
;; Apply to string
(tp-match "world" "Hello world world" '(face bold))
;; => #("Hello world world" 6 11 (face bold) 12 17 (face bold))
;; Match with (PATTERN STRING) format
(tp-match '("world" "Hello world") '(face bold))
;; => #("Hello world" 6 11 (face bold))
;; Using regexp
(tp-regexp "\\b[0-9]+\\b" '(face font-lock-number-face))
;; Reset variants (replace ALL properties on matches)
(tp-match-reset "TODO" '(face warning))
(tp-regexp-reset "[0-9]+" '(face bold))
;; Add variants (deep merge properties on matches)
(tp-match-add "TODO" '(face (:underline t)))
(tp-regexp-add "[0-9]+" '(face (:weight bold)))
API Reference
Core Property Functions
tp-set - Set Text Properties
Set text properties on a string or buffer region. Replaces only the specified properties, preserving others.
;; Current buffer (properties as a list)
(tp-set START END '(PROPERTY VALUE ...))
;; Specific buffer or string
(tp-set START END '(PROPERTY VALUE ...) OBJECT)
;; Entire string (flat properties)
(tp-set STRING PROPERTY VALUE ...)
Examples:
;; Set face on buffer region
(tp-set 1 10 '(face bold)) ; => (1 . 10)
;; Set multiple properties
(tp-set 1 10 '(face bold help-echo "Click me"))
;; Set on specific buffer
(tp-set 1 10 '(face italic) my-buffer)
;; Set properties on a string (0-indexed)
(setq my-string (tp-set 0 5 '(face italic) "Hello World"))
;; => #("Hello World" 0 5 (face italic))
;; Set properties on entire string
(tp-set "Hello" 'face 'bold 'mouse-face 'highlight)
;; => #("Hello" 0 5 (face bold mouse-face highlight))
tp-reset - Replace All Properties
Completely replace ALL text properties with the specified ones.
(tp-reset START END '(PROPERTY VALUE ...) &optional OBJECT)
(tp-reset STRING PROPERTY VALUE ...)
Examples:
;; Replace all properties in region
(tp-reset 1 10 '(face bold)) ; Any existing properties are removed
;; On string
(tp-reset "Hello" 'face 'italic)
tp-add - Add/Merge Properties
Add or update properties with deep merge support for nested plists.
(tp-add START END '(PROPERTY VALUE ...) &optional OBJECT)
(tp-add STRING PROPERTY VALUE ...)
Examples:
;; Add properties (preserves existing, merges nested)
(tp-add 1 10 '(help-echo "tooltip"))
;; Deep merge face properties
(tp-set 1 10 '(face (:foreground "red")))
(tp-add 1 10 '(face (:background "blue")))
;; Result: face is (:foreground "red" :background "blue")
;; Face prepending - symbol faces are prepended to face list
(tp-set "Hello" 'face 'bold)
(tp-add "Hello" 'face 'shadow)
;; Result: face is (shadow bold)
tp-set-face - Set Face Property
Set only the face property, preserving other properties.
(tp-set-face START END FACE &optional OBJECT)
(tp-set-face STRING FACE)
Examples:
(tp-set-face 1 10 'bold)
(tp-set-face 1 10 '(:foreground "red" :weight bold))
(tp-set-face "Hello" 'italic)
tp-set-display - Set Display Property
Set only the display property, preserving other properties.
(tp-set-display START END DISPLAY &optional OBJECT)
(tp-set-display STRING DISPLAY)
Examples:
(tp-set-display 1 10 '(space :width 10))
(tp-set-display " " '(space :width 20))
tp-get - Get Property Value
Get property value(s) from position or range, with support for nested sub-properties.
;; Single position
(tp-get POSITION PROPERTY)
(tp-get POSITION PROPERTY OBJECT)
;; Nested sub-property access
(tp-get POSITION PROPERTY SUB-KEY ...)
;; Range - specific property
(tp-get START END PROPERTY)
(tp-get START END PROPERTY OBJECT)
;; Range with property path as list
(tp-get START END '(PROPERTY) OBJECT)
(tp-get START END '(PROPERTY SUB-KEY ...) OBJECT)
;; Range - all properties
(tp-get START END)
(tp-get START END OBJECT)
;; Entire string
(tp-get STRING)
(tp-get STRING PROPERTY)
(tp-get STRING PROPERTY SUB-KEY ...)
Examples:
;; Get from current buffer
(tp-get 5 'face) ; => bold
;; Get nested sub-property
(tp-get 5 'face :foreground) ; => "red"
(tp-get 5 'face :box :color) ; => "blue"
(tp-get 5 'display :width) ; => 10
;; Get from string (0-indexed)
(tp-get 0 'face my-string) ; => italic
;; Get from range
(tp-get 1 10 'face) ; => bold
;; Get with property path as list
(tp-get 5 20 '(face :underline :style) my-string)
;; Get all properties from range
(tp-get 1 10) ; => (face bold help-echo "test")
;; Get from entire string
(tp-get "Hello World") ; => all properties
(tp-get "Hello World" 'face) ; => face value
(tp-get "Hello World" 'face :foreground) ; => foreground color
Fine-grained Property Functions
For manipulating sub-properties within complex properties like face or display:
;; Get sub-property
(tp-get-sub POSITION PROPERTY SUB-PROPERTY &optional OBJECT)
;; Set sub-property
(tp-put-sub START END PROPERTY SUB-PROPERTY VALUE &optional OBJECT)
;; Remove sub-property
(tp-remove-sub START END PROPERTY SUB-PROPERTY &optional OBJECT)
Examples:
;; Get :foreground from face
(tp-get-sub 1 'face :foreground) ; => "red"
;; Set :weight on face
(tp-put-sub 1 6 'face :weight 'bold)
;; Remove :background from face
(tp-remove-sub 1 6 'face :background)
tp-at - Get All Properties
(tp-at &optional POINT OBJECT)
Get all text properties at POINT as a plist.
Examples:
(tp-at 5) ; => (face bold help-echo "test")
(tp-at 0 my-string) ; Get from string
tp-remove - Remove Property
Remove a property or nested sub-property from a region or entire string.
;; Remove entire property (buffer)
(tp-remove START END PROPERTY &optional OBJECT)
;; Remove sub-property (buffer)
(tp-remove START END '(PROPERTY SUB-KEY) &optional OBJECT)
;; Remove nested sub-properties (buffer)
(tp-remove START END '(PROPERTY SUB-KEY (NESTED-KEYS...)) &optional OBJECT)
;; Remove from entire string
(tp-remove STRING PROP1 PROP2 ...)
(tp-remove STRING PROPERTY SUB-KEY)
(tp-remove STRING PROPERTY SUB-KEY '(NESTED-KEYS...))
Examples:
;; Remove entire property
(tp-remove 1 10 'face)
;; Remove sub-property from face
(tp-remove 1 10 '(face :underline))
;; Remove specific nested keys, keep others
(tp-remove 1 10 '(face :underline (:style :position)))
;; Removes :style and :position from :underline
;; If :color exists in :underline, it's preserved
;; Remove from entire string
(tp-remove "Hello World" 'face 'help-echo) ; Remove multiple properties
(tp-remove "Hello World" 'face :underline) ; Remove sub-property
(tp-remove "Hello World" 'face :underline '(:style :position)) ; Remove nested
tp-remove-list - Remove Multiple Properties
(tp-remove-list START END PROPERTIES &optional OBJECT)
Remove multiple properties at once.
Examples:
(tp-remove-list 1 10 '(face help-echo mouse-face))
tp-clear - Clear All Properties
(tp-clear &optional START END OBJECT)
Clear all text properties from a region.
Examples:
(tp-clear 1 10) ; Clear region
(tp-clear) ; Clear entire buffer
Pattern Matching Functions
tp-match - Match String
;; Buffer
(tp-match PATTERN '(PROPERTY VALUE ...))
;; String or Buffer object
(tp-match PATTERN OBJECT '(PROPERTY VALUE ...))
;; Pattern as (PATTERN STRING) format
(tp-match '(PATTERN STRING) '(PROPERTY VALUE ...))
Set properties on all occurrences of a string pattern.
Examples:
;; In buffer - returns list of (START . END) pairs
(tp-match "TODO" '(face warning))
;; => ((10 . 14) (50 . 54) ...)
;; On string - returns modified string
(tp-match "o" "Hello World" '(face bold))
;; => #("Hello World" 4 5 (face bold) 7 8 (face bold))
;; Using (PATTERN STRING) format
(tp-match '("world" "Hello world") '(face bold))
;; => #("Hello world" 6 11 (face bold))
tp-match-reset - Match and Reset
Reset (completely replace) all properties on matches.
(tp-match-reset PATTERN '(PROPERTY VALUE ...) &optional OBJECT)
Examples:
(tp-match-reset "TODO" '(face warning))
;; Replaces ALL properties on matched text
tp-match-add - Match and Add
Add/merge properties on matches with deep merge support.
(tp-match-add PATTERN '(PROPERTY VALUE ...) &optional OBJECT)
Examples:
(tp-match-add "TODO" '(face (:underline t)))
;; Merges with existing properties
tp-regexp - Match Regexp
;; Buffer
(tp-regexp PATTERN '(PROPERTY VALUE ...))
;; String or Buffer object
(tp-regexp PATTERN OBJECT '(PROPERTY VALUE ...))
Set properties on all matches of a regular expression.
Examples:
;; Highlight all numbers in buffer
(tp-regexp "[0-9]+" '(face font-lock-number-face))
;; On string
(tp-regexp "[A-Z]+" "Hello WORLD" '(face bold))
;; => #("Hello WORLD" 6 11 (face bold))
tp-regexp-reset - Regexp and Reset
Reset (completely replace) all properties on regexp matches.
(tp-regexp-reset PATTERN '(PROPERTY VALUE ...) &optional OBJECT)
tp-regexp-add - Regexp and Add
Add/merge properties on regexp matches with deep merge support.
(tp-regexp-add PATTERN '(PROPERTY VALUE ...) &optional OBJECT)
Propertize Functions
tp-layer-propertize - Apply Layer to Object
(tp-layer-propertize OBJECT LAYER &optional START END)
Apply a predefined layer's properties to an object.
Examples:
;; Define a layer first
(tp-layer-define highlight '(face (:background "yellow")))
;; Apply to string
(tp-layer-propertize "Important" 'highlight)
;; Apply to substring
(tp-layer-propertize "Hello World" 'highlight 0 5)
;; Apply to buffer region
(tp-layer-propertize (current-buffer) 'highlight 1 10)
tp-group-propertize - Apply Layer Group
(tp-group-propertize OBJECT LAYER-GROUP &optional START END)
Apply all layers from a layer group to an object.
Search & Navigation Functions
tp-forward / tp-backward
(tp-forward PROPERTY &optional VALUE PREDICATE NOT-CURRENT)
(tp-backward PROPERTY &optional VALUE PREDICATE NOT-CURRENT)
Search forward/backward for text with PROPERTY.
Examples:
;; Find next text with 'marker property
(tp-forward 'marker)
;; Find next text where 'type equals 'heading
(tp-forward 'type 'heading)
tp-next / tp-prev
(tp-next &optional POINT PROPERTY VALUE)
(tp-prev &optional POINT PROPERTY VALUE)
Get the next/previous position with text properties.
tp-goto-next / tp-goto-prev
(tp-goto-next &optional PROPERTY VALUE)
(tp-goto-prev &optional PROPERTY VALUE)
Move point to next/previous text with PROPERTY.
tp-regions-map / tp-strings-map
(tp-regions-map FUNCTION PROPERTY &optional VALUE PREDICATE COLLECT)
(tp-strings-map FUNCTION PROPERTY &optional VALUE PREDICATE COLLECT)
Apply a function to all regions/strings with PROPERTY.
Examples:
;; Upcase all marked text
(tp-strings-map
(lambda (str idx)
(message "Found: %s at index %d" str idx))
'marker)
Query Functions
tp-in - Find Regions with Property
(tp-in PROPERTY &optional VALUE START END)
Get all regions with PROPERTY in current buffer.
Examples:
;; Get all regions with 'marker property
(tp-in 'marker)
;; => ((1 5 (marker t ...)) (10 15 (marker t ...)))
;; Filter by value
(tp-in 'type 'heading)
tp-all - Get All Propertized Regions
(tp-all &optional START END)
Get all regions with any text properties.
tp-intervals - Get Property Intervals
(tp-intervals START END &optional OBJECT)
Get all text property intervals in a region.
tp-empty-p - Check for Properties
(tp-empty-p OBJECT)
Return t if OBJECT has no text properties.
tp-plist - Get Merged Properties
(tp-plist START END &optional OBJECT)
Get a merged plist of all properties in a region.
The Layer System
The layer system is tp.el's innovative feature that allows stacking multiple sets of properties on the same text region. Only the top layer is visible, but lower layers are preserved and can be revealed through rotation or pinning.
Layer Concept
┌─────────────────────────────┐
│ TOP LAYER (visible) │ ← What you see
├─────────────────────────────┤
│ Middle Layer (hidden) │ ← Preserved
├─────────────────────────────┤
│ Bottom Layer (hidden) │ ← Preserved
└─────────────────────────────┘
Layer Definition Functions
tp-layer-define - Define a Layer
(tp-layer-define NAME PROPERTIES)
Define a named layer with properties.
Examples:
(tp-layer-define highlight
'(face (:background "yellow" :foreground "black")))
(tp-layer-define error
'(face (:background "red" :foreground "white")
help-echo "Error!"))
(tp-layer-define info
'(face (:background "blue" :foreground "white")))
tp-group-define - Define Layer Group
(tp-group-define NAME
LAYER1 PROPERTIES1
LAYER2 PROPERTIES2
...)
Define a group of related layers.
Examples:
(tp-group-define status-colors
status-ok '(face (:foreground "green"))
status-warning '(face (:foreground "orange"))
status-error '(face (:foreground "red")))
tp-layer-props / tp-group-props
(tp-layer-props LAYER-NAME)
(tp-group-props GROUP-NAME)
Get properties for a layer or all layers in a group.
tp-layer-undefine / tp-group-undefine
(tp-layer-undefine NAME)
(tp-group-undefine NAME)
Remove layer or group definition.
tp-layer-reset
(tp-layer-reset)
Clear all layer and group definitions.
Layer Manipulation Functions
tp-layer-push - Add Layer
(tp-layer-push START END NAME &optional OBJECT)
Push a layer to the top of the stack.
Examples:
(tp-layer-define base '(face default))
(tp-layer-define highlight '(face (:background "yellow")))
;; Push base layer first
(tp-layer-push 1 10 'base)
;; Push highlight on top (now visible)
(tp-layer-push 1 10 'highlight)
tp-layer-delete - Remove Layer
(tp-layer-delete START END NAME &optional OBJECT)
Delete a layer from anywhere in the stack.
Examples:
;; Remove the highlight layer
(tp-layer-delete 1 10 'highlight)
;; base layer is now visible
tp-layer-rotate - Cycle Layers
(tp-layer-rotate START END &optional OBJECT)
Rotate layers - top goes to bottom, next becomes visible.
Examples:
;; Stack: highlight (top) -> base (bottom)
(tp-layer-rotate 1 10)
;; Stack: base (top) -> highlight (bottom)
tp-layer-pin - Bring Layer to Top
(tp-layer-pin START END NAME &optional OBJECT)
Move a specific layer to the top.
Examples:
;; Make 'base the top layer
(tp-layer-pin 1 10 'base)
tp-layer-hide / tp-layer-show
(tp-layer-hide START END NAME &optional OBJECT)
(tp-layer-show START END NAME &optional OBJECT)
Hide layer (move to bottom) or show layer (move to top).
tp-layer-merge
(tp-layer-merge START END LAYER1 LAYER2 NEW-NAME &optional OBJECT)
Merge two layers into one new layer.
Layer Query Functions
tp-layer-list - List All Layers
(tp-layer-list START END &optional OBJECT)
Get list of all layer names in region.
Examples:
(tp-layer-list 1 10) ; => (highlight base)
tp-layer-count
(tp-layer-count START END &optional OBJECT)
Count layers in region.
tp-layer-exists-p
(tp-layer-exists-p START END NAME &optional OBJECT)
Check if layer exists in region.
tp-layer-top
(tp-layer-top START END &optional OBJECT)
Get name of the top (visible) layer.
Practical Examples
Syntax Highlighting with Multiple Layers
;; Define layers for different highlighting purposes
(tp-layer-define code-base
'(face font-lock-keyword-face))
(tp-layer-define code-error
'(face (:underline (:color "red" :style wave))
help-echo "Syntax error"))
(tp-layer-define code-debug
'(face (:background "dark blue")))
;; Apply base highlighting
(tp-layer-push 1 100 'code-base)
;; Add error highlight on problematic code
(tp-layer-push 50 60 'code-error)
;; Toggle between error and normal view
(defun toggle-error-view ()
(interactive)
(tp-layer-rotate 50 60))
Status Indicator
(tp-group-define task-status
status-todo '(face (:foreground "gray"))
status-progress '(face (:foreground "yellow"))
status-done '(face (:foreground "green")))
;; Cycle through statuses
(defun cycle-task-status ()
(interactive)
(tp-layer-rotate (line-beginning-position) (line-end-position)))
Temporary Highlights
(tp-layer-define temp-highlight
'(face (:background "yellow")))
(defun flash-region (start end)
"Flash a region temporarily."
(tp-layer-push start end 'temp-highlight)
(run-with-timer 0.5 nil
(lambda ()
(tp-layer-delete start end 'temp-highlight))))
Aliases
For convenience, tp.el provides these aliases:
| Alias | Original Function |
|---|---|
tp-put |
tp-set |
tp-layer-properties |
tp-layer-props |
tp-layer-group-define |
tp-group-define |
tp-layer-group-properties |
tp-group-props |
tp-layer-group-propertize |
tp-group-propertize |
tp-layer-group-undefine |
tp-group-undefine |
Deprecated Functions
| Function | Replacement | Notes |
|---|---|---|
tp-propertize |
tp-set |
Use tp-set for new code |
License
GNU General Public License v2 or later.
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
tp.el - Making text properties powerful and easy to use