Showing preview only (217K chars total). Download the full file or copy to clipboard to get everything.
Repository: logue/vue-codemirror6
Branch: master
Commit: d836afe8e65d
Files: 45
Total size: 195.7 KB
Directory structure:
gitextract_ynp_uc9x/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── CODEOWNERS
│ ├── copilot-instructions.md
│ └── workflows/
│ └── build-docs.yml
├── .gitignore
├── .nvmrc
├── .oxlintrc.json
├── .prettierignore
├── .prettierrc.yaml
├── AGENT.md
├── CHANGELOG.md
├── LICENSE
├── README.ja.md
├── README.md
├── SSR_FIX_SUMMARY.md
├── TESTING.md
├── env.d.ts
├── eslint.config.ts
├── index.html
├── package.json
├── pnpm-workspace.yaml
├── scripts/
│ └── sync-dts-entry.mjs
├── src/
│ ├── __tests__/
│ │ ├── CodeMirror.spec.ts
│ │ └── CodeMirror.ssr.spec.ts
│ ├── helpers/
│ │ └── h-demi.ts
│ ├── index.ts
│ └── interfaces/
│ └── MetaInterface.ts
├── src-docs/
│ ├── App.vue
│ ├── DemoPage.vue
│ ├── components/
│ │ ├── KeyMapDemo.vue
│ │ ├── LinterAndCrossBindingDemo.vue
│ │ ├── MarkdownDemo.vue
│ │ ├── ReadonlyAndDisabledDemo.vue
│ │ ├── SlotDemo.vue
│ │ └── ToggleTheme.vue
│ ├── main.ts
│ └── style.scss
├── tsconfig.app.json
├── tsconfig.docs.json
├── tsconfig.json
├── tsconfig.node.json
├── tsconfig.vitest.json
├── vite.config.ts
└── vitest.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
================================================
FILE: .gitattributes
================================================
* text=auto
* vue eol=lf encoding=UTF-8
*.css eol=lf encoding=UTF-8
*.html eol=lf encoding=UTF-8
*.js eol=lf encoding=UTF-8
*.jsx eol=lf encoding=UTF-8
*.md eol=lf encoding=UTF-8
*.scss eol=lf encoding=UTF-8
*.ts eol=lf encoding=UTF-8
*.tsx eol=lf encoding=UTF-8
*.txt eol=lf encoding=UTF-8
*.xml eol=lf encoding=UTF-8
# Images
*.gif binary
*.ico binary
*.jpg binary
*.png binary
*.svg eol=lf encoding=UTF-8
*.webp binary
# Fonts
*.eot binary
*.otf binary
*.ttf binary
*.woff binary
================================================
FILE: .github/CODEOWNERS
================================================
# Generated by CODEOWNERS.com
================================================
FILE: .github/copilot-instructions.md
================================================
# Vue-CodeMirror6 Workspace Instructions
**Project**: A Vue 2 & 3 compatible CodeMirror 6 component library
**Tech Stack**: TypeScript, Vue 3 (with vue-demi for Vue 2 support), Vite, Vitest, CodeMirror 6
**Language**: Primarily TypeScript with Vue SFC components
## Essential Commands
| Task | Command |
| ---------------------- | -------------------------------------------------- |
| **Development server** | `pnpm dev` |
| **Build library** | `pnpm build` (includes type checking) |
| **Build docs** | `pnpm build:docs` then `pnpm preview` |
| **Run tests** | `pnpm test` (watch mode) or `pnpm test:run` (once) |
| **Test coverage** | `pnpm test:coverage` |
| **All linting** | `pnpm lint` (oxlint, eslint, prettier) |
| **Type check** | `pnpm type-check` (vue-tsc) |
## Architecture & Key Patterns
### Main Component (`src/index.ts`, `src/Meta.ts`)
- **Vue Composition API** with `vue-demi` for Vue 2/3 compatibility
- **Core Props**: `modelValue`, `lang`, `extensions`, `linter`, `keymap`, `dark`, `readonly`, `disabled`, etc.
- **Exposed Methods**: `getView()`, `focus()`, `getRange()`, `setCursor()`, `getSelection()` (CodeMirror5 API compatibility layer)
- **Event Emitters**: `ready`, `update`, `change`, `destroy`
- **Two Setup Modes**: `basic` (basicSetup) or `minimal` (minimalSetup)
### Design Principles
1. **Unidirectional + v-model binding**: Text content updates flow via v-model
2. **Optional ChainING on `view.value`**: All CodeMirror view access uses `view.value?.` to handle SSR
3. **Props over Extensions**: Explicit props (`lang`, `linter`, `keymap`) separate from generic `extensions[]` for better type safety and DX
4. **Lazy Initialization**: Editor only initializes in browser (client-side), SSR-safe with `onMounted` checks
### Testing Strategy (`src/__tests__/`)
- **`CodeMirror.spec.ts`**: Component functionality (rendering, props, v-model, events, slots, public methods)
- **`CodeMirror.ssr.spec.ts`**: SSR compatibility (server-side rendering, safe method calls, hydration, cleanup)
- **Framework**: Vitest with happy-dom environment
- **Setup**: Uses Vue Test Utils for component mounting
- **Coverage Targets**: All public methods and critical code paths (see vitest.config.ts for exclusions)
## Code Quality Standards
### Linting & Formatting
- **Oxlint**: Fast, Rust-based linting (primary)
- **ESLint**: Vue plugin + TypeScript rules + accessibility checks
- **Prettier**: Code formatting
- **vue-tsc**: Type checking before build
Run all checks: `pnpm lint` (automatically fixes most issues)
### TypeScript
- **Strict Mode**: Enabled
- **Vue Support**: `@vue/eslint-config-typescript`
- **Type Declarations**: Auto-generated via `vite-plugin-dts` during build
- **Aliases**: `@` → `src/`, `vue-codemirror6` → `src/`
## Important Context
### SSR Compatibility (Critical)
The component must work in SSR environments (Nuxt.js, etc.):
- `view.value` may be `undefined` on server ⚠️
- Always use optional chaining: `view.value?.method()`
- Browser-only code wrapped in `if (typeof window !== 'undefined')`
- See [SSR_FIX_SUMMARY.md](../SSR_FIX_SUMMARY.md) for detailed changes
### Build Outputs
### Generated Metadata
- `src/Meta.ts` is generated automatically when starting the dev server or running the library build.
- If `src/Meta.ts` is missing in a fresh checkout, run `pnpm dev` or `pnpm build` before treating it as a broken import.
Multiple formats in `dist/`:
- ES modules: `index.es.js`
- CommonJS: `index.cjs.js`
- UMD: `index.umd.js`
- IIFE: `index.iife.js`
- Types: `index.d.ts`
### Peer Dependencies
CodeMirror 6 packages are peer deps (not bundled):
```text
@codemirror/{commands,language,lint,search,state,view}
@codemirror/autocomplete
codemirror (state/view core)
style-mod
vue: ^2.7.14 || ^3.3.4
```
Users must install these separately to avoid duplication.
### Common Development Tasks
**Adding a new prop**: Add to interface → component props → applicable compartment/state → test it
**Adding language support demo**: Add to `src-docs/components/` and import in `App.vue`
**Fixing a bug**: Create test first in `__tests__/`, implement fix in `src/`, verify with `pnpm test`
**Updating themes or extensions**: Use the `extensions` prop or create a helper function in `src/helpers/`
## Key Files Reference
| File | Purpose |
| ------------------------------------------------------------------------------- | ----------------------------------------------- |
| [src/index.ts](../src/index.ts) | Main component definition |
| [src/Meta.ts](../src/Meta.ts) | Component metadata and type definitions |
| [src/**tests**/CodeMirror.spec.ts](../src/__tests__/CodeMirror.spec.ts) | Component tests |
| [src/**tests**/CodeMirror.ssr.spec.ts](../src/__tests__/CodeMirror.ssr.spec.ts) | SSR tests |
| [vite.config.ts](../vite.config.ts) | Build config (outputs, plugins, DTS generation) |
| [vitest.config.ts](../vitest.config.ts) | Test config (happy-dom, coverage) |
| [eslint.config.ts](../eslint.config.ts) | Linting config |
## Common Pitfalls to Avoid
1. **Direct `view` access without optional chaining** → SSR will break
2. **Importing `@codemirror` modules in component** → May cause bundling issues; prefer `extensions` prop
3. **Mutating props directly** → Use emitted events or expose methods instead
4. **Async state changes without `nextTick`** → Can cause race conditions in updates
5. **Forgetting to test SSR mode** → Use `pnpm test` to run all tests including SSR
## Workspace Conventions
- **Vue 3 syntax throughout** (vue-demi handles Vue 2 compatibility)
- **TypeScript strict mode**
- **Composition API only** (no Options API)
- **Kebab-case for component props** (auto-converted from camelCase)
- **PascalCase for type names**, camelCase for variables/functions
- **Comments for complex CodeMirror API usage** (document non-obvious patterns)
================================================
FILE: .github/workflows/build-docs.yml
================================================
name: NodeJS with Vite
on:
push:
branches: ['master']
pull_request:
branches: ['master']
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.x]
steps:
- name: Checkout ✅
uses: actions/checkout@v4.2.2
- name: Use Node.js ${{ matrix.node-version }} ⚡
uses: actions/setup-node@v4.1.0
with:
node-version: ${{ matrix.node-version }}
- name: Install pnpm 🎁
uses: pnpm/action-setup@v4
- name: Build 🔧
run: |
pnpm install
pnpm run build:docs
- name: Deploy to gh-pages 🚀
uses: JamesIves/github-pages-deploy-action@v4.6.8
with:
branch: gh-pages # The branch the action should deploy to.
folder: docs # The folder the action should deploy.
================================================
FILE: .gitignore
================================================
/.yarn/*
# !/.yarn/patches
# !/.yarn/plugins
!/.yarn/releases
# !/.yarn/sdks
# !/.yarn/cache
/.vscode/*
node_modules
.DS_Store
dist
dist-ssr
*.local
/src/Meta.ts
docs/
stats.html
*.tsbuildinfo
================================================
FILE: .nvmrc
================================================
24.14.1
================================================
FILE: .oxlintrc.json
================================================
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["eslint", "typescript", "unicorn", "oxc", "vue"],
"env": {
"browser": true
},
"categories": {
"correctness": "error"
}
}
================================================
FILE: .prettierignore
================================================
.husky/
.vscode/
.yarn/
dist/
public/assets/
docs/
stats.html
================================================
FILE: .prettierrc.yaml
================================================
printWidth: 80
tabWidth: 2
useTabs: false
semi: true
singleQuote: true
trailingComma: es5
bracketSpacing: true
bracketSameLine: false
arrowParens: avoid
htmlWhitespaceSensitivity: ignore
endOfLine: lf
================================================
FILE: AGENT.md
================================================
# AGENT.md
This file provides guidance for AI coding agents (GitHub Copilot, Claude, Cursor, etc.) working in this repository.
---
## Project Overview
CodeMirror component for Vue2 and Vue3.
- **Framework**: Vue 3 (`<script setup>` SFC only)
- **Build tool**: Vite 8
- **Language**: TypeScript (strict)
- **Package manager**: pnpm (do not use npm or yarn)
- **Node version**: `^20.19.0 || >=22.12.0`
---
## Commands
```bash
pnpm dev # Start dev server (http://localhost:5173)
pnpm build # Type-check + production build
pnpm lint # Run all linters (oxlint → eslint → prettier → stylelint)
pnpm type-check # vue-tsc type check only
pnpm test:unit # Vitest unit tests
pnpm test:coverage # Vitest with coverage report
pnpm test:e2e # Playwright E2E tests
pnpm build:analyze # Bundle size analysis (rollup-plugin-visualizer)
pnpm clean # Clear Vite dev cache
```
Always run `pnpm lint` and `pnpm type-check` before committing. These are also enforced by husky pre-commit hooks via lint-staged.
---
## TypeScript Rules
- **No `any`** — use `unknown` and narrow with type guards.
- **Explicit return types** on exported functions (Pinia stores are exempt due to setup-style inference).
- **Use `type` over `interface`** for object shapes; extend via intersection (`&`).
- **Union literal types** instead of magic strings:
```ts
type Status = 'active' | 'inactive' | 'pending';
```
- **Underscore prefix** for intentionally unused variables: `_value`, `_error`.
- **Array type syntax**: `string[]` not `Array<string>`.
- **Generic constructors**: left-hand side style — `const map: Map<string, User> = new Map()`.
---
## Vue SFC Rules
### Script
- Always use `<script setup lang="ts">` — Options API is prohibited.
- `defineProps` and `defineEmits` must use **type-based declarations** (runtime declarations are prohibited):
```ts
// OK
const props = defineProps<{ title: string; count?: number }>();
const emit = defineEmits<{ change: [value: string]; close: [] }>();
// NG
const props = defineProps({ title: String });
```
- Return values from composables as individual `ref`s (not `reactive`) to enable destructuring.
- Internal state exposed from composables should be wrapped in `readonly()`.
### Template
- **Self-closing void elements**: `<br />`, `<img />`, `<input />`.
- **Attribute order** (enforced by `vue/attributes-order`):
`DEFINITION` → `LIST_RENDERING` → `CONDITIONALS` → `RENDER_MODIFIERS` → `UNIQUE` → `TWO_WAY_BINDING` → `OTHER_DIRECTIVES` → `ATTR_DYNAMIC` → `ATTR_STATIC` → `ATTR_SHORTHAND_BOOL` → `EVENTS` → `CONTENT`
- Run `pnpm lint` to auto-fix attribute order.
### Style
- Always use `<style lang="scss" scoped>` — unscoped styles are prohibited.
- CSS custom properties (design tokens) must be defined in a shared file (e.g., `src/styles/variables.scss`) and not duplicated per component.
- CSS property order is enforced by stylelint-order — run `pnpm lint:style` to auto-fix.
---
## Component Naming
- Component files: **PascalCase**, multi-word required (e.g., `UserCard.vue`, `AppHeader.vue`).
- `src/components/**/*.vue` — `error`
- `src/pages/**/*.vue` and `src/layouts/**/*.vue` — `warn` (file-based routing constraint)
- Do not create single-word components like `Header.vue` or `Card.vue` outside of pages/layouts.
---
## Import Rules
- **Always use the `@/` alias** for internal imports — relative parent traversal (`../`) is prohibited in application code:
```ts
// OK
import { useUserStore } from '@/stores/user';
import type { User } from '@/types';
// NG
import { useUserStore } from '../../../stores/user';
```
> **Exception**: test files under `src/**/__tests__/` may use `../` to import the component under test (e.g., `import MyComponent from '../MyComponent.vue'`). This is intentional and the ESLint rule is disabled for that scope.
- The `~` alias maps to `node_modules` (e.g., `~/some-lib/style.css`).
- **Import order** (enforced by `import-x/order`, auto-fixed by `pnpm lint:eslint`):
1. Node built-ins
2. Vue core (`vue`, `vue-router`, `pinia`, `@vue/*`, `@vitejs/*`)
3. External packages
4. Internal (`@/**`)
5. Sibling / index
6. Type imports
A blank line is required between each group.
---
## Pinia Store Rules
- Use **setup-store style** exclusively (not options-store style):
```ts
// OK
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null);
function setUser(u: User) {
user.value = u;
}
return { user, setUser };
});
```
- Store ID must match the file name (e.g., `defineStore('user', ...)` in `stores/user.ts`).
- Persist state via `pinia-plugin-persistedstate` — do not manually read/write `localStorage`.
---
## Accessibility (a11y)
Rules are enforced by `eslint-plugin-vuejs-accessibility`. Key points:
- `<a>` elements must have text content (`vuejs-accessibility/anchor-has-content: error`).
- Avoid `autofocus` (`vuejs-accessibility/no-autofocus: warn`).
- For **single input** → use `<label for="id">` or nesting.
- For **compound UI** (e.g., slider + number input sharing a label) → use `aria-labelledby` or `<fieldset>` + `<legend>`:
```vue
<!-- Compound: aria-labelledby -->
<p id="volume-label">Volume</p>
<input type="range" v-model="volume" aria-labelledby="volume-label" />
<input type="number" v-model="volume" aria-labelledby="volume-label" />
<!-- Or: fieldset + legend -->
<fieldset>
<legend>Volume</legend>
<input type="range" v-model="volume" />
<input type="number" v-model="volume" />
</fieldset>
```
- `vuejs-accessibility/label-has-for` is set to `warn` for compound UI patterns. When disabling intentionally, add a description comment (required by `eslint-comments/require-description`):
```vue
<!-- eslint-disable-next-line vuejs-accessibility/label-has-for -- compound slider+input UI -->
```
---
## ESLint Disable Policy
`eslint-comments/require-description` is set to `error`. Every `eslint-disable` comment **must** include a reason:
```ts
// OK
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- third-party type gap, no @types available
// NG
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
```
---
## Testing
### Unit tests (Vitest)
- Test files: `src/**/__tests__/*.ts`
- Follow **Arrange / Act / Assert** structure.
- Mock external dependencies (API, DB, browser APIs) — tests must not make real network calls.
- Coverage threshold: **80%** lines and functions (enforced in CI).
### E2E tests (Playwright)
- Test files: `e2e/**/*.{test,spec}.{js,ts}`
- Test user-visible behavior, not implementation details.
---
## Git & PR Rules
- Commit messages follow **Conventional Commits**:
```
feat(auth): add JWT refresh token rotation
fix(api): handle 429 rate limit with exponential backoff
docs: update README setup instructions
```
- PRs should be focused on a single purpose; aim for diffs under ~400 lines.
- Minimum **1 approving review** required before merging to `master`.
- PR description must include: what changed, how to test, and screenshots if UI is affected.
---
## Environment Variables
- All client-side env vars must be prefixed with `VITE_APP_`:
```
VITE_APP_TITLE=My App
VITE_APP_API_BASE_URL=https://api.example.com
```
- Copy `.env.example` to `.env` before running the dev server.
- Never commit `.env` — it is in `.gitignore`.
- Access via `import.meta.env.VITE_APP_*` (typed in `env.d.ts`).
---
## What NOT to Do
- Do not use `any` — use `unknown` with type guards.
- Do not use Options API (`defineComponent`, `data()`, `methods:`).
- Do not use runtime `defineProps({ title: String })` declarations.
- Do not write `../` relative imports that traverse parent directories (exception: `src/**/__tests__/` may use `../` to reach the component under test).
- Do not use `<style>` without `scoped`.
- Do not write bare `localStorage` / `sessionStorage` access — use `pinia-plugin-persistedstate`.
- Do not add `eslint-disable` comments without a description.
- Do not install packages with `npm` or `yarn` — use `pnpm` only.
================================================
FILE: CHANGELOG.md
================================================
### Changelog
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.5.2](https://github.com/logue/vue-codemirror6.git/compare/1.5.1...1.5.2)
- Add AGENT.md. [`5514bf3`](https://github.com/logue/vue-codemirror6.git/commit/5514bf3664c4d1f5fdab926449780079362bc03b)
- Vulnerability response [`461d0b1`](https://github.com/logue/vue-codemirror6.git/commit/461d0b1e82c49088ffb0775310f01b2fb63b991c)
- Update dependencies. [`c8ef924`](https://github.com/logue/vue-codemirror6.git/commit/c8ef9247ec878390640c050c7e499ef508091354)
#### [1.5.1](https://github.com/logue/vue-codemirror6.gitcompare/1.5.0...1.5.1)
> 3 April 2026
- Fixed index.d.ts path. [`eed7df2`](https://github.com/logue/vue-codemirror6.git/commit/eed7df2e5dfc2310415e4b6c9e1f1a9cb3e32b3f)
#### [1.5.0](https://github.com/logue/vue-codemirror6.git/compare/1.4.3...1.5.0)
> 30 March 2026
- Add AI instructions. [`75fecba`](https://github.com/logue/vue-codemirror6.git/commit/75fecba1d79bbd308864dbbfb5884026c1c20be6)
- Fixed warnings output during the build process. [`4721a8d`](https://github.com/logue/vue-codemirror6.git/commit/4721a8d94faf0a32c5a7b2459f989e361acb0a27)
- Update Dependencies. [`c8c3d2a`](https://github.com/logue/vue-codemirror6.git/commit/c8c3d2a23ef8eecc6ab28d42d53bdc7f457ceac5)
#### [1.4.3](https://github.com/logue/vue-codemirror6.git/compare/1.4.2...1.4.3)
> 5 March 2026
- Replace eslint-plugin-import to eslint-plugin-x. [`e46491a`](https://github.com/logue/vue-codemirror6.git/commit/e46491a24270f8ea392a3354bbecbaf7092f8ef9)
- Add Japanese Readme. [`e279cc0`](https://github.com/logue/vue-codemirror6.git/commit/e279cc0e1b23bba6c1dd690fb6a963b82e8508f6)
- Update ChangeLog/ [`8648633`](https://github.com/logue/vue-codemirror6.git/commit/86486330c50af4c4c13eeabcc3b6a498ab8a65d1)
#### [1.4.2](https://github.com/logue/vue-codemirror6.git/compare/1.4.0...1.4.2)
> 17 February 2026
- Move the contents of Codemirror.ts to index.ts. [`d9a624b`](https://github.com/logue/vue-codemirror6.git/commit/d9a624bbe61034b2a5abe553b0e362818ea8e91b)
- Fixed dark mode in demo program. [`dd54015`](https://github.com/logue/vue-codemirror6.git/commit/dd54015c8ebb11cf24dd8d5b38e1e35e00ed56b5)
- Add oxilint. [`abb115e`](https://github.com/logue/vue-codemirror6.git/commit/abb115edd0bcafd641c4a7b35af566bdb1630334)
#### [1.4.0](https://github.com/logue/vue-codemirror6.gi/compare/1.3.13...1.4.0)
> 17 October 2025
- fix: full example link [`#58`](https://github.com/logue/vue-codemirror6.git/pull/58)
- Update dependencies. [`985f36f`](https://github.com/logue/vue-codemirror6.git/commit/985f36f23c4fc1b562b53b664deb7ff1eec79989)
- Reviewed dependencies. Moved them to peerDependencies and relaxed the version requirements. [`1ffab22`](https://github.com/logue/vue-codemirror6.git/commit/1ffab224a4274e142449e74abc71a988f6ad8d1c)
- Update vue, codemirror. [`bfbd153`](https://github.com/logue/vue-codemirror6.git/commit/bfbd153eeec6c718b95bb719117ad5a02dcf6a37)
#### [1.3.13](https://github.com/logue/vue-codemirror6.git/compare/1.3.8...1.3.13)
> 31 March 2025
- Rewrite to eslint.config.ts [`841f686`](https://github.com/logue/vue-codemirror6.git/commit/841f686ebab5f542a5b7bc1569660ce58a21af65)
- Fixed types path. [`2b86b2c`](https://github.com/logue/vue-codemirror6.git/commit/2b86b2c4da906f4591e13221e5c41e52b8792523)
- Update @codemirror/language, @codemirror/lint, @codemirror/state, @codemirror/view. [`916fa98`](https://github.com/logue/vue-codemirror6.git/commit/916fa9899e7c3806199f53bddb210d3cba9e7517)
#### [1.3.8](https://github.com/logue/vue-codemirror6.git/compare/1.1.16...1.3.8)
> 22 November 2024
- Fix sample code syntax error [`#47`](https://github.com/logue/vue-codemirror6.git/pull/47)
- chore: demo docs [`#43`](https://github.com/logue/vue-codemirror6.git/pull/43)
- fix: editor not update content when `modelValue` change and selection is out of range [`#44`](https://github.com/logue/vue-codemirror6.git/pull/44)
- chore: props and readme doc [`#31`](https://github.com/logue/vue-codemirror6.git/pull/31)
- Reduce unnecessary updates [`#22`](https://github.com/logue/vue-codemirror6.git/pull/22)
- fix: coalescing-operator [`#20`](https://github.com/logue/vue-codemirror6.git/pull/20)
- Migrate to pnpm. [`6d83aec`](https://github.com/logue/vue-codemirror6.git/commit/6d83aec625d50228cbfb248bc5811b6f83975219)
- Update dependencies. [`992f209`](https://github.com/logue/vue-codemirror6.git/commit/992f209c5e4c269b72be119f693b0d0376d529bb)
- Update dependencies. [`b839a33`](https://github.com/logue/vue-codemirror6.git/commit/b839a33569fadf3a9bb5e2a8f972c377fc1fce88)
#### [1.1.16](https://github.com/logue/vue-codemirror6.git/compare/1.1.13...1.1.16)
> 18 March 2023
- Expose `view`. #16 [`daf3e6b`](https://github.com/logue/vue-codemirror6.git/commit/daf3e6b17a161a5c0c5a35ea2442d85421f05d82)
- Fixed Maximum call stack size exceeded error. [`f10eaae`](https://github.com/logue/vue-codemirror6.git/commit/f10eaaea9145bc1d7a6605ab3cb8a82c0ca12df6)
- Partially fixed the problem that the values defined in props disappeared under certain conditions. [`1cc4468`](https://github.com/logue/vue-codemirror6.git/commit/1cc4468e35e39ee8eaae1b2f007e3425a757217f)
#### [1.1.13](https://github.com/logue/vue-codemirror6.git/compare/1.1.12...1.1.13)
> 13 March 2023
- Changed minimum Vue version requirement to 2.7.14. [`5b8b255`](https://github.com/logue/vue-codemirror6.git/commit/5b8b2558967f9fb6f6d9e83a0181afa50f182b98)
- Add allow-multiple-selections prop. [`ced91ed`](https://github.com/logue/vue-codemirror6.git/commit/ced91edab8fc25f8f1299341489f6bbbdace9ce8)
- Update CHANGELOG.md [`51d30e2`](https://github.com/logue/vue-codemirror6.git/commit/51d30e2f6370847ef888ab2b39ff6e1c8e3ec851)
#### [1.1.12](https://github.com/logue/vue-codemirror6.git/compare/1.1.11...1.1.12)
> 10 March 2023
- chore(CODEOWNERS): CODEOWNERS.com Bot, at your service! [`#14`](https://github.com/logue/vue-codemirror6.git/pull/14)
- Update demo code. (Replaced @codemirror/html to @codemirror/vue.) [`8da1a8a`](https://github.com/logue/vue-codemirror6.git/commit/8da1a8a4e37d713cc78281ce9c304c42a06940dc)
- Changed implementation to generate \*.d.ts with vite-plugin-dts. [`1caaa20`](https://github.com/logue/vue-codemirror6.git/commit/1caaa20b1423a3d252a6c770a630fb60e2e3440d)
- Update README.md. [`f8dd493`](https://github.com/logue/vue-codemirror6.git/commit/f8dd4934a7b9cc089cd830473db5250d28b3389b)
#### [1.1.11](https://github.com/logue/vue-codemirror6.git/compare/1.1.3...1.1.11)
> 4 February 2023
- [SECURITY FIX] CWE-1333: Inefficient Regular Expression Complexity #13 [`fc63a9f`](https://github.com/logue/vue-codemirror6.git/commit/fc63a9f14b5307089e9551157b011072971b112f)
- Fix for #11 [`c634b55`](https://github.com/logue/vue-codemirror6.git/commit/c634b555ffff93bb30da2f501ec366289fc86ec1)
- Add forceReconfigure() function. [`bbfa8ad`](https://github.com/logue/vue-codemirror6.git/commit/bbfa8adb5bd5cdb56a53bd69337a4e0313e52033)
#### [1.1.3](https://github.com/logue/vue-codemirror6.git/compare/1.1.2...1.1.3)
> 9 January 2023
- Bump json5 from 1.0.1 to 1.0.2 [`#10`](https://github.com/logue/vue-codemirror6.git/pull/10)
- Consolidated demo code generation settings into a single vite.config.ts [`7fe3ad2`](https://github.com/logue/vue-codemirror6.git/commit/7fe3ad25676eb34844b7bfe64b5dc73f30211a5b)
- Update dependencies. [`75b984e`](https://github.com/logue/vue-codemirror6.git/commit/75b984e38c13114ce30b853fba64493c55368be3)
#### [1.1.2](https://github.com/logue/vue-codemirror6.git/compare/1.1.1...1.1.2)
> 24 December 2022
- Update CHANGELOG.md. [`72efba5`](https://github.com/logue/vue-codemirror6.git/commit/72efba5e9e272da2dcb3bd941079ee415d1b4c53)
- Minor fix. [`d1e3118`](https://github.com/logue/vue-codemirror6.git/commit/d1e3118ca2f2efe4d63f64e505d8a01a7c29a104)
- Update dependencies. [`d747a21`](https://github.com/logue/vue-codemirror6.git/commit/d747a21778b42ed12921dd468a91a910895925e8)
#### [1.1.1](https://github.com/logue/vue-codemirror6.git/compare/1.1.0...1.1.1)
> 7 November 2022
- Fixed Markdown demo. [`399f32c`](https://github.com/logue/vue-codemirror6.git/commit/399f32c95440b24c253110670d46811673cf38bd)
- Fix cross-binding not works. [`137765b`](https://github.com/logue/vue-codemirror6.git/commit/137765b23c0b27ba3c4784d0708ee7172c94083a)
- Fixed taking URL class from other than node when building docs. [`fbf1d56`](https://github.com/logue/vue-codemirror6.git/commit/fbf1d56296b4b739b7ff84107ec33eb3681a0398)
#### [1.1.0](https://github.com/logue/vue-codemirror6.git/compare/1.0.3...1.1.0)
> 31 October 2022
- Rewrite the demo code with Vue3 setup. [`fdc7e3c`](https://github.com/logue/vue-codemirror6.git/commit/fdc7e3c172ff8f1710a74459b1ed7da70420b732)
- Fixed wrong typing of computed value. [`9b5a572`](https://github.com/logue/vue-codemirror6.git/commit/9b5a572c5a18bfd3cf9cd5f4c0b0866efab9e893)
- Add CHANGELOG.md. [`1de388f`](https://github.com/logue/vue-codemirror6.git/commit/1de388f5126a6afeaec250adefc82bc709251bbd)
#### [1.0.3](https://github.com/logue/vue-codemirror6.git/compare/1.0.2...1.0.3)
> 12 October 2022
- Update dependencies. [`7f096da`](https://github.com/logue/vue-codemirror6.git/commit/7f096da24bfc50c9f2932cac6ad023d3982281bf)
- Update demo code. [`ac33441`](https://github.com/logue/vue-codemirror6.git/commit/ac33441c2886664c11a91b5b838db74220f0b565)
- Fixed vite.config.docs.ts. [`b13628a`](https://github.com/logue/vue-codemirror6.git/commit/b13628aeaa29df6c525b899776ae7706cdf800bc)
#### [1.0.2](https://github.com/logue/vue-codemirror6.git/compare/1.0.1...1.0.2)
> 15 September 2022
- Enabled to specify HTML tags used in components. [`9b9eecd`](https://github.com/logue/vue-codemirror6.git/commit/9b9eecdb4da434295bf176c4c17d832871aba927)
#### [1.0.1](https://github.com/logue/vue-codemirror6.git/compare/1.0.0...1.0.1)
> 8 September 2022
- Update dependencies. [`f3631d0`](https://github.com/logue/vue-codemirror6.git/commit/f3631d0c3c00a07ba8e69916116db1d611397fb9)
- Disable drop console. [`e27b9ef`](https://github.com/logue/vue-codemirror6.git/commit/e27b9efea75180b3c6031c8e3a06c2bd5cee228b)
### [1.0.0](https://github.com/logue/vue-codemirror6.git/compare/0.6.8...1.0.0)
> 20 August 2022
- Fixed editable and readonly toggle. [`97ac1bf`](https://github.com/logue/vue-codemirror6.git/commit/97ac1bf8b24775daefb58af91c318771ea355c8d)
- 1.0 [`4493c74`](https://github.com/logue/vue-codemirror6.git/commit/4493c744d38002fbca943f43b14985a3ae2c592c)
#### [0.6.8](https://github.com/logue/vue-codemirror6.git/compare/0.6.5...0.6.8)
> 1 August 2022
- Unify props monitoring process. [`15b8a17`](https://github.com/logue/vue-codemirror6.git/commit/15b8a171cec652a03800fc9b53c9f3c5b78f74bf)
- Fixed an error around linter. [`b2fe3db`](https://github.com/logue/vue-codemirror6.git/commit/b2fe3dba9f6edd3167edeeff08f34139e444ecd7)
- Fixed dispatch may be executed multiple times when the value of prop is changed. [`2e19316`](https://github.com/logue/vue-codemirror6.git/commit/2e193168876e7e0da06695e68176433fd328c0b7)
#### [0.6.5](https://github.com/logue/vue-codemirror6.git/compare/0.6.4...0.6.5)
> 4 July 2022
- Remove banner from source code. [`043e0ed`](https://github.com/logue/vue-codemirror6.git/commit/043e0ed0769c0b3bf52777e0d64199e713377418)
- Delete unnecessary cursor movement processing and assignment processing. [`c89fd00`](https://github.com/logue/vue-codemirror6.git/commit/c89fd0020e241dca2d8292511a2b55ffbece5a82)
#### [0.6.4](https://github.com/logue/vue-codemirror6.git/compare/0.6.0...0.6.4)
> 28 June 2022
- Fixed a bug that the cursor may move to a strange place when inputting. [`9977976`](https://github.com/logue/vue-codemirror6.git/commit/9977976678fda8a268a5c998f6bb95bdf0b86a1e)
- Changed the logic when assigning text values to CodeMirror. [`313e701`](https://github.com/logue/vue-codemirror6.git/commit/313e70196573f9365519a7558cbc0f05c193a000)
- Fixed the problem that the definition file was omitted because the binary output by analyze was given to npm. [`503a52e`](https://github.com/logue/vue-codemirror6.git/commit/503a52ef7c5077241b72909ee27afb262b52312e)
#### [0.6.0](https://github.com/logue/vue-codemirror6.git/compare/0.5.5...0.6.0)
> 9 June 2022
- Added minimal prop. [`d454d25`](https://github.com/logue/vue-codemirror6.git/commit/d454d257c1fd04b765f5fdb555b240395fe030b5)
- Migrate to CodeMirror 6.0.0. [`50cb696`](https://github.com/logue/vue-codemirror6.git/commit/50cb696dcc3af61d77a7e74ef37041d20bfb71fc)
- Add methods jsdoc and manual. [`44975eb`](https://github.com/logue/vue-codemirror6.git/commit/44975ebedf76d1179ab2c1835e9f16b8e583a64a)
#### [0.5.5](https://github.com/logue/vue-codemirror6.git/compare/0.5.4...0.5.5)
> 8 June 2022
- Urgent release. [`7db9007`](https://github.com/logue/vue-codemirror6.git/commit/7db9007886b5daeea94f7986d256291911b7a55c)
#### [0.5.4](https://github.com/logue/vue-codemirror6.git/compare/0.5.3...0.5.4)
> 31 May 2022
- Fixed a bug that the cursor goes to a strange place when inputting. [`81af5b2`](https://github.com/logue/vue-codemirror6.git/commit/81af5b2e950cd972bfe540621fb75a6a8e18d544)
#### [0.5.3](https://github.com/logue/vue-codemirror6.git/compare/0.5.2...0.5.3)
> 31 May 2022
- Fixed an issue where parent-to-child binding did not work properly. [`d355f2c`](https://github.com/logue/vue-codemirror6.git/commit/d355f2c7de4437360fdbf021029506d6cce4adba)
- Changed the implementation to call the extension directly with a function. [`98d338f`](https://github.com/logue/vue-codemirror6.git/commit/98d338f05a48922529f15f9d706f9614447ff1cb)
- The initial value of linter is set to simple undefined. [`6cec8b0`](https://github.com/logue/vue-codemirror6.git/commit/6cec8b000ce18a44a4077c375285a45df2993931)
#### [0.5.2](https://github.com/logue/vue-codemirror6.git/compare/0.5.1...0.5.2)
> 30 May 2022
- Simplify extension processing. [`cd875ff`](https://github.com/logue/vue-codemirror6.git/commit/cd875ffdeffa072c6abb5b4e3964298bd490b582)
- Since lintGutter is displayed even for components for which linter is not specified, lintGutter is made an option. [`ab16cbb`](https://github.com/logue/vue-codemirror6.git/commit/ab16cbb1472a5b29c572279a303a4baa593f4eb3)
- Fixed innerText is undefined error. [`6466784`](https://github.com/logue/vue-codemirror6.git/commit/64667849779d0511a472cf8d116ecef8e51f1db8)
#### [0.5.1](https://github.com/logue/vue-codemirror6.git/compare/0.5.0...0.5.1)
> 28 May 2022
- Fixed linter bug. [`03013b4`](https://github.com/logue/vue-codemirror6.git/commit/03013b44ce89ba455acafaceac68b2084fe364cf)
#### [0.5.0](https://github.com/logue/vue-codemirror6.git/compare/0.3.7...0.5.0)
> 27 May 2022
- The output program is compatible with both Vue2 and Vue3.☺ [`7571423`](https://github.com/logue/vue-codemirror6.git/commit/7571423b0b4f6150300b184b3fc765cb3bf2e21b)
#### [0.3.7](https://github.com/logue/vue-codemirror6.git/compare/0.3.6...0.3.7)
> 26 May 2022
- Change the formatting settings. [`42188a5`](https://github.com/logue/vue-codemirror6.git/commit/42188a5de4ddf1163789cdc2fbd255ed19487fad)
- Update documents. [`5a78106`](https://github.com/logue/vue-codemirror6.git/commit/5a7810681c786020afdac8f5d8da8d15f8ad802c)
- Update docs. [`15d7f8e`](https://github.com/logue/vue-codemirror6.git/commit/15d7f8e7e1edda995420485160cb9cfd4980fe74)
#### [0.3.6](https://github.com/logue/vue-codemirror6.git/compare/0.3.2...0.3.6)
> 19 May 2022
- Rename serve.vue to DemoPage.vue. [`3ba582b`](https://github.com/logue/vue-codemirror6.git/commit/3ba582b6fa24259b49d1f123b79db185ab7e077a)
- Fixed an issue where CodeMirror may not work properly during initial display. [`8092b64`](https://github.com/logue/vue-codemirror6.git/commit/8092b64039e9a3340624ca4e347151609c933a12)
- Add basic and tab props. [`54eace6`](https://github.com/logue/vue-codemirror6.git/commit/54eace641678967163fdf93d051181f701167e0e)
#### [0.3.2](https://github.com/logue/vue-codemirror6.git/compare/0.3.0...0.3.2)
> 6 April 2022
- When building with vue3, it doesn't work with vue2, so build with vue2. [`87be702`](https://github.com/logue/vue-codemirror6.git/commit/87be702b278df9cf0f5b26bb8307f7a6d592fd73)
- Rewrite the wrapper part. [`1e77661`](https://github.com/logue/vue-codemirror6.git/commit/1e776619bb5f711a789bbe758d10142725de39aa)
- Update sample. [`f7ed0a5`](https://github.com/logue/vue-codemirror6.git/commit/f7ed0a507175b6c0d79cf6be55fbd317c5c999b6)
#### [0.3.0](https://github.com/logue/vue-codemirror6.git/compare/0.1.7...0.3.0)
> 29 March 2022
#### [0.1.7](https://github.com/logue/vue-codemirror6.git/compare/0.1.6...0.1.7)
> 26 May 2022
- Change Lint settings. [`89558c1`](https://github.com/logue/vue-codemirror6.git/commit/89558c1ad3a11bc5bd4ee55a11d6bd58022c99eb)
#### [0.1.6](https://github.com/logue/vue-codemirror6.git/compare/0.1.2...0.1.6)
> 19 May 2022
- Follow changes in the master branch [`d7a9e3f`](https://github.com/logue/vue-codemirror6.git/commit/d7a9e3f9cfd7580bb78c29f96e1f2c84d77d5801)
- Follow Vue3 version. [`1867c6c`](https://github.com/logue/vue-codemirror6.git/commit/1867c6c18fdc10f331e1dace37c5773242069150)
- Since past values may be included, nextTick processing was added to onMouted. [`dac2bdd`](https://github.com/logue/vue-codemirror6.git/commit/dac2bdd1ea935141ed25b93c9882f6ecc871099e)
#### [0.1.2](https://github.com/logue/vue-codemirror6.git/compare/0.1.0...0.1.2)
> 6 April 2022
- Squashed commit of the following: [`db4abdd`](https://github.com/logue/vue-codemirror6.git/commit/db4abdddda262a7fa4f69f028b423d13442cec68)
- When building with vue3, it doesn't work with vue2, so build with vue2. [`87be702`](https://github.com/logue/vue-codemirror6.git/commit/87be702b278df9cf0f5b26bb8307f7a6d592fd73)
- Rewrite the wrapper part. [`1e77661`](https://github.com/logue/vue-codemirror6.git/commit/1e776619bb5f711a789bbe758d10142725de39aa)
#### [0.1.0](https://github.com/logue/vue-codemirror6.git/compare/0.0.6...0.1.0)
> 18 March 2022
- Update Samples. (run `yarn run dev`) [`f658c65`](https://github.com/logue/vue-codemirror6.git/commit/f658c654f2910a133914f2fa0fdc8206dadb7232)
- Fixed IME probrem. [`7a42e63`](https://github.com/logue/vue-codemirror6.git/commit/7a42e63f75748039ebb082732fe46cd7967f32d7)
#### [0.0.6](https://github.com/logue/vue-codemirror6.git/compare/0.0.5...0.0.6)
> 17 March 2022
- Fixed d.ts file destination. [`2cd9cdf`](https://github.com/logue/vue-codemirror6.git/commit/2cd9cdf3f0658d62ae434600e4e8cda14c407a47)
#### [0.0.5](https://github.com/logue/vue-codemirror6.git/compare/0.0.4...0.0.5)
> 17 March 2022
- Fixed a bug that the cursor position may be at the top when entering a key. [`e7f5407`](https://github.com/logue/vue-codemirror6.git/commit/e7f5407c76185cc805c10fc0366c5e6dff5f44f6)
#### [0.0.4](https://github.com/logue/vue-codemirror6.git/compare/0.0.2...0.0.4)
> 16 February 2022
- Update demo code. [`599fa5f`](https://github.com/logue/vue-codemirror6.git/commit/599fa5f4d87cc7783c98f56a9436a314b368fb64)
- Removed code that depends on other libraries from the output code. [`7d9e383`](https://github.com/logue/vue-codemirror6.git/commit/7d9e383c17f31ed8721f94711e7cd1dc595f9e07)
- Add demo code. [`3e06ef7`](https://github.com/logue/vue-codemirror6.git/commit/3e06ef70f5ed30fce5a6cb72add2b41fea2745fe)
#### [0.0.2](https://github.com/logue/vue-codemirror6.git/compare/0.0.1...0.0.2)
> 14 February 2022
- Update package.json. Fixed typing settings. [`12ead23`](https://github.com/logue/vue-codemirror6.git/commit/12ead23c53c0a5ebc5a68c3bee7b192b587efafc)
#### 0.0.1
> 10 February 2022
- Initial commit. [`b410884`](https://github.com/logue/vue-codemirror6.git/commit/b41088482f82615e9380e6231a59a39387d172a2)
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022-2026 Masashi Yoshikawa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.ja.md
================================================
# vue-codemirror6
[English](README.md) | 日本語
<p align="center">
<img src="https://user-images.githubusercontent.com/480173/224358008-6ffad05d-1d97-4c18-8554-7d41b03f88ab.png" alt="logo" width="300" height="300" />
</p>
[](https://www.jsdelivr.com/package/npm/vue-codemirror6)
[](https://www.npmjs.com/package/vue-codemirror6)
[](https://uiwjs.github.io/npm-unpkg/#/pkg/vue-codemirror6/file/README.md)
[](https://www.npmjs.com/package/vue-codemirror6)
[](https://gitpod.io/#https://github.com/logue/vue-codemirror6)
[](https://twitter.com/logue256)
Vueで[CodeMirror6](https://codemirror.net/6/)を使用するためのコンポーネントです。このコンポーネントはVue2とVue3の両方で動作します。
- [変更履歴](./CHANGELOG.md)
## 使い方
```sh
yarn add vue-codemirror6 codemirror
```
Vue 2.7以下の場合は、[@vue/composition-api](https://www.npmjs.com/package/@vue/composition-api)が別途必要です。
```sh
yarn add vue-codemirror6 @vue/composition-api
```
このコンポーネントは、一般的なVueコンポーネントと同様に`v-model`で双方向バインディングを処理できます。
## Props
| Props | Type | 説明 |
| ------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| model-value | string \| Text | テキスト値。(`value`ではありません) |
| basic | boolean | [basicSetup](https://codemirror.net/docs/ref/#codemirror.basicSetup)を使用します。 |
| minimal | boolean | [miniSetup](https://codemirror.net/docs/ref/#codemirror.minimalSetup)を使用します。`basic` propも指定されている場合は、そちらの設定が優先されます。 |
| dark | boolean | ダークモードの切り替え。 |
| placeholder | string | 空白の場合にプレースホルダーテキスト(またはHTML DOM)を追加します。 |
| wrap | boolean | テキストの行折り返し。[lineWrapping](https://codemirror.net/6/docs/ref/#view.EditorView.lineWrapping)を参照してください。 |
| tab | boolean | タブインデントを有効にします。 |
| allow-multiple-selections | boolean | 複数選択を許可します。[allowMultipleSelections](https://codemirror.net/docs/ref/#state.EditorState^allowMultipleSelections)を参照してください。 |
| tab-size | number | この状態で使用するタブサイズを設定します。 |
| line-separator | string | 改行(区切り文字)を設定します。(デフォルトは`\n`です。) |
| theme | { [selector: string]: StyleSpec } | テーマを指定します。例えば、[@codemirror/theme-one-dark](https://github.com/codemirror/theme-one-dark)を使用する場合は、`oneDark`をインポートしてこのpropに設定します。 |
| readonly | boolean | カーソルを表示したり、テキストをドラッグできますが、値を編集することはできません。 |
| disabled | boolean | CodeMirrorのeditableの逆の値です。`readonly`に似ていますが、この値をtrueに設定するとドラッグも無効になります。 |
| lang | LanguageSupport | 構文ハイライトを使用したい言語。<https://codemirror.net/6/#languages>を参照してください。 |
| phrases | Record<string, string> | 表示される文字列を多言語化したい場合はここで指定します。<https://codemirror.net/6/examples/translate/>を参照してください。 |
| extensions | Extension[] | CodeMirrorを拡張する機能拡張が含まれます。 |
| linter | LintSource | リンターを設定します。`@codemirror/lang-javascript`の場合は`esLint([任意のルール])`関数、`@codemirror/json`の場合は`jsonParseLinter()`関数を入力します。詳細については、各言語ライブラリのソースを参照してください。 |
| linterConfig | Object | <https://codemirror.net/docs/ref/#lint.linter^config>を参照してください。 |
| forceLinting | boolean | <https://codemirror.net/docs/ref/#lint.forceLinting>を参照してください。 |
| gutter | boolean | `linter`が指定されている場合、エラーがあった行に🔴を表示します。`linter`が指定されていない場合は機能しません。 |
| gutterConfig | Object | <https://codemirror.net/docs/ref/#lint.lintGutter^config>を参照してください。 |
| tag | string | コンポーネントで使用されるHTMLタグ。(デフォルトは`div`タグです。) |
| scrollIntoView | boolean | 外部更新でフォームをスクロールできるようにします。(デフォルトは`true`です。) |
| preserveScrollPosition | boolean | `modelValue` が外部から更新されたときにエディタのスクロール位置を保持します。追記更新時に先頭へ戻る挙動の抑止に使えます。(デフォルトは`false`です。) |
| keymap | KeyBinding[] | キーバインディングは、キー名をコマンドスタイルの関数に関連付けます。<https://codemirror.net/docs/ref/#view.KeyBinding>を参照してください。 |
⚠ 注意: `lang`と`linter`は、`extensions`にまとめて設定することもできます。これらは、以前のバージョンのCodeMirror設定との互換性とpropsの型付けのために分離されています。
### サポートされている言語
#### 公式
- [`@codemirror/lang-angular`](https://www.npmjs.com/package/@codemirror/lang-angular)
- [`@codemirror/lang-cpp`](https://www.npmjs.com/package/@codemirror/lang-cpp)
- [`@codemirror/lang-css`](https://www.npmjs.com/package/@codemirror/lang-css)
- [`@codemirror/lang-html`](https://www.npmjs.com/package/@codemirror/lang-html)
- [`@codemirror/lang-java`](https://www.npmjs.com/package/@codemirror/lang-java)
- [`@codemirror/lang-javascript`](https://www.npmjs.com/package/@codemirror/lang-javascript)
- [`@codemirror/lang-json`](https://www.npmjs.com/package/@codemirror/lang-json)
- [`@codemirror/lang-lezer`](https://www.npmjs.com/package/@codemirror/lang-lezer)
- [`@codemirror/lang-markdown`](https://www.npmjs.com/package/@codemirror/lang-markdown)
- [`@codemirror/lang-php`](https://www.npmjs.com/package/@codemirror/lang-php)
- [`@codemirror/lang-python`](https://www.npmjs.com/package/@codemirror/lang-python)
- [`@codemirror/lang-rust`](https://www.npmjs.com/package/@codemirror/lang-rust)
- [`@codemirror/lang-sql`](https://www.npmjs.com/package/@codemirror/lang-sql)
- [`@codemirror/lang-vue`](https://www.npmjs.com/package/@codemirror/lang-vue)
- [`@codemirror/lang-west`](https://www.npmjs.com/package/@codemirror/lang-west)
- [`@codemirror/lang-xml`](https://www.npmjs.com/package/@codemirror/lang-xml)
### 非公式
- [`@phoenix-plugin-registry/petetnt.brackets-codemirror-fortran`](https://www.npmjs.com/package/@phoenix-plugin-registry/petetnt.brackets-codemirror-fortran)
- [`@phoenix-plugin-registry/petetnt.brackets-codemirror-go`](https://www.npmjs.com/package/@phoenix-plugin-registry/petetnt.brackets-codemirror-go)
- [`@acarl005/lang-sql`](https://www.npmjs.com/package/@acarl005/lang-sql)
- [`@ark-us/codemirror-lang-taylor`](https://www.npmjs.com/package/@ark-us/codemirror-lang-taylor)
- [`@formulavize/lang-fiz`](https://www.npmjs.com/package/@formulavize/lang-fiz)
- [`@gravitywiz/codemirror-lang-gfcalc`](https://www.npmjs.com/package/@gravitywiz/codemirror-lang-gfcalc)
- [`@nextjournal/lang-clojure`](https://www.npmjs.com/package/@nextjournal/lang-clojure)
- [`@plutojl/lang-julia`](https://www.npmjs.com/package/@plutojl/lang-julia)
- [`@polybase/codemirror-lang-javascript`](https://www.npmjs.com/package/@polybase/codemirror-lang-javascript)
- [`@replit/codemirror-lang-nix`](https://www.npmjs.com/package/@replit/codemirror-lang-nix)
- [`@replit/codemirror-lang-csharp`](https://www.npmjs.com/package/@replit/codemirror-lang-csharp)
- [`@replit/codemirror-lang-solidity`](https://www.npmjs.com/package/@replit/codemirror-lang-solidity)
- [`@replit/codemirror-lang-svelte`](https://www.npmjs.com/package/@replit/codemirror-lang-svelte)
- [`@zhijiu/lang-sql`](https://www.npmjs.com/package/@zhijiu/lang-sql)
- [`codemirror-lang-bool`](https://www.npmjs.com/package/codemirror-lang-bool)
- [`codemirror-lang-brainfuck`](https://www.npmjs.com/package/codemirror-lang-brainfuck)
- [`codemirror-lang-cherry`](https://www.npmjs.com/package/codemirror-lang-cherry)
- [`codemirror-lang-chordpro`](https://www.npmjs.com/package/codemirror-lang-chordpro)
- [`codemirror-lang-circom`](https://www.npmjs.com/package/codemirror-lang-circom)
- [`codemirror-lang-edn`](https://www.npmjs.com/package/codemirror-lang-edn)
- [`codemirror-lang-ejs`](https://www.npmjs.com/package/codemirror-lang-ejs)
- [`codemirror-lang-fsl`](https://www.npmjs.com/package/codemirror-lang-fsl)
- [`codemirror-lang-gml`](https://www.npmjs.com/package/codemirror-lang-gml)
- [`codemirror-lang-golfscript`](https://www.npmjs.com/package/codemirror-lang-golfscript)
- [`codemirror-lang-homescript`](https://www.npmjs.com/package/codemirror-lang-homescript)
- [`codemirror-lang-html-n8n`](https://www.npmjs.com/package/codemirror-lang-html-n8n)
- [`codemirror-lang-inform7`](https://www.npmjs.com/package/codemirror-lang-inform7)
- [`codemirror-lang-j`](https://www.npmjs.com/package/codemirror-lang-j)
- [`codemirror-lang-janet`](https://www.npmjs.com/package/codemirror-lang-janet)
- [`codemirror-lang-k`](https://www.npmjs.com/package/codemirror-lang-k)
- [`codemirror-lang-karol`](https://www.npmjs.com/package/codemirror-lang-karol)
- [`codemirror-lang-mermaid`](https://www.npmjs.com/package/codemirror-lang-mermaid)
- [`codemirror-lang-n8n-expression`](https://www.npmjs.com/package/codemirror-lang-n8n-expression)
- [`codemirror-lang-prolog`](https://www.npmjs.com/package/codemirror-lang-prolog)
- [`codemirror-lang-qpen`](https://www.npmjs.com/package/codemirror-lang-qpen)
- [`codemirror-lang-qtam`](https://www.npmjs.com/package/codemirror-lang-qtam)
- [`codemirror-lang-r`](https://www.npmjs.com/package/codemirror-lang-r)
- [`codemirror-lang-rome-ast`](https://www.npmjs.com/package/codemirror-lang-rome-ast)
- [`codemirror-lang-rome`](https://www.npmjs.com/package/codemirror-lang-rome)
- [`codemirror-lang-rush`](https://www.npmjs.com/package/codemirror-lang-rush)
- [`codemirror-lang-scopescript`](https://www.npmjs.com/package/codemirror-lang-scopescript)
- [`codemirror-lang-statement`](https://www.npmjs.com/package/codemirror-lang-statement)
- [`gcode-lang-codemirror`](https://www.npmjs.com/package/gcode-lang-codemirror)
- [`gmail-lang`](https://www.npmjs.com/package/gmail-lang)
- [`lang-bqn`](https://www.npmjs.com/package/lang-bqn)
- [`lang-clojure`](https://www.npmjs.com/package/lang-clojure)
- [`lang-d`](https://www.npmjs.com/package/lang-d)
- [`lang-feel`](https://www.npmjs.com/package/lang-feel)
- [`lang-firestore`](https://www.npmjs.com/package/lang-firestore)
### サポートされているテーマ
- [`@codemirror/theme-one-dark`](https://github.com/codemirror/theme-one-dark)
- [`upleveled/theme-vs-code-dark-plus`](https://github.com/upleveled/theme-vs-code-dark-plus)
- [`codemirror6-bootstrap-theme`](https://github.com/logue/codemirror6-bootstrap-theme)
## 例
最小限で動作させるには、以下のようにマークアップします。
```vue
<template>
<code-mirror v-model="value" />
</template>
<script>
import { ref, defineComponent } from 'vue';
import CodeMirror from 'vue-codemirror6';
export default defineComponent({
components: {
CodeMirror,
},
setup() {
const value = ref('Cozy lummox gives smart squid who asks for job pen.');
return { value };
},
});
</script>
```
### スロットを使用した例
スロットの内容は既存の`v-model`を上書きします。このため、`v-model`を使用せずに`readonly` propで単に表示する場合に使用することをお勧めします。
また、スロット内のテキストが自動的にフォーマットされないように、`<pre>`タグを挿入します。
```vue
<template>
<code-mirror :lang="lang" :linter="linter">
<pre>
{
"key": "value"
}</pre
>
</code-mirror>
</template>
<script>
import { ref, defineComponent } from 'vue';
import { json, jsonParseLinter } from '@codemirror/lang-json';
import CodeMirror from 'vue-codemirror6';
export default defineComponent({
components: {
CodeMirror,
},
setup() {
const lang = json();
const linter = jsonParseLinter();
return { lang, linter };
},
});
</script>
```
### SSR(Nuxt.jsなど)での使用
このコンポーネントはSSR互換になりました。CodeMirrorはクライアント側でのみ初期化され、サーバー側レンダリング中にコンポーネントがエラーなく安全にレンダリングされます。
Nuxt 3を使用している場合は、コンポーネントを直接使用できます:
```vue
<template>
<code-mirror v-model="value" :lang="lang" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import CodeMirror from 'vue-codemirror6';
import { javascript } from '@codemirror/lang-javascript';
const value = ref('console.log("Hello, World!");');
const lang = javascript();
</script>
```
Nuxt 2を使用している場合や問題が発生した場合は、コンポーネントを`<ClientOnly>`でラップできます:
```vue
<template>
<client-only>
<code-mirror v-model="value" :lang="lang" />
</client-only>
</template>
```
### 完全な例
[vite-vue3-ts-starter](https://github.com/logue/vite-vue3-ts-starter)でMarkdownエディターとして使用する場合。
```vue
<script lang="ts" setup>
import { ref, defineComponent, type Ref } from 'vue';
// コンポーネントの読み込み
import CodeMirror from 'vue-codemirror6';
// CodeMirror拡張機能
import { markdown as md } from '@codemirror/lang-markdown';
import type { LanguageSupport } from '@codemirror/language';
import type { Extension } from '@codemirror/state';
import type { ViewUpdate } from '@codemirror/view';
/** テキスト */
const value: Ref<string> = ref('');
/** ダークモード **/
const dark: Ref<boolean> = ref(
window.matchMedia('(prefers-color-scheme: dark)').matches
);
/**
* CodeMirror言語
*
* @see {@link https://codemirror.net/6/docs/ref/#language | @codemirror/language}
*/
const lang: LanguageSupport = md();
/**
* 国際化設定。
* この例では、表示言語を日本語にしています。
* vue-i18nと組み合わせて使用する場合は、リアクティブである必要があります。
*
* @see {@link https://codemirror.net/6/examples/translate/ | Example: Internationalization}
*/
const phrases: Record<string, string> = {
// @codemirror/view
'Control character': '制御文字',
// @codemirror/commands
'Selection deleted': '選択を削除',
// @codemirror/language
'Folded lines': '折り畳まれた行',
'Unfolded lines': '折り畳める行',
to: '行き先',
'folded code': '折り畳まれたコード',
unfold: '折り畳みを解除',
'Fold line': '行を折り畳む',
'Unfold line': '行の折り畳む解除',
// @codemirror/search
'Go to line': '行き先の行',
go: 'OK',
Find: '検索',
Replace: '置き換え',
next: '▼',
previous: '▲',
all: 'すべて',
'match case': '一致条件',
'by word': '全文検索',
regexp: '正規表現',
replace: '置き換え',
'replace all': 'すべてを置き換え',
close: '閉じる',
'current match': '現在の一致',
'replaced $ matches': '$ 件の一致を置き換え',
'replaced match on line $': '$ 行の一致を置き換え',
'on line': 'した行',
// @codemirror/autocomplete
Completions: '自動補完',
// @codemirror/lint
Diagnostics: 'エラー',
'No diagnostics': 'エラーなし',
};
</script>
<template>
<code-mirror
v-model="value"
basic
:dark="dark"
:lang="lang"
:phrases="phrases"
/>
</template>
```
## イベント
| イベント | 説明 |
| -------- | ------------------------------------------------------------------------------------------------------------------------ |
| ready | CodeMirrorが読み込まれたとき。 |
| focus | フォーカスが変更されたとき。 |
| update | CodeMirrorの状態が変更されたとき。[ViewUpdate](https://codemirror.net/docs/ref/#view.ViewUpdate)オブジェクトを返します。 |
| change | 値が変更されたとき。[EditorState](https://codemirror.net/docs/ref/#state.EditorState)を返します。 |
## パラメータ / 関数
```vue
<script setup lang="ts">
import { ref, onMounted, type Ref, type PropType } from 'vue';
import CodeMirror from 'vue-codemirror6';
const cm: Ref<InstanceType<typeof CodeMirror> | undefined> = ref();
onMounted(() => {
console.log(cm.value?.json);
});
</script>
<template>
<code-mirror ref="cm" />
</template>
```
| 関数 / パラメータ | 説明 |
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
| view | [EditorView](https://codemirror.net/docs/ref/#view.EditorView)を取得および設定します。 |
| selection | [EditorSelection](https://codemirror.net/docs/ref/#state.EditorSelection)インスタンスを取得および設定します。 |
| cursor | [Cursor](https://codemirror.net/docs/ref/#state.EditorSelection^cursor)の位置を取得および設定します。 |
| json | 状態をJSON直列化可能なオブジェクトとして取得および設定します。 |
| focus | [Focus](https://codemirror.net/docs/ref/#view.EditorView.focus)を取得および設定します。 |
| lint() | リンターを強制実行します(`linter`propが指定されている場合のみ)。 |
| forceReconfigure() | すべての拡張機能を再登録します。 |
### CodeMirror5の後方互換関数
以下の説明は、[codemirror5](https://codemirror.net/5/)に精通している方向けの互換メソッドです。
通常、上記のメソッドで十分であるため、**積極的な使用は推奨されません**。
| 関数 | 説明 |
| ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| getRange(from?: number, to?: number) | エディター内の指定されたポイント間のテキストを取得します。 |
| getLine(number: number) | 行の内容を取得します。 |
| lineCount() | エディターの行数を取得します。 |
| getCursor() | 主選択の一方の端を取得します。 |
| listSelections() | 現在のすべての選択のリストを取得します。 |
| getSelection() | 現在選択されているコードを取得します。 |
| getSelections() | 指定された配列の長さは、アクティブな選択の数と同じである必要があります。 |
| somethingSelected() | テキストが選択されている場合はtrueを返します。 |
| replaceRange(replacement: string \| Text, from: number, to: number) | fromからtoまでのドキュメントの部分を指定された文字列で置き換えます。 |
| replaceSelection(replacement: string \| Text) | 選択を指定された文字列で置き換えます。 |
| setCursor(position: number) | カーソル位置を設定します。 |
| setSelection(anchor: number, head?: number) | 単一の選択範囲を設定します。 |
| setSelections(ranges: readonly SelectionRange[], primary?: number) | 新しい選択のセットを設定します。 |
| extendSelectionsBy(f: Function) | すべての既存の選択に指定された関数を適用し、結果に対してextendSelectionsを呼び出します。 |
## 推奨事項
CodeMirrorは比較的大容量であるため、[vite](https://vitejs.dev)を使用する場合は、ビルド時に以下のように[`manualChunks`](https://vitejs.dev/guide/build.html#chunking-strategy)オプションを使用して別ファイルとして出力するように設定することをお勧めします。
```ts
const config: UserConfig = {
// ...
build: {
rollupOptions: {
output: {
manualChunks: {
// ...
codemirror: ['vue-codemirror6'],
'codemirror-lang': [
// 必要に応じて以下を追加してください。
'@codemirror/lang-html',
'@codemirror/lang-javascript',
'@codemirror/lang-markdown',
],
// ...
},
},
},
},
// ...
};
```
## 開発
### テスト
このプロジェクトは、ユニットテストに[Vitest](https://vitest.dev/)を使用しています。
```bash
# テストを実行
pnpm test
# ウォッチモードでテストを実行
pnpm test:watch
# UIを使用してテストを実行
pnpm test:ui
# カバレッジを使用してテストを実行
pnpm test:coverage
```
テストスイートには以下が含まれます:
- **コンポーネントテスト**: 基本的なレンダリング、props、イベント、v-modelバインディングのテスト
- **SSRテスト**: Nuxt.jsやその他のSSRフレームワークの適切なサーバー側レンダリング互換性の確保
- **メソッドテスト**: 公開されているすべてのメソッドが正しく動作することを検証
- **エッジケース**: エラー処理と異常なシナリオのテスト
## ライセンス
©2022-2026 by Logue.
[MITライセンス](LICENSE)の下でライセンスされています。
## 🎨 開発者のために作られました
このライブラリは、**最新の開発者体験**に焦点を当てて構築されています。これを維持するには、すべてがシームレスに動作することを確認するための継続的なテストと更新が必要です。
このプロジェクトの細部へのこだわりを評価していただける場合は、Vue.jsとMetaverseエコシステム全体での私の仕事をサポートするために、小額のスポンサーシップをいただければ幸いです。
[](https://github.com/sponsors/logue)
================================================
FILE: README.md
================================================
# vue-codemirror6
English | [日本語](README.ja.md)
<p align="center">
<img src="https://user-images.githubusercontent.com/480173/224358008-6ffad05d-1d97-4c18-8554-7d41b03f88ab.png" alt="logo" width="300" height="300" />
</p>
[](https://www.jsdelivr.com/package/npm/vue-codemirror6)
[](https://www.npmjs.com/package/vue-codemirror6)
[](https://uiwjs.github.io/npm-unpkg/#/pkg/vue-codemirror6/file/README.md)
[](https://www.npmjs.com/package/vue-codemirror6)
[](https://gitpod.io/#https://github.com/logue/vue-codemirror6)
[](https://twitter.com/logue256)
A component for using [CodeMirror6](https://codemirror.net/6/) with Vue. This component works in both Vue2 and Vue3.
- [CHANGELOG](./CHANGELOG.md)
## Usage
```sh
yarn add vue-codemirror6 codemirror
```
For Vue 2.7 or below, [@vue/composition-api](https://www.npmjs.com/package/@vue/composition-api) is required separately.
```sh
yarn add vue-codemirror6 @vue/composition-api
```
This component can handle bidirectional binding by `v-model` like a general Vue component.
## Props
| Props | Type | Information |
| ------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| model-value | string \| Text | Text value. (Not `value`) |
| basic | boolean | Use [basicSetup](https://codemirror.net/docs/ref/#codemirror.basicSetup). |
| minimal | boolean | Use [miniSetup](https://codemirror.net/docs/ref/#codemirror.minimalSetup). If a `basic` prop is also specified, that setting will take precedence. |
| dark | boolean | Toggle Darkmode. |
| placeholder | string | Add placeholder text (or HTML DOM) when blank |
| wrap | boolean | Line text wrapping. see [lineWrapping](https://codemirror.net/6/docs/ref/#view.EditorView.lineWrapping). |
| tab | boolean | Enables tab indentation. |
| allow-multiple-selections | boolean | Allow Multiple Selection. See [allowMultipleSelections](https://codemirror.net/docs/ref/#state.EditorState^allowMultipleSelections) |
| tab-size | number | Configures the tab size to use in this state. |
| line-separator | string | Set line break (separetor) char. (Default is `\n`.) |
| theme | { [selector: string]: StyleSpec } | Specify the theme. For example, if you use [@codemirror/theme-one-dark](https://github.com/codemirror/theme-one-dark), import `oneDark` and put it in this prop. |
| readonly | boolean | Makes the cursor visible or you can drag the text but not edit the value. |
| disabled | boolean | This is the reversed value of the CodeMirror editable. Similar to `readonly`, but setting this value to true disables dragging. |
| lang | LanguageSupport | The language you want to have syntax highlighting. see <https://codemirror.net/6/#languages> |
| phrases | Record<string, string> | Specify here if you want to make the displayed character string multilingual. see <https://codemirror.net/6/examples/translate/> |
| extensions | Extension[] | Includes enhancements to extend CodeMirror. |
| linter | LintSource | Set Linter. Enter a linter (eg `esLint([arbitrary rule])` function for `@codemirror/lang-javascript`, `jsonParseLinter()`function for`@codemirror/json`). See the sources for various language libraries for more information. |
| linterConfig | Object | see <https://codemirror.net/docs/ref/#lint.linter^config> |
| forceLinting | boolean | see <https://codemirror.net/docs/ref/#lint.forceLinting> |
| gutter | boolean | Display 🔴 on the line where there was an error when `linter` was specified. It will not work if `linter` is not specified. |
| gutterConfig | Object | see <https://codemirror.net/docs/ref/#lint.lintGutter^config> |
| tag | string | HTML tags used in the component. (Default is `div` tag.) |
| scrollIntoView | boolean | Allows an external update to scroll the form. (Default is `true`) |
| preserveScrollPosition | boolean | Preserves the editor scroll position when `modelValue` is updated externally. Useful to prevent jumping to the top during appended updates. (Default is `false`) |
| keymap | KeyBinding[] | Key bindings associate key names with command-style functions. See <https://codemirror.net/docs/ref/#view.KeyBinding> |
⚠ Notice: `lang` and `linter` can also be set together in `extensions`. These are separated for compatibility with previous versions of CodeMirror settings and for typing props.
### Supported Languages
#### Official
- [`@codemirror/lang-angular`](https://www.npmjs.com/package/@codemirror/lang-angular)
- [`@codemirror/lang-cpp`](https://www.npmjs.com/package/@codemirror/lang-cpp)
- [`@codemirror/lang-css`](https://www.npmjs.com/package/@codemirror/lang-css)
- [`@codemirror/lang-html`](https://www.npmjs.com/package/@codemirror/lang-html)
- [`@codemirror/lang-java`](https://www.npmjs.com/package/@codemirror/lang-java)
- [`@codemirror/lang-javascript`](https://www.npmjs.com/package/@codemirror/lang-javascript)
- [`@codemirror/lang-json`](https://www.npmjs.com/package/@codemirror/lang-json)
- [`@codemirror/lang-lezer`](https://www.npmjs.com/package/@codemirror/lang-lezer)
- [`@codemirror/lang-markdown`](https://www.npmjs.com/package/@codemirror/lang-markdown)
- [`@codemirror/lang-php`](https://www.npmjs.com/package/@codemirror/lang-php)
- [`@codemirror/lang-python`](https://www.npmjs.com/package/@codemirror/lang-python)
- [`@codemirror/lang-rust`](https://www.npmjs.com/package/@codemirror/lang-rust)
- [`@codemirror/lang-sql`](https://www.npmjs.com/package/@codemirror/lang-sql)
- [`@codemirror/lang-vue`](https://www.npmjs.com/package/@codemirror/lang-vue)
- [`@codemirror/lang-west`](https://www.npmjs.com/package/@codemirror/lang-west)
- [`@codemirror/lang-xml`](https://www.npmjs.com/package/@codemirror/lang-xml)
### Unofficial
- [`@phoenix-plugin-registry/petetnt.brackets-codemirror-fortran`](https://www.npmjs.com/package/@phoenix-plugin-registry/petetnt.brackets-codemirror-fortran)
- [`@phoenix-plugin-registry/petetnt.brackets-codemirror-go`](https://www.npmjs.com/package/@phoenix-plugin-registry/petetnt.brackets-codemirror-go)
- [`@acarl005/lang-sql`](https://www.npmjs.com/package/@acarl005/lang-sql)
- [`@ark-us/codemirror-lang-taylor`](https://www.npmjs.com/package/@ark-us/codemirror-lang-taylor)
- [`@formulavize/lang-fiz`](https://www.npmjs.com/package/@formulavize/lang-fiz)
- [`@gravitywiz/codemirror-lang-gfcalc`](https://www.npmjs.com/package/@gravitywiz/codemirror-lang-gfcalc)
- [`@nextjournal/lang-clojure`](https://www.npmjs.com/package/@nextjournal/lang-clojure)
- [`@plutojl/lang-julia`](https://www.npmjs.com/package/@plutojl/lang-julia)
- [`@polybase/codemirror-lang-javascript`](https://www.npmjs.com/package/@polybase/codemirror-lang-javascript) -[`@replit/codemirror-lang-nix`](https://www.npmjs.com/package/@replit/codemirror-lang-nix)
- [`@replit/codemirror-lang-csharp`](https://www.npmjs.com/package/@replit/codemirror-lang-csharp)
- [`@replit/codemirror-lang-solidity`](https://www.npmjs.com/package/@replit/codemirror-lang-solidity)
- [`@replit/codemirror-lang-svelte`](https://www.npmjs.com/package/@replit/codemirror-lang-svelte)
- [`@zhijiu/lang-sql`](https://www.npmjs.com/package/@zhijiu/lang-sql)
- [`codemirror-lang-bool`](https://www.npmjs.com/package/codemirror-lang-bool)
- [`codemirror-lang-brainfuck`](https://www.npmjs.com/package/codemirror-lang-brainfuck)
- [`codemirror-lang-cherry`](https://www.npmjs.com/package/codemirror-lang-cherry)
- [`codemirror-lang-chordpro`](https://www.npmjs.com/package/codemirror-lang-chordpro)
- [`codemirror-lang-circom`](https://www.npmjs.com/package/codemirror-lang-circom)
- [`codemirror-lang-edn`](https://www.npmjs.com/package/codemirror-lang-edn)
- [`codemirror-lang-ejs`](https://www.npmjs.com/package/codemirror-lang-ejs)
- [`codemirror-lang-fsl`](https://www.npmjs.com/package/codemirror-lang-fsl)
- [`codemirror-lang-gml`](https://www.npmjs.com/package/codemirror-lang-gml)
- [`codemirror-lang-golfscript`](https://www.npmjs.com/package/codemirror-lang-golfscript)
- [`codemirror-lang-homescript`](https://www.npmjs.com/package/codemirror-lang-homescript)
- [`codemirror-lang-html-n8n`](https://www.npmjs.com/package/codemirror-lang-html-n8n)
- [`codemirror-lang-inform7`](https://www.npmjs.com/package/codemirror-lang-inform7)
- [`codemirror-lang-j`](https://www.npmjs.com/package/codemirror-lang-j)
- [`codemirror-lang-janet`](https://www.npmjs.com/package/codemirror-lang-janet)
- [`codemirror-lang-k`](https://www.npmjs.com/package/codemirror-lang-k)
- [`codemirror-lang-karol`](https://www.npmjs.com/package/codemirror-lang-karol)
- [`codemirror-lang-mermaid`](https://www.npmjs.com/package/codemirror-lang-mermaid)
- [`codemirror-lang-n8n-expression`](https://www.npmjs.com/package/codemirror-lang-n8n-expression)
- [`codemirror-lang-prolog`](https://www.npmjs.com/package/codemirror-lang-prolog)
- [`codemirror-lang-qpen`](https://www.npmjs.com/package/codemirror-lang-qpen)
- [`codemirror-lang-qtam`](https://www.npmjs.com/package/codemirror-lang-qtam)
- [`codemirror-lang-r`](https://www.npmjs.com/package/codemirror-lang-r)
- [`codemirror-lang-rome-ast`](https://www.npmjs.com/package/codemirror-lang-rome-ast)
- [`codemirror-lang-rome`](https://www.npmjs.com/package/codemirror-lang-rome)
- [`codemirror-lang-rush`](https://www.npmjs.com/package/codemirror-lang-rush)
- [`codemirror-lang-scopescript`](https://www.npmjs.com/package/codemirror-lang-scopescript)
- [`codemirror-lang-statement`](https://www.npmjs.com/package/codemirror-lang-statement)
- [`gcode-lang-codemirror`](https://www.npmjs.com/package/gcode-lang-codemirror)
- [`gmail-lang`](https://www.npmjs.com/package/gmail-lang)
- [`lang-bqn`](https://www.npmjs.com/package/lang-bqn)
- [`lang-clojure`](https://www.npmjs.com/package/lang-clojure)
- [`lang-d`](https://www.npmjs.com/package/lang-d)
- [`lang-feel`](https://www.npmjs.com/package/lang-feel)
- [`lang-firestore`](https://www.npmjs.com/package/lang-firestore)
### Supported Themes
- [`@codemirror/theme-one-dark`](https://github.com/codemirror/theme-one-dark)
- [`upleveled/theme-vs-code-dark-plus`](https://github.com/upleveled/theme-vs-code-dark-plus)
- [`codemirror6-bootstrap-theme`](https://github.com/logue/codemirror6-bootstrap-theme)
## Example
Mark up as follows to make it work at a minimum.
```vue
<template>
<code-mirror v-model="value" />
</template>
<script>
import { ref, defineComponent } from 'vue';
import CodeMirror from 'vue-codemirror6';
export default defineComponent({
components: {
CodeMirror,
},
setup() {
const value = ref('Cozy lummox gives smart squid who asks for job pen.');
return { value };
},
});
</script>
```
### Example using Slots
The contents of the slot will overwrite the existing `v-model`. For this reason, it is recommended to use it when simply displaying with a `readonly` prop without using `v-model`.
Also, insert a `<pre>` tag to prevent the text in the slot from being automatically formatted.
```vue
<template>
<code-mirror :lang="lang" :linter="linter">
<pre>
{
"key": "value"
}</pre
>
</code-mirror>
</template>
<script>
import { ref, defineComponent } from 'vue';
import { json, jsonParseLinter } from '@codemirror/lang-json';
import CodeMirror from 'vue-codemirror6';
export default defineComponent({
components: {
CodeMirror,
},
setup() {
const lang = json();
const linter = jsonParseLinter();
return { lang, linter };
},
});
</script>
```
### Using with SSR (Nuxt.js, etc.)
This component is now SSR-compatible. CodeMirror will only be initialized on the client side, and the component will safely render without errors during server-side rendering.
If you're using Nuxt 3, you can use the component directly:
```vue
<template>
<code-mirror v-model="value" :lang="lang" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import CodeMirror from 'vue-codemirror6';
import { javascript } from '@codemirror/lang-javascript';
const value = ref('console.log("Hello, World!");');
const lang = javascript();
</script>
```
For Nuxt 2 or if you encounter any issues, you can wrap the component with `<ClientOnly>`:
```vue
<template>
<client-only>
<code-mirror v-model="value" :lang="lang" />
</client-only>
</template>
```
### Full Example
When using as a Markdown editor on [vite-vue3-ts-starter](https://github.com/logue/vite-vue3-ts-starter).
```vue
<script lang="ts" setup>
import { ref, defineComponent, type Ref } from 'vue';
// Load component
import CodeMirror from 'vue-codemirror6';
// CodeMirror extensions
import { markdown as md } from '@codemirror/lang-markdown';
import type { LanguageSupport } from '@codemirror/language';
import type { Extension } from '@codemirror/state';
import type { ViewUpdate } from '@codemirror/view';
/** text */
const value: Ref<string> = ref('');
/** Dark mode **/
const dark: Ref<boolean> = ref(
window.matchMedia('(prefers-color-scheme: dark)').matches
);
/**
* CodeMirror Language
*
* @see {@link https://codemirror.net/6/docs/ref/#language | @codemirror/language}
*/
const lang: LanguageSupport = md();
/**
* Internationalization Config.
* In this example, the display language to Japanese.
* Must be reactive when used in combination with vue-i18n.
*
* @see {@link https://codemirror.net/6/examples/translate/ | Example: Internationalization}
*/
const phrases: Record<string, string> = {
// @codemirror/view
'Control character': '制御文字',
// @codemirror/commands
'Selection deleted': '選択を削除',
// @codemirror/language
'Folded lines': '折り畳まれた行',
'Unfolded lines': '折り畳める行',
to: '行き先',
'folded code': '折り畳まれたコード',
unfold: '折り畳みを解除',
'Fold line': '行を折り畳む',
'Unfold line': '行の折り畳む解除',
// @codemirror/search
'Go to line': '行き先の行',
go: 'OK',
Find: '検索',
Replace: '置き換え',
next: '▼',
previous: '▲',
all: 'すべて',
'match case': '一致条件',
'by word': '全文検索',
regexp: '正規表現',
replace: '置き換え',
'replace all': 'すべてを置き換え',
close: '閉じる',
'current match': '現在の一致',
'replaced $ matches': '$ 件の一致を置き換え',
'replaced match on line $': '$ 行の一致を置き換え',
'on line': 'した行',
// @codemirror/autocomplete
Completions: '自動補完',
// @codemirror/lint
Diagnostics: 'エラー',
'No diagnostics': 'エラーなし',
};
</script>
<template>
<code-mirror
v-model="value"
basic
:dark="dark"
:lang="lang"
:phrases="phrases"
/>
</template>
```
## Events
| Event | Description |
| ------ | ------------------------------------------------------------------------------------------------------------- |
| ready | When CodeMirror loaded. |
| focus | When focus changed. |
| update | When CodeMirror state changed. Returns [ViewUpdate](https://codemirror.net/docs/ref/#view.ViewUpdate) object. |
| change | Value changed. Returns [EditorState](https://codemirror.net/docs/ref/#state.EditorState) |
## Parameter / Function
```vue
<script setup lang="ts">
import { ref, onMounted, type Ref, type PropType } from 'vue';
import CodeMirror from 'vue-codemirror6';
const cm: Ref<InstanceType<typeof CodeMirror> | undefined> = ref();
onMounted(() => {
console.log(cm.value?.json);
});
</script>
<template>
<code-mirror ref="cm" />
</template>
```
| Function / Parameter | Description |
| -------------------- | --------------------------------------------------------------------------------------------------- |
| view | Get and set [EditorView](https://codemirror.net/docs/ref/#view.EditorView). |
| selection | Get and set the [EditorSelection](https://codemirror.net/docs/ref/#state.EditorSelection) instance. |
| cursor | Get and set the [cursor](https://codemirror.net/docs/ref/#state.EditorSelection^cursor) location. |
| json | Get and set state to a JSON-serializable object. |
| focus | Get and set [focus](https://codemirror.net/docs/ref/#view.EditorView.focus). |
| lint() | Force run linter (Only if `linter` prop is specified) |
| forceReconfigure() | Re register all extensions. |
### CodeMirror5 backward compatible functions
The instructions below are compatible methods for those familiar with [codemirror5](https://codemirror.net/5/).
Since the above method is usually sufficient, its **active use is not recommended**.
| Function | Description |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| getRange(from?: number, to?: number) | Get the text between the given points in the editor. |
| getLine(number: number) | Get the content of line. |
| lineCount() | Get the number of lines in the editor. |
| getCursor() | Retrieve one end of the primary selection. |
| listSelections() | Retrieves a list of all current selections. |
| getSelection() | Get the currently selected code. |
| getSelections() | The length of the given array should be the same as the number of active selections. |
| somethingSelected() | Return true if any text is selected. |
| replaceRange(replacement: string \| Text, from: number, to: number) | Replace the part of the document between from and to with the given string. |
| replaceSelection(replacement: string \| Text) | Replace the selection(s) with the given string. |
| setCursor(position: number) | Set the cursor position. |
| setSelection(anchor: number, head?: number) | Set a single selection range. |
| setSelections(ranges: readonly SelectionRange[], primary?: number) | Sets a new set of selections. |
| extendSelectionsBy(f: Function) | Applies the given function to all existing selections, and calls extendSelections on the result. |
## Recommendations
Since CodeMirror has a relatively large capacity, when using [vite](https://vitejs.dev), it is recommended to set it to output as a separate file using the [`manualChunks`](https://vitejs.dev/guide/build.html#chunking-strategy) option at build time as shown below.
```ts
const config: UserConfig = {
// ...
build: {
rollupOptions: {
output: {
manualChunks: {
// ...
codemirror: ['vue-codemirror6'],
'codemirror-lang': [
// Add the following as needed.
'@codemirror/lang-html',
'@codemirror/lang-javascript',
'@codemirror/lang-markdown',
],
// ...
},
},
},
},
// ...
};
```
## Development
### Testing
This project uses [Vitest](https://vitest.dev/) for unit testing.
```bash
# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run tests with UI
pnpm test:ui
# Run tests with coverage
pnpm test:coverage
```
The test suite includes:
- **Component Tests**: Testing basic rendering, props, events, and v-model binding
- **SSR Tests**: Ensuring proper server-side rendering compatibility for Nuxt.js and other SSR frameworks
- **Method Tests**: Verifying all exposed methods work correctly
- **Edge Cases**: Testing error handling and unusual scenarios
## LICENSE
©2022-2026 by Logue.
Licensed under the [MIT License](LICENSE).
## 🎨 Crafted for Developers
This library is built with a focus **modern developer experience**. Maintaining it involves constant testing and updates to ensure everything works seamlessly.
If you appreciate the attention to detail in this project, a small sponsorship would go a long way in supporting my work across the Vue.js and Metaverse ecosystems.
[](https://github.com/sponsors/logue)
================================================
FILE: SSR_FIX_SUMMARY.md
================================================
# SSR対応の修正サマリー
## 変更内容
このプルリクエストでは、Nuxt.jsなどのSSR(Server-Side Rendering)環境での動作をサポートするための修正を行いました。
## 主な変更点
### 1. `src/components/CodeMirror.ts`
#### SSR環境のチェック
- `onMounted`内でブラウザ環境をチェックし、サーバーサイドでは初期化をスキップ
- `typeof window !== 'undefined'` でブラウザ環境を確認
#### `view`の型変更
- `ShallowRef<EditorView>` から `ShallowRef<EditorView | undefined>` に変更
- サーバーサイドでは`undefined`となる可能性を考慮
#### すべての`view.value`アクセスを安全に
- `view.value?.` の Optional Chaining を使用
- 各関数で`view.value`の存在をチェック
#### computed プロパティの修正
- `focus`: `view.value?.hasFocus ?? false` で安全にアクセス
- `selection`: `view.value?.state.selection` でOptionalに
- `cursor`: `view.value?.state.selection.main.head ?? 0` でデフォルト値を提供
- `json`: `view.value?.state.toJSON()` でOptionalに
#### ヘルパー関数の修正
すべてのCodeMirror5互換関数を安全に修正:
- `getRange()`, `getLine()`, `lineCount()`, `getCursor()`
- `listSelections()`, `getSelection()`, `getSelections()`
- `somethingSelected()`
- `replaceRange()`, `replaceSelection()`
- `setCursor()`, `setSelection()`, `setSelections()`
- `extendSelectionsBy()`
各関数で`view.value`の存在を確認し、存在しない場合は適切なデフォルト値を返すか、処理をスキップ
### 2. `README.md`
#### SSR使用例セクションの追加
- Nuxt.jsでの使用方法を説明
- Nuxt 3での直接使用方法
- Nuxt 2や問題が発生した場合の`<ClientOnly>`ラッパーの使用方法
## テスト方法
### ローカルでのビルド確認
```bash
pnpm type-check # 型チェック成功
pnpm build # ビルド成功
```
### Nuxtでのテスト方法
#### Nuxt 3での使用例
```vue
<template>
<code-mirror v-model="value" :lang="lang" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import CodeMirror from 'vue-codemirror6';
import { javascript } from '@codemirror/lang-javascript';
const value = ref('console.log("Hello, World!");');
const lang = javascript();
</script>
```
#### Nuxt 2での使用例
```vue
<template>
<client-only>
<code-mirror v-model="value" :lang="lang" />
</client-only>
</template>
<script>
import CodeMirror from 'vue-codemirror6';
import { javascript } from '@codemirror/lang-javascript';
export default {
components: {
CodeMirror,
},
data() {
return {
value: 'console.log("Hello, World!");',
lang: javascript(),
};
},
};
</script>
```
## 互換性
- ✅ Vue 2.7以上
- ✅ Vue 3.3以上
- ✅ Nuxt 2
- ✅ Nuxt 3
- ✅ その他のSSRフレームワーク(VitePress、Quasar SSRなど)
## 破壊的変更
なし。既存の使用方法はすべて互換性を維持しています。
## 次のバージョンで推奨される変更
次のメジャーバージョン(2.0.0)で以下を検討:
- `view`、`selection`、`json`などの型を常にOptionalとして扱う
- TypeScript strictモードでのより厳密な型チェック
## 関連Issue
この修正は、NuxtやVitePressなどのSSR環境でコンポーネントが正しく動作しない問題を解決します。
## ライセンス
©2022-2025 by Logue.
Licensed under the MIT License.
================================================
FILE: TESTING.md
================================================
# Testing Guide
このプロジェクトは[Vitest](https://vitest.dev/)を使用してテストを行っています。
## テストの実行
### 基本的なコマンド
```bash
# 全テストを実行
pnpm test:run
# ウォッチモードでテストを実行(開発時に便利)
pnpm test
# UIモードでテストを実行
pnpm test:ui
# カバレッジレポートを生成
pnpm test:coverage
```
## テストの構成
### 1. コンポーネントテスト (`CodeMirror.spec.ts`)
基本的なコンポーネントの機能をテストします:
- **レンダリング**: コンポーネントが正しくレンダリングされるか
- **Props**: 各プロパティが正しく動作するか
- **イベント**: `ready`, `update`, `change`, `destroy`などのイベントが正しく発火するか
- **V-Model**: 双方向バインディングが正しく動作するか
- **スロット**: スロットコンテンツが正しく表示されるか
- **公開メソッド**: `getRange()`, `setCursor()`などのメソッドが正しく動作するか
### 2. SSR互換性テスト (`CodeMirror.ssr.spec.ts`)
サーバーサイドレンダリング環境での動作をテストします:
- **サーバーサイドレンダリング**: SSR環境でエラーなくレンダリングされるか
- **安全なメソッド呼び出し**: `view`が未初期化でもメソッドがエラーを投げないか
- **クライアントサイドハイドレーション**: ブラウザでの初期化が正しく行われるか
- **グレースフルデグラデーション**: 機能が段階的に提供されるか
- **メモリリーク防止**: コンポーネントが正しくクリーンアップされるか
## テストの追加
新しい機能を追加する場合は、対応するテストも追加してください:
```typescript
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import CodeMirror from '@/components/CodeMirror';
describe('New Feature', () => {
it('should work correctly', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
// 新機能のprops
},
});
// テストロジック
expect(wrapper.exists()).toBe(true);
});
});
```
## テスト環境
- **テストランナー**: Vitest
- **DOM環境**: happy-dom(軽量で高速)
- **Vueテストユーティリティ**: @vue/test-utils
- **アサーション**: Vitest標準のexpect API
## カバレッジ
カバレッジレポートは以下を除外しています:
- `node_modules/`
- `src-docs/` (ドキュメントサイト)
- `dist/` (ビルド出力)
- `**/*.d.ts` (型定義ファイル)
- `**/*.config.*` (設定ファイル)
- `src/Meta.ts` (自動生成ファイル)
- `src/helpers/h-demi.ts` (Vue 2/3互換レイヤー)
## ベストプラクティス
### 1. テストは独立させる
各テストは他のテストに依存しないようにしてください。
```typescript
beforeEach(() => {
// 各テストの前にクリーンアップ
document.body.innerHTML = '';
});
```
### 2. 非同期処理を待つ
コンポーネントのライフサイクルを待つために`nextTick()`を使用してください。
```typescript
await nextTick();
await nextTick(); // onMountedを待つ
```
### 3. クリーンアップ
テスト後はコンポーネントをアンマウントしてください。
```typescript
wrapper.unmount();
```
### 4. 意味のあるアサーション
テストは何をテストしているかが明確になるようにしてください。
```typescript
// 良い例
expect(wrapper.props('readonly')).toBe(true);
// 避けるべき例
expect(wrapper.props('readonly')).toBeTruthy();
```
## トラブルシューティング
### テストがタイムアウトする
長時間かかるテストにはタイムアウトを設定できます:
```typescript
it('long running test', { timeout: 10000 }, async () => {
// テストコード
});
```
### DOMが見つからない
`attachTo`オプションを使用してDOMに直接マウントしてください:
```typescript
const wrapper = mount(CodeMirror, {
props: { modelValue: 'test' },
attachTo: document.body,
});
// 忘れずにクリーンアップ
wrapper.unmount();
```
### メモリリーク
テスト後に適切にクリーンアップされているか確認してください:
```typescript
afterEach(() => {
// 必要に応じてグローバルな状態をリセット
});
```
## CI/CD
GitHub ActionsなどのCI環境でテストを実行する場合は、`pnpm test:run`を使用してください。これはウォッチモードなしで一度だけテストを実行します。
```yaml
- name: Run tests
run: pnpm test:run
```
## 参考リンク
- [Vitest Documentation](https://vitest.dev/)
- [Vue Test Utils](https://test-utils.vuejs.org/)
- [Happy DOM](https://github.com/capricorn86/happy-dom)
================================================
FILE: env.d.ts
================================================
/// <reference types="vite/client" />
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type -- Vite env extension point; add VITE_APP_* variable types here as needed
interface ImportMetaEnv {
// see https://vitejs.dev/guide/env-and-mode.html#env-files
// add .env variables.
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
================================================
FILE: eslint.config.ts
================================================
import configPrettier from '@vue/eslint-config-prettier';
import {
defineConfigWithVueTs,
vueTsConfigs,
} from '@vue/eslint-config-typescript';
import markdown from '@eslint/markdown';
import comments from '@eslint-community/eslint-plugin-eslint-comments/configs';
import pluginVitest from '@vitest/eslint-plugin';
import { globalIgnores } from 'eslint/config';
import pluginImport from 'eslint-plugin-import-x';
import pluginOxlint from 'eslint-plugin-oxlint';
import pluginPlaywright from 'eslint-plugin-playwright';
// @ts-ignore
import pluginSecurity from 'eslint-plugin-security';
import pluginVue from 'eslint-plugin-vue';
import pluginVueA11y from 'eslint-plugin-vuejs-accessibility';
import type { Linter } from 'eslint';
// Lint policy:
// 1) Keep oxlint + prettier as primary formatting/quick-check tools.
// 2) Keep ESLint focused on framework/type/import correctness.
// 3) Scope plugin presets to relevant file types to avoid cross-file crashes.
// 4) Restrict markdown lint to workspace instruction docs under .github.
// 5) Prefer small, explicit overrides over broad global exceptions.
const APP_FILES = ['**/*.{vue,ts,mts,tsx}'];
const VUE_FILES = ['*.vue', '**/*.vue'];
const MARKDOWN_FILES = ['.github/**/*.md'];
const E2E_FILES = ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'];
const UNIT_TEST_FILES = ['src/**/__tests__/*'];
const GLOBAL_IGNORES = ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'];
const scopeConfigsToFiles = (configs: Linter.Config[], files: string[]) =>
configs.map(config => (config.files ? config : { ...config, files }));
const markdownRecommendedConfigs = markdown.configs.recommended.map(config => ({
...config,
files: MARKDOWN_FILES,
}));
const appRules: Linter.Config['rules'] = {
'@eslint-community/eslint-comments/require-description': 'error',
'no-unused-vars': 'off',
// const lines: string[] = []; style
'@typescript-eslint/array-type': [
'error',
{
default: 'array',
},
],
// Enable @ts-ignore etc.
'@typescript-eslint/ban-ts-comment': 'off',
// Left-hand side style
'@typescript-eslint/consistent-generic-constructors': [
'error',
'type-annotation',
],
// Enable import sort order, see bellow.
'@typescript-eslint/consistent-type-imports': [
'off',
{
prefer: 'type-imports',
},
],
// Fix for pinia
'@typescript-eslint/explicit-function-return-type': 'off',
// Exclude variables with leading underscores
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
// Fix for vite import.meta.env
'@typescript-eslint/strict-boolean-expressions': 'off',
// Fix for vite env.d.ts.
'@typescript-eslint/triple-slash-reference': 'off',
// Fix for Vue setup style
'import-x/default': 'off',
// Fix for vite
'import-x/namespace': 'off',
'import-x/no-default-export': 'off',
'import-x/no-named-as-default-member': 'off',
'import-x/no-named-as-default': 'off',
// Sort Import Order.
// see https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md#importorder-enforce-a-convention-in-module-import-order
'import-x/order': [
'error',
{
groups: [
'builtin',
'external',
'parent',
'sibling',
'index',
'object',
'type',
],
pathGroups: [
// Vue Core
{
pattern:
'{vue,vue-router,vuex,@/store,vue-i18n,pinia,vite,vitest,vitest/**,@vitejs/**,@vue/**}',
group: 'external',
position: 'before',
},
// Internal Codes
{
pattern: '{@/**}',
group: 'internal',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['builtin'],
alphabetize: {
order: 'asc',
},
'newlines-between': 'always',
},
],
// Using `../` to navigate back to parent directories is completely prohibited (using `./foo` at the same level is OK).
// Alias imports like `@/` are excluded because they resolve via the configured alias, not a relative parent path.
'import-x/no-relative-parent-imports': ['error', { ignore: ['^@/', '^~/'] }],
};
const appSettings = {
// This will do the trick
'import-x/parsers': {
espree: ['.js', '.cjs', '.mjs', '.jsx'],
'@typescript-eslint/parser': ['.ts', '.tsx'],
'vue-eslint-parser': ['.vue'],
},
'import-x/resolver': {
// You will also need to install and configure the TypeScript resolver
// See also https://github.com/import-js/eslint-import-resolver-typescript#configuration
typescript: true,
node: true,
'eslint-import-resolver-custom-alias': {
alias: {
'@': './src',
'~': './node_modules',
},
extensions: ['.js', '.ts', '.jsx', '.tsx', '.vue'],
},
},
};
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
export default defineConfigWithVueTs(
...markdownRecommendedConfigs,
globalIgnores(GLOBAL_IGNORES),
...scopeConfigsToFiles(pluginVue.configs['flat/recommended'], VUE_FILES),
...scopeConfigsToFiles(pluginVueA11y.configs['flat/recommended'], VUE_FILES),
vueTsConfigs.recommended,
comments.recommended,
{
...pluginImport.flatConfigs.recommended,
files: APP_FILES,
},
{
...pluginImport.flatConfigs.typescript,
files: APP_FILES,
},
{
...pluginSecurity.configs.recommended,
files: APP_FILES,
},
{
name: 'app/rules',
files: APP_FILES,
settings: appSettings,
rules: appRules,
},
{
name: 'vue/rules',
files: VUE_FILES,
rules: {
// <script setup> required(Prohibit Options API)
'vue/component-api-style': ['error', ['script-setup']],
// defineProps / defineEmits only support type-based declarations (Prohibit runtime declaration style)
'vue/define-props-declaration': ['error', 'type-based'],
'vue/define-emits-declaration': ['error', 'type-based'],
// <style scoped> required(Prohibit global styles in components)
'vue/enforce-style-attribute': ['error', { allow: ['scoped'] }],
'vue/attributes-order': [
'warn',
{
order: [
'DEFINITION', // is, v-is
'LIST_RENDERING', // v-for
'CONDITIONALS', // v-if, v-else-if, v-else, v-show
'RENDER_MODIFIERS', // v-pre, v-once
'UNIQUE', // ref, key
'TWO_WAY_BINDING', // v-model
'OTHER_DIRECTIVES', // Other directives
'ATTR_DYNAMIC', // :prop
'ATTR_STATIC', // prop="value"
'ATTR_SHORTHAND_BOOL', // disabled
'EVENTS', // @click
'CONTENT', // v-html, v-text
],
alphabetical: false,
},
],
// A tag with no content should be written like <br />.
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
},
},
],
// Mitigate non-multiword component name errors to warnings.
'vue/multi-word-component-names': 'warn',
'vuejs-accessibility/label-has-for': [
'error',
{
components: ['VLabel'], // UI library label components are also targeted
controlComponents: ['VInput'], // Corresponding control components
required: { some: ['nesting', 'id'] },
},
],
'vuejs-accessibility/no-autofocus': 'warn', // autofocus breaks screen readers and keyboard navigation
'vuejs-accessibility/anchor-has-content': 'error', // <a> elements must have content
},
},
{
name: 'vue/components-strict',
files: ['src/components/**/*.vue'],
rules: {
'vue/multi-word-component-names': 'error',
},
},
{
...pluginPlaywright.configs['flat/recommended'],
files: E2E_FILES,
},
{
...pluginVitest.configs.recommended,
files: UNIT_TEST_FILES,
},
{
// Test files intentionally import from parent directories (the component under test).
name: 'test/relax-parent-imports',
files: UNIT_TEST_FILES,
rules: {
'import-x/no-relative-parent-imports': 'off',
},
},
...pluginOxlint.buildFromOxlintConfigFile('.oxlintrc.json'),
configPrettier,
{
name: 'markdown/final-overrides',
files: MARKDOWN_FILES,
language: 'markdown/gfm',
rules: {
'prettier/prettier': 'off',
},
}
);
================================================
FILE: index.html
================================================
<!doctype html>
<html lang="en" class="h-100">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<link rel="icon" href="/favicon.ico" />
<title>Vue CodeMirror6 Demo</title>
<!-- Google tag (gtag.js) -->
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-2Y2FW3QEG4"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-2Y2FW3QEG4');
</script>
</head>
<body class="h-100 bg-body text-body">
<div id="app" class="d-flex flex-column h-100">
We're sorry but this site doesn't work properly without JavaScript
enabled. Please enable it to continue.
</div>
<script type="module" src="/src-docs/main.ts"></script>
</body>
</html>
================================================
FILE: package.json
================================================
{
"$schema": "https://json.schemastore.org/package.json",
"name": "vue-codemirror6",
"version": "1.5.2",
"license": "MIT",
"description": "CodeMirror6 Component for vue2 and vue3.",
"keywords": [
"vuejs",
"vue",
"vue-components",
"vue-codemirror",
"code-editor",
"text-editor",
"vue2",
"vue3",
"web-editor",
"vue-plugin",
"vue-component",
"codemirror-editor",
"vue-resource",
"codemirror6"
],
"type": "module",
"author": {
"name": "Logue",
"email": "logue@hotmail.co.jp",
"url": "https://logue.dev/"
},
"homepage": "https://github.com/logue/vue-codemirror6",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/logue/vue-codemirror6.git"
},
"bugs": {
"url": "https://github.com/logue/vue-codemirror6/issues"
},
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.es.js",
"types": "./dist/index.d.ts",
"require": "./dist/index.cjs.js",
"default": "./dist/index.es.js"
},
"./umd": {
"default": "./dist/index.umd.js"
},
"./iife": {
"default": "./dist/index.iife.js"
}
},
"files": [
"CHANGELOG.md",
"/dist"
],
"sideEffects": false,
"engines": {
"node": "^20.19.0 || >=22.12.0",
"pnpm": ">=10.3.0"
},
"packageManager": "pnpm@10.33.2",
"scripts": {
"dev": "vite",
"clean": "rimraf node_modules/.vite",
"build": "run-p type-check \"build-only {@}\" --",
"build:analyze": "vite build --mode=analyze",
"build:clean": "rimraf dist docs",
"build:docs": "vite build --mode=docs",
"lint": "run-s lint:*",
"lint:oxlint": "oxlint . --fix",
"lint:eslint": "eslint . --fix --cache --cache-location ./node_modules/.vite/eslint-cache",
"lint:prettier": "prettier \"./**/*.{js,ts,json,css,sass,scss,htm,html,vue,md}\" -w -u",
"preview": "vite preview --mode=docs",
"build-only": "vite build && node scripts/sync-dts-entry.mjs",
"type-check": "vue-tsc --declaration --emitDeclarationOnly",
"test": "vitest",
"test:ui": "vitest --ui",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"prepare": "husky",
"version": "auto-changelog -p && git add CHANGELOG.md"
},
"dependencies": {
"vue-demi": "latest"
},
"peerDependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"codemirror": "^6.0.0",
"style-mod": "^4.0.0",
"vue": "^2.7.14 || ^3.3.4"
},
"devDependencies": {
"@codemirror/autocomplete": "^6.20.1",
"@codemirror/commands": "^6.10.3",
"@codemirror/lang-javascript": "^6.2.5",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-markdown": "^6.5.0",
"@codemirror/lang-vue": "^0.1.3",
"@codemirror/language": "^6.12.3",
"@codemirror/lint": "^6.9.5",
"@codemirror/search": "^6.7.0",
"@codemirror/state": "^6.6.0",
"@codemirror/view": "^6.41.1",
"@eslint-community/eslint-plugin-eslint-comments": "^4.7.1",
"@eslint/markdown": "^8.0.1",
"@tsconfig/node-lts": "^24.0.0",
"@types/node": "^25.6.0",
"@vitejs/plugin-vue": "^6.0.6",
"@vitest/eslint-plugin": "^1.6.16",
"@vitest/ui": "^4.1.5",
"@vue/compiler-sfc": "^3.5.33",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.7.0",
"@vue/test-utils": "^2.4.10",
"@vue/tsconfig": "^0.9.1",
"@vueuse/core": "^14.3.0",
"bootstrap": "^5.3.8",
"codemirror": "^6.0.2",
"eslint": "^10.2.1",
"eslint-import-resolver-custom-alias": "^1.3.2",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-linter-browserify": "^10.2.1",
"eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-oxlint": "^1.62.0",
"eslint-plugin-playwright": "^2.10.2",
"eslint-plugin-security": "^4.0.0",
"eslint-plugin-vue": "^10.9.0",
"eslint-plugin-vuejs-accessibility": "^2.5.0",
"happy-dom": "^20.9.0",
"husky": "^9.1.7",
"jiti": "^2.6.1",
"lint-staged": "^16.4.0",
"npm-run-all2": "^8.0.4",
"oxlint": "^1.62.0",
"prettier": "^3.8.3",
"rimraf": "^6.1.3",
"rollup-plugin-visualizer": "^7.0.1",
"sass-embedded": "^1.99.0",
"style-mod": "^4.1.3",
"supports-color": "^10.2.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.1",
"vite": "^8.0.10",
"vite-plugin-banner": "^0.8.1",
"vite-plugin-checker": "^0.13.0",
"vite-plugin-dts": "^5.0.0",
"vitest": "^4.1.5",
"vue": "^3.5.33",
"vue-eslint-parser": "^10.4.0",
"vue-markdown-wasm": "^1.0.1",
"vue-tsc": "^3.2.7"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,ts,json,htm,html,vue}": "eslint --fix --cache --cache-location ./node_modules/.vite/vite-plugin-eslint",
"*": "prettier -w -u"
},
"resolutions": {
"lodash": ">=4.18.1"
}
}
================================================
FILE: pnpm-workspace.yaml
================================================
onlyBuiltDependencies:
- '@parcel/watcher'
- esbuild
- unrs-resolver
- vue-demi
================================================
FILE: scripts/sync-dts-entry.mjs
================================================
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
const src = resolve('dist/src/index.d.ts');
const dest = resolve('dist/index.d.ts');
if (!existsSync(src)) {
console.error(`[sync-dts-entry] Source declaration not found: ${src}`);
process.exit(1);
}
const content = readFileSync(src, 'utf8').replaceAll(
"from '../Meta'",
"from './src/Meta'"
);
writeFileSync(dest, content, 'utf8');
console.log(`[sync-dts-entry] Synced declaration entry: ${src} -> ${dest}`);
================================================
FILE: src/__tests__/CodeMirror.spec.ts
================================================
import { mount } from '@vue/test-utils';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { nextTick, ref } from 'vue';
import { javascript } from '@codemirror/lang-javascript';
import { EditorView } from '@codemirror/view';
import CodeMirror, { type CodeMirrorExposed } from '../index';
describe('CodeMirror Component', () => {
beforeEach(() => {
// Clear any previous DOM
document.body.innerHTML = '';
});
describe('Basic Rendering', () => {
it('should render the component', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test code',
},
});
expect(wrapper.exists()).toBe(true);
expect(wrapper.classes()).toContain('vue-codemirror');
});
it('should use custom tag when specified', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
tag: 'section',
},
});
expect(wrapper.element.tagName.toLowerCase()).toBe('section');
});
it('should apply custom class', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
attrs: {
class: 'custom-class',
},
});
expect(wrapper.classes()).toContain('vue-codemirror');
expect(wrapper.classes()).toContain('custom-class');
});
});
describe('Props', () => {
it('should accept modelValue prop', () => {
const testValue = 'const x = 42;';
const wrapper = mount(CodeMirror, {
props: {
modelValue: testValue,
},
});
expect(wrapper.props('modelValue')).toBe(testValue);
});
it('should accept basic setup prop', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
basic: true,
},
});
expect(wrapper.props('basic')).toBe(true);
});
it('should accept minimal setup prop', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
minimal: true,
},
});
expect(wrapper.props('minimal')).toBe(true);
});
it('should accept dark mode prop', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
dark: true,
},
});
expect(wrapper.props('dark')).toBe(true);
});
it('should accept readonly prop', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
readonly: true,
},
});
expect(wrapper.props('readonly')).toBe(true);
});
it('should accept disabled prop', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
disabled: true,
},
});
expect(wrapper.props('disabled')).toBe(true);
});
it('should accept wrap prop', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
wrap: true,
},
});
expect(wrapper.props('wrap')).toBe(true);
});
it('should accept tab prop', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
tab: true,
},
});
expect(wrapper.props('tab')).toBe(true);
});
it('should accept placeholder prop', () => {
const placeholderText = 'Enter code here...';
const wrapper = mount(CodeMirror, {
props: {
modelValue: '',
placeholder: placeholderText,
},
});
expect(wrapper.props('placeholder')).toBe(placeholderText);
});
it('should accept lang prop', { timeout: 10000 }, () => {
const lang = javascript();
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'const x = 1;',
lang,
},
});
// Just verify the prop is set, don't compare object identity
expect(wrapper.props('lang')).toBeDefined();
expect(wrapper.props('lang')).toHaveProperty('language');
});
it('should accept preserveScrollPosition prop', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
preserveScrollPosition: true,
},
});
expect(wrapper.props('preserveScrollPosition')).toBe(true);
});
});
describe('Events', () => {
it('should emit ready event after mount', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
await nextTick();
await nextTick(); // Wait for onMounted
const readyEvents = wrapper.emitted('ready');
expect(readyEvents).toBeTruthy();
expect(readyEvents!.length).toBeGreaterThan(0);
const firstEvent = readyEvents![0]?.[0];
expect(firstEvent).toHaveProperty('view');
expect(firstEvent).toHaveProperty('state');
expect(firstEvent).toHaveProperty('container');
});
it('should emit update:modelValue on text change', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'initial',
},
});
await nextTick();
await nextTick();
// Simulate text change through exposed view
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(vm.view).toBeDefined();
vm.view!.dispatch({
changes: { from: 0, to: vm.view!.state.doc.length, insert: 'updated' },
});
await nextTick();
const updateEvents = wrapper.emitted('update:modelValue');
expect(updateEvents).toBeTruthy();
});
it('should emit destroy event on unmount', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
await nextTick();
await nextTick();
wrapper.unmount();
const destroyEvents = wrapper.emitted('destroy');
expect(destroyEvents).toBeTruthy();
});
});
describe('SSR Compatibility', () => {
it('should handle missing window object gracefully', () => {
// This test ensures the component doesn't crash in SSR environment
// In happy-dom, window exists, but we test the defensive coding
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
expect(wrapper.exists()).toBe(true);
});
it('should not initialize EditorView before mount', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
attachTo: document.body,
});
const vm = wrapper.vm as unknown as CodeMirrorExposed;
// Before nextTick, view might not be fully initialized
expect(vm).toBeDefined();
wrapper.unmount();
});
it('should safely handle view operations when view is undefined', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
const vm = wrapper.vm as unknown as CodeMirrorExposed;
// These should not throw even if view is undefined
expect(() => vm.getRange()).not.toThrow();
expect(() => vm.getLine(0)).not.toThrow();
expect(() => vm.lineCount()).not.toThrow();
expect(() => vm.getCursor()).not.toThrow();
expect(() => vm.listSelections()).not.toThrow();
expect(() => vm.getSelection()).not.toThrow();
expect(() => vm.getSelections()).not.toThrow();
expect(() => vm.somethingSelected()).not.toThrow();
wrapper.unmount();
});
});
describe('Exposed Methods', () => {
it('should expose editor ref', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(vm.editor).toBeDefined();
});
it('should expose view', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(vm.view).toBeDefined();
expect(vm.view).toBeInstanceOf(EditorView);
});
it('should expose cursor getter/setter', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test code',
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
const initialCursor = vm.cursor;
expect(typeof initialCursor).toBe('number');
});
it('should expose length property', async () => {
const testValue = 'hello world';
const wrapper = mount(CodeMirror, {
props: {
modelValue: testValue,
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
// length is updated in the update listener, so it might be 0 initially
expect(typeof vm.length).toBe('number');
});
it('should expose getRange method', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'hello world',
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(vm.view).toBeDefined();
const range = vm.getRange(0, 5);
expect(range).toBe('hello');
});
it('should expose lineCount method', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'line1\nline2\nline3',
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(vm.view).toBeDefined();
const count = vm.lineCount();
expect(count).toBeGreaterThan(0);
});
it('should expose getLine method', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'first line\nsecond line',
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(vm.view).toBeDefined();
const line = vm.getLine(0);
expect(line).toBe('first line');
});
it('should expose lint method', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(() => vm.lint()).not.toThrow();
});
it('should expose forceReconfigure method', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(() => vm.forceReconfigure()).not.toThrow();
});
});
describe('V-Model Binding', () => {
it('should update when modelValue prop changes', async () => {
const modelValue = ref('initial');
const wrapper = mount(CodeMirror, {
props: {
modelValue: modelValue.value,
'onUpdate:modelValue': (
value?: string | import('@codemirror/state').Text
) => {
modelValue.value =
typeof value === 'string' ? value : (value?.toString() ?? '');
},
},
});
await nextTick();
await nextTick();
// Update the prop
modelValue.value = 'updated';
await wrapper.setProps({ modelValue: modelValue.value });
await nextTick();
expect(wrapper.props('modelValue')).toBe('updated');
});
it('should preserve scroll position on external updates when enabled', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'initial',
preserveScrollPosition: true,
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(vm.view).toBeDefined();
const scrollSnapshotSpy = vi.spyOn(
vm.view as EditorView,
'scrollSnapshot'
);
await wrapper.setProps({ modelValue: 'initial\nupdated' });
await nextTick();
expect(scrollSnapshotSpy).toHaveBeenCalledTimes(1);
});
it('should not preserve scroll position on external updates by default', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'initial',
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(vm.view).toBeDefined();
const scrollSnapshotSpy = vi.spyOn(
vm.view as EditorView,
'scrollSnapshot'
);
await wrapper.setProps({ modelValue: 'initial\nupdated' });
await nextTick();
expect(scrollSnapshotSpy).not.toHaveBeenCalled();
});
it('should emit update:modelValue when content changes', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'initial',
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
expect(vm.view).toBeDefined();
vm.view!.dispatch({
changes: { from: 0, to: vm.view!.state.doc.length, insert: 'changed' },
});
await nextTick();
const updateEvents = wrapper.emitted('update:modelValue');
expect(updateEvents).toBeTruthy();
expect(updateEvents!.at(-1)?.[0]).toBe('changed');
});
});
describe('Slots', () => {
it('should render slot content', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: '',
},
slots: {
default: '<pre>Slot Content</pre>',
},
});
expect(wrapper.html()).toContain('Slot Content');
});
it('should hide slot content with aria-hidden', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: '',
},
slots: {
default: '<pre>Hidden Content</pre>',
},
});
const aside = wrapper.find('aside');
expect(aside.exists()).toBe(true);
expect(aside.attributes('aria-hidden')).toBe('true');
});
});
describe('Edge Cases', () => {
it('should handle empty modelValue', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: '',
},
});
expect(wrapper.props('modelValue')).toBe('');
});
it('should handle very long content', async () => {
const longContent = 'x'.repeat(10000);
const wrapper = mount(CodeMirror, {
props: {
modelValue: longContent,
},
});
await nextTick();
await nextTick();
const vm = wrapper.vm as unknown as CodeMirrorExposed;
// length is a reactive value that gets updated
expect(typeof vm.length).toBe('number');
expect(vm.length).toBeGreaterThanOrEqual(0);
});
it('should handle special characters', () => {
const specialContent = '日本語\n한글\n中文\n😀🎉';
const wrapper = mount(CodeMirror, {
props: {
modelValue: specialContent,
},
});
expect(wrapper.props('modelValue')).toBe(specialContent);
});
it('should handle line separators', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'line1\nline2',
lineSeparator: '\n',
},
});
expect(wrapper.props('lineSeparator')).toBe('\n');
});
});
});
================================================
FILE: src/__tests__/CodeMirror.ssr.spec.ts
================================================
import { mount } from '@vue/test-utils';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { nextTick } from 'vue';
import CodeMirror, { type CodeMirrorExposed } from '../index';
describe('CodeMirror SSR Compatibility', () => {
let originalWindow: Window & typeof globalThis;
beforeEach(() => {
originalWindow = globalThis.window;
});
afterEach(() => {
// Restore window
if (!(globalThis as Record<string, unknown>).window && originalWindow) {
(globalThis as Record<string, unknown>).window = originalWindow;
}
});
describe('Server-Side Rendering', () => {
it('should render without errors in SSR environment', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test code for SSR',
},
});
expect(wrapper.exists()).toBe(true);
expect(wrapper.classes()).toContain('vue-codemirror');
});
it('should not initialize EditorView on server', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'server side code',
},
});
const vm = wrapper.vm as unknown as CodeMirrorExposed;
// In SSR, view should remain undefined until client-side hydration
// Since we're testing in happy-dom (which has window), we test the defensive approach
expect(vm).toBeDefined();
});
it('should handle props correctly in SSR', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'ssr test',
basic: true,
dark: true,
readonly: true,
placeholder: 'Enter code',
},
});
expect(wrapper.props('modelValue')).toBe('ssr test');
expect(wrapper.props('basic')).toBe(true);
expect(wrapper.props('dark')).toBe(true);
expect(wrapper.props('readonly')).toBe(true);
expect(wrapper.props('placeholder')).toBe('Enter code');
});
it('should safely render with all prop combinations', () => {
const props = {
modelValue: 'test',
basic: true,
minimal: false,
dark: true,
wrap: true,
tab: true,
readonly: false,
disabled: false,
placeholder: 'Type here...',
allowMultipleSelections: true,
tabSize: 4,
lineSeparator: '\n',
};
const wrapper = mount(CodeMirror, { props });
expect(wrapper.exists()).toBe(true);
for (const [key, value] of Object.entries(props)) {
expect(wrapper.props(key as keyof typeof props)).toBe(value);
}
});
});
describe('Safe Method Calls in SSR', () => {
it('should return safe defaults when view is undefined', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
const vm = wrapper.vm as unknown as CodeMirrorExposed;
// Test all exposed methods return safe values
expect(vm.getCursor()).toBe(0);
expect(vm.lineCount()).toBeGreaterThanOrEqual(0); // May be 1 if doc exists
// listSelections may return a default selection if view is initialized
expect(Array.isArray(vm.listSelections())).toBe(true);
expect(typeof vm.getSelection()).toBe('string');
expect(Array.isArray(vm.getSelections())).toBe(true);
expect(vm.somethingSelected()).toBe(false);
// getRange and getLine return string or undefined depending on whether view is initialized
expect(['string', 'undefined']).toContain(typeof vm.getRange());
expect(['string', 'undefined']).toContain(typeof vm.getLine(0));
});
it('should not throw when calling methods before view initialization', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
const vm = wrapper.vm as unknown as CodeMirrorExposed;
// None of these should throw
expect(() => vm.lint()).not.toThrow();
expect(() => vm.forceReconfigure()).not.toThrow();
expect(() => vm.setCursor(0)).not.toThrow();
// setSelection and setSelections with invalid args might throw, which is expected behavior
// These are only tested to verify they don't crash when view exists
expect(() => vm.replaceRange('new', 0, 3)).not.toThrow();
expect(() => vm.replaceSelection('new')).not.toThrow();
// extendSelectionsBy should be safe
expect(() => vm.extendSelectionsBy(noopExtendSelectionsBy)).not.toThrow();
});
// Move to outer scope
function noopExtendSelectionsBy() {}
it('should handle computed properties safely', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
const vm = wrapper.vm as unknown as CodeMirrorExposed;
// These should return safe defaults
expect(typeof vm.focus).toBe('boolean');
expect(typeof vm.cursor).toBe('number');
expect(typeof vm.length).toBe('number');
});
});
describe('Client-Side Hydration', () => {
it('should initialize view after mount in browser', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'hydration test',
},
attachTo: document.body,
});
await nextTick();
await nextTick(); // Wait for onMounted to complete
const vm = wrapper.vm as unknown as CodeMirrorExposed;
// After mounting in browser environment, view should be initialized
expect(vm.view).toBeDefined();
wrapper.unmount();
});
it('should emit ready event after client-side initialization', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'ready test',
},
attachTo: document.body,
});
await nextTick();
await nextTick();
const readyEvents = wrapper.emitted('ready');
expect(readyEvents).toBeTruthy();
wrapper.unmount();
});
it('should handle modelValue updates after hydration', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'initial',
},
attachTo: document.body,
});
await nextTick();
await nextTick();
await wrapper.setProps({ modelValue: 'updated' });
await nextTick();
expect(wrapper.props('modelValue')).toBe('updated');
wrapper.unmount();
});
});
describe('Graceful Degradation', () => {
it('should render placeholder in SSR', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: '',
placeholder: 'Enter your code here',
},
});
expect(wrapper.props('placeholder')).toBe('Enter your code here');
});
it('should preserve readonly state in SSR', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'readonly content',
readonly: true,
},
});
expect(wrapper.props('readonly')).toBe(true);
});
it('should preserve disabled state in SSR', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'disabled content',
disabled: true,
},
});
expect(wrapper.props('disabled')).toBe(true);
});
it('should handle slots in SSR', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: '',
},
slots: {
default: '<pre>SSR Slot Content</pre>',
},
});
expect(wrapper.html()).toContain('SSR Slot Content');
});
});
describe('Edge Cases in SSR', () => {
it('should handle missing editor ref', () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
const vm = wrapper.vm as unknown as CodeMirrorExposed;
// Should not throw
expect(vm.editor).toBeDefined();
});
it('should handle rapid prop changes before initialization', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'v1',
},
});
// Rapidly change props before view is initialized
await wrapper.setProps({ modelValue: 'v2' });
await wrapper.setProps({ modelValue: 'v3' });
await wrapper.setProps({ modelValue: 'v4' });
expect(wrapper.props('modelValue')).toBe('v4');
});
it('should handle unmount before initialization', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'test',
},
});
// Unmount immediately without waiting for initialization
expect(() => wrapper.unmount()).not.toThrow();
});
it('should handle multiple instances', () => {
const wrapper1 = mount(CodeMirror, {
props: { modelValue: 'instance 1' },
});
const wrapper2 = mount(CodeMirror, {
props: { modelValue: 'instance 2' },
});
const wrapper3 = mount(CodeMirror, {
props: { modelValue: 'instance 3' },
});
expect(wrapper1.props('modelValue')).toBe('instance 1');
expect(wrapper2.props('modelValue')).toBe('instance 2');
expect(wrapper3.props('modelValue')).toBe('instance 3');
wrapper1.unmount();
wrapper2.unmount();
wrapper3.unmount();
});
});
describe('Memory Leaks Prevention', () => {
it('should clean up properly on unmount', async () => {
const wrapper = mount(CodeMirror, {
props: {
modelValue: 'cleanup test',
},
attachTo: document.body,
});
await nextTick();
await nextTick();
wrapper.unmount();
// After unmount, destroy event should be emitted
const destroyEvents = wrapper.emitted('destroy');
expect(destroyEvents).toBeTruthy();
});
it('should handle multiple mount/unmount cycles', async () => {
for (let i = 0; i < 5; i++) {
const wrapper = mount(CodeMirror, {
props: {
modelValue: `cycle ${i}`,
},
attachTo: document.body,
});
await nextTick();
await nextTick();
expect(wrapper.exists()).toBe(true);
wrapper.unmount();
}
});
});
});
================================================
FILE: src/helpers/h-demi.ts
================================================
/**
* h-demi - h function for Vue 2 and 3
*
* @see {@link https://github.com/vueuse/vue-demi/issues/65}
*/
import {
h as hDemi,
isVue2,
type Component,
type Slots,
type VNode,
type VNodeProps,
} from 'vue-demi';
interface Options extends VNodeProps {
class?: string;
domProps?: VNodeProps;
on?: Record<string, () => void>;
props?: VNodeProps;
style?: string;
'aria-hidden'?: string;
}
const adaptOnsV3 = (
ons: Record<string, () => void>
): Record<string, () => void> => {
if (!ons) return {};
return Object.entries(ons).reduce((ret, [key, handler]) => {
key = key.charAt(0).toUpperCase() + key.slice(1);
key = `on${key}`;
return { ...ret, [key]: handler };
}, {});
};
/**
* hDemi function.
*/
export default function h(
type: string | Component,
options: Options = {},
children?: VNode | VNode[] | null
): VNode {
if (isVue2) {
// Makeshift support :(
// Since Vue2.7 includes the Composition API, the functions in vue-demi are not used.
return hDemi(type, options, children);
}
const { props, domProps, on, ...extraOptions } = options;
const ons = on ? adaptOnsV3(on) : {};
return hDemi(
type,
{ ...extraOptions, ...props, ...domProps, ...ons },
children
);
}
export const slot = (
defaultSlots: (() => VNode[]) | VNode[] | undefined
): VNode[] =>
typeof defaultSlots === 'function' ? defaultSlots() : (defaultSlots ?? []);
================================================
FILE: src/index.ts
================================================
import { indentWithTab } from '@codemirror/commands';
import { indentUnit, type LanguageSupport } from '@codemirror/language';
import {
diagnosticCount as linterDiagnosticCount,
forceLinting,
linter,
lintGutter,
type Diagnostic,
type LintSource,
} from '@codemirror/lint';
import {
Compartment,
EditorSelection,
EditorState,
StateEffect,
type Transaction,
type Extension,
type SelectionRange,
type StateField,
type Text,
} from '@codemirror/state';
import {
EditorView,
keymap,
placeholder,
type KeyBinding,
type ViewUpdate,
} from '@codemirror/view';
import { basicSetup, minimalSetup } from 'codemirror';
import {
computed,
defineComponent,
nextTick,
onMounted,
onUnmounted,
ref,
shallowRef,
watch,
type App,
type ComputedRef,
type PropType,
type Ref,
type ShallowRef,
type WritableComputedRef,
} from 'vue-demi';
import type { StyleSpec } from 'style-mod';
import Meta from '@/Meta';
import h, { slot } from '@/helpers/h-demi';
/** CodeMirror Component */
const CodeMirror = defineComponent({
/** Component Name */
name: 'CodeMirror',
/** Model Definition */
model: {
prop: 'modelValue',
event: 'update:modelValue',
},
/** Props Definition */
props: {
/** Model value */
modelValue: {
type: String as PropType<string | Text>,
default: '',
},
/**
* Theme
*
* @see {@link https://codemirror.net/docs/ref/#view.EditorView^theme}
*/
theme: {
type: Object as PropType<Record<string, StyleSpec>>,
default: () => {
return {};
},
},
/** Dark Mode */
dark: {
type: Boolean,
default: false,
},
/**
* Use Basic Setup
*
* This will enable the basic setup for the editor.
*
* @see {@link https://codemirror.net/docs/ref/#codemirror.basicSetup}
*/
basic: {
type: Boolean,
default: false,
},
/**
* Use Minimal Setup (The basic setting has priority.)
*
* @see {@link https://codemirror.net/docs/ref/#codemirror.minimalSetup}
*/
minimal: {
type: Boolean,
default: false,
},
/**
* Placeholder
*
* @see {@link https://codemirror.net/docs/ref/#view.placeholder}
*/
placeholder: {
type: String as PropType<string | HTMLElement>,
default: undefined,
},
/**
* Line wrapping
*
* An extension that enables line wrapping in the editor (by setting CSS white-space to pre-wrap in the content).
*
* @see {@link https://codemirror.net/docs/ref/#view.EditorView%5ElineWrapping}
*/
wrap: {
type: Boolean,
default: false,
},
/**
* Allow tab key indent.
*
* This will enable the tab key to indent the current line or selection.
*
* @see {@link https://codemirror.net/examples/tab/}
*/
tab: {
type: Boolean,
default: false,
},
/**
* Tab character
*
* This is the unit of indentation used when the editor is configured to indent with tabs.
* It is also used to determine the size of the tab character when the editor is configured to use tabs for indentation..
*
* @see {@link https://codemirror.net/docs/ref/#state.EditorState^indentUnit}
*/
indentUnit: {
type: String,
default: undefined,
},
/**
* Allow Multiple Selection.
*
* This allows the editor to have multiple selections at the same time.
* This is useful for editing multiple parts of the document at once.
* If this is set to true, the editor will allow multiple selections.
* If this is set to false, the editor will only allow a single selection.
*
* @see {@link https://codemirror.net/docs/ref/#state.EditorState^allowMultipleSelections}
*/
allowMultipleSelections: {
type: Boolean,
default: false,
},
/**
* Tab size
*
* This is the number of spaces that a tab character represents in the editor.
* It is used to determine the size of the tab character when the editor is configured to use tabs for indentation.
* If this is set to a number, the editor will use that number of spaces for each tab character.
*
* @see {@link https://codemirror.net/docs/ref/#state.EditorState^tabSize}
*/
tabSize: {
type: Number,
default: undefined,
},
/**
* Set line break (separetor) char.
*
* This is the character that is used to separate lines in the editor.
* It is used to determine the line break character when the editor is configured to use a specific line break character.
*
* @see {@link https://codemirror.net/docs/ref/#state.EditorState^lineSeparator}
*/
lineSeparator: {
type: String,
default: undefined,
},
/**
* Readonly
*
* This is a CodeMirror Facet that allows you to set the editor to read-only mode.
* When this is set to true, the editor will not allow any changes to be made to the document.
* This is useful for displaying code that should not be edited, such as documentation or examples.
* If this is set to false, the editor will allow changes to be made to the document.
*
* @see {@link https://codemirror.net/docs/ref/#state.EditorState^readOnly}
*/
readonly: {
type: Boolean,
default: false,
},
/**
* Disable input.
*
* This is the reversed value of the CodeMirror editable.
* Similar to `readonly`, but setting this value to true disables dragging.
*
* @see {@link https://codemirror.net/docs/ref/#view.EditorView^editable}
*/
disabled: {
type: Boolean,
default: false,
},
/**
* Additional Extension
*
* You can use this to add any additional extensions that you want to use in the editor.
*
* @see {@link https://codemirror.net/docs/ref/#state.Extension}
*/
extensions: {
type: Array as PropType<Extension[]>,
default: () => {
return [];
},
},
/**
* Language Phreses
*
* This is a CodeMirror Facet that allows you to define custom phrases for the editor.
* It can be used to override default phrases or add new ones.
* This is useful for translating the editor to different languages or for customizing the editor's UI.
*
* @see {@link https://codemirror.net/examples/translate/}
*/
phrases: {
type: Object as PropType<Record<string, string>>,
default: undefined,
},
/**
* CodeMirror Language
*
* This is a CodeMirror Facet that allows you to define the language of the editor.
* It can be used to enable syntax highlighting and other language-specific features.
* It is useful for displaying code in a specific language, such as JavaScript, Python, or HTML.
*
* @see {@link https://codemirror.net/docs/ref/#language}
*/
lang: {
type: Object as PropType<LanguageSupport>,
default: undefined,
},
/**
* CodeMirror Linter
*
* This is a CodeMirror Facet that allows you to define a linter for the editor.
* It can be used to check the code for errors and warnings, and to provide feedback to the user.
* It is useful for displaying code in a specific language, such as JavaScript, Python, or HTML.
* This is useful for providing feedback to the user about the code they are writing.
*
* @see {@link https://codemirror.net/docs/ref/#lint.linter}
*/
linter: {
type: Function as PropType<LintSource>,
default: undefined,
},
/**
* Linter Config
*
* This is a CodeMirror Facet that allows you to define the configuration for the linter.
* It can be used to specify options for the linter, such as the severity of errors and warnings, and to customize the behavior of the linter.
* This is useful for providing feedback to the user about the code they are writing.
*
* @see {@link https://codemirror.net/docs/ref/#lint.linter^config}
*/
linterConfig: {
type: Object,
default: () => {
return {};
},
},
/**
* Forces any linters configured to run when the editor is idle to run right away.
*
* This is useful for running linters on the initial load of the editor, or when the user has made changes to the code and wants to see the results immediately.
*
* @see {@link https://codemirror.net/docs/ref/#lint.forceLinting}
*/
forceLinting: {
type: Boolean,
default: false,
},
/**
* Show Linter Gutter
*
* An area to 🔴 the lines with errors will be displayed.
* This feature is not enabled if `linter` is not specified.
*
* @see {@link https://codemirror.net/docs/ref/#lint.lintGutter}
*/
gutter: {
type: Boolean,
default: false,
},
/**
* Gutter Config
*
* This is a CodeMirror Facet that allows you to define the configuration for the gutter.
* It can be used to specify options for the gutter, such as the size of the gutter, the position of the gutter, and to customize the behavior of the gutter.
* This is useful for providing feedback to the user about the code they are writing.
*
* @see {@link https://codemirror.net/docs/ref/#lint.lintGutter^config}
*/
gutterConfig: {
type: Object,
default: undefined,
},
/**
* Using tag
*/
tag: {
type: String,
default: 'div',
},
/**
* Allows an external update to scroll the form.
*
* This is useful for scrolling the editor to a specific position when the user has made changes to the code and wants to see the results immediately.
* If this is set to true, the editor will scroll to the position specified in the transaction.
* If this is set to false, the editor will not scroll to the position specified in the transaction.
*
* @see {@link https://codemirror.net/docs/ref/#state.TransactionSpec.scrollIntoView}
*/
scrollIntoView: {
type: Boolean,
default: true,
},
/**
* Preserve the current scroll position on external updates.
*
* This keeps the editor's own scroll container from jumping when
* `modelValue` is updated from outside the component.
*
* @see {@link https://codemirror.net/docs/ref/#view.EditorView.scrollSnapshot}
*/
preserveScrollPosition: {
type: Boolean,
default: false,
},
/**
* Key map
* This is a CodeMirror Facet that allows you to define custom key bindings.
* It can be used to override default key bindings or add new ones.
*
* @see {@link https://codemirror.net/docs/ref/#view.keymap}
*/
keymap: {
type: Array as PropType<KeyBinding[]>,
default: () => [],
},
},
/** Emits */
emits: {
/** Model Update */
'update:modelValue': (_value: string | Text = '') => true,
/** CodeMirror ViewUpdate */
update: (_value: ViewUpdate) => true,
/** CodeMirror onReady */
ready: (_value: {
view: EditorView;
state: EditorState;
container: HTMLElement;
}) => true,
/** CodeMirror onFocus */
focus: (_value: boolean) => true,
/** State Changed */
change: (_value: EditorState) => true,
/** CodeMirror onDestroy */
destroy: () => true,
},
/**
* Setup
*
* @param props - Props
* @param context - Context
*/
setup(props, context) {
/** Editor DOM */
const editor: Ref<HTMLElement | undefined> = ref();
/** Internal value */
const doc: Ref<string | Text> = ref(props.modelValue);
/**
* CodeMirror Editor View
*
* @see {@link https://codemirror.net/docs/ref/#view.EditorView}
*/
const view: ShallowRef<EditorView | undefined> = shallowRef(undefined);
/**
* Focus
*
* @see {@link https://codemirror.net/docs/ref/#view.EditorView.hasFocus}
*/
const focus: WritableComputedRef<boolean> = computed({
get: () => view.value?.hasFocus ?? false,
set: f => {
if (f && view.value) {
view.value.focus();
}
},
});
/**
* Editor Selection
*
* @see {@link https://codemirror.net/docs/ref/#state.EditorSelection}
*/
const selection: WritableComputedRef<EditorSelection | undefined> =
computed({
get: () => view.value?.state.selection,
set: selection => {
if (view.value && selection) {
view.value.dispatch({ selection });
}
},
});
/** Cursor Position */
const cursor: WritableComputedRef<number> = computed({
get: () => view.value?.state.selection.main.head ?? 0,
set: anchor => {
if (view.value) {
view.value.dispatch({ selection: { anchor } });
}
},
});
/** JSON */
const json: WritableComputedRef<
Record<string, StateField<unknown>> | undefined
> = computed({
get: () => view.value?.state.toJSON(),
set: j => {
if (view.value && j) {
view.value.setState(EditorState.fromJSON(j));
}
},
});
/** Text length */
const length: Ref<number> = ref(0);
/**
* Returns the number of active lint diagnostics in the given state.
*
* @see {@link https://codemirror.net/docs/ref/#lint.diagnosticCount}
*/
const diagnosticCount: Ref<number> = ref(0);
/** Get CodeMirror Extension */
const extensions: ComputedRef<Extension[]> = computed(() => {
// Synamic Reconfiguration
// @see https://codemirror.net/examples/config/
const language = new Compartment();
const tabSize = new Compartment();
if (props.basic && props.minimal) {
throw new Error(
'[Vue CodeMirror] Both basic and minimal cannot be specified.'
);
}
/** Keymap */
let keymaps: KeyBinding[] = [];
if (props.keymap && props.keymap.length > 0) {
// If keymap is specified, use it.
keymaps = props.keymap;
}
if (props.tab) {
// If tab is enabled, add indentWithTab to keymap.
keymaps.push(indentWithTab);
}
// TODO: Ignore previous prop was not changed.
return [
// Toggle basic setup
props.basic && !props.minimal ? basicSetup : undefined,
// Toggle minimal setup
props.minimal && !props.basic ? minimalSetup : undefined,
// ViewUpdate event listener
EditorView.updateListener.of((update: ViewUpdate): void => {
if (!view.value) {
return;
}
// Emit focus status
context.emit('focus', view.value.hasFocus);
// Update count
length.value = view.value.state.doc?.length;
if (update.changes.empty || !update.docChanged) {
// Suppress event firing if no change
return;
}
if (props.linter) {
// Linter process
if (props.forceLinting) {
// If forceLinting enabled, first liting.
forceLinting(view.value);
}
// Count diagnostics.
diagnosticCount.value = (
props.linter(view.value) as readonly Diagnostic[]
).length;
}
context.emit('update', update);
}),
// Toggle light/dark mode.
EditorView.theme(props.theme, { dark: props.dark }),
// Toggle line wrapping
props.wrap ? EditorView.lineWrapping : undefined,
// Tab character
props.indentUnit ? indentUnit.of(props.indentUnit) : undefined,
// Allow Multiple Selections
EditorState.allowMultipleSelections.of(props.allowMultipleSelections),
// Indent tab size
props.tabSize
? tabSize.of(EditorState.tabSize.of(props.tabSize))
: undefined,
// locale settings
props.phrases ? EditorState.phrases.of(props.phrases) : undefined,
// Readonly option
EditorState.readOnly.of(props.readonly),
// Editable option
EditorView.editable.of(!props.disabled),
// Set Line break char
props.lineSeparator
? EditorState.lineSeparator.of(props.lineSeparator)
: undefined,
// Lang
props.lang ? language.of(props.lang) : undefined,
// Append Linter settings
props.linter ? linter(props.linter, props.linterConfig) : undefined,
// Show 🔴 to error line when linter enabled.
props.linter && props.gutter
? lintGutter(props.gutterConfig)
: undefined,
// Placeholder
props.placeholder ? placeholder(props.placeholder) : undefined,
// Keymap and Indent with Tab
keymaps.length > 0 ? keymap.of(keymaps) : undefined,
// Append Extensions
...props.extensions,
].filter((extension): extension is Extension => !!extension); // Filter undefined
});
// Extension (mostly props) Changed
watch(
extensions,
exts =>
view.value?.dispatch({ effects: StateEffect.reconfigure.of(exts) }),
{ immediate: true }
);
// for parent-to-child binding.
watch(
() => props.modelValue,
async value => {
if (!view.value) {
return;
}
if (
view.value.composing || // IME fix
view.value.state.doc.toJSON().join(props.lineSeparator ?? '\n') ===
value // don't need to update
) {
// Do not commit CodeMirror's store.
return;
}
// Range Fix ?
// https://github.com/logue/vue-codemirror6/issues/27
const isSelectionOutOfRange = !view.value.state.selection.ranges.every(
range => range.anchor < value.length && range.head < value.length
);
/** Scroll Fix */
const changes = {
from: 0,
to: view.value.state.doc.length,
insert: value,
};
// If preserveScrollPosition is enabled, create a scroll snapshot before updating the document.
const scrollSnapshot = props.preserveScrollPosition
? view.value.scrollSnapshot().map(view.value.state.changes(changes))
: undefined;
// Update
view.value.dispatch({
changes,
selection: isSelectionOutOfRange
? { anchor: 0, head: 0 }
: view.value.state.selection,
scrollIntoView: props.scrollIntoView,
effects: scrollSnapshot ? [scrollSnapshot] : undefined,
});
},
{ immediate: true }
);
/** When loaded */
onMounted(async () => {
// Skip initialization in SSR environment
if (globalThis.window === undefined || !editor.value) {
return;
}
/** Initial value */
let value: string | Text = doc.value;
if (editor.value.childNodes[0]) {
// when slot mode, overwrite initial value
if (doc.value !== '') {
console.warn(
'[CodeMirror.vue] The <code-mirror> tag contains child elements that overwrite the `v-model` values.'
);
}
value = (editor.value.childNodes[0] as HTMLElement).innerText.trim();
}
// Register Codemirror
view.value = new EditorView({
parent: editor.value,
state: EditorState.create({ doc: value, extensions: extensions.value }),
dispatch: (tr: Transaction) => {
if (!view.value) {
return;
}
view.value.update([tr]);
if (tr.changes.empty || !tr.docChanged) {
// if not change value, no fire emit event
return;
}
// console.log(view.state.doc.toString(), tr);
// state.toString() is not defined, so use toJSON and toText function to convert string.
context.emit('update:modelValue', tr.state.doc.toString());
// Emit EditorState
context.emit('change', tr.state);
},
});
await nextTick();
context.emit('ready', {
view: view.value,
state: view.value.state,
container: editor.value,
});
});
/** Destroy */
onUnmounted(() => {
if (view.value) {
view.value.destroy();
context.emit('destroy');
}
});
/**
* Forces any linters configured to run when the editor is idle to run right away.
*
* @see {@link https://codemirror.net/docs/ref/#lint.forceLinting}
*/
const lint = (): void => {
if (!props.linter || !view.value) {
return;
}
if (props.forceLinting) {
forceLinting(view.value);
}
diagnosticCount.value = linterDiagnosticCount(view.value.state);
};
/**
* Force Reconfigure Extension
*
* @see {@link https://codemirror.net/examples/config/#top-level-reconfiguration}
*/
const forceReconfigure = (): void => {
// Deconfigure all Extensions
view.value?.dispatch({
effects: StateEffect.reconfigure.of([]),
});
// Register extensions
view.value?.dispatch({
effects: StateEffect.appendConfig.of(extensions.value),
});
};
/* ----- Bellow is experimental. ------ */
/**
* Get the text between the given points in the editor.
*
* @param from - start line number
* @param to - end line number
*/
const getRange = (from?: number, to?: number): string | undefined =>
view.value?.state.sliceDoc(from, to);
/**
* Get the content of line.
*
* @param number - line number
*/
const getLine = (number: number): string | undefined =>
view.value?.state.doc.line(number + 1).text;
/** Get the number of lines in the editor. */
const lineCount = (): number => view.value?.state.doc.lines ?? 0;
/** Retrieve one end of the primary selection. */
const getCursor = (): number => view.value?.state.selection.main.head ?? 0;
/** Retrieves a list of all current selections. */
const listSelections = (): readonly SelectionRange[] => {
return view.value?.state.selection.ranges ?? [];
};
/** Get the currently selected code. */
const getSelection = (): string => {
if (!view.value) {
return '';
}
return view.value.state.sliceDoc(
view.value.state.selection.main.from,
view.value.state.selection.main.to
);
};
/**
* The length of the given array should be the same as the number of active selections.
* Replaces the content of the selections with the strings in the array.
*/
const getSelections = (): string[] => {
const s = view.value?.state;
if (!s) {
return [];
}
return s.selection.ranges.map((r: { from: number; to: number }) =>
s.sliceDoc(r.from, r.to)
);
};
/** Return true if any text is selected. */
const somethingSelected = (): boolean =>
view.value?.state.selection.ranges.some(
(r: { empty: boolean }) => !r.empty
) ?? false;
/**
* Replace the part of the document between from and to with the given string.
*
* @param replacement - replacement text
* @param from - start string at position
* @param to - insert the string at position
*/
const replaceRange = (
replacement: string | Text,
from: number,
to: number
): void => {
if (view.value) {
view.value.dispatch({
changes: { from, to, insert: replacement },
});
}
};
/**
* Replace the selection(s) with the given string.
* By default, the new selection ends up after the inserted text.
*
* @param replacement - replacement text
*/
const replaceSelection = (replacement: string | Text): void => {
if (view.value) {
view.value.dispatch(view.value.state.replaceSelection(replacement));
}
};
/**
* Set the cursor position.
*
* @param position - position.
*/
const setCursor = (position: number): void => {
if (view.value) {
view.value.dispatch({ selection: { anchor: position } });
}
};
/**
* Set a single selection range.
*
* @param anchor - anchor position
* @param head -
*/
const setSelection = (anchor: number, head?: number): void => {
if (view.value) {
view.value.dispatch({ selection: { anchor, head } });
}
};
/**
* Sets a new set of selections. There must be at least one selection in the given array.
*
* @param ranges - Selection range
* @param primary -
*/
const setSelections = (
ranges: readonly SelectionRange[],
primary?: number
): void => {
if (view.value) {
view.value.dispatch({
selection: EditorSelection.create(ranges, primary),
});
}
};
/**
* Applies the given function to all existing selections, and calls extendSelections on the result.
*
* @param f - function
*/
const extendSelectionsBy = (f: (range: SelectionRange) => number): void => {
if (view.value && selection.value) {
view.value.dispatch({
selection: EditorSelection.create(
selection.value.ranges.map((r: SelectionRange) => r.extend(f(r)))
),
});
}
};
const exposed = {
editor,
view,
cursor,
selection,
focus,
length,
json,
diagnosticCount,
dom: view.value?.contentDOM,
lint,
forceReconfigure,
// Bellow is CodeMirror5's function
getRange,
getLine,
lineCount,
getCursor,
listSelections,
getSelection,
getSelections,
somethingSelected,
replaceRange,
replaceSelection,
setCursor,
setSelection,
setSelections,
extendSelectionsBy,
};
/** Export properties and functions */
context.expose(exposed);
return exposed;
},
render() {
// <template>
// <div ref="editor" class="vue-codemirror">
// <aside v-show="!context.slots.default" aria-hidden><slot /></aside>
// </div>
// </template>
return h(
this.$props.tag,
{
ref: 'editor',
class: 'vue-codemirror',
},
this.$slots.default
? // Hide original content
h(
'aside',
{ style: 'display: none;', 'aria-hidden': 'true' },
slot(this.$slots.default)
)
: undefined
);
},
});
const installCodeMirror = (app: App): void => {
app.component('CodeMirror', CodeMirror);
};
/** Public API exposed from the CodeMirror component instance.
* Note: Vue 3 auto-unwraps refs on the component instance proxy,
* so reactive refs appear as their underlying value types here.
*/
export type CodeMirrorExposed = {
editor: HTMLElement | undefined;
view: EditorView | undefined;
cursor: number;
selection: EditorSelection | undefined;
focus: boolean;
length: number;
json: Record<string, StateField<unknown>> | undefined;
diagnosticCount: number;
dom: Element | undefined;
lint: () => void;
forceReconfigure: () => void;
getRange: (from?: number, to?: number) => string | undefined;
getLine: (number: number) => string | undefined;
lineCount: () => number;
getCursor: () => number;
listSelections: () => readonly SelectionRange[];
getSelection: () => string;
getSelections: () => string[];
somethingSelected: () => boolean;
replaceRange: (replacement: string | Text, from: number, to: number) => void;
replaceSelection: (replacement: string | Text) => void;
setCursor: (position: number) => void;
setSelection: (anchor: number, head?: number) => void;
setSelections: (ranges: readonly SelectionRange[], primary?: number) => void;
extendSelectionsBy: (f: (range: SelectionRange) => number) => void;
};
export { CodeMirror as default, installCodeMirror as install, Meta };
================================================
FILE: src/interfaces/MetaInterface.ts
================================================
/** Build information meta data */
type MetaInterface = {
/** Version */
version: string;
/** Build date */
date: string;
};
export default MetaInterface;
================================================
FILE: src-docs/App.vue
================================================
<script setup lang="ts">
import DemoPage from './DemoPage.vue';
import ToggleTheme from './components/ToggleTheme.vue';
</script>
<!-- eslint-disable vuejs-accessibility/anchor-has-content -->
<template>
<nav class="navbar navbar-expand-md bg-dark" data-bs-theme="dark">
<div class="container-fluid d-flex justify-content-between">
<a class="navbar-brand" href="#">Vue CodeMirror6</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarCollapse"
aria-controls="navbarCollapse"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon" />
</button>
<div id="navbarCollapse" class="collapse navbar-collapse flex-grow-0">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="https://github.com/logue/vue-codemirror6">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-github"
viewBox="0 0 16 16"
>
<path
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
/>
</svg>
</a>
</li>
<li class="nav-item">
<toggle-theme class="nav-link" attribute="data-bs-theme" />
</li>
</ul>
</div>
</div>
</nav>
<header class="bg-body-tertiary">
<div class="container py-3">
<h1>Vue CodeMirror6 Demo</h1>
<p class="lead">
A rough demo of Vue CodeMirror6 in action. You can switch between dark
modes from the
<i class="bi bi-circle-half" />
icon in the upper right.
</p>
</div>
</header>
<main class="flex-glow-0 pt-4 bg-body">
<demo-page />
</main>
<footer class="footer mt-auto py-3 mb-0 bg-body-tertiary">
<div class="container mb-0">
© 2022-2026 by
<a href="http://logue.dev/">Logue</a>
. Licensed under the
<a href="http://opensource.org/licenses/mit-license.php">MIT License</a>
.
</div>
</footer>
</template>
================================================
FILE: src-docs/DemoPage.vue
================================================
<!-- eslint-disable import-x/no-duplicates -- for Demo source code use. -->
<script setup lang="ts">
import { vue } from '@codemirror/lang-vue';
import { useDark } from '@vueuse/core';
// eslint-disable-next-line import-x/no-unresolved -- This is a demo component, and the CodeMirror component is only used here for demonstration purposes. It is not intended to be imported in other components.
import CodeMirror from 'vue-codemirror6';
import KeyMapDemo from './components/KeyMapDemo.vue';
import KeyMapDemoSrc from './components/KeyMapDemo.vue?raw';
import LinterAndCrossBindingDemo from './components/LinterAndCrossBindingDemo.vue';
import LinterAndCrossBindingDemoSrc from './components/LinterAndCrossBindingDemo.vue?raw';
import MarkdownDemo from './components/MarkdownDemo.vue';
import MarkdownDemoSrc from './components/MarkdownDemo.vue?raw';
import ReadonlyAndDisabledDemo from './components/ReadonlyAndDisabledDemo.vue';
import ReadonlyAndDisabledDemoSrc from './components/ReadonlyAndDisabledDemo.vue?raw';
import SlotDemo from './components/SlotDemo.vue';
import SlotDemoSrc from './components/SlotDemo.vue?raw';
const dark = useDark();
const markdownDemoSrc = MarkdownDemoSrc.trim();
const slotDemoSrc = SlotDemoSrc.trim();
const readonlyAndDisabledDemoSrc = ReadonlyAndDisabledDemoSrc.trim();
const linterAndCrossBindingDemoSrc = LinterAndCrossBindingDemoSrc.trim();
const keyMapDemoSrc = KeyMapDemoSrc.trim();
</script>
<template>
<div class="container">
<section class="mb-5">
<h2>Markdown Editor Demo</h2>
<p>
This is an example of simply pouring text into CodeMirror using
<code>v-model</code>
.
</p>
<p>
<code>basic</code>
is an alias for loading
<a
href="https://codemirror.net/6/docs/ref/#basic-setup"
target="_blank"
>
basic-setup
</a>
.
<br />
Use
<code>wrap</code>
when you want to use columns. (Enable text wrapping)
</p>
<code-mirror
v-model="markdownDemoSrc"
:dark="dark"
:lang="vue()"
basic
wrap
readonly
/>
<h3>Demo</h3>
<markdown-demo :dark="dark" class="mb-3" />
<div class="alert alert-info d-flex align-items-center my-3" role="alert">
<div class="bi flex-shrink-0 me-2 fs-2" role="img" aria-label="Info:">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
fill="currentColor"
class="bi bi-info-circle"
viewBox="0 0 16 16"
>
<path
d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"
/>
<path
d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"
/>
</svg>
</div>
<div>
The process of converting Markdown to HTML uses
<a href="https://github.com/logue/vue-markdown-wasm" target="_blank">
vue-markdown-wasm
</a>
.
<br />
Full demo is
<a href="https://logue.dev/vue-markdown-wasm" target="_blank">here</a>
.
</div>
</div>
</section>
<section class="mb-5">
<h2>Slot Method</h2>
<p>
In this sample, the text is put directly inside the
<code><code-mirror></code>
tag to make it the initial string. On the Vue side, it is evaluated as a
DOM node and only the text node is used as the value. In addition, it
does not work with Vue2.7.
</p>
<p>
It's just for simple syntax highlighting. Do not use with
<code>v-model</code>
.
</p>
<code-mirror
v-model="slotDemoSrc"
:dark="dark"
:lang="vue()"
basic
wrap
readonly
/>
<h3>Sample</h3>
<slot-demo :dark="dark" />
</section>
<section class="mb-5">
<h2>Linter and cross binding demo</h2>
<p>This is a sample using JavaScript and linter.</p>
<p>
When using
<code>gutter</code>
prop, 🔴 is displayed on the line with the error.
</p>
<p>
This sample uses
<a
href="https://github.com/UziTech/eslint-linter-browserify"
target="_blank"
>
eslint-linter-browserify
</a>
for the eslint linter.
</p>
<code-mirror
v-model="linterAndCrossBindingDemoSrc"
:dark="dark"
:lang="vue()"
basic
wrap
readonly
/>
<h3>Sample</h3>
<p>Make sure you see 🔴 when you change the value to get an error.</p>
<p>
The value of
<code>@update</code>
gets the
<a
href="https://codemirror.net/6/docs/ref/#view.ViewUpdate"
target="_blank"
>
ViewUpdate
</a>
object at that time when there is any update in the target form.
</p>
<p>
In this demo code, the
<a
href="https://codemirror.net/docs/ref/#lint.diagnosticCount"
target="_blank"
>
diagnosticCount
</a>
function is used to display the count of locations where grammatical
errors are found.
</p>
<linter-and-cross-binding-demo :dark="dark" />
<p>Also, make sure that changing either value reflects that value.</p>
</section>
<section class="mb-5">
<h2>
Toggle
<code>readonly</code>
and
<code>disabled</code>
a demo
</h2>
<p>
<a
href="https://codemirror.net/docs/ref/#view.EditorView%5Eeditable"
target="_blank"
>
<code>readonly</code>
</a>
a specifies whether the state is rewritable or not. Similar to
<code>disabled</code>
(Inverse value of
<a
href="https://codemirror.net/docs/ref/#view.EditorView%5Eeditable"
target="_blank"
>
<code>editable</code>
</a>
) , except that it is focusable. In short, add
<code>disabled</code>
prop to if you want to use it as a simple syntax highlighter.
</p>
<div class="row">
<div class="col-sm">
<code-mirror
v-model="readonlyAndDisabledDemoSrc"
:dark="dark"
:lang="vue()"
basic
wrap
readonly
/>
</div>
<div class="col-sm">
<h3>Demo</h3>
<readonly-and-disabled-demo :dark="dark" />
</div>
</div>
</section>
<section class="mb-5">
<h2>Key Map Demo</h2>
<p>
This is a sample that allows you to define your own keymap. The
<code>keymap</code>
prop is an array of objects that define the keymap.
</p>
<p>
The
<code>run</code>
function is called when the keymap is matched. If it returns
<code>true</code>
, the default behavior of the keymap is not executed.
</p>
<div class="row">
<div class="col-sm">
<code-mirror
v-model="keyMapDemoSrc"
:dark="dark"
:lang="vue()"
basic
wrap
readonly
/>
</div>
<div class="col-sm">
<h3>Demo</h3>
<p>Press Shift+Ctrl+Enter to see the console log.</p>
<key-map-demo />
</div>
</div>
</section>
</div>
</template>
================================================
FILE: src-docs/components/KeyMapDemo.vue
================================================
<script lang="ts" setup>
import { ref } from 'vue';
// eslint-disable-next-line import-x/no-unresolved -- This is a demo component, and the CodeMirror component is only used here for demonstration purposes. It is not intended to be imported in other components.
import CodeMirror from 'vue-codemirror6';
const temp = ref('Press Shift+Ctrl+Enter here to see the console log.');
</script>
<template>
<code-mirror
v-model="temp"
:keymap="[
{
key: 'Shift-Ctrl-Enter',
run: () => {
console.log('Shift+Ctrl+Enter');
return true;
},
},
]"
tab
basic
/>
</template>
================================================
FILE: src-docs/components/LinterAndCrossBindingDemo.vue
================================================
<script setup lang="ts">
import { ref, type Ref } from 'vue';
import { javascript, esLint } from '@codemirror/lang-javascript';
// Uses linter.mjs
import eslint from 'eslint-linter-browserify';
// eslint-disable-next-line import-x/no-unresolved -- This is a demo component, and the CodeMirror component is only used here for demonstration purposes. It is not intended to be imported in other components.
import CodeMirror from 'vue-codemirror6';
// Sync Dark mode
defineProps<{ dark?: boolean }>();
/** CodeMirror Instance */
const cm: Ref<InstanceType<typeof CodeMirror> | undefined> = ref();
/** Demo code */
const value: Ref<string> = ref(`document.querySelectorAll('.btn').forEach(
element => ああああelement.addEventListner('click', alert('あああああ'));
);`);
const focused: Ref<boolean> = ref(false);
/**
* JavaScript language Linter Setting.
* Using eslint-linter-browserify
*
* @see {@link https://github.com/UziTech/eslint-linter-browserify#eslint-linter-browserify}
*/
const linter = esLint(new eslint.Linter(), {
languageOptions: {
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
rules: {
semi: ['error', 'never'],
},
});
const onFocus = (f: boolean): void => {
focused.value = f;
};
</script>
<!-- eslint-disable vuejs-accessibility/label-has-for -->
<template>
<div class="row">
<div class="col-6">
<code-mirror
ref="cm"
v-model="value"
:dark="dark"
:lang="javascript()"
:linter="linter"
class="mb-3"
basic
gutter
wrap
@focus="onFocus"
/>
<div class="row mb-3">
<div class="col-4">
<div class="input-group">
<label for="count" class="input-group-text">Count</label>
<input
id="count"
:value="cm?.length"
type="text"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-5">
<div class="input-group">
<label for="diagnosticCount" class="input-group-text">
Diagnostic Count
</label>
<input
id="diagnosticCount"
:value="cm?.diagnosticCount"
type="number"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-3">
<div class="form-check form-check-inline">
<input
id="focused"
v-model="focused"
class="form-check-input"
type="checkbox"
checked
disabled
/>
<label class="form-check-label" for="focused">Focused</label>
</div>
</div>
</div>
</div>
<div class="col-6">
<!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label, vue/html-self-closing -->
<textarea v-model="value" rows="4" class="form-control"></textarea>
</div>
</div>
<p>
<kbd>Ctrl-Shift-m</kbd>
(
<kbd>Cmd-Shift-m</kbd>
on macOS) to show lint panel.
<kbd>F8</kbd>
key shows the next error.
</p>
</template>
================================================
FILE: src-docs/components/MarkdownDemo.vue
================================================
<script setup lang="ts">
import { ref, type Ref } from 'vue';
import { markdown } from '@codemirror/lang-markdown';
// eslint-disable-next-line import-x/no-unresolved -- This is a demo component, and the CodeMirror component is only used here for demonstration purposes. It is not intended to be imported in other components.
import CodeMirror from 'vue-codemirror6';
import VueMarkdown from 'vue-markdown-wasm';
/** Demo text */
const input: Ref<string> = ref(`# The quick brown fox jumps over the lazy dog.
[Lorem ipsum](https://www.lipsum.com/) dolor sit amet, **consectetur** adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`);
// Sync dark mode
defineProps<{ dark?: boolean }>();
</script>
<template>
<div class="row">
<div class="col-6">
<code-mirror
ref="cm"
v-model="input"
:dark="dark"
:lang="markdown()"
wrap
basic
/>
</div>
<div class="col-6">
<vue-markdown
v-model="input"
:data-color-mode="dark ? 'dark' : 'light'"
class="markdown-body"
/>
</div>
</div>
</template>
================================================
FILE: src-docs/components/ReadonlyAndDisabledDemo.vue
================================================
<script setup lang="ts">
import { ref, type Ref } from 'vue';
// eslint-disable-next-line import-x/no-unresolved -- This is a demo component, and the CodeMirror component is only used here for demonstration purposes. It is not intended to be imported in other components.
import CodeMirror from 'vue-codemirror6';
/** Readonly */
const isReadonly: Ref<boolean> = ref(true);
/** Disabled (Not Editable) */
const isDisabled: Ref<boolean> = ref(false);
// Sync dark mode
defineProps<{ dark?: boolean }>();
</script>
<!-- eslint-disable no-irregular-whitespace -->
<!-- eslint-disable vuejs-accessibility/label-has-for -->
<template>
<div class="form-check form-switch">
<input
id="readonly"
v-model="isReadonly"
:aria-checked="isReadonly"
type="checkbox"
class="form-check-input"
role="switch"
/>
<label class="form-check-label" for="readonly">Readonly</label>
</div>
<div class="form-check form-switch">
<input
id="disabled"
v-model="isDisabled"
:aria-checked="isDisabled"
type="checkbox"
class="form-check-input"
role="switch"
/>
<label class="form-check-label" for="disabled">Disabled</label>
</div>
<code-mirror :dark="dark" :readonly="isReadonly" :disabled="isDisabled" basic>
<pre>
色は匂へど 散りぬるを
我が世誰そ 常ならむ
有為の奥山 今日越えて
浅き夢見じ 酔ひもせず</pre
>
</code-mirror>
</template>
================================================
FILE: src-docs/components/SlotDemo.vue
================================================
<script setup lang="ts">
import { json, jsonParseLinter } from '@codemirror/lang-json';
// eslint-disable-next-line import-x/no-unresolved -- This is a demo component, and the CodeMirror component is only used here for demonstration purposes. It is not intended to be imported in other components.
import CodeMirror from 'vue-codemirror6';
// Sync Dark mode
defineProps<{ dark?: boolean }>();
</script>
<!-- prettier-ignore -->
<template>
<code-mirror
:dark="dark"
:lang="json()"
:linter="jsonParseLinter()"
basic
readonly
>
<pre>{
"@schema": "https://json.schemastore.org/jsonld.json",
"@context": "http://schema.org",
"@type": "WebSite",
"name": "vue-codemirror6 Demo",
"url": "https://github.com/logue/vue-codemirror6",
"description": "CodeMirror6 for Vue3 and Vue2 component"
}</pre
>
</code-mirror>
</template>
================================================
FILE: src-docs/components/ToggleTheme.vue
================================================
<script setup lang="ts">
/** Bootstrap 5.3 Toggle Dark mode */
import { watch } from 'vue';
import { useDark, useToggle } from '@vueuse/core';
const isDark = useDark();
const toggleDark = useToggle(isDark);
watch(
() => isDark.value,
dark =>
document.documentElement.setAttribute(
'data-bs-theme',
dark ? 'dark' : 'light'
),
{ immediate: true }
);
</script>
<template>
<a href="#" aria-label="Toggle Dark Mode" @click="toggleDark()">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-circle-half"
viewBox="0 0 16 16"
>
<path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z" />
</svg>
</a>
</template>
================================================
FILE: src-docs/main.ts
================================================
import './style.scss';
/** Demo Code */
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
================================================
FILE: src-docs/style.scss
================================================
// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)
@import '~/bootstrap/scss/functions';
// 2. Include any default variable overrides here
// 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
@import '~/bootstrap/scss/variables';
@import '~/bootstrap/scss/variables-dark';
// 4. Include any default map overrides here
// 5. Include remainder of required parts
@import '~/bootstrap/scss/maps';
@import '~/bootstrap/scss/mixins';
@import '~/bootstrap/scss/root';
// 6. Optionally include any other parts as needed
@import '~/bootstrap/scss/utilities';
@import '~/bootstrap/scss/reboot';
// @import '~/bootstrap/scss/type';
@import '~/bootstrap/scss/forms';
// @import '~/bootstrap/scss/dropdown';
@import '~/bootstrap/scss/alert';
@import '~/bootstrap/scss/nav';
@import '~/bootstrap/scss/navbar';
@import '~/bootstrap/scss/containers';
@import '~/bootstrap/scss/grid';
@import '~/bootstrap/scss/helpers';
// 7. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss`
@import '~/bootstrap/scss/utilities/api';
// 8. Add additional custom code here
.vue-codemirror * {
font-family: var(--bs-font-monospace);
}
// GitHub Markdown CSS (used in MarkdownDemo)
@import url(https://cdn.jsdelivr.net/npm/github-markdown-css@5.1.0/github-markdown.min.css);
.markdown-body {
// Override prefers-color-scheme to respect Vue's dark mode setting
@media (prefers-color-scheme: dark) {
&[data-color-mode='light'] {
color-scheme: light;
--color-prettylights-syntax-comment: #57606a;
--color-prettylights-syntax-constant: #0550ae;
--color-prettylights-syntax-entity: #6639ba;
--color-prettylights-syntax-storage-modifier-import: #24292f;
--color-prettylights-syntax-entity-tag: #116329;
--color-prettylights-syntax-keyword: #cf222e;
--color-prettylights-syntax-string: #0a3069;
--color-prettylights-syntax-variable: #953800;
--color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
--color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
--color-prettylights-syntax-invalid-illegal-bg: #82071e;
--color-prettylights-syntax-carriage-return-text: #f6f8fa;
--color-prettylights-syntax-carriage-return-bg: #cf222e;
--color-prettylights-syntax-string-regexp: #116329;
--color-prettylights-syntax-markup-list: #3b2300;
--color-prettylights-syntax-markup-heading: #0550ae;
--color-prettylights-syntax-markup-italic: #24292f;
--color-prettylights-syntax-markup-bold: #24292f;
--color-prettylights-syntax-markup-deleted-text: #82071e;
--color-prettylights-syntax-markup-deleted-bg: #ffebe9;
--color-prettylights-syntax-markup-inserted-text: #116329;
--color-prettylights-syntax-markup-inserted-bg: #dafbe1;
--color-prettylights-syntax-markup-changed-text: #953800;
--color-prettylights-syntax-markup-changed-bg: #ffd8b5;
--color-prettylights-syntax-markup-ignored-text: #eaeef2;
--color-prettylights-syntax-markup-ignored-bg: #0550ae;
--color-prettylights-syntax-meta-diff-range: #8250df;
--color-prettylights-syntax-brackethighlighter-angle: #57606a;
--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
--color-prettylights-syntax-constant-other-reference-link: #0a3069;
--color-fg-default: #24292f;
--color-fg-muted: #57606a;
--color-fg-subtle: #6e7781;
--color-canvas-default: #ffffff;
--color-canvas-subtle: #f6f8fa;
--color-border-default: #d0d7de;
--color-border-muted: hsla(210, 18%, 87%, 1);
--color-neutral-muted: rgba(175, 184, 193, 0.2);
--color-accent-fg: #0969da;
--color-accent-emphasis: #0969da;
--color-attention-subtle: #fff8c5;
--color-danger-fg: #cf222e;
}
}
@media (prefers-color-scheme: light) {
&[data-color-mode='dark'] {
color-scheme: dark;
--color-prettylights-syntax-comment: #8b949e;
--color-prettylights-syntax-constant: #79c0ff;
--color-prettylights-syntax-entity: #d2a8ff;
--color-prettylights-syntax-storage-modifier-import: #c9d1d9;
--color-prettylights-syntax-entity-tag: #7ee787;
--color-prettylights-syntax-keyword: #ff7b72;
--color-prettylights-syntax-string: #a5d6ff;
--color-prettylights-syntax-variable: #ffa657;
--color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
--color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
--color-prettylights-syntax-invalid-illegal-bg: #8e1519;
--color-prettylights-syntax-carriage-return-text: #f0f6fc;
--color-prettylights-syntax-carriage-return-bg: #b62324;
--color-prettylights-syntax-string-regexp: #7ee787;
--color-prettylights-syntax-markup-list: #f2cc60;
--color-prettylights-syntax-markup-heading: #1f6feb;
--color-prettylights-syntax-markup-italic: #c9d1d9;
--color-prettylights-syntax-markup-bold: #c9d1d9;
--color-prettylights-syntax-markup-deleted-text: #ffdcd7;
--color-prettylights-syntax-markup-deleted-bg: #67060c;
--color-prettylights-syntax-markup-inserted-text: #aff5b4;
--color-prettylights-syntax-markup-inserted-bg: #033a16;
--color-prettylights-syntax-markup-changed-text: #ffdfb6;
--color-prettylights-syntax-markup-changed-bg: #5a1e02;
--color-prettylights-syntax-markup-ignored-text: #c9d1d9;
--color-prettylights-syntax-markup-ignored-bg: #1158c7;
--color-prettylights-syntax-meta-diff-range: #d2a8ff;
--color-prettylights-syntax-brackethighlighter-angle: #8b949e;
--color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
--color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
--color-fg-default: #c9d1d9;
--color-fg-muted: #8b949e;
--color-fg-subtle: #6e7681;
--color-canvas-default: #0d1117;
--color-canvas-subtle: #161b22;
--color-border-default: #30363d;
--color-border-muted: #21262d;
--color-neutral-muted: rgba(110, 118, 129, 0.4);
--color-accent-fg: #58a6ff;
--color-accent-emphasis: #1f6feb;
--color-attention-subtle: rgba(187, 128, 9, 0.15);
--color-danger-fg: #f85149;
}
}
h1 > a.anchor,
h2 > a.anchor,
h3 > a.anchor,
h4 > a.anchor,
h5 > a.anchor,
h6 > a.anchor {
display: block;
float: left;
height: 1.2em;
width: 1em;
margin-left: -1em;
position: relative;
outline: none;
}
/*.anchor:target { background: yellow; }*/
h1 > a.anchor:before,
h2 > a.anchor:before,
h3 > a.anchor:before,
h4 > a.anchor:before,
h5 > a.anchor:before,
h6 > a.anchor:before {
visibility: hidden;
position: absolute;
opacity: 0.2;
right: 0;
top: 0;
width: 1.2em;
line-height: inherit;
content: '🔗';
}
h1 > a.anchor:hover:before,
h2 > a.anchor:hover:before,
h3 > a.anchor:hover:before,
h4 > a.anchor:hover:before,
h5 > a.anchor:hover:before,
h6 > a.anchor:hover:before {
visibility: visible;
opacity: 0.8;
}
h1:hover .anchor:before,
h2:hover .anchor:before,
h3:hover .anchor:before,
h4:hover .anchor:before,
h5:hover .anchor:before,
h6:hover .anchor:before {
visibility: visible;
}
}
================================================
FILE: tsconfig.app.json
================================================
{
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"allowImportingTsExtensions": true,
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
================================================
FILE: tsconfig.docs.json
================================================
{
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": "./tsconfig.app.json",
"include": [
"./env.d.ts",
"./src/**/*",
"./src/**/*.vue",
"./src-docs/**/*",
"./src-docs/**/*.vue"
],
"compilerOptions": {
"composite": true,
"declarationMap": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"],
"vue-codemirror6": ["./src/index.ts"]
}
}
}
================================================
FILE: tsconfig.json
================================================
{
"$schema": "https://json.schemastore.org/tsconfig.json",
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.docs.json" },
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.vitest.json" }
]
}
================================================
FILE: tsconfig.node.json
================================================
{
"extends": "@tsconfig/node-lts/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}
================================================
FILE: tsconfig.vitest.json
================================================
{
"extends": "./tsconfig.app.json",
"include": ["src/**/*", "src/**/__tests__/*", "env.d.ts"],
"exclude": [],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
"composite": true,
"lib": ["ES2022", "DOM"],
"types": ["node", "vitest/globals", "@vue/test-utils"]
}
}
================================================
FILE: vite.config.ts
================================================
import { writeFileSync } from 'node:fs';
import { fileURLToPath, URL } from 'node:url';
import Vue from '@vitejs/plugin-vue';
import { defineConfig, type UserConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
import banner from 'vite-pl
gitextract_ynp_uc9x/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── copilot-instructions.md │ └── workflows/ │ └── build-docs.yml ├── .gitignore ├── .nvmrc ├── .oxlintrc.json ├── .prettierignore ├── .prettierrc.yaml ├── AGENT.md ├── CHANGELOG.md ├── LICENSE ├── README.ja.md ├── README.md ├── SSR_FIX_SUMMARY.md ├── TESTING.md ├── env.d.ts ├── eslint.config.ts ├── index.html ├── package.json ├── pnpm-workspace.yaml ├── scripts/ │ └── sync-dts-entry.mjs ├── src/ │ ├── __tests__/ │ │ ├── CodeMirror.spec.ts │ │ └── CodeMirror.ssr.spec.ts │ ├── helpers/ │ │ └── h-demi.ts │ ├── index.ts │ └── interfaces/ │ └── MetaInterface.ts ├── src-docs/ │ ├── App.vue │ ├── DemoPage.vue │ ├── components/ │ │ ├── KeyMapDemo.vue │ │ ├── LinterAndCrossBindingDemo.vue │ │ ├── MarkdownDemo.vue │ │ ├── ReadonlyAndDisabledDemo.vue │ │ ├── SlotDemo.vue │ │ └── ToggleTheme.vue │ ├── main.ts │ └── style.scss ├── tsconfig.app.json ├── tsconfig.docs.json ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.vitest.json ├── vite.config.ts └── vitest.config.ts
SYMBOL INDEX (15 symbols across 6 files)
FILE: env.d.ts
type ImportMetaEnv (line 8) | interface ImportMetaEnv {
type ImportMeta (line 13) | interface ImportMeta {
FILE: eslint.config.ts
constant APP_FILES (line 27) | const APP_FILES = ['**/*.{vue,ts,mts,tsx}'];
constant VUE_FILES (line 28) | const VUE_FILES = ['*.vue', '**/*.vue'];
constant MARKDOWN_FILES (line 29) | const MARKDOWN_FILES = ['.github/**/*.md'];
constant E2E_FILES (line 30) | const E2E_FILES = ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'];
constant UNIT_TEST_FILES (line 31) | const UNIT_TEST_FILES = ['src/**/__tests__/*'];
constant GLOBAL_IGNORES (line 32) | const GLOBAL_IGNORES = ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'];
FILE: src/__tests__/CodeMirror.ssr.spec.ts
function noopExtendSelectionsBy (line 135) | function noopExtendSelectionsBy() {}
FILE: src/helpers/h-demi.ts
type Options (line 16) | interface Options extends VNodeProps {
function h (line 39) | function h(
FILE: src/index.ts
method setup (line 385) | setup(props, context) {
method render (line 868) | render() {
type CodeMirrorExposed (line 900) | type CodeMirrorExposed = {
FILE: src/interfaces/MetaInterface.ts
type MetaInterface (line 2) | type MetaInterface = {
Condensed preview — 45 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (218K chars).
[
{
"path": ".editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\ntrim_"
},
{
"path": ".gitattributes",
"chars": 486,
"preview": "* text=auto\n\n* vue eol=lf encoding=UTF-8\n*.css eol=lf encoding=UTF-8\n*.html eol=lf encoding=UTF-8\n*.js eol=lf encoding=U"
},
{
"path": ".github/CODEOWNERS",
"chars": 31,
"preview": "# Generated by CODEOWNERS.com\n\n"
},
{
"path": ".github/copilot-instructions.md",
"chars": 6575,
"preview": "# Vue-CodeMirror6 Workspace Instructions\n\n**Project**: A Vue 2 & 3 compatible CodeMirror 6 component library\n**Tech Stac"
},
{
"path": ".github/workflows/build-docs.yml",
"chars": 834,
"preview": "name: NodeJS with Vite\n\non:\n push:\n branches: ['master']\n pull_request:\n branches: ['master']\n\njobs:\n build:\n "
},
{
"path": ".gitignore",
"chars": 193,
"preview": "/.yarn/*\n# !/.yarn/patches\n# !/.yarn/plugins\n!/.yarn/releases\n# !/.yarn/sdks\n# !/.yarn/cache\n/.vscode/*\nnode_modules\n.DS"
},
{
"path": ".nvmrc",
"chars": 8,
"preview": "24.14.1\n"
},
{
"path": ".oxlintrc.json",
"chars": 217,
"preview": "{\n \"$schema\": \"./node_modules/oxlint/configuration_schema.json\",\n \"plugins\": [\"eslint\", \"typescript\", \"unicorn\", \"oxc\""
},
{
"path": ".prettierignore",
"chars": 62,
"preview": ".husky/\n.vscode/\n.yarn/\ndist/\npublic/assets/\ndocs/\nstats.html\n"
},
{
"path": ".prettierrc.yaml",
"chars": 201,
"preview": "printWidth: 80\ntabWidth: 2\nuseTabs: false\nsemi: true\nsingleQuote: true\ntrailingComma: es5\nbracketSpacing: true\nbracketSa"
},
{
"path": "AGENT.md",
"chars": 8179,
"preview": "# AGENT.md\n\nThis file provides guidance for AI coding agents (GitHub Copilot, Claude, Cursor, etc.) working in this repo"
},
{
"path": "CHANGELOG.md",
"chars": 19814,
"preview": "### Changelog\n\nAll notable changes to this project will be documented in this file. Dates are displayed in UTC.\n\nGenerat"
},
{
"path": "LICENSE",
"chars": 1079,
"preview": "MIT License\n\nCopyright (c) 2022-2026 Masashi Yoshikawa\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "README.ja.md",
"chars": 23972,
"preview": "# vue-codemirror6\n\n[English](README.md) | 日本語\n\n<p align=\"center\">\n <img src=\"https://user-images.githubusercontent.com/"
},
{
"path": "README.md",
"chars": 26633,
"preview": "# vue-codemirror6\n\nEnglish | [日本語](README.ja.md)\n\n<p align=\"center\">\n <img src=\"https://user-images.githubusercontent.c"
},
{
"path": "SSR_FIX_SUMMARY.md",
"chars": 2485,
"preview": "# SSR対応の修正サマリー\n\n## 変更内容\n\nこのプルリクエストでは、Nuxt.jsなどのSSR(Server-Side Rendering)環境での動作をサポートするための修正を行いました。\n\n## 主な変更点\n\n### 1. `sr"
},
{
"path": "TESTING.md",
"chars": 3007,
"preview": "# Testing Guide\n\nこのプロジェクトは[Vitest](https://vitest.dev/)を使用してテストを行っています。\n\n## テストの実行\n\n### 基本的なコマンド\n\n```bash\n# 全テストを実行\npnpm"
},
{
"path": "env.d.ts",
"chars": 427,
"preview": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.vue' {\n import Vue from 'vue';\n export default Vue;\n}\n// esli"
},
{
"path": "eslint.config.ts",
"chars": 8790,
"preview": "import configPrettier from '@vue/eslint-config-prettier';\nimport {\n defineConfigWithVueTs,\n vueTsConfigs,\n} from '@vue"
},
{
"path": "index.html",
"chars": 962,
"preview": "<!doctype html>\n<html lang=\"en\" class=\"h-100\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\""
},
{
"path": "package.json",
"chars": 5153,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/package.json\",\n \"name\": \"vue-codemirror6\",\n \"version\": \"1.5.2\",\n \"licens"
},
{
"path": "pnpm-workspace.yaml",
"chars": 88,
"preview": "onlyBuiltDependencies:\n - '@parcel/watcher'\n - esbuild\n - unrs-resolver\n - vue-demi\n"
},
{
"path": "scripts/sync-dts-entry.mjs",
"chars": 526,
"preview": "import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nconst src = res"
},
{
"path": "src/__tests__/CodeMirror.spec.ts",
"chars": 15753,
"preview": "import { mount } from '@vue/test-utils';\nimport { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { nextTic"
},
{
"path": "src/__tests__/CodeMirror.ssr.spec.ts",
"chars": 10236,
"preview": "import { mount } from '@vue/test-utils';\nimport { describe, it, expect, beforeEach, afterEach } from 'vitest';\nimport { "
},
{
"path": "src/helpers/h-demi.ts",
"chars": 1428,
"preview": "/**\n * h-demi - h function for Vue 2 and 3\n *\n * @see {@link https://github.com/vueuse/vue-demi/issues/65}\n */\n\nimport {"
},
{
"path": "src/index.ts",
"chars": 28144,
"preview": "import { indentWithTab } from '@codemirror/commands';\nimport { indentUnit, type LanguageSupport } from '@codemirror/lang"
},
{
"path": "src/interfaces/MetaInterface.ts",
"chars": 164,
"preview": "/** Build information meta data */\ntype MetaInterface = {\n /** Version */\n version: string;\n /** Build date */\n date"
},
{
"path": "src-docs/App.vue",
"chars": 2766,
"preview": "<script setup lang=\"ts\">\nimport DemoPage from './DemoPage.vue';\nimport ToggleTheme from './components/ToggleTheme.vue';\n"
},
{
"path": "src-docs/DemoPage.vue",
"chars": 7795,
"preview": "<!-- eslint-disable import-x/no-duplicates -- for Demo source code use. -->\n<script setup lang=\"ts\">\nimport { vue } from"
},
{
"path": "src-docs/components/KeyMapDemo.vue",
"chars": 640,
"preview": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\n// eslint-disable-next-line import-x/no-unresolved -- This is a dem"
},
{
"path": "src-docs/components/LinterAndCrossBindingDemo.vue",
"chars": 3202,
"preview": "<script setup lang=\"ts\">\nimport { ref, type Ref } from 'vue';\n\nimport { javascript, esLint } from '@codemirror/lang-java"
},
{
"path": "src-docs/components/MarkdownDemo.vue",
"chars": 1457,
"preview": "<script setup lang=\"ts\">\nimport { ref, type Ref } from 'vue';\n\nimport { markdown } from '@codemirror/lang-markdown';\n// "
},
{
"path": "src-docs/components/ReadonlyAndDisabledDemo.vue",
"chars": 1386,
"preview": "<script setup lang=\"ts\">\nimport { ref, type Ref } from 'vue';\n\n// eslint-disable-next-line import-x/no-unresolved -- Thi"
},
{
"path": "src-docs/components/SlotDemo.vue",
"chars": 864,
"preview": "<script setup lang=\"ts\">\nimport { json, jsonParseLinter } from '@codemirror/lang-json';\n// eslint-disable-next-line impo"
},
{
"path": "src-docs/components/ToggleTheme.vue",
"chars": 748,
"preview": "<script setup lang=\"ts\">\n/** Bootstrap 5.3 Toggle Dark mode */\nimport { watch } from 'vue';\n\nimport { useDark, useToggle"
},
{
"path": "src-docs/main.ts",
"chars": 135,
"preview": "import './style.scss';\n\n/** Demo Code */\nimport { createApp } from 'vue';\n\nimport App from './App.vue';\n\ncreateApp(App)."
},
{
"path": "src-docs/style.scss",
"chars": 7314,
"preview": "// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)\n@import '~/bootstrap/scss/functions';\n\n// "
},
{
"path": "tsconfig.app.json",
"chars": 783,
"preview": "{\n \"include\": [\"env.d.ts\", \"src/**/*\", \"src/**/*.vue\"],\n \"exclude\": [\"src/**/__tests__/*\"],\n \"compilerOptions\": {\n "
},
{
"path": "tsconfig.docs.json",
"chars": 466,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/tsconfig.json\",\n \"extends\": \"./tsconfig.app.json\",\n \"include\": [\n \"./e"
},
{
"path": "tsconfig.json",
"chars": 260,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/tsconfig.json\",\n \"files\": [],\n \"references\": [\n { \"path\": \"./tsconfig."
},
{
"path": "tsconfig.node.json",
"chars": 416,
"preview": "{\n \"extends\": \"@tsconfig/node-lts/tsconfig.json\",\n \"include\": [\n \"vite.config.*\",\n \"vitest.config.*\",\n \"cypre"
},
{
"path": "tsconfig.vitest.json",
"chars": 331,
"preview": "{\n \"extends\": \"./tsconfig.app.json\",\n \"include\": [\"src/**/*\", \"src/**/__tests__/*\", \"env.d.ts\"],\n \"exclude\": [],\n \"c"
},
{
"path": "vite.config.ts",
"chars": 5521,
"preview": "import { writeFileSync } from 'node:fs';\nimport { fileURLToPath, URL } from 'node:url';\n\nimport Vue from '@vitejs/plugin"
},
{
"path": "vitest.config.ts",
"chars": 701,
"preview": "import { fileURLToPath, URL } from 'node:url';\n\nimport Vue from '@vitejs/plugin-vue';\nimport { defineConfig } from 'vite"
}
]
About this extraction
This page contains the full source code of the logue/vue-codemirror6 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 45 files (195.7 KB), approximately 54.3k tokens, and a symbol index with 15 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.