input.min.js (5379B)
1 import { runInOp } from "../display/operations.js" 2 import { ensureCursorVisible } from "../display/scrolling.js" 3 import { Pos } from "../line/pos.js" 4 import { getLine } from "../line/utils_line.js" 5 import { makeChange } from "../model/changes.js" 6 import { ios, webkit } from "../util/browser.js" 7 import { elt } from "../util/dom.js" 8 import { lst, map } from "../util/misc.js" 9 import { signalLater } from "../util/operation_group.js" 10 import { splitLinesAuto } from "../util/feature_detection.js" 11 12 import { indentLine } from "./indent.js" 13 14 // This will be set to a {lineWise: bool, text: [string]} object, so 15 // that, when pasting, we know what kind of selections the copied 16 // text was made out of. 17 export let lastCopied = null 18 19 export function setLastCopied(newLastCopied) { 20 lastCopied = newLastCopied 21 } 22 23 export function applyTextInput(cm, inserted, deleted, sel, origin) { 24 let doc = cm.doc 25 cm.display.shift = false 26 if (!sel) sel = doc.sel 27 28 let paste = cm.state.pasteIncoming || origin == "paste" 29 let textLines = splitLinesAuto(inserted), multiPaste = null 30 // When pasing N lines into N selections, insert one line per selection 31 if (paste && sel.ranges.length > 1) { 32 if (lastCopied && lastCopied.text.join("\n") == inserted) { 33 if (sel.ranges.length % lastCopied.text.length == 0) { 34 multiPaste = [] 35 for (let i = 0; i < lastCopied.text.length; i++) 36 multiPaste.push(doc.splitLines(lastCopied.text[i])) 37 } 38 } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { 39 multiPaste = map(textLines, l => [l]) 40 } 41 } 42 43 let updateInput 44 // Normal behavior is to insert the new text into every selection 45 for (let i = sel.ranges.length - 1; i >= 0; i--) { 46 let range = sel.ranges[i] 47 let from = range.from(), to = range.to() 48 if (range.empty()) { 49 if (deleted && deleted > 0) // Handle deletion 50 from = Pos(from.line, from.ch - deleted) 51 else if (cm.state.overwrite && !paste) // Handle overwrite 52 to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)) 53 else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) 54 from = to = Pos(from.line, 0) 55 } 56 updateInput = cm.curOp.updateInput 57 let changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, 58 origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")} 59 makeChange(cm.doc, changeEvent) 60 signalLater(cm, "inputRead", cm, changeEvent) 61 } 62 if (inserted && !paste) 63 triggerElectric(cm, inserted) 64 65 ensureCursorVisible(cm) 66 cm.curOp.updateInput = updateInput 67 cm.curOp.typing = true 68 cm.state.pasteIncoming = cm.state.cutIncoming = false 69 } 70 71 export function handlePaste(e, cm) { 72 let pasted = e.clipboardData && e.clipboardData.getData("Text") 73 if (pasted) { 74 e.preventDefault() 75 if (!cm.isReadOnly() && !cm.options.disableInput) 76 runInOp(cm, () => applyTextInput(cm, pasted, 0, null, "paste")) 77 return true 78 } 79 } 80 81 export function triggerElectric(cm, inserted) { 82 // When an 'electric' character is inserted, immediately trigger a reindent 83 if (!cm.options.electricChars || !cm.options.smartIndent) return 84 let sel = cm.doc.sel 85 86 for (let i = sel.ranges.length - 1; i >= 0; i--) { 87 let range = sel.ranges[i] 88 if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) continue 89 let mode = cm.getModeAt(range.head) 90 let indented = false 91 if (mode.electricChars) { 92 for (let j = 0; j < mode.electricChars.length; j++) 93 if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { 94 indented = indentLine(cm, range.head.line, "smart") 95 break 96 } 97 } else if (mode.electricInput) { 98 if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) 99 indented = indentLine(cm, range.head.line, "smart") 100 } 101 if (indented) signalLater(cm, "electricInput", cm, range.head.line) 102 } 103 } 104 105 export function copyableRanges(cm) { 106 let text = [], ranges = [] 107 for (let i = 0; i < cm.doc.sel.ranges.length; i++) { 108 let line = cm.doc.sel.ranges[i].head.line 109 let lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)} 110 ranges.push(lineRange) 111 text.push(cm.getRange(lineRange.anchor, lineRange.head)) 112 } 113 return {text: text, ranges: ranges} 114 } 115 116 export function disableBrowserMagic(field, spellcheck) { 117 field.setAttribute("autocorrect", "off") 118 field.setAttribute("autocapitalize", "off") 119 field.setAttribute("spellcheck", !!spellcheck) 120 } 121 122 export function hiddenTextarea() { 123 let te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none") 124 let div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;") 125 // The textarea is kept positioned near the cursor to prevent the 126 // fact that it'll be scrolled into view on input from scrolling 127 // our fake cursor out of view. On webkit, when wrap=off, paste is 128 // very slow. So make the area wide instead. 129 if (webkit) te.style.width = "1000px" 130 else te.setAttribute("wrap", "off") 131 // If border: 0; -- iOS fails to open keyboard (issue #1287) 132 if (ios) te.style.border = "1px solid black" 133 disableBrowserMagic(te) 134 return div 135 }