codemirror — правило для Cursor: CodeMirror 6 без ошибок
Правило для Cursor задаёт, как писать редакторы на CodeMirror 6: модульные импорты, неизменяемое состояние, конфигурация через расширения.
.cursor/rules/codemirror.mdcЧто делает codemirror
Правило описывает работу с CodeMirror 6 и прямо запрещает CodeMirror 5 в новом коде. Старые места на CM5 предлагается переводить на шестёрку. Внутри — набор требований с примерами «плохо» и «хорошо»: как импортировать пакеты, как менять документ, как добавлять возможности редактора.
Конфигурация идёт только через расширения EditorState. Прямая правка DOM или присвоение свойств вроде view.dom.style не приветствуется. Состояние неизменяемое: изменения применяют через Transaction и view.dispatch, а не мутацией EditorState. Для новых модулей правило советует TypeScript, чтобы параметры вроде ViewUpdate были типизированы.
Производительность тоже разобрана: несколько правок собирают в один Transaction, а внешние обработчики — изменение размера окна, ответы сети — советует оборачивать в debounce или throttle. Файл подхватывается по маске **/*.{js,jsx}, так что правило сработает и в обычных JS-файлах, где CodeMirror не используется. Это повод сузить globs под свой проект.
Какие задачи решает
- перевести редактор с CodeMirror 5 на шестёрку
- настроить редактор без правки DOM напрямую
- применить изменение документа через Transaction
- сократить размер бандла за счёт точных импортов
- собрать несколько правок в один dispatch
Примеры запросов агенту
- Добавь редактор кода на CodeMirror 6 в этот компонент
- Перепиши этот файл с CM5 на CodeMirror 6
- Почему мой dispatch не меняет документ в CodeMirror?
Как установить codemirror
Вручную: положить файлы
.cursor/rules/codemirror.mdcВо frontmatter: description, globs, alwaysApply
Вручную: положить файлы
.claude/rules/codemirror.mdПоле paths во frontmatter ограничивает файлы: globs из источника → paths
AGENTS.md
Codex читает AGENTS.md: вставьте текст правила туда
Вручную: положить файлы
.github/instructions/codemirror.instructions.mdПоле applyTo задаёт, к каким файлам применять: globs из источника → applyTo
Вручную: положить файлы
.devin/rules/codemirror.mdВручную: положить файлы
.clinerules/codemirror.mdПо шагам, с командами для каждой платформы: Cursor rules: файлы .mdc, четыре режима и замена .cursorrules
Вопросы о codemirror
Как установить правило codemirror в Cursor?
Создайте файл .cursor/rules/codemirror.mdc в проекте и вставьте в него содержимое правила. Cursor подхватит его автоматически.
Что делает правило codemirror?
Задаёт стандарты для редакторов на CodeMirror 6: модульные импорты, конфигурация через расширения, неизменяемое состояние и батчинг обновлений.
Можно ли использовать CodeMirror 5 с этим правилом?
Нет. Правило требует CM6 в новом коде и советует переводить существующие места на CM5.
Исходный текст
codemirror.mdc · CC0-1.0
---
description: This guide defines best practices for building and maintaining CodeMirror 6-based editors, focusing on modularity, immutable state, extension-driven configuration, and modern JavaScript/TypeScript patterns.
globs: **/*.{js,jsx}
---
# CodeMirror 6 Best Practices
This document outlines the definitive best practices for developing with CodeMirror. We exclusively target **CodeMirror 6 (CM6)**. Any reference to CodeMirror 5 (CM5) is for historical context only; **CM5 must not be used in new development and should be actively migrated away from.**
## Code Organization and Structure
### 1. Always Use CodeMirror 6
CM6 is a complete rewrite with a modern, modular architecture. It offers superior performance, extensibility, and maintainability. CM5 is deprecated and unsupported.
❌ **BAD: Using CodeMirror 5**
```javascript
// Old CM5 global API
var myCodeMirror = CodeMirror(document.body, {
value: "function myScript(){}",
mode: "javascript"
});
```
✅ **GOOD: Using CodeMirror 6**
```javascript
import { EditorState } from "@codemirror/state";
import { EditorView, basicSetup } from "codemirror";
import { javascript } from "@codemirror/lang-javascript";
const view = new EditorView({
state: EditorState.create({
doc: "console.log('Hello CM6!');",
extensions: [basicSetup, javascript()],
}),
parent: document.body,
});
```
### 2. Import Only What You Need (Modular Imports)
CM6 is designed for tree-shaking. Only import the specific packages and modules required to minimize bundle size and improve load times.
❌ **BAD: Importing monolithic packages (if they existed for CM6)**
```javascript
// Hypothetical bad example: importing a giant 'codemirror-all' package
import { EditorView, EditorState, basicSetup, javascript, ... } from "codemirror-all";
```
✅ **GOOD: Granular, specific imports**
```javascript
import { EditorState } from "@codemirror/state"; // Core state management
import { EditorView, keymap } from "@codemirror/view"; // Core view rendering
import { defaultKeymap } from "@codemirror/commands"; // Standard editing commands
import { javascript } from "@codemirror/lang-javascript"; // Language support
```
### 3. Configure Exclusively via Extensions
All editor features, from basic setup to complex language services, must be added as extensions to the `EditorState`. Avoid direct DOM manipulation or non-extension-based configuration.
❌ **BAD: Direct property assignment or DOM manipulation**
```javascript
const view = new EditorView({ state: EditorState.create({ doc: "" }) });
// Don't do this: Bypassing the extension system
view.dom.style.fontSize = "16px";
```
✅ **GOOD: Everything is an extension**
```javascript
import { EditorState } from "@codemirror/state";
import { EditorView, lineNumbers } from "@codemirror/view";
import { oneDark } from "@codemirror/theme-one-dark"; // A theme extension
const view = new EditorView({
state: EditorState.create({
doc: "const x = 1;",
extensions: [
lineNumbers(), // Line numbers extension
oneDark, // Theme extension
EditorView.lineWrapping, // Line wrapping extension
EditorState.tabSize.of(2), // Configuration option as an extension
],
}),
parent: document.body,
});
```
## Common Patterns and Anti-patterns
### 4. Embrace Immutable State Management
`EditorState` objects are immutable. All changes must be applied by creating a `Transaction` and dispatching it to the `EditorView`. This enables reliable undo/redo, time-travel debugging, and predictable behavior.
❌ **BAD: Mutating `EditorState` or `Text` directly**
```javascript
let state = EditorState.create({ doc: "hello" });
// This will NOT work as expected and breaks CM6's core design
// state.doc = Text.of("world");
// console.log(state.doc.toString()); // Still "hello"
```
✅ **GOOD: Using `Transaction` and `dispatch`**
```javascript
import { EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
const startState = EditorState.create({ doc: "Hello World" });
const view = new EditorView({ state: startState, parent: document.body });
// Create a transaction to change the document
const transaction = view.state.update({
changes: { from: 0, to: 5, insert: "Goodbye" }, // Change "Hello" to "Goodbye"
selection: { anchor: 7 }, // Move cursor
// Add other state changes like effects or annotations here
});
view.dispatch(transaction); // Apply the transaction
console.log(view.state.doc.toString()); // "Goodbye World"
```
### 5. Prioritize TypeScript for New Code
For new CodeMirror components, always use TypeScript. It provides static type checking, improved IDE support, and reduces runtime errors, leading to more robust and maintainable code.
❌ **BAD: Writing new complex modules in plain JavaScript**
```javascript
// my-complex-cm-plugin.js
export function myComplexPlugin() {
// No type safety, easy to introduce bugs
return ViewPlugin.define(view => ({
update(update) {
if (update.docChanged) {
// What type is update.docChanged?
}
}
}));
}
```
✅ **GOOD: Writing new complex modules in TypeScript**
```typescript
// my-complex-cm-plugin.ts
import { EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view";
import { Extension } from "@codemirror/state";
export function myComplexPlugin(): Extension {
return ViewPlugin.define(view => ({
// Explicitly typed parameters for clarity and safety
update(update: ViewUpdate) {
if (update.docChanged) {
console.log("Document changed:", update.changes.toString());
}
},
}));
}
```
## Performance Considerations
### 6. Batch Editor Updates
Avoid dispatching multiple transactions in quick succession. Group related changes into a single `Transaction` to minimize DOM updates and improve performance.
❌ **BAD: Frequent, unbatched dispatches**
```javascript
// In a loop or event handler
for (let i = 0; i < 100; i++) {
view.dispatch(view.state.update({ changes: { from: i, insert: "a" } }));
}
```
✅ **GOOD: Batching changes into a single transaction**
```javascript
let changes = [];
for (let i = 0; i < 100; i++) {
changes.push({ from: i, insert: "a" });
}
view.dispatch(view.state.update({ changes })); // Single dispatch
```
### 7. Debounce/Throttle External Event Handlers
When external events (e.g., window resize, network responses, user input outside the editor) trigger editor updates or expensive computations, always debounce or throttle them to prevent excessive processing.
```javascript
import { EditorView } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import { javascript } from "@codemirror/lang-javascript";
const view = new EditorView({
state: EditorState.create({
doc: "function expensiveLint() { /* ... */ }",
extensions: [javascript()],
}),
parent: document.body,
});
// Assume `lintDocument` is an expensive operation
const debouncedLint = debounce(() => {
// Trigger linting based on current editor state
console.log("Linting document...");
}, 500);
// Listen for editor state changes
view.dom.addEventListener("input", debouncedLint);
function debounce(func, delay) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), delay);
};
}
```
## Common Pitfalls and Gotchas
### 8. Never Mix CodeMirror 5 and 6
The architectures are fundamentally different. Attempting to use CM5 add-ons or APIs with a CM6 editor will lead to unpredictable behavior and errors.
❌ **BAD: Using CM5 add-ons with CM6**
```javascript
// This will NOT work with CM6
import "codemirror/addon/search/searchcursor";
```
✅ **GOOD: Use CM6-native extensions**
```javascript
// For search, use CM6's @codemirror/search
import { search, searchKeymap, highlightSelectionMatches } from "@codemirror/search";
import { EditorState } from "@codemirror/state";
import { EditorView, keymap } from "@codemirror/view";
const view = new EditorView({
state: EditorState.create({
doc: "search me",
extensions: [
search(),
keymap.of(searchKeymap),
highlightSelectionMatches(),
],
}),
parent: document.body,
});
```
### 9. Understand Extension Precedence
When multiple extensions affect the same aspect of the editor (e.g., keymaps, decorations), their order in the `extensions` array and explicit `Prec` modifiers determine precedence. Debug carefully if an extension isn't behaving as expected.
```javascript
import { EditorState, Prec } from "@codemirror/state";
import { EditorView, keymap } from "@codemirror/view";
import { defaultKeymap } from "@codemirror/commands";
// A custom keymap that should override default behavior
const myCustomKeymap = keymap.of([
{
key: "Mod-s",
run: (view) => {
console.log("Custom save action!");
return true; // Indicate that the event was handled
},
},
]);
const view = new EditorView({
state: EditorState.create({
doc: "Press Cmd-S",
extensions: [
defaultKeymap, // Default keymap is defined first
Prec.highest(myCustomKeymap), // Custom keymap with highest precedence will override
],
}),
parent: document.body,
});
```
## Testing Approaches
### 10. Unit Test `EditorState` Logic
Isolate and unit test functions that operate on or produce `EditorState` objects. Since `EditorState` is immutable, these functions are pure and easy to test without a browser environment.
```javascript
// my-state-transformer.js
import { EditorState } from "@codemirror/state";
export function addCommentToLine(state, lineNumber, commentText) {
const line = state.doc.line(lineNumber);
return state.update({
changes: { from: line.from, insert: `// ${commentText}\n` + line.text },
});
}
// my-state-transformer.test.js
import { EditorState } from "@codemirror/state";
import { addCommentToLine } from "./my-state-transformer";
test("addCommentToLine adds comment correctly", () => {
const initialState = EditorState.create({ doc: "line 1\nline 2" });
const newState = addCommentToLine(initialState, 2, "My comment");
expect(newState.doc.toString()).toBe("line 1\n// My comment\nline 2");
});
```
### 11. Use End-to-End Tests for `EditorView` Interactions
For testing UI interactions (typing, selection, scrolling, extension rendering), use browser-based end-to-end testing frameworks like Playwright or Cypress. These simulate real user behavior.
```javascript
// playwright.test.js (conceptual example)
import { test, expect } from "@playwright/test";
test("editor displays line numbers and allows typing", async ({ page }) => {
await page.goto("http://localhost:3000/editor"); // Your app's editor page
const editor = page.locator(".cm-editor");
await expect(editor).toBeVisible();
// Check if line numbers are visible
await expect(page.locator(".cm-lineNumbers")).toBeVisible();
// Type into the editor
await editor.type("Hello, CodeMirror!");
await expect(editor).toHaveText(/Hello, CodeMirror!/);
});
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc