Cursor Multi-Cursor + AI: Bulk Refactoring Patterns That Save Hours

Use Cursor's multi-cursor editing with AI to refactor entire codebases in minutes. Patterns for renaming, type migration, API updates, and more.

Problem: Refactoring 200 Files by Hand Is Burning Your Afternoon

You renamed a core interface. Now UserData is UserProfile — and it appears in 47 files, sometimes as a type, sometimes as a prop, sometimes embedded in JSDoc. Find-and-replace breaks context. Doing it file-by-file takes hours.

Cursor's multi-cursor editing combined with AI gives you a third option: describe the transformation once and apply it intelligently across your entire codebase.

You'll learn:

  • How to combine multi-cursor selection with AI inline edits
  • Four production-grade bulk refactoring patterns (rename, type migration, API shape change, comment sync)
  • When to use Cmd+Shift+L vs Cursor's Composer vs inline AI edit

Time: 20 min | Difficulty: Intermediate


Why Cursor's Approach Is Different

Standard find-and-replace is text-blind. It doesn't know whether UserData is a type annotation, a variable name, or part of a string literal. It can't update the JSDoc comment that describes the field, and it won't rename a destructured alias.

Cursor solves this with two primitives that compound each other:

Multi-cursor selection — select every instance of a pattern across a file or across files simultaneously.

AI inline edit (Cmd+K) — apply a natural-language transformation to every selected region at once, with context from surrounding code.

Used together, you get semantically-aware bulk edits that would take an hour in a normal editor in under two minutes.


The Four Patterns

Pattern 1: Rename Across Context

Use when: a type, interface, or function name changes and you need the rename to understand context (generics, props, destructuring).

Step 1: Select All Occurrences in the File

Place your cursor on UserData. Press Cmd+Shift+L (macOS) or Ctrl+Shift+L (Windows/Linux) to select every occurrence in the current file.

Cmd+Shift+L    → selects all occurrences of word under cursor

You now have multi-cursors on every instance simultaneously.

Step 2: Apply AI Inline Edit

With all occurrences selected, press Cmd+K to open the inline AI prompt. Type:

Rename to UserProfile. If this is a type annotation, update generics too.
If it appears in a JSDoc @param or @returns, update that line as well.

Cursor applies the rename with awareness of each cursor's surrounding context — a UserData[] becomes UserProfile[], a Record<string, UserData> becomes Record<string, UserProfile>.

Step 3: Scale to the Full Repo with Composer

Open Composer (Cmd+Shift+I), switch to Agent mode, and run:

Rename UserData to UserProfile across the entire codebase.
- Update type annotations, generics, and JSDoc comments
- Do NOT rename string literals or log messages that say "userData"
- Do NOT rename local variables named userData (only the type)

Agent mode reads every relevant file, stages the changes, and lets you review diffs before applying.

Expected output: A file-by-file diff showing only type-level renames, not string literal replacements.


Pattern 2: Type Migration (e.g., any → Specific Type)

Use when: you're tightening types after a prototype phase and need to replace any with real types based on how values are actually used.

Step 1: Find All any Annotations in a File

Use Cmd+F to search for : any in the file. Then press Alt+Enter to place a cursor on every match.

Step 2: Inline AI Edit Per Match

With multi-cursors on every : any, press Cmd+K:

Infer the correct TypeScript type from how this variable is used in the function body.
Use the most specific type possible. If unsure, use unknown instead of any.

Cursor reads the surrounding code for each cursor position independently and infers types from usage — response.data.items.map(...) becomes { items: unknown[] }, for example.

Step 3: Validate

# Confirm no new type errors introduced
npx tsc --noEmit

If it fails:

  • Type 'X' is not assignable to type 'Y' → Cursor over-inferred. Press Cmd+K on that line: Widen this type to accept both X and Y using a union.
  • Hundreds of errors → The any values were load-bearing. Run Composer: Add explicit type assertions where needed to preserve runtime behavior while removing any.

Pattern 3: API Shape Change

Use when: a function's signature changed — new required param, renamed field in a return object, callback → promise.

This is the pattern most likely to introduce subtle bugs with plain find-and-replace.

Step 1: Define the Change in a Composer Prompt

Open Composer in Agent mode. Be explicit:

The fetchUser function signature changed from:
  fetchUser(id: string): Promise<{ name: string; email: string }>

To:
  fetchUser(id: string, options?: { includeProfile: boolean }): Promise<UserProfile>

Update every call site to:
1. Add options parameter only where the caller previously accessed response.profile
2. Destructure name and email from UserProfile shape (they're still present)
3. Do NOT add the options param where it's not needed

The specificity here matters. Vague prompts like "update fetchUser calls" produce inconsistent results. Define the before/after contract exactly.

Step 2: Review by File

Composer will stage diffs file-by-file. Review each one. If a file has an incorrect change, click into that file and press Cmd+K inline:

This call site doesn't need the options parameter. Remove it and keep the rest.

Step 3: Run Tests

# Run your test suite before committing
npm test -- --passWithNoTests

Any call site the AI got wrong will surface as a failing test rather than a runtime surprise.


Pattern 4: Comment and Documentation Sync

Use when: you've already refactored the code but JSDoc, inline comments, and README snippets are now stale.

This is the most underused pattern. Stale comments are a hidden maintenance cost.

Step 1: Select All Comment Blocks

In a refactored file, use the multi-cursor shortcut to select every /** block:

Cmd+F  →  search for /**
Alt+Enter  →  cursor on every match
Cmd+Shift+L  →  extend to end of comment block

Step 2: AI Sync

Rewrite each JSDoc comment to accurately describe the current function signature and behavior.
Remove any @param entries for parameters that no longer exist.
Add @param entries for any new parameters not yet documented.
Keep the @returns description but update the type to match the current return type.

Step 3: Sync README Code Snippets with Composer

In README.md, find every code block that calls fetchUser.
Update them to match the new signature. Keep the surrounding explanation text unchanged.

Keyboard Reference

ActionmacOSWindows / Linux
Select all occurrences in fileCmd+Shift+LCtrl+Shift+L
Add next occurrence to selectionCmd+DCtrl+D
Skip current, add nextCmd+K Cmd+DCtrl+K Ctrl+D
Multi-cursor from search resultsCmd+FAlt+EnterCtrl+FAlt+Enter
Inline AI edit on selectionCmd+KCtrl+K
Open ComposerCmd+Shift+ICtrl+Shift+I
Toggle Agent mode in ComposerClick "Agent" in Composer barSame

When to Use Each Tool

SituationBest tool
Same transformation in one fileMulti-cursor + Cmd+K
Same transformation across 2–10 filesComposer chat mode
Complex semantic change across entire repoComposer Agent mode
Rename with full symbol awarenessLSP rename (F2) first, then AI for comments
Exploratory refactor (not sure what to change)Composer chat — ask first, apply second

One rule: if a change would take fewer than 5 manual edits, just do it manually. The overhead of prompting and reviewing AI output isn't worth it below that threshold.


Verification

After any bulk refactor, run this sequence before committing:

# 1. Type check
npx tsc --noEmit

# 2. Lint
npx eslint . --max-warnings 0

# 3. Tests
npm test

# 4. Quick grep to confirm old name is gone (adjust for your rename)
grep -r "UserData" src/ --include="*.ts" --include="*.tsx"

You should see: zero TypeScript errors, zero lint warnings related to the refactor, all tests passing, and no grep results for the old identifier (in type positions).


Real-World Time Comparison

Renaming UserDataUserProfile across a 40-file TypeScript codebase including types, generics, JSDoc, and README:

MethodTimeError rate
Manual, file by file~90 min~5% miss rate
Find-and-replace~15 minRenames string literals, misses aliases
LSP rename only~2 minMisses comments and README
Multi-cursor + Composer Agent~8 minNear zero with test coverage

The 8 minutes includes review time. Without tests, you pay for any errors later — that's true of any method.


What You Learned

  • Cmd+Shift+L + Cmd+K is the fastest path for single-file bulk edits
  • Composer Agent handles cross-file semantic changes — but you need to describe the before/after contract precisely
  • Comment sync is a first-class refactoring step, not an afterthought
  • Always validate with tsc --noEmit + tests; AI edits are fast but not infallible

Limitation: Composer Agent mode works best in repos under ~50k lines. For monorepos, scope the agent to a specific package directory rather than running it at the root.

Tested on Cursor 0.45, TypeScript 5.7, macOS Sequoia and Ubuntu 24.04