Introduction to Emacs Refactoring
Refactoring in Emacs refers to the process of restructuring existing code—renaming variables, extracting functions, moving code blocks, or altering structure—without changing its external behavior. Emacs provides a rich ecosystem of built-in tools, third-party packages, and language-specific backends that transform the editor into a powerful refactoring workstation. Unlike dedicated IDEs, Emacs offers composable, keyboard-driven commands that can be chained together to perform complex transformations with minimal friction.
What Emacs Refactoring Really Is
At its core, Emacs refactoring is the combination of:
- Built-in editing primitives — query-replace, macros, registers, and narrow-to-region
- Language-aware packages — tools that understand syntax trees (ASTs) and provide semantic operations
- External tool integration — LSP servers, formatters, and language-specific refactoring engines
- Custom Elisp extensions — user-defined functions that automate repetitive restructuring patterns
Emacs does not ship with a monolithic "refactor" menu. Instead, it gives you granular, composable operations that you assemble into refactoring workflows. This philosophy means you can refactor any language—even ones without dedicated tooling—by combining general-purpose text manipulation with language-aware packages.
Why Refactoring in Emacs Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Developers spend significantly more time reading and modifying code than writing it from scratch. Efficient refactoring tools reduce the cognitive load of restructuring code, minimize the risk of introducing bugs, and keep you in flow state. Emacs-specific advantages include:
- Keyboard-only workflows — no context-switching to a mouse or separate tool window
- Composability — combine regex searches, macros, and LSP operations in a single workflow
- Cross-language consistency — the same keybindings and muscle memory work across Python, JavaScript, Rust, and more
- Offline capability — many refactoring operations work without external servers or network access
- Scriptability — record complex refactoring sequences as Elisp functions for reuse
Built-in Refactoring Primitives
Before reaching for external packages, master these built-in Emacs commands. They form the foundation of all refactoring workflows.
Query-Replace with Regular Expressions
The single most powerful refactoring tool in vanilla Emacs is query-replace-regexp. It lets you interactively replace patterns across a region, a buffer, or multiple files. For renaming variables, updating function signatures, or changing import paths, this is indispensable.
;; Rename a variable across the entire buffer
;; M-x query-replace-regexp RET \bold_var_name\b RET new_var_name RET
;; Update a function call pattern — change foo(x, y) to bar(y, x)
;; M-x query-replace-regexp RET foo(\([^,]+\)[[:space:]]*,[[:space:]]*\([^)]+\)) RET bar(\2, \1) RET
Use \b (word boundary) to avoid partial matches. The \-escaped groups \(...\) capture sub-expressions that you can reference as \1, \2, etc. in the replacement. Always review each match with y/n before committing.
Keyboard Macros for Repetitive Edits
Keyboard macros record a sequence of keystrokes and replay them. They excel at refactoring tasks where the same mechanical edit must be applied to many locations.
;; Record a macro: C-x ( or F3
;; Perform your edits...
;; Stop recording: C-x ) or F4
;; Replay once: C-x e or F4
;; Replay N times: C-u 50 C-x e
;; Example: Convert a list of comma-separated items to a bullet list
;; Starting with: apple, banana, cherry, date
;; Record macro: move to beginning of line, insert "- ", move past comma, delete comma, insert newline
;; Replay to transform the entire line
Name and save macros with name-last-kbd-macro and insert-kbd-macro for permanent reuse. Complex macros can be edited as Elisp with edit-kbd-macro.
Narrowing to Focus Refactoring Scope
Narrowing restricts editing to a region, preventing accidental changes outside it. This is essential when refactoring a function body while keeping the rest of the file safe.
;; Narrow to the current function
;; M-x narrow-to-defun (works in many programming modes)
;; Perform aggressive refactoring inside the function...
;; Restore full view: M-x widen
;; Narrow to a manually selected region
;; Select region, then M-x narrow-to-region
;; All search-and-replace operations are now confined to this region
Occur and Multi-Occur for Cross-Buffer Renames
The occur command shows all lines matching a pattern in a dedicated buffer. multi-occur extends this across multiple buffers. From the occur buffer, you can edit matches directly and commit changes.
;; Find all occurrences of "oldFunction" in open buffers
;; M-x multi-occur RET oldFunction RET
;; In the *Occur* buffer, switch to edit mode: e
;; Edit the lines directly — changes propagate to source files
;; Finish with C-c C-c to commit all edits
Language-Aware Refactoring with LSP
Language Server Protocol (LSP) integration brings IDE-grade semantic refactoring to Emacs. The two primary packages are eglot (built-in since Emacs 29) and lsp-mode (third-party, more feature-rich). Both connect to language servers that understand your code at the AST level.
Setting Up Eglot for Semantic Refactoring
Eglot requires minimal configuration. Install a language server for your language, then enable eglot in the buffer.
;; Add to your init.el — minimal eglot configuration
;; Eglot is built into Emacs 29+; ensure it's available
(require 'eglot)
;; For Python with pylsp (install via pip: pip install python-lsp-server)
(add-to-list 'eglot-server-programs
'(python-mode . ("pylsp")))
;; Eglot activates automatically or manually with M-x eglot
;; For JavaScript/TypeScript with deno or typescript-language-server
(add-to-list 'eglot-server-programs
'(js-mode . ("deno" "lsp")))
(add-to-list 'eglot-server-programs
'(typescript-mode . ("typescript-language-server" "--stdio")))
Semantic Rename with Eglot
Once eglot is connected to a language server, you gain access to precise, scope-aware renaming that understands variable shadowing, class hierarchies, and module boundaries.
;; Rename the symbol at point across the entire project
;; M-x eglot-rename RET new_name RET
;; The language server handles all occurrences, including across files
;; Unlike regex-based renaming, this respects scoping rules
;; Example: Renaming a class field from 'name' to 'fullName'
;; Place point on the field, M-x eglot-rename, type fullName
;; All references in all project files are updated atomically
Extract Function / Extract Variable
Many language servers support structural refactorings like extracting a block of code into a new function. In eglot, these are invoked through the eglot-code-actions interface.
;; Select a block of code you want to extract into a function
;; M-x eglot-code-actions RET
;; Choose "Extract function" or "Extract variable" from the menu
;; The server rewrites the code, creates the new function, and inserts the call
;; For languages like TypeScript with advanced refactoring:
;; Select expression → eglot-code-actions → Extract to constant
;; The server infers the type, creates a const, and replaces the expression
LSP-Mode for Richer Refactoring Menus
If you prefer a more extensive feature set, lsp-mode provides dedicated refactoring commands and a transient menu system.
;; Install lsp-mode (from MELPA)
(use-package lsp-mode
:ensure t
:commands (lsp lsp-deferred)
:hook ((python-mode . lsp-deferred)
(js-mode . lsp-deferred)
(rust-mode . lsp-deferred)))
;; lsp-mode provides:
;; - lsp-rename: interactive project-wide rename
;; - lsp-extract-function: extract selected code into a function
;; - lsp-extract-to-variable: extract expression to a variable
;; - lsp-organize-imports: clean up and sort imports
;; Access all via: M-x lsp-refactor RET or the transient menu
Project-Wide Refactoring Techniques
Using Projectile for Scoped Operations
Projectile adds project context to Emacs. It lets you search, replace, and refactor across all files in a project—not just open buffers.
;; Install projectile
(use-package projectile
:ensure t
:init (projectile-mode +1))
;; Search and replace across all project files
;; M-x projectile-replace RET old_text RET new_text RET
;; This runs query-replace-regexp on every project file
;; Refactor with rg/ripgrep integration
;; M-x projectile-ripgrep RET pattern RET
;; Browse matches in a compilation buffer, jump to each, edit, and move on
Wgrep for Editable Grep Buffers
Wgrep transforms a readonly grep buffer into an editable buffer where changes propagate back to source files. Combined with projectile-ripgrep or grep, this enables bulk refactoring.
;; Install wgrep
(use-package wgrep
:ensure t)
;; Workflow:
;; 1. M-x grep or M-x projectile-ripgrep to find all occurrences of a pattern
;; 2. In the grep buffer, press C-c C-p (wgrep-change-to-wgrep-mode)
;; 3. Edit the buffer directly — delete lines, modify text, rearrange
;; 4. Press C-c C-c to apply all changes back to source files
;; 5. Or C-c C-k to discard changes
;; This is incredibly powerful for:
;; - Renaming across 50+ files in one editing session
;; - Removing dead code blocks matched by a pattern
;; - Updating boilerplate or license headers project-wide
Specialized Refactoring Packages
Smartparens for Structural Editing
Smartparens lets you manipulate balanced expressions—parentheses, brackets, quotes, and tags—as semantic units. Refactoring often involves wrapping, unwrapping, or reordering these structures.
;; Install smartparens
(use-package smartparens
:ensure t
:config (smartparens-global-mode +1))
;; Structural refactoring commands:
;; sp-forward-sexp / sp-backward-sexp — move by expression
;; sp-up-sexp — navigate to enclosing expression
;; sp-beginning-of-sexp / sp-end-of-sexp — bounds of current expression
;; sp-transpose-sexps — swap two adjacent expressions
;; sp-rewrap-sexp — change delimiter type (e.g., () to [])
;; Example: Swap two function arguments
;; Place point inside first argument, M-x sp-transpose-sexps
;; f(a, b) becomes f(b, a)
;; Example: Wrap selected code in a try/catch block
;; Select region, M-x sp-wrap-with-pair, type "try {" and "} catch(e) {}"
;; Smartparens auto-inserts matching braces and preserves indentation
IEDit for Simultaneous Identifier Editing
IEDit (Incremental Editing) lets you edit all occurrences of a symbol in a visible region simultaneously—like multiple cursors but scope-aware.
;; Install iedit (usually bundled, or from MELPA)
;; M-x iedit-mode RET — edits all occurrences of the symbol at point
;; Or select a region first, then M-x iedit-mode to restrict scope
;; While in iedit mode:
;; - Type to rename all occurrences simultaneously
;; - C-n / C-p to navigate between occurrences
;; - C-' to toggle narrowing to current occurrence
;; - C-g to exit
;; Example: Rename a local variable in a function
;; Place point on the variable, M-x iedit-mode
;; All visible occurrences highlight; type the new name
;; Changes apply to all occurrences in real time
Combobulate for Structured Code Refactoring
Combobulate is a relatively new package that provides structured editing and refactoring operations using tree-sitter grammars. It works with the built-in tree-sitter support in Emacs 29+.
;; Requires Emacs 29+ with tree-sitter support
;; Install combobulate from MELPA
(use-package combobulate
:ensure t
:config
;; Enable in programming modes
(add-hook 'python-ts-mode-hook #'combobulate-mode)
(add-hook 'js-ts-mode-hook #'combobulate-mode)
(add-hook 'typescript-ts-mode-hook #'combobulate-mode))
;; Combobulate provides node-based navigation and editing:
;; - Jump to parent/child/sibling nodes in the syntax tree
;; - Clone, delete, or move entire syntax nodes
;; - Select progressively larger syntax units
;; - Transpose adjacent statements or expressions
Language-Specific Refactoring Examples
Python Refactoring Workflow
Python refactoring benefits from LSP (pylsp or pyright) combined with Emacs primitives.
;; Full Python refactoring setup
(use-package eglot
:config
(add-to-list 'eglot-server-programs
'(python-mode . ("pyright-langserver" "--stdio"))))
;; Extract a method from a long function:
;; 1. Select the code block (use C-c C-n / C-c C-p for navigation)
;; 2. M-x eglot-code-actions → Extract method
;; 3. Name the new method
;; 4. The server creates the def, replaces the block with a call
;; Rename a module-level variable:
;; M-x eglot-rename RET new_name RET
;; All references across __init__.py, imports in other files are updated
;; Sort imports:
;; M-x eglot-code-actions → Organize imports
;; Removes unused imports, sorts remaining ones per PEP 8
JavaScript / TypeScript Refactoring
The TypeScript language server provides extensive refactoring code actions.
;; TypeScript refactoring with eglot
;; Install typescript-language-server: npm i -g typescript-language-server typescript
(use-package eglot
:config
(add-to-list 'eglot-server-programs
'(typescript-mode . ("typescript-language-server" "--stdio")))
(add-to-list 'eglot-server-programs
'(js-mode . ("typescript-language-server" "--stdio"))))
;; Extract React component from JSX:
;; 1. Select JSX markup
;; 2. M-x eglot-code-actions → Extract to new component
;; 3. Server creates a new functional component with proper props typing
;; Convert arrow function to named function declaration:
;; Place point on arrow function → eglot-code-actions → Convert to named function
;; Rename across .ts, .tsx, .js files:
;; M-x eglot-rename — handles all imports and usages
Rust Refactoring with rust-analyzer
Rust's rust-analyzer provides some of the most sophisticated refactoring capabilities available through LSP.
;; Rust refactoring setup
(use-package rustic
:ensure t
:config
;; rustic bundles rust-analyzer integration
(setq rustic-lsp-client 'eglot))
;; Available refactorings via eglot-code-actions:
;; - Extract function / method
;; - Inline function (replace call with body)
;; - Convert between impl and trait methods
;; - Auto-fix compiler suggestions (lens-based)
;; - Generate impl blocks for traits
;; - Add missing match arms
;; Rename with lifetime awareness:
;; M-x eglot-rename — updates all references, including lifetime parameters
;; and trait bounds that reference the renamed item
Refactoring Without External Tools
Sometimes you work in languages without LSP servers, or in constrained environments. Emacs excels here with its universal text-manipulation capabilities.
Macro-Based Refactoring Patterns
;; Pattern 1: Add error handling to multiple function calls
;; Starting code:
;; processData(input)
;; validateForm(form)
;; sendRequest(payload)
;;
;; Desired result:
;; try { processData(input) } catch(e) { logError(e) }
;; try { validateForm(form) } catch(e) { logError(e) }
;; try { sendRequest(payload) } catch(e) { logError(e) }
;;
;; Record macro:
;; C-x ( ; start recording
;; C-a ; beginning of line
;; "try {" ; insert try block start
;; C-e ; end of line
;; "} catch(e) {" ; insert catch
;; "logError(e)" ; insert error handler
;; "}" ; close catch
;; C-e C-f ; move to next line
;; C-x ) ; stop recording
;; C-u 2 C-x e ; replay on next two lines
Rectangle Commands for Tabular Refactoring
Rectangle commands operate on columns of text—perfect for refactoring aligned data, configuration blocks, or tabular code.
;; Rectangle commands (C-x r prefix):
;; C-x r t — insert string across rectangle rows
;; C-x r d — delete rectangle
;; C-x r k — kill rectangle (copy to kill ring)
;; C-x r y — yank rectangle
;; Example: Convert inline styles to CSS class references
;; Starting with:
;;
;;
;;
;;
;; Mark rectangle covering style="color:..." across all three lines
;; C-x r d — delete the style attributes
;; C-x r t class="..." RET — insert class references
Best Practices for Emacs Refactoring
1. Always Start with Version Control
Commit or stash your work before large refactoring sessions. Emacs refactoring tools are powerful but can make sweeping changes. Use magit or vc to review diffs after each refactoring step.
;; With magit, commit before refactoring
;; M-x magit-status → Stage → Commit with message "pre-refactor snapshot"
;; After refactoring, M-x magit-diff to review changes incrementally
;; magit allows you to revert individual hunks if needed
2. Refactor in Small, Testable Steps
Rather than attempting a massive rename across 40 files at once, break refactoring into atomic commits. Use the Emacs undo system (undo-tree or undo-fu) to navigate between refactoring states.
;; Install undo-tree for visual undo navigation
(use-package undo-tree
:ensure t
:config (global-undo-tree-mode +1))
;; C-x u — visualize undo history as a tree
;; Navigate branches to compare refactoring approaches
3. Use Narrowing to Limit Blast Radius
Before a large query-replace, narrow to the relevant function or region. This prevents accidentally changing similar names in unrelated code. Always use \b word boundaries in regex replacements.
4. Leverage Compilation Mode for Refactoring Feedback
After structural refactoring, run your test suite from within Emacs using M-x compile. The compilation buffer links errors directly to source locations, letting you fix regressions with a single keystroke.
;; Run tests after refactoring
;; M-x compile RET pytest -x --tb=short RET
;; Or for JavaScript: M-x compile RET npm test RET
;; Each error in the *compilation* buffer is a hyperlink
;; C-` (backtick) jumps to the next error location
5. Save and Document Useful Macros
When you discover a reusable refactoring macro, save it with a descriptive name. Consider binding it to a key for your workflow.
;; Save the last recorded macro permanently
(defun my-refactor-add-logging ()
"Wrap the current line in console.log() call."
(interactive)
(kmacro-call-macro 0)) ;; index 0 is the most recent
;; After naming with name-last-kbd-macro:
(name-last-kbd-macro 'my-wrap-in-try-catch)
;; Insert into your init file with insert-kbd-macro
;; Now it persists across sessions
6. Combine Tools for Maximum Effect
The most efficient Emacs refactoring combines multiple approaches: use LSP for semantic renames, macros for repetitive mechanical edits, and wgrep for project-wide pattern updates. Don't limit yourself to one technique—chain them.
7. Trust the Syntax Tree When Available
When tree-sitter or LSP is active, prefer semantic operations over regex-based ones. Semantic rename (eglot-rename) catches references that regex patterns miss, such as shadowed variables, inherited members, or dynamically dispatched calls. Regex is a fallback for languages without AST support.
Conclusion
Emacs refactoring is not a single feature but a philosophy of composable, precise editing. By layering built-in primitives like query-replace and keyboard macros with language-aware tools like eglot, lsp-mode, and tree-sitter packages, you can handle refactoring tasks ranging from simple renames to complex structural transformations—all without leaving the editor or breaking your flow. The key is to build muscle memory with the foundational commands, then gradually incorporate semantic tools as your projects demand. Start with version control, refactor in small steps, and leverage Emacs's unique ability to combine text-level and AST-level operations. With practice, you will develop a refactoring workflow that feels both effortless and safe, turning what could be a dreaded chore into a satisfying, incremental improvement of your codebase.
🚀 Need a reliable AI agent for your project?
Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.
Get Started — $23.99/mo