Skip to content

feat: surround operations, textobject registry, and target expansion#155

Merged
FluxxField merged 1 commit into
devfrom
feat/surround-textobjects
Mar 16, 2026
Merged

feat: surround operations, textobject registry, and target expansion#155
FluxxField merged 1 commit into
devfrom
feat/surround-textobjects

Conversation

@FluxxField

Copy link
Copy Markdown
Owner

Summary

  • Textobject registry: Separate registry from composable motions with key resolver for i/a prefix routing. Pattern-based pair detection fallback when treesitter can't parse.
  • Surround operators: ds/cs/ys/gza/gzp/visual S with configurable padding (surround_pad), visual feedback (delimiter/target highlighting), and hint label clearing during prompts.
  • Special surround types: q (any quote), t (HTML/XML tags with live multi-cursor rename), f (function calls with in-place name editing).
  • Target expansion: +/- keys to grow/shrink selection after picking a target. Dim hints on adjacent targets. Zero friction for single-target use.
  • Bug fixes: undojoin safety, change indentation preservation, module metadata precedence, keyword-only word pattern for surround.

47 files changed, 5366 insertions. 563 tests passing, 0 failures.

Test plan

  • di(/da(/vi(/ci" — pair text objects with labels on all matching pairs
  • ds(/ds) — delete surround (opening strips padding, closing doesn't)
  • cs( — change surround with delimiter highlighting, padding-aware
  • ysaw(/ysaw) — add surround with/without padding
  • dsq/csq — quote alias (any type)
  • dst — delete surrounding HTML/XML tag
  • cst — live multi-cursor tag rename (type in opening, closing syncs real-time)
  • dsf — unwrap function call
  • csf — in-place function name editing (insert mode)
  • gza — standalone surround add with keyword-only word targets
  • gzp — paste surround with stored pair
  • Visual S — wrap selection with prompted delimiter
  • Target expansion: ysaw → pick → ++( wraps 3 words
  • +/-/ hints appear on adjacent targets during expansion
  • dw/yw/cw/dd/yy — no regression on existing motions
  • daf/dif/dac/dia — treesitter text objects unchanged
  • diw/daw — native Vim fallback for unregistered textobject keys
  • Pattern fallback works on files with broken syntax
  • presets = { surround = true } backwards compatible

Introduces a complete surround system and textobject architecture:

Textobject Registry:
- Separate registry from composable motions (same key, different contexts)
- Key resolver with priority-based resolution (pre-set → i/a prefix → composable)
- i/a keymaps in x/o modes for native operator compatibility
- Pattern-based pair detection fallback when treesitter can't parse

Surround Operations:
- ds/cs/ys operators for delete, change, add surround
- gza/gzp standalone add and paste surround
- Visual S to wrap selection with delimiter
- Configurable padding: opening chars pad, closing chars don't (surround_pad)
- Visual feedback: delimiter/target highlighting during prompts

Special Surround Types:
- q: quote alias matching any quote type (", ', backtick)
- t: HTML/XML tags with live multi-cursor rename on cst
- f: function calls (dsf unwraps, csf enters insert mode at name)

Target Expansion:
- +/- keys to grow/shrink selection after picking a target
- Dim +/-/backspace hints on adjacent targets
- Works with ys and gza surround operations
- Any non-expansion key confirms implicitly (zero friction)

Additional fixes:
- undojoin pcall for post-undo safety across all surround actions
- Change action preserves indentation for line-type targets
- Module metadata defaults don't overwrite motion config in infer
- Keyword-only word pattern for surround word targets
- SmartMotionSelected highlight updated to neon blue theme

563 tests passing, 0 failures.
@FluxxField
FluxxField merged commit cf1ce90 into dev Mar 16, 2026
0 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant