forked from surmon-china/vue-codemirror
-
Notifications
You must be signed in to change notification settings - Fork 0
/
codemirror.ts
151 lines (135 loc) · 4.86 KB
/
codemirror.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import type { CSSProperties } from 'vue'
import { EditorState, EditorStateConfig, Compartment, Extension, StateEffect } from '@codemirror/state'
import { EditorView, EditorViewConfig, ViewUpdate, keymap, placeholder } from '@codemirror/view'
import { indentWithTab } from '@codemirror/commands'
import { indentUnit } from '@codemirror/language'
import { syntaxTree } from '@codemirror/language'
import { linter, Diagnostic } from '@codemirror/lint'
const parseLinter = linter((view) => {
let diagnostics: Diagnostic[] = []
syntaxTree(view.state)
.cursor()
.iterate((node) => {
if (node.type.isError) {
diagnostics.push({
from: node.from,
to: node.to,
severity: 'error',
message: 'Parse error: ' + JSON.stringify(view.state.sliceDoc(node.from, node.to))
})
}
})
return diagnostics
})
export interface CreateStateOptions extends EditorStateConfig {
onChange(doc: string, viewUpdate: ViewUpdate): void
onUpdate(viewUpdate: ViewUpdate): void
onFocus(viewUpdate: ViewUpdate): void
onBlur(viewUpdate: ViewUpdate): void
}
export const createEditorState = ({ onUpdate, onChange, onFocus, onBlur, ...config }: CreateStateOptions) => {
return EditorState.create({
doc: config.doc,
selection: config.selection,
extensions: [
...(Array.isArray(config.extensions) ? config.extensions : [config.extensions]),
EditorView.updateListener.of((viewUpdate) => {
// https://discuss.codemirror.net/t/codemirror-6-proper-way-to-listen-for-changes/2395/11
onUpdate(viewUpdate)
// doc changed
if (viewUpdate.docChanged) {
onChange(viewUpdate.state.doc.toString(), viewUpdate)
}
// focus state change
if (viewUpdate.focusChanged) {
viewUpdate.view.hasFocus ? onFocus(viewUpdate) : onBlur(viewUpdate)
}
}),
parseLinter
]
})
}
export const createEditorView = (config: EditorViewConfig) => new EditorView({ ...config })
export const destroyEditorView = (view: EditorView) => view.destroy()
// https://codemirror.net/examples/config/
// https://github.com/uiwjs/react-codemirror/blob/22cc81971a/src/useCodeMirror.ts#L144
// https://gist.github.com/s-cork/e7104bace090702f6acbc3004228f2cb
export const createEditorCompartment = (view: EditorView) => {
const compartment = new Compartment()
const run = (extension: Extension) => {
compartment.get(view.state)
? view.dispatch({ effects: compartment.reconfigure(extension) }) // reconfigure
: view.dispatch({ effects: StateEffect.appendConfig.of(compartment.of(extension)) }) // inject
}
return { compartment, run }
}
// https://codemirror.net/examples/reconfigure/
export const createEditorExtensionToggler = (view: EditorView, extension: Extension) => {
const { compartment, run } = createEditorCompartment(view)
return (targetApply?: boolean) => {
const exExtension = compartment.get(view.state)
const apply = targetApply ?? exExtension !== extension
run(apply ? extension : [])
}
}
export const getEditorTools = (view: EditorView) => {
// doc state
const getDoc = () => view.state.doc.toString()
const setDoc = (newDoc: string) => {
if (newDoc !== getDoc()) {
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: newDoc
}
})
}
}
// UX operations
const focus = () => view.focus()
// reconfigure extension
const { run: reExtensions } = createEditorCompartment(view)
// disabled editor
const toggleDisabled = createEditorExtensionToggler(view, [
EditorView.editable.of(false),
EditorState.readOnly.of(true)
])
// https://codemirror.net/examples/tab/
const toggleIndentWithTab = createEditorExtensionToggler(view, keymap.of([indentWithTab]))
// tab size
// https://gist.github.com/s-cork/e7104bace090702f6acbc3004228f2cb
const { run: reTabSize } = createEditorCompartment(view)
const setTabSize = (tabSize: number) => {
reTabSize([EditorState.tabSize.of(tabSize), indentUnit.of(' '.repeat(tabSize))])
}
// phrases
// https://codemirror.net/examples/translate/
const { run: rePhrases } = createEditorCompartment(view)
const setPhrases = (phrases: Record<string, string>) => {
rePhrases([EditorState.phrases.of(phrases)])
}
// set editor's placeholder
const { run: rePlaceholder } = createEditorCompartment(view)
const setPlaceholder = (value: string) => {
rePlaceholder(placeholder(value))
}
// set style to editor element
// https://codemirror.net/examples/styling/
const { run: reStyle } = createEditorCompartment(view)
const setStyle = (style: CSSProperties = {}) => {
reStyle(EditorView.theme({ '&': { ...(style as any) } }))
}
return {
focus,
getDoc,
setDoc,
reExtensions,
toggleDisabled,
toggleIndentWithTab,
setTabSize,
setPhrases,
setPlaceholder,
setStyle
}
}