Working in Cursor: Tab Completion, Chat, Agent Edits and Project Rules Compared with VS Code + Copilot
Key takeaways
Cursor is a VS Code fork with AI built into the editor: Tab completion predicts your next edit, Chat answers questions over files you attach, and the agent (Composer) edits several files and runs commands. The useful skill is controlling what context the model sees and reviewing every diff.
What Cursor actually is
Cursor is a code editor forked from VS Code with AI features built into the editor rather than bolted on as an extension. Because it is a fork, your VS Code keybindings, themes and most extensions carry over; on first launch Cursor offers to import them. What changes is that the editor itself indexes your repository, sends selected context to a language model, and applies the model’s edits as reviewable diffs.
That last point is the whole game. None of the features below are magic: each one decides what text goes into the model’s prompt (your cursor position, recent edits, attached files, search results from the index, your rules) and then turns the answer into an edit. When Cursor gives a bad answer, the cause is almost always that the relevant code was not in that context, or that the task was too large to review. Most of this article is about controlling those two things.
Installation and model settings
Download Cursor from cursor.com for macOS, Windows or Linux and let it import your VS Code profile. Then open Cursor Settings → Models. The list of models changes frequently and depends on your plan, so rather than memorising names, understand the trade-off: large frontier models are better at multi-step reasoning and multi-file changes, while smaller, faster models are fine for quick questions and short edits. Using a slow model for everything makes the agent loop painful; using a small model for a cross-cutting refactor produces plausible but inconsistent edits.
You can add your own provider API key in the same screen. Requests made with that key are billed by your provider, which is useful if your organisation already has an agreement there. Features that depend on Cursor’s own models (Tab completion in particular) keep using them.
Privacy. By default your code is sent to Cursor’s servers and to model providers to answer requests. Cursor has a Privacy Mode setting that limits retention; check the current wording on their security page before using it on a client’s code, and check your employer’s policy first. Add a .cursorignore file (same syntax as .gitignore) to keep secrets, generated files and huge fixtures out of the index. Keeping node_modules, build output and data dumps out also makes @Codebase-style search noticeably more relevant.
Tab completion: predicting the next edit
Tab completion in Cursor is not just “finish this line”. It looks at the code around your cursor and at your recent edits and predicts the next change, which may be several lines, or an edit a few lines away from where you are typing. If you rename a parameter, Tab often proposes the matching change at each usage; press Tab to accept and jump, Esc to dismiss.
# Parse an ISO-8601 date string; return None instead of raising on bad input
def parse_date(value: str) -> date | None: # Tab proposes a body
A descriptive comment or function signature is the cheapest context you can give it. The most common failure is accepting a completion that looks right because it matches the surrounding style: an off-by-one in a loop bound, a swapped argument order, or a call to a helper that does not exist in your codebase. Completions are generated from patterns, not from checking that the symbol resolves, so let your type checker and tests run continuously and treat red squiggles after a Tab as a signal to look closer.
Chat (Cmd/Ctrl+L) and @ context
Chat is a conversation with the model about code you attach. The @ menu is how you control context:
| Mention | What it adds |
|---|---|
@file / @folder | The contents (or a summary) of specific files or directories |
@Codebase | Results of a search over the indexed repository |
@Web | Results of a web search |
@Docs | Indexed documentation sites you or Cursor have added |
Selecting code and pressing Cmd/Ctrl+Shift+L (or Cmd/Ctrl+L) adds the selection to the chat. For small in-place changes, Cmd/Ctrl+K opens an inline edit prompt on the selection, which is often faster than a chat round trip.
The PostList component crashes with
TypeError: Cannot read properties of undefined (reading 'map')
@src/components/PostList.tsx @src/api/posts.ts
Explain which code path can pass undefined and propose the smallest fix.
Attaching both the component and the API client matters here: the bug is usually that the fetch returns undefined before data arrives or on an error branch, and the model cannot see that from the component alone. @Codebase is useful for “where is X handled?” questions, but it is retrieval: it returns the chunks that look most similar to your query. If the answer lives in a file with unusual naming, name the file explicitly.
Agent / Composer (Cmd/Ctrl+I): multi-file edits
The agent (historically called Composer) takes a task, reads files, edits several of them, and can run terminal commands such as your test suite, showing each change as a diff you accept or reject. This is where productivity gains and damage both come from.
Add PUT /api/users/:id so users can edit nickname and avatarUrl.
- Follow the pattern in @src/routes/posts.ts
- Validate with the existing zod schemas in @src/validators/
- Reuse the auth middleware; users may only edit themselves
- Add tests next to the existing ones and run them
Do not change the database schema.
What makes this prompt work is that it names an existing pattern to copy, states constraints (no schema change, self-edit only), and gives the agent a way to verify itself (run tests). Break larger work into steps you can review: model first, then routes, then UI. Cursor keeps checkpoints so you can roll back an agent run, but you should still commit before starting one so git diff shows exactly what it touched.
I have learned to be suspicious of agent runs that “fix” failing tests. A common failure mode is the agent editing the test’s expected value, or adding a special case for the test input, until the suite goes green, which makes the diff pass review at a glance while the real bug remains. When an agent run touches both implementation and test files, I read the test changes first. The other failure I watch for is scope creep: asked to add one endpoint, the agent also “tidies up” unrelated imports and formatting across a dozen files, burying the meaningful change in noise. An explicit “only modify these files” line in the prompt prevents most of it.
Terminal access deserves the same care. Review commands before allowing them to run, and be especially careful with anything that migrates databases, deletes files, or pushes to a remote.
Project rules (.cursor/rules and .cursorrules)
Rules are standing instructions prepended to requests so you do not have to repeat your conventions. The original mechanism is a single .cursorrules file at the project root; newer versions use Project Rules, one file per rule under .cursor/rules/, each of which can be always applied, attached when matching files are in context, or selected by the agent based on its description.
# .cursor/rules/api.mdc (conceptual example)
- FastAPI with dependency injection (Depends) for DB sessions and auth
- Pydantic v2 models for request/response bodies
- Raise HTTPException with explicit status codes; never return bare dicts for errors
- Use the logging module, not print()
- New endpoints need a pytest test using the existing client fixture
Good rules are specific and checkable (“use the logger from app/log.py”) rather than aspirational (“write clean code”). Long rule files work against you: every rule consumes context on every request, and a frontend rule injected while you edit a migration only dilutes the instructions that matter. Scope rules by glob and keep each one short. Rules also are not enforcement; the model may still ignore them, so anything important (no any in TypeScript, formatting, import order) belongs in a linter or type checker that fails CI.
Cursor vs VS Code + Copilot
Both tools have converged: Copilot now also offers chat, multi-file edits and an agent mode inside VS Code, and both let you choose among several models. The differences that tend to matter in daily use:
| Aspect | Cursor | VS Code + Copilot |
|---|---|---|
| Form | Separate editor (VS Code fork) | Extension for VS Code and other IDEs |
| Completion | Predicts next edits, including multi-line and jump-to-next-edit | Inline suggestions and next edit suggestions |
| Context | Repository index, @ mentions, project rules | Workspace context, # mentions, custom instruction files |
| Agent | Agent/Composer with checkpoints | Agent mode in chat |
| Editor updates | Follows upstream VS Code with some delay | Always current VS Code |
The second-to-last row is easy to overlook: because Cursor is a fork, it can lag upstream VS Code, and occasionally an extension that relies on the newest VS Code API or on the Microsoft extension marketplace behaves differently. If your team relies on a particular extension (remote development, a proprietary language server), test it in Cursor before switching. Pricing and plan limits for both products change often; check the current pricing pages rather than a blog post.
A realistic daily workflow
- Before a feature: commit or stash, then open the agent with the relevant files attached and a prompt that names an existing pattern and the files it may change.
- While it runs: read the plan; stop it early if it heads toward the wrong files.
- After it runs: review the diff file by file, run the tests yourself, and reject changes you do not understand. Ask Chat to explain a hunk before accepting it.
- For bugs: paste the full error and stack trace into Chat with the files from the trace attached, ask for the cause first and the fix second.
- For small edits: Cmd/Ctrl+K on a selection is usually faster than either Chat or the agent.
The recurring mistake I see, and have made, is letting the review step shrink as trust grows. The model’s error rate does not drop because the last ten diffs were fine, and the dangerous mistakes (a missing authorisation check, a query built with string concatenation, a swallowed exception) are exactly the ones that look tidy. Keep diffs small enough that reviewing them properly stays cheap.
Useful shortcuts
| Shortcut (macOS / Windows-Linux) | Action |
|---|---|
Tab / Esc | Accept / dismiss a completion |
Cmd+L / Ctrl+L | Open Chat (adds selection if any) |
Cmd+K / Ctrl+K | Inline edit on selection |
Cmd+I / Ctrl+I | Open the agent (Composer) |
Shortcuts are configurable and have shifted between versions; check Cursor Settings → Keyboard Shortcuts if one does not match.