Mercurial
comparison .cms/lib/codemirror/src/model/selection_updates.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 { signalLater } from "../util/operation_group.js" | |
2 import { ensureCursorVisible } from "../display/scrolling.js" | |
3 import { clipPos, cmp, Pos } from "../line/pos.js" | |
4 import { getLine } from "../line/utils_line.js" | |
5 import { hasHandler, signal, signalCursorActivity } from "../util/event.js" | |
6 import { lst, sel_dontScroll } from "../util/misc.js" | |
7 | |
8 import { addSelectionToHistory } from "./history.js" | |
9 import { normalizeSelection, Range, Selection, simpleSelection } from "./selection.js" | |
10 | |
11 // The 'scroll' parameter given to many of these indicated whether | |
12 // the new cursor position should be scrolled into view after | |
13 // modifying the selection. | |
14 | |
15 // If shift is held or the extend flag is set, extends a range to | |
16 // include a given position (and optionally a second position). | |
17 // Otherwise, simply returns the range between the given positions. | |
18 // Used for cursor motion and such. | |
19 export function extendRange(range, head, other, extend) { | |
20 if (extend) { | |
21 let anchor = range.anchor | |
22 if (other) { | |
23 let posBefore = cmp(head, anchor) < 0 | |
24 if (posBefore != (cmp(other, anchor) < 0)) { | |
25 anchor = head | |
26 head = other | |
27 } else if (posBefore != (cmp(head, other) < 0)) { | |
28 head = other | |
29 } | |
30 } | |
31 return new Range(anchor, head) | |
32 } else { | |
33 return new Range(other || head, head) | |
34 } | |
35 } | |
36 | |
37 // Extend the primary selection range, discard the rest. | |
38 export function extendSelection(doc, head, other, options, extend) { | |
39 if (extend == null) extend = doc.cm && (doc.cm.display.shift || doc.extend) | |
40 setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options) | |
41 } | |
42 | |
43 // Extend all selections (pos is an array of selections with length | |
44 // equal the number of selections) | |
45 export function extendSelections(doc, heads, options) { | |
46 let out = [] | |
47 let extend = doc.cm && (doc.cm.display.shift || doc.extend) | |
48 for (let i = 0; i < doc.sel.ranges.length; i++) | |
49 out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend) | |
50 let newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex) | |
51 setSelection(doc, newSel, options) | |
52 } | |
53 | |
54 // Updates a single range in the selection. | |
55 export function replaceOneSelection(doc, i, range, options) { | |
56 let ranges = doc.sel.ranges.slice(0) | |
57 ranges[i] = range | |
58 setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options) | |
59 } | |
60 | |
61 // Reset the selection to a single range. | |
62 export function setSimpleSelection(doc, anchor, head, options) { | |
63 setSelection(doc, simpleSelection(anchor, head), options) | |
64 } | |
65 | |
66 // Give beforeSelectionChange handlers a change to influence a | |
67 // selection update. | |
68 function filterSelectionChange(doc, sel, options) { | |
69 let obj = { | |
70 ranges: sel.ranges, | |
71 update: function(ranges) { | |
72 this.ranges = [] | |
73 for (let i = 0; i < ranges.length; i++) | |
74 this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), | |
75 clipPos(doc, ranges[i].head)) | |
76 }, | |
77 origin: options && options.origin | |
78 } | |
79 signal(doc, "beforeSelectionChange", doc, obj) | |
80 if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj) | |
81 if (obj.ranges != sel.ranges) return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) | |
82 else return sel | |
83 } | |
84 | |
85 export function setSelectionReplaceHistory(doc, sel, options) { | |
86 let done = doc.history.done, last = lst(done) | |
87 if (last && last.ranges) { | |
88 done[done.length - 1] = sel | |
89 setSelectionNoUndo(doc, sel, options) | |
90 } else { | |
91 setSelection(doc, sel, options) | |
92 } | |
93 } | |
94 | |
95 // Set a new selection. | |
96 export function setSelection(doc, sel, options) { | |
97 setSelectionNoUndo(doc, sel, options) | |
98 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) | |
99 } | |
100 | |
101 export function setSelectionNoUndo(doc, sel, options) { | |
102 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) | |
103 sel = filterSelectionChange(doc, sel, options) | |
104 | |
105 let bias = options && options.bias || | |
106 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1) | |
107 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)) | |
108 | |
109 if (!(options && options.scroll === false) && doc.cm && doc.cm.getOption("readOnly") != "nocursor") | |
110 ensureCursorVisible(doc.cm) | |
111 } | |
112 | |
113 function setSelectionInner(doc, sel) { | |
114 if (sel.equals(doc.sel)) return | |
115 | |
116 doc.sel = sel | |
117 | |
118 if (doc.cm) { | |
119 doc.cm.curOp.updateInput = 1 | |
120 doc.cm.curOp.selectionChanged = true | |
121 signalCursorActivity(doc.cm) | |
122 } | |
123 signalLater(doc, "cursorActivity", doc) | |
124 } | |
125 | |
126 // Verify that the selection does not partially select any atomic | |
127 // marked ranges. | |
128 export function reCheckSelection(doc) { | |
129 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)) | |
130 } | |
131 | |
132 // Return a selection that does not partially select any atomic | |
133 // ranges. | |
134 function skipAtomicInSelection(doc, sel, bias, mayClear) { | |
135 let out | |
136 for (let i = 0; i < sel.ranges.length; i++) { | |
137 let range = sel.ranges[i] | |
138 let old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i] | |
139 let newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear) | |
140 let newHead = range.head == range.anchor ? newAnchor : skipAtomic(doc, range.head, old && old.head, bias, mayClear) | |
141 if (out || newAnchor != range.anchor || newHead != range.head) { | |
142 if (!out) out = sel.ranges.slice(0, i) | |
143 out[i] = new Range(newAnchor, newHead) | |
144 } | |
145 } | |
146 return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel | |
147 } | |
148 | |
149 function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { | |
150 let line = getLine(doc, pos.line) | |
151 if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) { | |
152 let sp = line.markedSpans[i], m = sp.marker | |
153 | |
154 // Determine if we should prevent the cursor being placed to the left/right of an atomic marker | |
155 // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it | |
156 // is with selectLeft/Right | |
157 let preventCursorLeft = ("selectLeft" in m) ? !m.selectLeft : m.inclusiveLeft | |
158 let preventCursorRight = ("selectRight" in m) ? !m.selectRight : m.inclusiveRight | |
159 | |
160 if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && | |
161 (sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) { | |
162 if (mayClear) { | |
163 signal(m, "beforeCursorEnter") | |
164 if (m.explicitlyCleared) { | |
165 if (!line.markedSpans) break | |
166 else {--i; continue} | |
167 } | |
168 } | |
169 if (!m.atomic) continue | |
170 | |
171 if (oldPos) { | |
172 let near = m.find(dir < 0 ? 1 : -1), diff | |
173 if (dir < 0 ? preventCursorRight : preventCursorLeft) | |
174 near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) | |
175 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) | |
176 return skipAtomicInner(doc, near, pos, dir, mayClear) | |
177 } | |
178 | |
179 let far = m.find(dir < 0 ? -1 : 1) | |
180 if (dir < 0 ? preventCursorLeft : preventCursorRight) | |
181 far = movePos(doc, far, dir, far.line == pos.line ? line : null) | |
182 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null | |
183 } | |
184 } | |
185 return pos | |
186 } | |
187 | |
188 // Ensure a given position is not inside an atomic range. | |
189 export function skipAtomic(doc, pos, oldPos, bias, mayClear) { | |
190 let dir = bias || 1 | |
191 let found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || | |
192 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || | |
193 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || | |
194 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)) | |
195 if (!found) { | |
196 doc.cantEdit = true | |
197 return Pos(doc.first, 0) | |
198 } | |
199 return found | |
200 } | |
201 | |
202 function movePos(doc, pos, dir, line) { | |
203 if (dir < 0 && pos.ch == 0) { | |
204 if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1)) | |
205 else return null | |
206 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { | |
207 if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0) | |
208 else return null | |
209 } else { | |
210 return new Pos(pos.line, pos.ch + dir) | |
211 } | |
212 } | |
213 | |
214 export function selectAll(cm) { | |
215 cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll) | |
216 } |