Mercurial
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) |