comparison .cms/lib/codemirror/src/edit/CodeMirror.js @ 0:78edf6b517a0 draft

24.10
author Coffee CMS <info@coffee-cms.ru>
date Fri, 11 Oct 2024 22:40:23 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:78edf6b517a0
1 import { Display } from "../display/Display.js"
2 import { onFocus, onBlur } from "../display/focus.js"
3 import { maybeUpdateLineNumberWidth } from "../display/line_numbers.js"
4 import { endOperation, operation, startOperation } from "../display/operations.js"
5 import { initScrollbars } from "../display/scrollbars.js"
6 import { onScrollWheel } from "../display/scroll_events.js"
7 import { setScrollLeft, updateScrollTop } from "../display/scrolling.js"
8 import { clipPos, Pos } from "../line/pos.js"
9 import { posFromMouse } from "../measurement/position_measurement.js"
10 import { eventInWidget } from "../measurement/widgets.js"
11 import Doc from "../model/Doc.js"
12 import { attachDoc } from "../model/document_data.js"
13 import { Range } from "../model/selection.js"
14 import { extendSelection } from "../model/selection_updates.js"
15 import { ie, ie_version, mobile, webkit } from "../util/browser.js"
16 import { e_preventDefault, e_stop, on, signal, signalDOMEvent } from "../util/event.js"
17 import { copyObj, Delayed } from "../util/misc.js"
18
19 import { clearDragCursor, onDragOver, onDragStart, onDrop } from "./drop_events.js"
20 import { ensureGlobalHandlers } from "./global_events.js"
21 import { onKeyDown, onKeyPress, onKeyUp } from "./key_events.js"
22 import { clickInGutter, onContextMenu, onMouseDown } from "./mouse_events.js"
23 import { themeChanged } from "./utils.js"
24 import { defaults, optionHandlers, Init } from "./options.js"
25
26 // A CodeMirror instance represents an editor. This is the object
27 // that user code is usually dealing with.
28
29 export function CodeMirror(place, options) {
30 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options)
31
32 this.options = options = options ? copyObj(options) : {}
33 // Determine effective options based on given values and defaults.
34 copyObj(defaults, options, false)
35
36 let doc = options.value
37 if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction)
38 else if (options.mode) doc.modeOption = options.mode
39 this.doc = doc
40
41 let input = new CodeMirror.inputStyles[options.inputStyle](this)
42 let display = this.display = new Display(place, doc, input, options)
43 display.wrapper.CodeMirror = this
44 themeChanged(this)
45 if (options.lineWrapping)
46 this.display.wrapper.className += " CodeMirror-wrap"
47 initScrollbars(this)
48
49 this.state = {
50 keyMaps: [], // stores maps added by addKeyMap
51 overlays: [], // highlighting overlays, as added by addOverlay
52 modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
53 overwrite: false,
54 delayingBlurEvent: false,
55 focused: false,
56 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
57 pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll
58 selectingText: false,
59 draggingText: false,
60 highlight: new Delayed(), // stores highlight worker timeout
61 keySeq: null, // Unfinished key sequence
62 specialChars: null
63 }
64
65 if (options.autofocus && !mobile) display.input.focus()
66
67 // Override magic textarea content restore that IE sometimes does
68 // on our hidden textarea on reload
69 if (ie && ie_version < 11) setTimeout(() => this.display.input.reset(true), 20)
70
71 registerEventHandlers(this)
72 ensureGlobalHandlers()
73
74 startOperation(this)
75 this.curOp.forceUpdate = true
76 attachDoc(this, doc)
77
78 if ((options.autofocus && !mobile) || this.hasFocus())
79 setTimeout(() => {
80 if (this.hasFocus() && !this.state.focused) onFocus(this)
81 }, 20)
82 else
83 onBlur(this)
84
85 for (let opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
86 optionHandlers[opt](this, options[opt], Init)
87 maybeUpdateLineNumberWidth(this)
88 if (options.finishInit) options.finishInit(this)
89 for (let i = 0; i < initHooks.length; ++i) initHooks[i](this)
90 endOperation(this)
91 // Suppress optimizelegibility in Webkit, since it breaks text
92 // measuring on line wrapping boundaries.
93 if (webkit && options.lineWrapping &&
94 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility")
95 display.lineDiv.style.textRendering = "auto"
96 }
97
98 // The default configuration options.
99 CodeMirror.defaults = defaults
100 // Functions to run when options are changed.
101 CodeMirror.optionHandlers = optionHandlers
102
103 export default CodeMirror
104
105 // Attach the necessary event handlers when initializing the editor
106 function registerEventHandlers(cm) {
107 let d = cm.display
108 on(d.scroller, "mousedown", operation(cm, onMouseDown))
109 // Older IE's will not fire a second mousedown for a double click
110 if (ie && ie_version < 11)
111 on(d.scroller, "dblclick", operation(cm, e => {
112 if (signalDOMEvent(cm, e)) return
113 let pos = posFromMouse(cm, e)
114 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return
115 e_preventDefault(e)
116 let word = cm.findWordAt(pos)
117 extendSelection(cm.doc, word.anchor, word.head)
118 }))
119 else
120 on(d.scroller, "dblclick", e => signalDOMEvent(cm, e) || e_preventDefault(e))
121 // Some browsers fire contextmenu *after* opening the menu, at
122 // which point we can't mess with it anymore. Context menu is
123 // handled in onMouseDown for these browsers.
124 on(d.scroller, "contextmenu", e => onContextMenu(cm, e))
125 on(d.input.getField(), "contextmenu", e => {
126 if (!d.scroller.contains(e.target)) onContextMenu(cm, e)
127 })
128
129 // Used to suppress mouse event handling when a touch happens
130 let touchFinished, prevTouch = {end: 0}
131 function finishTouch() {
132 if (d.activeTouch) {
133 touchFinished = setTimeout(() => d.activeTouch = null, 1000)
134 prevTouch = d.activeTouch
135 prevTouch.end = +new Date
136 }
137 }
138 function isMouseLikeTouchEvent(e) {
139 if (e.touches.length != 1) return false
140 let touch = e.touches[0]
141 return touch.radiusX <= 1 && touch.radiusY <= 1
142 }
143 function farAway(touch, other) {
144 if (other.left == null) return true
145 let dx = other.left - touch.left, dy = other.top - touch.top
146 return dx * dx + dy * dy > 20 * 20
147 }
148 on(d.scroller, "touchstart", e => {
149 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) {
150 d.input.ensurePolled()
151 clearTimeout(touchFinished)
152 let now = +new Date
153 d.activeTouch = {start: now, moved: false,
154 prev: now - prevTouch.end <= 300 ? prevTouch : null}
155 if (e.touches.length == 1) {
156 d.activeTouch.left = e.touches[0].pageX
157 d.activeTouch.top = e.touches[0].pageY
158 }
159 }
160 })
161 on(d.scroller, "touchmove", () => {
162 if (d.activeTouch) d.activeTouch.moved = true
163 })
164 on(d.scroller, "touchend", e => {
165 let touch = d.activeTouch
166 if (touch && !eventInWidget(d, e) && touch.left != null &&
167 !touch.moved && new Date - touch.start < 300) {
168 let pos = cm.coordsChar(d.activeTouch, "page"), range
169 if (!touch.prev || farAway(touch, touch.prev)) // Single tap
170 range = new Range(pos, pos)
171 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap
172 range = cm.findWordAt(pos)
173 else // Triple tap
174 range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0)))
175 cm.setSelection(range.anchor, range.head)
176 cm.focus()
177 e_preventDefault(e)
178 }
179 finishTouch()
180 })
181 on(d.scroller, "touchcancel", finishTouch)
182
183 // Sync scrolling between fake scrollbars and real scrollable
184 // area, ensure viewport is updated when scrolling.
185 on(d.scroller, "scroll", () => {
186 if (d.scroller.clientHeight) {
187 updateScrollTop(cm, d.scroller.scrollTop)
188 setScrollLeft(cm, d.scroller.scrollLeft, true)
189 signal(cm, "scroll", cm)
190 }
191 })
192
193 // Listen to wheel events in order to try and update the viewport on time.
194 on(d.scroller, "mousewheel", e => onScrollWheel(cm, e))
195 on(d.scroller, "DOMMouseScroll", e => onScrollWheel(cm, e))
196
197 // Prevent wrapper from ever scrolling
198 on(d.wrapper, "scroll", () => d.wrapper.scrollTop = d.wrapper.scrollLeft = 0)
199
200 d.dragFunctions = {
201 enter: e => {if (!signalDOMEvent(cm, e)) e_stop(e)},
202 over: e => {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }},
203 start: e => onDragStart(cm, e),
204 drop: operation(cm, onDrop),
205 leave: e => {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }}
206 }
207
208 let inp = d.input.getField()
209 on(inp, "keyup", e => onKeyUp.call(cm, e))
210 on(inp, "keydown", operation(cm, onKeyDown))
211 on(inp, "keypress", operation(cm, onKeyPress))
212 on(inp, "focus", e => onFocus(cm, e))
213 on(inp, "blur", e => onBlur(cm, e))
214 }
215
216 let initHooks = []
217 CodeMirror.defineInitHook = f => initHooks.push(f)