Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol diffdown.com
1import { Annotation, Facet, combineConfig, StateField, Transaction, ChangeSet, ChangeDesc, EditorSelection, StateEffect, Text, findClusterBreak, countColumn, CharCategory } from '@codemirror/state'; 2import { EditorView, Direction } from '@codemirror/view'; 3import { IndentContext, getIndentation, indentString, matchBrackets, syntaxTree, getIndentUnit, indentUnit } from '@codemirror/language'; 4import { NodeProp } from '@lezer/common'; 5 6/** 7Comment or uncomment the current selection. Will use line comments 8if available, otherwise falling back to block comments. 9*/ 10const toggleComment = target => { 11 let { state } = target, line = state.doc.lineAt(state.selection.main.from), config = getConfig(target.state, line.from); 12 return config.line ? toggleLineComment(target) : config.block ? toggleBlockCommentByLine(target) : false; 13}; 14function command(f, option) { 15 return ({ state, dispatch }) => { 16 if (state.readOnly) 17 return false; 18 let tr = f(option, state); 19 if (!tr) 20 return false; 21 dispatch(state.update(tr)); 22 return true; 23 }; 24} 25/** 26Comment or uncomment the current selection using line comments. 27The line comment syntax is taken from the 28[`commentTokens`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) [language 29data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt). 30*/ 31const toggleLineComment = /*@__PURE__*/command(changeLineComment, 0 /* CommentOption.Toggle */); 32/** 33Comment the current selection using line comments. 34*/ 35const lineComment = /*@__PURE__*/command(changeLineComment, 1 /* CommentOption.Comment */); 36/** 37Uncomment the current selection using line comments. 38*/ 39const lineUncomment = /*@__PURE__*/command(changeLineComment, 2 /* CommentOption.Uncomment */); 40/** 41Comment or uncomment the current selection using block comments. 42The block comment syntax is taken from the 43[`commentTokens`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) [language 44data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt). 45*/ 46const toggleBlockComment = /*@__PURE__*/command(changeBlockComment, 0 /* CommentOption.Toggle */); 47/** 48Comment the current selection using block comments. 49*/ 50const blockComment = /*@__PURE__*/command(changeBlockComment, 1 /* CommentOption.Comment */); 51/** 52Uncomment the current selection using block comments. 53*/ 54const blockUncomment = /*@__PURE__*/command(changeBlockComment, 2 /* CommentOption.Uncomment */); 55/** 56Comment or uncomment the lines around the current selection using 57block comments. 58*/ 59const toggleBlockCommentByLine = /*@__PURE__*/command((o, s) => changeBlockComment(o, s, selectedLineRanges(s)), 0 /* CommentOption.Toggle */); 60function getConfig(state, pos) { 61 let data = state.languageDataAt("commentTokens", pos, 1); 62 return data.length ? data[0] : {}; 63} 64const SearchMargin = 50; 65/** 66Determines if the given range is block-commented in the given 67state. 68*/ 69function findBlockComment(state, { open, close }, from, to) { 70 let textBefore = state.sliceDoc(from - SearchMargin, from); 71 let textAfter = state.sliceDoc(to, to + SearchMargin); 72 let spaceBefore = /\s*$/.exec(textBefore)[0].length, spaceAfter = /^\s*/.exec(textAfter)[0].length; 73 let beforeOff = textBefore.length - spaceBefore; 74 if (textBefore.slice(beforeOff - open.length, beforeOff) == open && 75 textAfter.slice(spaceAfter, spaceAfter + close.length) == close) { 76 return { open: { pos: from - spaceBefore, margin: spaceBefore && 1 }, 77 close: { pos: to + spaceAfter, margin: spaceAfter && 1 } }; 78 } 79 let startText, endText; 80 if (to - from <= 2 * SearchMargin) { 81 startText = endText = state.sliceDoc(from, to); 82 } 83 else { 84 startText = state.sliceDoc(from, from + SearchMargin); 85 endText = state.sliceDoc(to - SearchMargin, to); 86 } 87 let startSpace = /^\s*/.exec(startText)[0].length, endSpace = /\s*$/.exec(endText)[0].length; 88 let endOff = endText.length - endSpace - close.length; 89 if (startText.slice(startSpace, startSpace + open.length) == open && 90 endText.slice(endOff, endOff + close.length) == close) { 91 return { open: { pos: from + startSpace + open.length, 92 margin: /\s/.test(startText.charAt(startSpace + open.length)) ? 1 : 0 }, 93 close: { pos: to - endSpace - close.length, 94 margin: /\s/.test(endText.charAt(endOff - 1)) ? 1 : 0 } }; 95 } 96 return null; 97} 98function selectedLineRanges(state) { 99 let ranges = []; 100 for (let r of state.selection.ranges) { 101 let fromLine = state.doc.lineAt(r.from); 102 let toLine = r.to <= fromLine.to ? fromLine : state.doc.lineAt(r.to); 103 if (toLine.from > fromLine.from && toLine.from == r.to) 104 toLine = r.to == fromLine.to + 1 ? fromLine : state.doc.lineAt(r.to - 1); 105 let last = ranges.length - 1; 106 if (last >= 0 && ranges[last].to > fromLine.from) 107 ranges[last].to = toLine.to; 108 else 109 ranges.push({ from: fromLine.from + /^\s*/.exec(fromLine.text)[0].length, to: toLine.to }); 110 } 111 return ranges; 112} 113// Performs toggle, comment and uncomment of block comments in 114// languages that support them. 115function changeBlockComment(option, state, ranges = state.selection.ranges) { 116 let tokens = ranges.map(r => getConfig(state, r.from).block); 117 if (!tokens.every(c => c)) 118 return null; 119 let comments = ranges.map((r, i) => findBlockComment(state, tokens[i], r.from, r.to)); 120 if (option != 2 /* CommentOption.Uncomment */ && !comments.every(c => c)) { 121 return { changes: state.changes(ranges.map((range, i) => { 122 if (comments[i]) 123 return []; 124 return [{ from: range.from, insert: tokens[i].open + " " }, { from: range.to, insert: " " + tokens[i].close }]; 125 })) }; 126 } 127 else if (option != 1 /* CommentOption.Comment */ && comments.some(c => c)) { 128 let changes = []; 129 for (let i = 0, comment; i < comments.length; i++) 130 if (comment = comments[i]) { 131 let token = tokens[i], { open, close } = comment; 132 changes.push({ from: open.pos - token.open.length, to: open.pos + open.margin }, { from: close.pos - close.margin, to: close.pos + token.close.length }); 133 } 134 return { changes }; 135 } 136 return null; 137} 138// Performs toggle, comment and uncomment of line comments. 139function changeLineComment(option, state, ranges = state.selection.ranges) { 140 let lines = []; 141 let prevLine = -1; 142 ranges: for (let { from, to } of ranges) { 143 let startI = lines.length, minIndent = 1e9, token; 144 for (let pos = from; pos <= to;) { 145 let line = state.doc.lineAt(pos); 146 if (token == undefined) { 147 token = getConfig(state, line.from).line; 148 if (!token) 149 continue ranges; 150 } 151 if (line.from > prevLine && (from == to || to > line.from)) { 152 prevLine = line.from; 153 let indent = /^\s*/.exec(line.text)[0].length; 154 let empty = indent == line.length; 155 let comment = line.text.slice(indent, indent + token.length) == token ? indent : -1; 156 if (indent < line.text.length && indent < minIndent) 157 minIndent = indent; 158 lines.push({ line, comment, token, indent, empty, single: false }); 159 } 160 pos = line.to + 1; 161 } 162 if (minIndent < 1e9) 163 for (let i = startI; i < lines.length; i++) 164 if (lines[i].indent < lines[i].line.text.length) 165 lines[i].indent = minIndent; 166 if (lines.length == startI + 1) 167 lines[startI].single = true; 168 } 169 if (option != 2 /* CommentOption.Uncomment */ && lines.some(l => l.comment < 0 && (!l.empty || l.single))) { 170 let changes = []; 171 for (let { line, token, indent, empty, single } of lines) 172 if (single || !empty) 173 changes.push({ from: line.from + indent, insert: token + " " }); 174 let changeSet = state.changes(changes); 175 return { changes: changeSet, selection: state.selection.map(changeSet, 1) }; 176 } 177 else if (option != 1 /* CommentOption.Comment */ && lines.some(l => l.comment >= 0)) { 178 let changes = []; 179 for (let { line, comment, token } of lines) 180 if (comment >= 0) { 181 let from = line.from + comment, to = from + token.length; 182 if (line.text[to - line.from] == " ") 183 to++; 184 changes.push({ from, to }); 185 } 186 return { changes }; 187 } 188 return null; 189} 190 191const fromHistory = /*@__PURE__*/Annotation.define(); 192/** 193Transaction annotation that will prevent that transaction from 194being combined with other transactions in the undo history. Given 195`"before"`, it'll prevent merging with previous transactions. With 196`"after"`, subsequent transactions won't be combined with this 197one. With `"full"`, the transaction is isolated on both sides. 198*/ 199const isolateHistory = /*@__PURE__*/Annotation.define(); 200/** 201This facet provides a way to register functions that, given a 202transaction, provide a set of effects that the history should 203store when inverting the transaction. This can be used to 204integrate some kinds of effects in the history, so that they can 205be undone (and redone again). 206*/ 207const invertedEffects = /*@__PURE__*/Facet.define(); 208const historyConfig = /*@__PURE__*/Facet.define({ 209 combine(configs) { 210 return combineConfig(configs, { 211 minDepth: 100, 212 newGroupDelay: 500, 213 joinToEvent: (_t, isAdjacent) => isAdjacent, 214 }, { 215 minDepth: Math.max, 216 newGroupDelay: Math.min, 217 joinToEvent: (a, b) => (tr, adj) => a(tr, adj) || b(tr, adj) 218 }); 219 } 220}); 221const historyField_ = /*@__PURE__*/StateField.define({ 222 create() { 223 return HistoryState.empty; 224 }, 225 update(state, tr) { 226 let config = tr.state.facet(historyConfig); 227 let fromHist = tr.annotation(fromHistory); 228 if (fromHist) { 229 let item = HistEvent.fromTransaction(tr, fromHist.selection), from = fromHist.side; 230 let other = from == 0 /* BranchName.Done */ ? state.undone : state.done; 231 if (item) 232 other = updateBranch(other, other.length, config.minDepth, item); 233 else 234 other = addSelection(other, tr.startState.selection); 235 return new HistoryState(from == 0 /* BranchName.Done */ ? fromHist.rest : other, from == 0 /* BranchName.Done */ ? other : fromHist.rest); 236 } 237 let isolate = tr.annotation(isolateHistory); 238 if (isolate == "full" || isolate == "before") 239 state = state.isolate(); 240 if (tr.annotation(Transaction.addToHistory) === false) 241 return !tr.changes.empty ? state.addMapping(tr.changes.desc) : state; 242 let event = HistEvent.fromTransaction(tr); 243 let time = tr.annotation(Transaction.time), userEvent = tr.annotation(Transaction.userEvent); 244 if (event) 245 state = state.addChanges(event, time, userEvent, config, tr); 246 else if (tr.selection) 247 state = state.addSelection(tr.startState.selection, time, userEvent, config.newGroupDelay); 248 if (isolate == "full" || isolate == "after") 249 state = state.isolate(); 250 return state; 251 }, 252 toJSON(value) { 253 return { done: value.done.map(e => e.toJSON()), undone: value.undone.map(e => e.toJSON()) }; 254 }, 255 fromJSON(json) { 256 return new HistoryState(json.done.map(HistEvent.fromJSON), json.undone.map(HistEvent.fromJSON)); 257 } 258}); 259/** 260Create a history extension with the given configuration. 261*/ 262function history(config = {}) { 263 return [ 264 historyField_, 265 historyConfig.of(config), 266 EditorView.domEventHandlers({ 267 beforeinput(e, view) { 268 let command = e.inputType == "historyUndo" ? undo : e.inputType == "historyRedo" ? redo : null; 269 if (!command) 270 return false; 271 e.preventDefault(); 272 return command(view); 273 } 274 }) 275 ]; 276} 277/** 278The state field used to store the history data. Should probably 279only be used when you want to 280[serialize](https://codemirror.net/6/docs/ref/#state.EditorState.toJSON) or 281[deserialize](https://codemirror.net/6/docs/ref/#state.EditorState^fromJSON) state objects in a way 282that preserves history. 283*/ 284const historyField = historyField_; 285function cmd(side, selection) { 286 return function ({ state, dispatch }) { 287 if (!selection && state.readOnly) 288 return false; 289 let historyState = state.field(historyField_, false); 290 if (!historyState) 291 return false; 292 let tr = historyState.pop(side, state, selection); 293 if (!tr) 294 return false; 295 dispatch(tr); 296 return true; 297 }; 298} 299/** 300Undo a single group of history events. Returns false if no group 301was available. 302*/ 303const undo = /*@__PURE__*/cmd(0 /* BranchName.Done */, false); 304/** 305Redo a group of history events. Returns false if no group was 306available. 307*/ 308const redo = /*@__PURE__*/cmd(1 /* BranchName.Undone */, false); 309/** 310Undo a change or selection change. 311*/ 312const undoSelection = /*@__PURE__*/cmd(0 /* BranchName.Done */, true); 313/** 314Redo a change or selection change. 315*/ 316const redoSelection = /*@__PURE__*/cmd(1 /* BranchName.Undone */, true); 317function depth(side) { 318 return function (state) { 319 let histState = state.field(historyField_, false); 320 if (!histState) 321 return 0; 322 let branch = side == 0 /* BranchName.Done */ ? histState.done : histState.undone; 323 return branch.length - (branch.length && !branch[0].changes ? 1 : 0); 324 }; 325} 326/** 327The amount of undoable change events available in a given state. 328*/ 329const undoDepth = /*@__PURE__*/depth(0 /* BranchName.Done */); 330/** 331The amount of redoable change events available in a given state. 332*/ 333const redoDepth = /*@__PURE__*/depth(1 /* BranchName.Undone */); 334// History events store groups of changes or effects that need to be 335// undone/redone together. 336class HistEvent { 337 constructor( 338 // The changes in this event. Normal events hold at least one 339 // change or effect. But it may be necessary to store selection 340 // events before the first change, in which case a special type of 341 // instance is created which doesn't hold any changes, with 342 // changes == startSelection == undefined 343 changes, 344 // The effects associated with this event 345 effects, 346 // Accumulated mapping (from addToHistory==false) that should be 347 // applied to events below this one. 348 mapped, 349 // The selection before this event 350 startSelection, 351 // Stores selection changes after this event, to be used for 352 // selection undo/redo. 353 selectionsAfter) { 354 this.changes = changes; 355 this.effects = effects; 356 this.mapped = mapped; 357 this.startSelection = startSelection; 358 this.selectionsAfter = selectionsAfter; 359 } 360 setSelAfter(after) { 361 return new HistEvent(this.changes, this.effects, this.mapped, this.startSelection, after); 362 } 363 toJSON() { 364 var _a, _b, _c; 365 return { 366 changes: (_a = this.changes) === null || _a === void 0 ? void 0 : _a.toJSON(), 367 mapped: (_b = this.mapped) === null || _b === void 0 ? void 0 : _b.toJSON(), 368 startSelection: (_c = this.startSelection) === null || _c === void 0 ? void 0 : _c.toJSON(), 369 selectionsAfter: this.selectionsAfter.map(s => s.toJSON()) 370 }; 371 } 372 static fromJSON(json) { 373 return new HistEvent(json.changes && ChangeSet.fromJSON(json.changes), [], json.mapped && ChangeDesc.fromJSON(json.mapped), json.startSelection && EditorSelection.fromJSON(json.startSelection), json.selectionsAfter.map(EditorSelection.fromJSON)); 374 } 375 // This does not check `addToHistory` and such, it assumes the 376 // transaction needs to be converted to an item. Returns null when 377 // there are no changes or effects in the transaction. 378 static fromTransaction(tr, selection) { 379 let effects = none; 380 for (let invert of tr.startState.facet(invertedEffects)) { 381 let result = invert(tr); 382 if (result.length) 383 effects = effects.concat(result); 384 } 385 if (!effects.length && tr.changes.empty) 386 return null; 387 return new HistEvent(tr.changes.invert(tr.startState.doc), effects, undefined, selection || tr.startState.selection, none); 388 } 389 static selection(selections) { 390 return new HistEvent(undefined, none, undefined, undefined, selections); 391 } 392} 393function updateBranch(branch, to, maxLen, newEvent) { 394 let start = to + 1 > maxLen + 20 ? to - maxLen - 1 : 0; 395 let newBranch = branch.slice(start, to); 396 newBranch.push(newEvent); 397 return newBranch; 398} 399function isAdjacent(a, b) { 400 let ranges = [], isAdjacent = false; 401 a.iterChangedRanges((f, t) => ranges.push(f, t)); 402 b.iterChangedRanges((_f, _t, f, t) => { 403 for (let i = 0; i < ranges.length;) { 404 let from = ranges[i++], to = ranges[i++]; 405 if (t >= from && f <= to) 406 isAdjacent = true; 407 } 408 }); 409 return isAdjacent; 410} 411function eqSelectionShape(a, b) { 412 return a.ranges.length == b.ranges.length && 413 a.ranges.filter((r, i) => r.empty != b.ranges[i].empty).length === 0; 414} 415function conc(a, b) { 416 return !a.length ? b : !b.length ? a : a.concat(b); 417} 418const none = []; 419const MaxSelectionsPerEvent = 200; 420function addSelection(branch, selection) { 421 if (!branch.length) { 422 return [HistEvent.selection([selection])]; 423 } 424 else { 425 let lastEvent = branch[branch.length - 1]; 426 let sels = lastEvent.selectionsAfter.slice(Math.max(0, lastEvent.selectionsAfter.length - MaxSelectionsPerEvent)); 427 if (sels.length && sels[sels.length - 1].eq(selection)) 428 return branch; 429 sels.push(selection); 430 return updateBranch(branch, branch.length - 1, 1e9, lastEvent.setSelAfter(sels)); 431 } 432} 433// Assumes the top item has one or more selectionAfter values 434function popSelection(branch) { 435 let last = branch[branch.length - 1]; 436 let newBranch = branch.slice(); 437 newBranch[branch.length - 1] = last.setSelAfter(last.selectionsAfter.slice(0, last.selectionsAfter.length - 1)); 438 return newBranch; 439} 440// Add a mapping to the top event in the given branch. If this maps 441// away all the changes and effects in that item, drop it and 442// propagate the mapping to the next item. 443function addMappingToBranch(branch, mapping) { 444 if (!branch.length) 445 return branch; 446 let length = branch.length, selections = none; 447 while (length) { 448 let event = mapEvent(branch[length - 1], mapping, selections); 449 if (event.changes && !event.changes.empty || event.effects.length) { // Event survived mapping 450 let result = branch.slice(0, length); 451 result[length - 1] = event; 452 return result; 453 } 454 else { // Drop this event, since there's no changes or effects left 455 mapping = event.mapped; 456 length--; 457 selections = event.selectionsAfter; 458 } 459 } 460 return selections.length ? [HistEvent.selection(selections)] : none; 461} 462function mapEvent(event, mapping, extraSelections) { 463 let selections = conc(event.selectionsAfter.length ? event.selectionsAfter.map(s => s.map(mapping)) : none, extraSelections); 464 // Change-less events don't store mappings (they are always the last event in a branch) 465 if (!event.changes) 466 return HistEvent.selection(selections); 467 let mappedChanges = event.changes.map(mapping), before = mapping.mapDesc(event.changes, true); 468 let fullMapping = event.mapped ? event.mapped.composeDesc(before) : before; 469 return new HistEvent(mappedChanges, StateEffect.mapEffects(event.effects, mapping), fullMapping, event.startSelection.map(before), selections); 470} 471const joinableUserEvent = /^(input\.type|delete)($|\.)/; 472class HistoryState { 473 constructor(done, undone, prevTime = 0, prevUserEvent = undefined) { 474 this.done = done; 475 this.undone = undone; 476 this.prevTime = prevTime; 477 this.prevUserEvent = prevUserEvent; 478 } 479 isolate() { 480 return this.prevTime ? new HistoryState(this.done, this.undone) : this; 481 } 482 addChanges(event, time, userEvent, config, tr) { 483 let done = this.done, lastEvent = done[done.length - 1]; 484 if (lastEvent && lastEvent.changes && !lastEvent.changes.empty && event.changes && 485 (!userEvent || joinableUserEvent.test(userEvent)) && 486 ((!lastEvent.selectionsAfter.length && 487 time - this.prevTime < config.newGroupDelay && 488 config.joinToEvent(tr, isAdjacent(lastEvent.changes, event.changes))) || 489 // For compose (but not compose.start) events, always join with previous event 490 userEvent == "input.type.compose")) { 491 done = updateBranch(done, done.length - 1, config.minDepth, new HistEvent(event.changes.compose(lastEvent.changes), conc(StateEffect.mapEffects(event.effects, lastEvent.changes), lastEvent.effects), lastEvent.mapped, lastEvent.startSelection, none)); 492 } 493 else { 494 done = updateBranch(done, done.length, config.minDepth, event); 495 } 496 return new HistoryState(done, none, time, userEvent); 497 } 498 addSelection(selection, time, userEvent, newGroupDelay) { 499 let last = this.done.length ? this.done[this.done.length - 1].selectionsAfter : none; 500 if (last.length > 0 && 501 time - this.prevTime < newGroupDelay && 502 userEvent == this.prevUserEvent && userEvent && /^select($|\.)/.test(userEvent) && 503 eqSelectionShape(last[last.length - 1], selection)) 504 return this; 505 return new HistoryState(addSelection(this.done, selection), this.undone, time, userEvent); 506 } 507 addMapping(mapping) { 508 return new HistoryState(addMappingToBranch(this.done, mapping), addMappingToBranch(this.undone, mapping), this.prevTime, this.prevUserEvent); 509 } 510 pop(side, state, onlySelection) { 511 let branch = side == 0 /* BranchName.Done */ ? this.done : this.undone; 512 if (branch.length == 0) 513 return null; 514 let event = branch[branch.length - 1], selection = event.selectionsAfter[0] || 515 (event.startSelection ? event.startSelection.map(event.changes.invertedDesc, 1) : state.selection); 516 if (onlySelection && event.selectionsAfter.length) { 517 return state.update({ 518 selection: event.selectionsAfter[event.selectionsAfter.length - 1], 519 annotations: fromHistory.of({ side, rest: popSelection(branch), selection }), 520 userEvent: side == 0 /* BranchName.Done */ ? "select.undo" : "select.redo", 521 scrollIntoView: true 522 }); 523 } 524 else if (!event.changes) { 525 return null; 526 } 527 else { 528 let rest = branch.length == 1 ? none : branch.slice(0, branch.length - 1); 529 if (event.mapped) 530 rest = addMappingToBranch(rest, event.mapped); 531 return state.update({ 532 changes: event.changes, 533 selection: event.startSelection, 534 effects: event.effects, 535 annotations: fromHistory.of({ side, rest, selection }), 536 filter: false, 537 userEvent: side == 0 /* BranchName.Done */ ? "undo" : "redo", 538 scrollIntoView: true 539 }); 540 } 541 } 542} 543HistoryState.empty = /*@__PURE__*/new HistoryState(none, none); 544/** 545Default key bindings for the undo history. 546 547- Mod-z: [`undo`](https://codemirror.net/6/docs/ref/#commands.undo). 548- Mod-y (Mod-Shift-z on macOS) + Ctrl-Shift-z on Linux: [`redo`](https://codemirror.net/6/docs/ref/#commands.redo). 549- Mod-u: [`undoSelection`](https://codemirror.net/6/docs/ref/#commands.undoSelection). 550- Alt-u (Mod-Shift-u on macOS): [`redoSelection`](https://codemirror.net/6/docs/ref/#commands.redoSelection). 551*/ 552const historyKeymap = [ 553 { key: "Mod-z", run: undo, preventDefault: true }, 554 { key: "Mod-y", mac: "Mod-Shift-z", run: redo, preventDefault: true }, 555 { linux: "Ctrl-Shift-z", run: redo, preventDefault: true }, 556 { key: "Mod-u", run: undoSelection, preventDefault: true }, 557 { key: "Alt-u", mac: "Mod-Shift-u", run: redoSelection, preventDefault: true } 558]; 559 560function updateSel(sel, by) { 561 return EditorSelection.create(sel.ranges.map(by), sel.mainIndex); 562} 563function setSel(state, selection) { 564 return state.update({ selection, scrollIntoView: true, userEvent: "select" }); 565} 566function moveSel({ state, dispatch }, how) { 567 let selection = updateSel(state.selection, how); 568 if (selection.eq(state.selection, true)) 569 return false; 570 dispatch(setSel(state, selection)); 571 return true; 572} 573function rangeEnd(range, forward) { 574 return EditorSelection.cursor(forward ? range.to : range.from); 575} 576function cursorByChar(view, forward) { 577 return moveSel(view, range => range.empty ? view.moveByChar(range, forward) : rangeEnd(range, forward)); 578} 579function ltrAtCursor(view) { 580 return view.textDirectionAt(view.state.selection.main.head) == Direction.LTR; 581} 582/** 583Move the selection one character to the left (which is backward in 584left-to-right text, forward in right-to-left text). 585*/ 586const cursorCharLeft = view => cursorByChar(view, !ltrAtCursor(view)); 587/** 588Move the selection one character to the right. 589*/ 590const cursorCharRight = view => cursorByChar(view, ltrAtCursor(view)); 591/** 592Move the selection one character forward. 593*/ 594const cursorCharForward = view => cursorByChar(view, true); 595/** 596Move the selection one character backward. 597*/ 598const cursorCharBackward = view => cursorByChar(view, false); 599function byCharLogical(state, range, forward) { 600 let pos = range.head, line = state.doc.lineAt(pos); 601 if (pos == (forward ? line.to : line.from)) 602 pos = forward ? Math.min(state.doc.length, line.to + 1) : Math.max(0, line.from - 1); 603 else 604 pos = line.from + findClusterBreak(line.text, pos - line.from, forward); 605 return EditorSelection.cursor(pos, forward ? -1 : 1); 606} 607function moveByCharLogical(target, forward) { 608 return moveSel(target, range => range.empty ? byCharLogical(target.state, range, forward) : rangeEnd(range, forward)); 609} 610/** 611Move the selection one character forward, in logical 612(non-text-direction-aware) string index order. 613*/ 614const cursorCharForwardLogical = target => moveByCharLogical(target, true); 615/** 616Move the selection one character backward, in logical string index 617order. 618*/ 619const cursorCharBackwardLogical = target => moveByCharLogical(target, false); 620function cursorByGroup(view, forward) { 621 return moveSel(view, range => range.empty ? view.moveByGroup(range, forward) : rangeEnd(range, forward)); 622} 623/** 624Move the selection to the left across one group of word or 625non-word (but also non-space) characters. 626*/ 627const cursorGroupLeft = view => cursorByGroup(view, !ltrAtCursor(view)); 628/** 629Move the selection one group to the right. 630*/ 631const cursorGroupRight = view => cursorByGroup(view, ltrAtCursor(view)); 632/** 633Move the selection one group forward. 634*/ 635const cursorGroupForward = view => cursorByGroup(view, true); 636/** 637Move the selection one group backward. 638*/ 639const cursorGroupBackward = view => cursorByGroup(view, false); 640function toGroupStart(view, pos, start) { 641 let categorize = view.state.charCategorizer(pos); 642 let cat = categorize(start), initial = cat != CharCategory.Space; 643 return (next) => { 644 let nextCat = categorize(next); 645 if (nextCat != CharCategory.Space) 646 return initial && nextCat == cat; 647 initial = false; 648 return true; 649 }; 650} 651/** 652Move the cursor one group forward in the default Windows style, 653where it moves to the start of the next group. 654*/ 655const cursorGroupForwardWin = view => { 656 return moveSel(view, range => range.empty 657 ? view.moveByChar(range, true, start => toGroupStart(view, range.head, start)) 658 : rangeEnd(range, true)); 659}; 660const segmenter = typeof Intl != "undefined" && Intl.Segmenter ? 661 /*@__PURE__*/new (Intl.Segmenter)(undefined, { granularity: "word" }) : null; 662function moveBySubword(view, range, forward) { 663 let categorize = view.state.charCategorizer(range.from); 664 let cat = CharCategory.Space, pos = range.from, steps = 0; 665 let done = false, sawUpper = false, sawLower = false; 666 let step = (next) => { 667 if (done) 668 return false; 669 pos += forward ? next.length : -next.length; 670 let nextCat = categorize(next), ahead; 671 if (nextCat == CharCategory.Word && next.charCodeAt(0) < 128 && /[\W_]/.test(next)) 672 nextCat = -1; // Treat word punctuation specially 673 if (cat == CharCategory.Space) 674 cat = nextCat; 675 if (cat != nextCat) 676 return false; 677 if (cat == CharCategory.Word) { 678 if (next.toLowerCase() == next) { 679 if (!forward && sawUpper) 680 return false; 681 sawLower = true; 682 } 683 else if (sawLower) { 684 if (forward) 685 return false; 686 done = true; 687 } 688 else { 689 if (sawUpper && forward && categorize(ahead = view.state.sliceDoc(pos, pos + 1)) == CharCategory.Word && 690 ahead.toLowerCase() == ahead) 691 return false; 692 sawUpper = true; 693 } 694 } 695 steps++; 696 return true; 697 }; 698 let end = view.moveByChar(range, forward, start => { 699 step(start); 700 return step; 701 }); 702 if (segmenter && cat == CharCategory.Word && end.from == range.from + steps * (forward ? 1 : -1)) { 703 let from = Math.min(range.head, end.head), to = Math.max(range.head, end.head); 704 let skipped = view.state.sliceDoc(from, to); 705 if (skipped.length > 1 && /[\u4E00-\uffff]/.test(skipped)) { 706 let segments = Array.from(segmenter.segment(skipped)); 707 if (segments.length > 1) { 708 if (forward) 709 return EditorSelection.cursor(range.head + segments[1].index, -1); 710 return EditorSelection.cursor(end.head + segments[segments.length - 1].index, 1); 711 } 712 } 713 } 714 return end; 715} 716function cursorBySubword(view, forward) { 717 return moveSel(view, range => range.empty ? moveBySubword(view, range, forward) : rangeEnd(range, forward)); 718} 719/** 720Move the selection one group or camel-case subword forward. 721*/ 722const cursorSubwordForward = view => cursorBySubword(view, true); 723/** 724Move the selection one group or camel-case subword backward. 725*/ 726const cursorSubwordBackward = view => cursorBySubword(view, false); 727function interestingNode(state, node, bracketProp) { 728 if (node.type.prop(bracketProp)) 729 return true; 730 let len = node.to - node.from; 731 return len && (len > 2 || /[^\s,.;:]/.test(state.sliceDoc(node.from, node.to))) || node.firstChild; 732} 733function moveBySyntax(state, start, forward) { 734 let pos = syntaxTree(state).resolveInner(start.head); 735 let bracketProp = forward ? NodeProp.closedBy : NodeProp.openedBy; 736 // Scan forward through child nodes to see if there's an interesting 737 // node ahead. 738 for (let at = start.head;;) { 739 let next = forward ? pos.childAfter(at) : pos.childBefore(at); 740 if (!next) 741 break; 742 if (interestingNode(state, next, bracketProp)) 743 pos = next; 744 else 745 at = forward ? next.to : next.from; 746 } 747 let bracket = pos.type.prop(bracketProp), match, newPos; 748 if (bracket && (match = forward ? matchBrackets(state, pos.from, 1) : matchBrackets(state, pos.to, -1)) && match.matched) 749 newPos = forward ? match.end.to : match.end.from; 750 else 751 newPos = forward ? pos.to : pos.from; 752 return EditorSelection.cursor(newPos, forward ? -1 : 1); 753} 754/** 755Move the cursor over the next syntactic element to the left. 756*/ 757const cursorSyntaxLeft = view => moveSel(view, range => moveBySyntax(view.state, range, !ltrAtCursor(view))); 758/** 759Move the cursor over the next syntactic element to the right. 760*/ 761const cursorSyntaxRight = view => moveSel(view, range => moveBySyntax(view.state, range, ltrAtCursor(view))); 762function cursorByLine(view, forward) { 763 return moveSel(view, range => { 764 if (!range.empty) 765 return rangeEnd(range, forward); 766 let moved = view.moveVertically(range, forward); 767 return moved.head != range.head ? moved : view.moveToLineBoundary(range, forward); 768 }); 769} 770/** 771Move the selection one line up. 772*/ 773const cursorLineUp = view => cursorByLine(view, false); 774/** 775Move the selection one line down. 776*/ 777const cursorLineDown = view => cursorByLine(view, true); 778function pageInfo(view) { 779 let selfScroll = view.scrollDOM.clientHeight < view.scrollDOM.scrollHeight - 2; 780 let marginTop = 0, marginBottom = 0, height; 781 if (selfScroll) { 782 for (let source of view.state.facet(EditorView.scrollMargins)) { 783 let margins = source(view); 784 if (margins === null || margins === void 0 ? void 0 : margins.top) 785 marginTop = Math.max(margins === null || margins === void 0 ? void 0 : margins.top, marginTop); 786 if (margins === null || margins === void 0 ? void 0 : margins.bottom) 787 marginBottom = Math.max(margins === null || margins === void 0 ? void 0 : margins.bottom, marginBottom); 788 } 789 height = view.scrollDOM.clientHeight - marginTop - marginBottom; 790 } 791 else { 792 height = (view.dom.ownerDocument.defaultView || window).innerHeight; 793 } 794 return { marginTop, marginBottom, selfScroll, 795 height: Math.max(view.defaultLineHeight, height - 5) }; 796} 797function cursorByPage(view, forward) { 798 let page = pageInfo(view); 799 let { state } = view, selection = updateSel(state.selection, range => { 800 return range.empty ? view.moveVertically(range, forward, page.height) 801 : rangeEnd(range, forward); 802 }); 803 if (selection.eq(state.selection)) 804 return false; 805 let effect; 806 if (page.selfScroll) { 807 let startPos = view.coordsAtPos(state.selection.main.head); 808 let scrollRect = view.scrollDOM.getBoundingClientRect(); 809 let scrollTop = scrollRect.top + page.marginTop, scrollBottom = scrollRect.bottom - page.marginBottom; 810 if (startPos && startPos.top > scrollTop && startPos.bottom < scrollBottom) 811 effect = EditorView.scrollIntoView(selection.main.head, { y: "start", yMargin: startPos.top - scrollTop }); 812 } 813 view.dispatch(setSel(state, selection), { effects: effect }); 814 return true; 815} 816/** 817Move the selection one page up. 818*/ 819const cursorPageUp = view => cursorByPage(view, false); 820/** 821Move the selection one page down. 822*/ 823const cursorPageDown = view => cursorByPage(view, true); 824function moveByLineBoundary(view, start, forward) { 825 let line = view.lineBlockAt(start.head), moved = view.moveToLineBoundary(start, forward); 826 if (moved.head == start.head && moved.head != (forward ? line.to : line.from)) 827 moved = view.moveToLineBoundary(start, forward, false); 828 if (!forward && moved.head == line.from && line.length) { 829 let space = /^\s*/.exec(view.state.sliceDoc(line.from, Math.min(line.from + 100, line.to)))[0].length; 830 if (space && start.head != line.from + space) 831 moved = EditorSelection.cursor(line.from + space); 832 } 833 return moved; 834} 835/** 836Move the selection to the next line wrap point, or to the end of 837the line if there isn't one left on this line. 838*/ 839const cursorLineBoundaryForward = view => moveSel(view, range => moveByLineBoundary(view, range, true)); 840/** 841Move the selection to previous line wrap point, or failing that to 842the start of the line. If the line is indented, and the cursor 843isn't already at the end of the indentation, this will move to the 844end of the indentation instead of the start of the line. 845*/ 846const cursorLineBoundaryBackward = view => moveSel(view, range => moveByLineBoundary(view, range, false)); 847/** 848Move the selection one line wrap point to the left. 849*/ 850const cursorLineBoundaryLeft = view => moveSel(view, range => moveByLineBoundary(view, range, !ltrAtCursor(view))); 851/** 852Move the selection one line wrap point to the right. 853*/ 854const cursorLineBoundaryRight = view => moveSel(view, range => moveByLineBoundary(view, range, ltrAtCursor(view))); 855/** 856Move the selection to the start of the line. 857*/ 858const cursorLineStart = view => moveSel(view, range => EditorSelection.cursor(view.lineBlockAt(range.head).from, 1)); 859/** 860Move the selection to the end of the line. 861*/ 862const cursorLineEnd = view => moveSel(view, range => EditorSelection.cursor(view.lineBlockAt(range.head).to, -1)); 863function toMatchingBracket(state, dispatch, extend) { 864 let found = false, selection = updateSel(state.selection, range => { 865 let matching = matchBrackets(state, range.head, -1) 866 || matchBrackets(state, range.head, 1) 867 || (range.head > 0 && matchBrackets(state, range.head - 1, 1)) 868 || (range.head < state.doc.length && matchBrackets(state, range.head + 1, -1)); 869 if (!matching || !matching.end) 870 return range; 871 found = true; 872 let head = matching.start.from == range.head ? matching.end.to : matching.end.from; 873 return extend ? EditorSelection.range(range.anchor, head) : EditorSelection.cursor(head); 874 }); 875 if (!found) 876 return false; 877 dispatch(setSel(state, selection)); 878 return true; 879} 880/** 881Move the selection to the bracket matching the one it is currently 882on, if any. 883*/ 884const cursorMatchingBracket = ({ state, dispatch }) => toMatchingBracket(state, dispatch, false); 885/** 886Extend the selection to the bracket matching the one the selection 887head is currently on, if any. 888*/ 889const selectMatchingBracket = ({ state, dispatch }) => toMatchingBracket(state, dispatch, true); 890function extendSel(target, how) { 891 let selection = updateSel(target.state.selection, range => { 892 let head = how(range); 893 return EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined); 894 }); 895 if (selection.eq(target.state.selection)) 896 return false; 897 target.dispatch(setSel(target.state, selection)); 898 return true; 899} 900function selectByChar(view, forward) { 901 return extendSel(view, range => view.moveByChar(range, forward)); 902} 903/** 904Move the selection head one character to the left, while leaving 905the anchor in place. 906*/ 907const selectCharLeft = view => selectByChar(view, !ltrAtCursor(view)); 908/** 909Move the selection head one character to the right. 910*/ 911const selectCharRight = view => selectByChar(view, ltrAtCursor(view)); 912/** 913Move the selection head one character forward. 914*/ 915const selectCharForward = view => selectByChar(view, true); 916/** 917Move the selection head one character backward. 918*/ 919const selectCharBackward = view => selectByChar(view, false); 920/** 921Move the selection head one character forward by logical 922(non-direction aware) string index order. 923*/ 924const selectCharForwardLogical = target => extendSel(target, range => byCharLogical(target.state, range, true)); 925/** 926Move the selection head one character backward by logical string 927index order. 928*/ 929const selectCharBackwardLogical = target => extendSel(target, range => byCharLogical(target.state, range, false)); 930function selectByGroup(view, forward) { 931 return extendSel(view, range => view.moveByGroup(range, forward)); 932} 933/** 934Move the selection head one [group](https://codemirror.net/6/docs/ref/#commands.cursorGroupLeft) to 935the left. 936*/ 937const selectGroupLeft = view => selectByGroup(view, !ltrAtCursor(view)); 938/** 939Move the selection head one group to the right. 940*/ 941const selectGroupRight = view => selectByGroup(view, ltrAtCursor(view)); 942/** 943Move the selection head one group forward. 944*/ 945const selectGroupForward = view => selectByGroup(view, true); 946/** 947Move the selection head one group backward. 948*/ 949const selectGroupBackward = view => selectByGroup(view, false); 950/** 951Move the selection head one group forward in the default Windows 952style, skipping to the start of the next group. 953*/ 954const selectGroupForwardWin = view => { 955 return extendSel(view, range => view.moveByChar(range, true, start => toGroupStart(view, range.head, start))); 956}; 957function selectBySubword(view, forward) { 958 return extendSel(view, range => moveBySubword(view, range, forward)); 959} 960/** 961Move the selection head one group or camel-case subword forward. 962*/ 963const selectSubwordForward = view => selectBySubword(view, true); 964/** 965Move the selection head one group or subword backward. 966*/ 967const selectSubwordBackward = view => selectBySubword(view, false); 968/** 969Move the selection head over the next syntactic element to the left. 970*/ 971const selectSyntaxLeft = view => extendSel(view, range => moveBySyntax(view.state, range, !ltrAtCursor(view))); 972/** 973Move the selection head over the next syntactic element to the right. 974*/ 975const selectSyntaxRight = view => extendSel(view, range => moveBySyntax(view.state, range, ltrAtCursor(view))); 976function selectByLine(view, forward) { 977 return extendSel(view, range => view.moveVertically(range, forward)); 978} 979/** 980Move the selection head one line up. 981*/ 982const selectLineUp = view => selectByLine(view, false); 983/** 984Move the selection head one line down. 985*/ 986const selectLineDown = view => selectByLine(view, true); 987function selectByPage(view, forward) { 988 return extendSel(view, range => view.moveVertically(range, forward, pageInfo(view).height)); 989} 990/** 991Move the selection head one page up. 992*/ 993const selectPageUp = view => selectByPage(view, false); 994/** 995Move the selection head one page down. 996*/ 997const selectPageDown = view => selectByPage(view, true); 998/** 999Move the selection head to the next line boundary. 1000*/ 1001const selectLineBoundaryForward = view => extendSel(view, range => moveByLineBoundary(view, range, true)); 1002/** 1003Move the selection head to the previous line boundary. 1004*/ 1005const selectLineBoundaryBackward = view => extendSel(view, range => moveByLineBoundary(view, range, false)); 1006/** 1007Move the selection head one line boundary to the left. 1008*/ 1009const selectLineBoundaryLeft = view => extendSel(view, range => moveByLineBoundary(view, range, !ltrAtCursor(view))); 1010/** 1011Move the selection head one line boundary to the right. 1012*/ 1013const selectLineBoundaryRight = view => extendSel(view, range => moveByLineBoundary(view, range, ltrAtCursor(view))); 1014/** 1015Move the selection head to the start of the line. 1016*/ 1017const selectLineStart = view => extendSel(view, range => EditorSelection.cursor(view.lineBlockAt(range.head).from)); 1018/** 1019Move the selection head to the end of the line. 1020*/ 1021const selectLineEnd = view => extendSel(view, range => EditorSelection.cursor(view.lineBlockAt(range.head).to)); 1022/** 1023Move the selection to the start of the document. 1024*/ 1025const cursorDocStart = ({ state, dispatch }) => { 1026 dispatch(setSel(state, { anchor: 0 })); 1027 return true; 1028}; 1029/** 1030Move the selection to the end of the document. 1031*/ 1032const cursorDocEnd = ({ state, dispatch }) => { 1033 dispatch(setSel(state, { anchor: state.doc.length })); 1034 return true; 1035}; 1036/** 1037Move the selection head to the start of the document. 1038*/ 1039const selectDocStart = ({ state, dispatch }) => { 1040 dispatch(setSel(state, { anchor: state.selection.main.anchor, head: 0 })); 1041 return true; 1042}; 1043/** 1044Move the selection head to the end of the document. 1045*/ 1046const selectDocEnd = ({ state, dispatch }) => { 1047 dispatch(setSel(state, { anchor: state.selection.main.anchor, head: state.doc.length })); 1048 return true; 1049}; 1050/** 1051Select the entire document. 1052*/ 1053const selectAll = ({ state, dispatch }) => { 1054 dispatch(state.update({ selection: { anchor: 0, head: state.doc.length }, userEvent: "select" })); 1055 return true; 1056}; 1057/** 1058Expand the selection to cover entire lines. 1059*/ 1060const selectLine = ({ state, dispatch }) => { 1061 let ranges = selectedLineBlocks(state).map(({ from, to }) => EditorSelection.range(from, Math.min(to + 1, state.doc.length))); 1062 dispatch(state.update({ selection: EditorSelection.create(ranges), userEvent: "select" })); 1063 return true; 1064}; 1065/** 1066Select the next syntactic construct that is larger than the 1067selection. Note that this will only work insofar as the language 1068[provider](https://codemirror.net/6/docs/ref/#language.language) you use builds up a full 1069syntax tree. 1070*/ 1071const selectParentSyntax = ({ state, dispatch }) => { 1072 let selection = updateSel(state.selection, range => { 1073 let tree = syntaxTree(state), stack = tree.resolveStack(range.from, 1); 1074 if (range.empty) { 1075 let stackBefore = tree.resolveStack(range.from, -1); 1076 if (stackBefore.node.from >= stack.node.from && stackBefore.node.to <= stack.node.to) 1077 stack = stackBefore; 1078 } 1079 for (let cur = stack; cur; cur = cur.next) { 1080 let { node } = cur; 1081 if (((node.from < range.from && node.to >= range.to) || 1082 (node.to > range.to && node.from <= range.from)) && 1083 cur.next) 1084 return EditorSelection.range(node.to, node.from); 1085 } 1086 return range; 1087 }); 1088 if (selection.eq(state.selection)) 1089 return false; 1090 dispatch(setSel(state, selection)); 1091 return true; 1092}; 1093function addCursorVertically(view, forward) { 1094 let { state } = view, sel = state.selection, ranges = state.selection.ranges.slice(); 1095 for (let range of state.selection.ranges) { 1096 let line = state.doc.lineAt(range.head); 1097 if (forward ? line.to < view.state.doc.length : line.from > 0) 1098 for (let cur = range;;) { 1099 let next = view.moveVertically(cur, forward); 1100 if (next.head < line.from || next.head > line.to) { 1101 if (!ranges.some(r => r.head == next.head)) 1102 ranges.push(next); 1103 break; 1104 } 1105 else if (next.head == cur.head) { 1106 break; 1107 } 1108 else { 1109 cur = next; 1110 } 1111 } 1112 } 1113 if (ranges.length == sel.ranges.length) 1114 return false; 1115 view.dispatch(setSel(state, EditorSelection.create(ranges, ranges.length - 1))); 1116 return true; 1117} 1118/** 1119Expand the selection by adding a cursor above the heads of 1120currently selected ranges. 1121*/ 1122const addCursorAbove = view => addCursorVertically(view, false); 1123/** 1124Expand the selection by adding a cursor below the heads of 1125currently selected ranges. 1126*/ 1127const addCursorBelow = view => addCursorVertically(view, true); 1128/** 1129Simplify the current selection. When multiple ranges are selected, 1130reduce it to its main range. Otherwise, if the selection is 1131non-empty, convert it to a cursor selection. 1132*/ 1133const simplifySelection = ({ state, dispatch }) => { 1134 let cur = state.selection, selection = null; 1135 if (cur.ranges.length > 1) 1136 selection = EditorSelection.create([cur.main]); 1137 else if (!cur.main.empty) 1138 selection = EditorSelection.create([EditorSelection.cursor(cur.main.head)]); 1139 if (!selection) 1140 return false; 1141 dispatch(setSel(state, selection)); 1142 return true; 1143}; 1144function deleteBy(target, by) { 1145 if (target.state.readOnly) 1146 return false; 1147 let event = "delete.selection", { state } = target; 1148 let changes = state.changeByRange(range => { 1149 let { from, to } = range; 1150 if (from == to) { 1151 let towards = by(range); 1152 if (towards < from) { 1153 event = "delete.backward"; 1154 towards = skipAtomic(target, towards, false); 1155 } 1156 else if (towards > from) { 1157 event = "delete.forward"; 1158 towards = skipAtomic(target, towards, true); 1159 } 1160 from = Math.min(from, towards); 1161 to = Math.max(to, towards); 1162 } 1163 else { 1164 from = skipAtomic(target, from, false); 1165 to = skipAtomic(target, to, true); 1166 } 1167 return from == to ? { range } : { changes: { from, to }, range: EditorSelection.cursor(from, from < range.head ? -1 : 1) }; 1168 }); 1169 if (changes.changes.empty) 1170 return false; 1171 target.dispatch(state.update(changes, { 1172 scrollIntoView: true, 1173 userEvent: event, 1174 effects: event == "delete.selection" ? EditorView.announce.of(state.phrase("Selection deleted")) : undefined 1175 })); 1176 return true; 1177} 1178function skipAtomic(target, pos, forward) { 1179 if (target instanceof EditorView) 1180 for (let ranges of target.state.facet(EditorView.atomicRanges).map(f => f(target))) 1181 ranges.between(pos, pos, (from, to) => { 1182 if (from < pos && to > pos) 1183 pos = forward ? to : from; 1184 }); 1185 return pos; 1186} 1187const deleteByChar = (target, forward, byIndentUnit) => deleteBy(target, range => { 1188 let pos = range.from, { state } = target, line = state.doc.lineAt(pos), before, targetPos; 1189 if (byIndentUnit && !forward && pos > line.from && pos < line.from + 200 && 1190 !/[^ \t]/.test(before = line.text.slice(0, pos - line.from))) { 1191 if (before[before.length - 1] == "\t") 1192 return pos - 1; 1193 let col = countColumn(before, state.tabSize), drop = col % getIndentUnit(state) || getIndentUnit(state); 1194 for (let i = 0; i < drop && before[before.length - 1 - i] == " "; i++) 1195 pos--; 1196 targetPos = pos; 1197 } 1198 else { 1199 targetPos = findClusterBreak(line.text, pos - line.from, forward, forward) + line.from; 1200 if (targetPos == pos && line.number != (forward ? state.doc.lines : 1)) 1201 targetPos += forward ? 1 : -1; 1202 else if (!forward && /[\ufe00-\ufe0f]/.test(line.text.slice(targetPos - line.from, pos - line.from))) 1203 targetPos = findClusterBreak(line.text, targetPos - line.from, false, false) + line.from; 1204 } 1205 return targetPos; 1206}); 1207/** 1208Delete the selection, or, for cursor selections, the character or 1209indentation unit before the cursor. 1210*/ 1211const deleteCharBackward = view => deleteByChar(view, false, true); 1212/** 1213Delete the selection or the character before the cursor. Does not 1214implement any extended behavior like deleting whole indentation 1215units in one go. 1216*/ 1217const deleteCharBackwardStrict = view => deleteByChar(view, false, false); 1218/** 1219Delete the selection or the character after the cursor. 1220*/ 1221const deleteCharForward = view => deleteByChar(view, true, false); 1222const deleteByGroup = (target, forward) => deleteBy(target, range => { 1223 let pos = range.head, { state } = target, line = state.doc.lineAt(pos); 1224 let categorize = state.charCategorizer(pos); 1225 for (let cat = null;;) { 1226 if (pos == (forward ? line.to : line.from)) { 1227 if (pos == range.head && line.number != (forward ? state.doc.lines : 1)) 1228 pos += forward ? 1 : -1; 1229 break; 1230 } 1231 let next = findClusterBreak(line.text, pos - line.from, forward) + line.from; 1232 let nextChar = line.text.slice(Math.min(pos, next) - line.from, Math.max(pos, next) - line.from); 1233 let nextCat = categorize(nextChar); 1234 if (cat != null && nextCat != cat) 1235 break; 1236 if (nextChar != " " || pos != range.head) 1237 cat = nextCat; 1238 pos = next; 1239 } 1240 return pos; 1241}); 1242/** 1243Delete the selection or backward until the end of the next 1244[group](https://codemirror.net/6/docs/ref/#view.EditorView.moveByGroup), only skipping groups of 1245whitespace when they consist of a single space. 1246*/ 1247const deleteGroupBackward = target => deleteByGroup(target, false); 1248/** 1249Delete the selection or forward until the end of the next group. 1250*/ 1251const deleteGroupForward = target => deleteByGroup(target, true); 1252/** 1253Variant of [`deleteGroupForward`](https://codemirror.net/6/docs/ref/#commands.deleteGroupForward) 1254that uses the Windows convention of also deleting the whitespace 1255after a word. 1256*/ 1257const deleteGroupForwardWin = view => deleteBy(view, range => view.moveByChar(range, true, start => toGroupStart(view, range.head, start)).head); 1258/** 1259Delete the selection, or, if it is a cursor selection, delete to 1260the end of the line. If the cursor is directly at the end of the 1261line, delete the line break after it. 1262*/ 1263const deleteToLineEnd = view => deleteBy(view, range => { 1264 let lineEnd = view.lineBlockAt(range.head).to; 1265 return range.head < lineEnd ? lineEnd : Math.min(view.state.doc.length, range.head + 1); 1266}); 1267/** 1268Delete the selection, or, if it is a cursor selection, delete to 1269the start of the line. If the cursor is directly at the start of the 1270line, delete the line break before it. 1271*/ 1272const deleteToLineStart = view => deleteBy(view, range => { 1273 let lineStart = view.lineBlockAt(range.head).from; 1274 return range.head > lineStart ? lineStart : Math.max(0, range.head - 1); 1275}); 1276/** 1277Delete the selection, or, if it is a cursor selection, delete to 1278the start of the line or the next line wrap before the cursor. 1279*/ 1280const deleteLineBoundaryBackward = view => deleteBy(view, range => { 1281 let lineStart = view.moveToLineBoundary(range, false).head; 1282 return range.head > lineStart ? lineStart : Math.max(0, range.head - 1); 1283}); 1284/** 1285Delete the selection, or, if it is a cursor selection, delete to 1286the end of the line or the next line wrap after the cursor. 1287*/ 1288const deleteLineBoundaryForward = view => deleteBy(view, range => { 1289 let lineStart = view.moveToLineBoundary(range, true).head; 1290 return range.head < lineStart ? lineStart : Math.min(view.state.doc.length, range.head + 1); 1291}); 1292/** 1293Delete all whitespace directly before a line end from the 1294document. 1295*/ 1296const deleteTrailingWhitespace = ({ state, dispatch }) => { 1297 if (state.readOnly) 1298 return false; 1299 let changes = []; 1300 for (let pos = 0, prev = "", iter = state.doc.iter();;) { 1301 iter.next(); 1302 if (iter.lineBreak || iter.done) { 1303 let trailing = prev.search(/\s+$/); 1304 if (trailing > -1) 1305 changes.push({ from: pos - (prev.length - trailing), to: pos }); 1306 if (iter.done) 1307 break; 1308 prev = ""; 1309 } 1310 else { 1311 prev = iter.value; 1312 } 1313 pos += iter.value.length; 1314 } 1315 if (!changes.length) 1316 return false; 1317 dispatch(state.update({ changes, userEvent: "delete" })); 1318 return true; 1319}; 1320/** 1321Replace each selection range with a line break, leaving the cursor 1322on the line before the break. 1323*/ 1324const splitLine = ({ state, dispatch }) => { 1325 if (state.readOnly) 1326 return false; 1327 let changes = state.changeByRange(range => { 1328 return { changes: { from: range.from, to: range.to, insert: Text.of(["", ""]) }, 1329 range: EditorSelection.cursor(range.from) }; 1330 }); 1331 dispatch(state.update(changes, { scrollIntoView: true, userEvent: "input" })); 1332 return true; 1333}; 1334/** 1335Flip the characters before and after the cursor(s). 1336*/ 1337const transposeChars = ({ state, dispatch }) => { 1338 if (state.readOnly) 1339 return false; 1340 let changes = state.changeByRange(range => { 1341 if (!range.empty || range.from == 0 || range.from == state.doc.length) 1342 return { range }; 1343 let pos = range.from, line = state.doc.lineAt(pos); 1344 let from = pos == line.from ? pos - 1 : findClusterBreak(line.text, pos - line.from, false) + line.from; 1345 let to = pos == line.to ? pos + 1 : findClusterBreak(line.text, pos - line.from, true) + line.from; 1346 return { changes: { from, to, insert: state.doc.slice(pos, to).append(state.doc.slice(from, pos)) }, 1347 range: EditorSelection.cursor(to) }; 1348 }); 1349 if (changes.changes.empty) 1350 return false; 1351 dispatch(state.update(changes, { scrollIntoView: true, userEvent: "move.character" })); 1352 return true; 1353}; 1354function selectedLineBlocks(state) { 1355 let blocks = [], upto = -1; 1356 for (let range of state.selection.ranges) { 1357 let startLine = state.doc.lineAt(range.from), endLine = state.doc.lineAt(range.to); 1358 if (!range.empty && range.to == endLine.from) 1359 endLine = state.doc.lineAt(range.to - 1); 1360 if (upto >= startLine.number) { 1361 let prev = blocks[blocks.length - 1]; 1362 prev.to = endLine.to; 1363 prev.ranges.push(range); 1364 } 1365 else { 1366 blocks.push({ from: startLine.from, to: endLine.to, ranges: [range] }); 1367 } 1368 upto = endLine.number + 1; 1369 } 1370 return blocks; 1371} 1372function moveLine(state, dispatch, forward) { 1373 if (state.readOnly) 1374 return false; 1375 let changes = [], ranges = []; 1376 for (let block of selectedLineBlocks(state)) { 1377 if (forward ? block.to == state.doc.length : block.from == 0) 1378 continue; 1379 let nextLine = state.doc.lineAt(forward ? block.to + 1 : block.from - 1); 1380 let size = nextLine.length + 1; 1381 if (forward) { 1382 changes.push({ from: block.to, to: nextLine.to }, { from: block.from, insert: nextLine.text + state.lineBreak }); 1383 for (let r of block.ranges) 1384 ranges.push(EditorSelection.range(Math.min(state.doc.length, r.anchor + size), Math.min(state.doc.length, r.head + size))); 1385 } 1386 else { 1387 changes.push({ from: nextLine.from, to: block.from }, { from: block.to, insert: state.lineBreak + nextLine.text }); 1388 for (let r of block.ranges) 1389 ranges.push(EditorSelection.range(r.anchor - size, r.head - size)); 1390 } 1391 } 1392 if (!changes.length) 1393 return false; 1394 dispatch(state.update({ 1395 changes, 1396 scrollIntoView: true, 1397 selection: EditorSelection.create(ranges, state.selection.mainIndex), 1398 userEvent: "move.line" 1399 })); 1400 return true; 1401} 1402/** 1403Move the selected lines up one line. 1404*/ 1405const moveLineUp = ({ state, dispatch }) => moveLine(state, dispatch, false); 1406/** 1407Move the selected lines down one line. 1408*/ 1409const moveLineDown = ({ state, dispatch }) => moveLine(state, dispatch, true); 1410function copyLine(state, dispatch, forward) { 1411 if (state.readOnly) 1412 return false; 1413 let changes = []; 1414 for (let block of selectedLineBlocks(state)) { 1415 if (forward) 1416 changes.push({ from: block.from, insert: state.doc.slice(block.from, block.to) + state.lineBreak }); 1417 else 1418 changes.push({ from: block.to, insert: state.lineBreak + state.doc.slice(block.from, block.to) }); 1419 } 1420 let changeSet = state.changes(changes); 1421 dispatch(state.update({ 1422 changes: changeSet, 1423 selection: state.selection.map(changeSet, forward ? 1 : -1), 1424 scrollIntoView: true, 1425 userEvent: "input.copyline" 1426 })); 1427 return true; 1428} 1429/** 1430Create a copy of the selected lines. Keep the selection in the top copy. 1431*/ 1432const copyLineUp = ({ state, dispatch }) => copyLine(state, dispatch, false); 1433/** 1434Create a copy of the selected lines. Keep the selection in the bottom copy. 1435*/ 1436const copyLineDown = ({ state, dispatch }) => copyLine(state, dispatch, true); 1437/** 1438Delete selected lines. 1439*/ 1440const deleteLine = view => { 1441 if (view.state.readOnly) 1442 return false; 1443 let { state } = view, changes = state.changes(selectedLineBlocks(state).map(({ from, to }) => { 1444 if (from > 0) 1445 from--; 1446 else if (to < state.doc.length) 1447 to++; 1448 return { from, to }; 1449 })); 1450 let selection = updateSel(state.selection, range => { 1451 let dist = undefined; 1452 if (view.lineWrapping) { 1453 let block = view.lineBlockAt(range.head), pos = view.coordsAtPos(range.head, range.assoc || 1); 1454 if (pos) 1455 dist = (block.bottom + view.documentTop) - pos.bottom + view.defaultLineHeight / 2; 1456 } 1457 return view.moveVertically(range, true, dist); 1458 }).map(changes); 1459 view.dispatch({ changes, selection, scrollIntoView: true, userEvent: "delete.line" }); 1460 return true; 1461}; 1462/** 1463Replace the selection with a newline. 1464*/ 1465const insertNewline = ({ state, dispatch }) => { 1466 dispatch(state.update(state.replaceSelection(state.lineBreak), { scrollIntoView: true, userEvent: "input" })); 1467 return true; 1468}; 1469/** 1470Replace the selection with a newline and the same amount of 1471indentation as the line above. 1472*/ 1473const insertNewlineKeepIndent = ({ state, dispatch }) => { 1474 dispatch(state.update(state.changeByRange(range => { 1475 let indent = /^\s*/.exec(state.doc.lineAt(range.from).text)[0]; 1476 return { 1477 changes: { from: range.from, to: range.to, insert: state.lineBreak + indent }, 1478 range: EditorSelection.cursor(range.from + indent.length + 1) 1479 }; 1480 }), { scrollIntoView: true, userEvent: "input" })); 1481 return true; 1482}; 1483function isBetweenBrackets(state, pos) { 1484 if (/\(\)|\[\]|\{\}/.test(state.sliceDoc(pos - 1, pos + 1))) 1485 return { from: pos, to: pos }; 1486 let context = syntaxTree(state).resolveInner(pos); 1487 let before = context.childBefore(pos), after = context.childAfter(pos), closedBy; 1488 if (before && after && before.to <= pos && after.from >= pos && 1489 (closedBy = before.type.prop(NodeProp.closedBy)) && closedBy.indexOf(after.name) > -1 && 1490 state.doc.lineAt(before.to).from == state.doc.lineAt(after.from).from && 1491 !/\S/.test(state.sliceDoc(before.to, after.from))) 1492 return { from: before.to, to: after.from }; 1493 return null; 1494} 1495/** 1496Replace the selection with a newline and indent the newly created 1497line(s). If the current line consists only of whitespace, this 1498will also delete that whitespace. When the cursor is between 1499matching brackets, an additional newline will be inserted after 1500the cursor. 1501*/ 1502const insertNewlineAndIndent = /*@__PURE__*/newlineAndIndent(false); 1503/** 1504Create a blank, indented line below the current line. 1505*/ 1506const insertBlankLine = /*@__PURE__*/newlineAndIndent(true); 1507function newlineAndIndent(atEof) { 1508 return ({ state, dispatch }) => { 1509 if (state.readOnly) 1510 return false; 1511 let changes = state.changeByRange(range => { 1512 let { from, to } = range, line = state.doc.lineAt(from); 1513 let explode = !atEof && from == to && isBetweenBrackets(state, from); 1514 if (atEof) 1515 from = to = (to <= line.to ? line : state.doc.lineAt(to)).to; 1516 let cx = new IndentContext(state, { simulateBreak: from, simulateDoubleBreak: !!explode }); 1517 let indent = getIndentation(cx, from); 1518 if (indent == null) 1519 indent = countColumn(/^\s*/.exec(state.doc.lineAt(from).text)[0], state.tabSize); 1520 while (to < line.to && /\s/.test(line.text[to - line.from])) 1521 to++; 1522 if (explode) 1523 ({ from, to } = explode); 1524 else if (from > line.from && from < line.from + 100 && !/\S/.test(line.text.slice(0, from))) 1525 from = line.from; 1526 let insert = ["", indentString(state, indent)]; 1527 if (explode) 1528 insert.push(indentString(state, cx.lineIndent(line.from, -1))); 1529 return { changes: { from, to, insert: Text.of(insert) }, 1530 range: EditorSelection.cursor(from + 1 + insert[1].length) }; 1531 }); 1532 dispatch(state.update(changes, { scrollIntoView: true, userEvent: "input" })); 1533 return true; 1534 }; 1535} 1536function changeBySelectedLine(state, f) { 1537 let atLine = -1; 1538 return state.changeByRange(range => { 1539 let changes = []; 1540 for (let pos = range.from; pos <= range.to;) { 1541 let line = state.doc.lineAt(pos); 1542 if (line.number > atLine && (range.empty || range.to > line.from)) { 1543 f(line, changes, range); 1544 atLine = line.number; 1545 } 1546 pos = line.to + 1; 1547 } 1548 let changeSet = state.changes(changes); 1549 return { changes, 1550 range: EditorSelection.range(changeSet.mapPos(range.anchor, 1), changeSet.mapPos(range.head, 1)) }; 1551 }); 1552} 1553/** 1554Auto-indent the selected lines. This uses the [indentation service 1555facet](https://codemirror.net/6/docs/ref/#language.indentService) as source for auto-indent 1556information. 1557*/ 1558const indentSelection = ({ state, dispatch }) => { 1559 if (state.readOnly) 1560 return false; 1561 let updated = Object.create(null); 1562 let context = new IndentContext(state, { overrideIndentation: start => { 1563 let found = updated[start]; 1564 return found == null ? -1 : found; 1565 } }); 1566 let changes = changeBySelectedLine(state, (line, changes, range) => { 1567 let indent = getIndentation(context, line.from); 1568 if (indent == null) 1569 return; 1570 if (!/\S/.test(line.text)) 1571 indent = 0; 1572 let cur = /^\s*/.exec(line.text)[0]; 1573 let norm = indentString(state, indent); 1574 if (cur != norm || range.from < line.from + cur.length) { 1575 updated[line.from] = indent; 1576 changes.push({ from: line.from, to: line.from + cur.length, insert: norm }); 1577 } 1578 }); 1579 if (!changes.changes.empty) 1580 dispatch(state.update(changes, { userEvent: "indent" })); 1581 return true; 1582}; 1583/** 1584Add a [unit](https://codemirror.net/6/docs/ref/#language.indentUnit) of indentation to all selected 1585lines. 1586*/ 1587const indentMore = ({ state, dispatch }) => { 1588 if (state.readOnly) 1589 return false; 1590 dispatch(state.update(changeBySelectedLine(state, (line, changes) => { 1591 changes.push({ from: line.from, insert: state.facet(indentUnit) }); 1592 }), { userEvent: "input.indent" })); 1593 return true; 1594}; 1595/** 1596Remove a [unit](https://codemirror.net/6/docs/ref/#language.indentUnit) of indentation from all 1597selected lines. 1598*/ 1599const indentLess = ({ state, dispatch }) => { 1600 if (state.readOnly) 1601 return false; 1602 dispatch(state.update(changeBySelectedLine(state, (line, changes) => { 1603 let space = /^\s*/.exec(line.text)[0]; 1604 if (!space) 1605 return; 1606 let col = countColumn(space, state.tabSize), keep = 0; 1607 let insert = indentString(state, Math.max(0, col - getIndentUnit(state))); 1608 while (keep < space.length && keep < insert.length && space.charCodeAt(keep) == insert.charCodeAt(keep)) 1609 keep++; 1610 changes.push({ from: line.from + keep, to: line.from + space.length, insert: insert.slice(keep) }); 1611 }), { userEvent: "delete.dedent" })); 1612 return true; 1613}; 1614/** 1615Enables or disables 1616[tab-focus mode](https://codemirror.net/6/docs/ref/#view.EditorView.setTabFocusMode). While on, this 1617prevents the editor's key bindings from capturing Tab or 1618Shift-Tab, making it possible for the user to move focus out of 1619the editor with the keyboard. 1620*/ 1621const toggleTabFocusMode = view => { 1622 view.setTabFocusMode(); 1623 return true; 1624}; 1625/** 1626Temporarily enables [tab-focus 1627mode](https://codemirror.net/6/docs/ref/#view.EditorView.setTabFocusMode) for two seconds or until 1628another key is pressed. 1629*/ 1630const temporarilySetTabFocusMode = view => { 1631 view.setTabFocusMode(2000); 1632 return true; 1633}; 1634/** 1635Insert a tab character at the cursor or, if something is selected, 1636use [`indentMore`](https://codemirror.net/6/docs/ref/#commands.indentMore) to indent the entire 1637selection. 1638*/ 1639const insertTab = ({ state, dispatch }) => { 1640 if (state.selection.ranges.some(r => !r.empty)) 1641 return indentMore({ state, dispatch }); 1642 dispatch(state.update(state.replaceSelection("\t"), { scrollIntoView: true, userEvent: "input" })); 1643 return true; 1644}; 1645/** 1646Array of key bindings containing the Emacs-style bindings that are 1647available on macOS by default. 1648 1649 - Ctrl-b: [`cursorCharLeft`](https://codemirror.net/6/docs/ref/#commands.cursorCharLeft) ([`selectCharLeft`](https://codemirror.net/6/docs/ref/#commands.selectCharLeft) with Shift) 1650 - Ctrl-f: [`cursorCharRight`](https://codemirror.net/6/docs/ref/#commands.cursorCharRight) ([`selectCharRight`](https://codemirror.net/6/docs/ref/#commands.selectCharRight) with Shift) 1651 - Ctrl-p: [`cursorLineUp`](https://codemirror.net/6/docs/ref/#commands.cursorLineUp) ([`selectLineUp`](https://codemirror.net/6/docs/ref/#commands.selectLineUp) with Shift) 1652 - Ctrl-n: [`cursorLineDown`](https://codemirror.net/6/docs/ref/#commands.cursorLineDown) ([`selectLineDown`](https://codemirror.net/6/docs/ref/#commands.selectLineDown) with Shift) 1653 - Ctrl-a: [`cursorLineStart`](https://codemirror.net/6/docs/ref/#commands.cursorLineStart) ([`selectLineStart`](https://codemirror.net/6/docs/ref/#commands.selectLineStart) with Shift) 1654 - Ctrl-e: [`cursorLineEnd`](https://codemirror.net/6/docs/ref/#commands.cursorLineEnd) ([`selectLineEnd`](https://codemirror.net/6/docs/ref/#commands.selectLineEnd) with Shift) 1655 - Ctrl-d: [`deleteCharForward`](https://codemirror.net/6/docs/ref/#commands.deleteCharForward) 1656 - Ctrl-h: [`deleteCharBackward`](https://codemirror.net/6/docs/ref/#commands.deleteCharBackward) 1657 - Ctrl-k: [`deleteToLineEnd`](https://codemirror.net/6/docs/ref/#commands.deleteToLineEnd) 1658 - Ctrl-Alt-h: [`deleteGroupBackward`](https://codemirror.net/6/docs/ref/#commands.deleteGroupBackward) 1659 - Ctrl-o: [`splitLine`](https://codemirror.net/6/docs/ref/#commands.splitLine) 1660 - Ctrl-t: [`transposeChars`](https://codemirror.net/6/docs/ref/#commands.transposeChars) 1661 - Ctrl-v: [`cursorPageDown`](https://codemirror.net/6/docs/ref/#commands.cursorPageDown) 1662 - Alt-v: [`cursorPageUp`](https://codemirror.net/6/docs/ref/#commands.cursorPageUp) 1663*/ 1664const emacsStyleKeymap = [ 1665 { key: "Ctrl-b", run: cursorCharLeft, shift: selectCharLeft, preventDefault: true }, 1666 { key: "Ctrl-f", run: cursorCharRight, shift: selectCharRight }, 1667 { key: "Ctrl-p", run: cursorLineUp, shift: selectLineUp }, 1668 { key: "Ctrl-n", run: cursorLineDown, shift: selectLineDown }, 1669 { key: "Ctrl-a", run: cursorLineStart, shift: selectLineStart }, 1670 { key: "Ctrl-e", run: cursorLineEnd, shift: selectLineEnd }, 1671 { key: "Ctrl-d", run: deleteCharForward }, 1672 { key: "Ctrl-h", run: deleteCharBackward }, 1673 { key: "Ctrl-k", run: deleteToLineEnd }, 1674 { key: "Ctrl-Alt-h", run: deleteGroupBackward }, 1675 { key: "Ctrl-o", run: splitLine }, 1676 { key: "Ctrl-t", run: transposeChars }, 1677 { key: "Ctrl-v", run: cursorPageDown }, 1678]; 1679/** 1680An array of key bindings closely sticking to platform-standard or 1681widely used bindings. (This includes the bindings from 1682[`emacsStyleKeymap`](https://codemirror.net/6/docs/ref/#commands.emacsStyleKeymap), with their `key` 1683property changed to `mac`.) 1684 1685 - ArrowLeft: [`cursorCharLeft`](https://codemirror.net/6/docs/ref/#commands.cursorCharLeft) ([`selectCharLeft`](https://codemirror.net/6/docs/ref/#commands.selectCharLeft) with Shift) 1686 - ArrowRight: [`cursorCharRight`](https://codemirror.net/6/docs/ref/#commands.cursorCharRight) ([`selectCharRight`](https://codemirror.net/6/docs/ref/#commands.selectCharRight) with Shift) 1687 - Ctrl-ArrowLeft (Alt-ArrowLeft on macOS): [`cursorGroupLeft`](https://codemirror.net/6/docs/ref/#commands.cursorGroupLeft) ([`selectGroupLeft`](https://codemirror.net/6/docs/ref/#commands.selectGroupLeft) with Shift) 1688 - Ctrl-ArrowRight (Alt-ArrowRight on macOS): [`cursorGroupRight`](https://codemirror.net/6/docs/ref/#commands.cursorGroupRight) ([`selectGroupRight`](https://codemirror.net/6/docs/ref/#commands.selectGroupRight) with Shift) 1689 - Cmd-ArrowLeft (on macOS): [`cursorLineStart`](https://codemirror.net/6/docs/ref/#commands.cursorLineStart) ([`selectLineStart`](https://codemirror.net/6/docs/ref/#commands.selectLineStart) with Shift) 1690 - Cmd-ArrowRight (on macOS): [`cursorLineEnd`](https://codemirror.net/6/docs/ref/#commands.cursorLineEnd) ([`selectLineEnd`](https://codemirror.net/6/docs/ref/#commands.selectLineEnd) with Shift) 1691 - ArrowUp: [`cursorLineUp`](https://codemirror.net/6/docs/ref/#commands.cursorLineUp) ([`selectLineUp`](https://codemirror.net/6/docs/ref/#commands.selectLineUp) with Shift) 1692 - ArrowDown: [`cursorLineDown`](https://codemirror.net/6/docs/ref/#commands.cursorLineDown) ([`selectLineDown`](https://codemirror.net/6/docs/ref/#commands.selectLineDown) with Shift) 1693 - Cmd-ArrowUp (on macOS): [`cursorDocStart`](https://codemirror.net/6/docs/ref/#commands.cursorDocStart) ([`selectDocStart`](https://codemirror.net/6/docs/ref/#commands.selectDocStart) with Shift) 1694 - Cmd-ArrowDown (on macOS): [`cursorDocEnd`](https://codemirror.net/6/docs/ref/#commands.cursorDocEnd) ([`selectDocEnd`](https://codemirror.net/6/docs/ref/#commands.selectDocEnd) with Shift) 1695 - Ctrl-ArrowUp (on macOS): [`cursorPageUp`](https://codemirror.net/6/docs/ref/#commands.cursorPageUp) ([`selectPageUp`](https://codemirror.net/6/docs/ref/#commands.selectPageUp) with Shift) 1696 - Ctrl-ArrowDown (on macOS): [`cursorPageDown`](https://codemirror.net/6/docs/ref/#commands.cursorPageDown) ([`selectPageDown`](https://codemirror.net/6/docs/ref/#commands.selectPageDown) with Shift) 1697 - PageUp: [`cursorPageUp`](https://codemirror.net/6/docs/ref/#commands.cursorPageUp) ([`selectPageUp`](https://codemirror.net/6/docs/ref/#commands.selectPageUp) with Shift) 1698 - PageDown: [`cursorPageDown`](https://codemirror.net/6/docs/ref/#commands.cursorPageDown) ([`selectPageDown`](https://codemirror.net/6/docs/ref/#commands.selectPageDown) with Shift) 1699 - Home: [`cursorLineBoundaryBackward`](https://codemirror.net/6/docs/ref/#commands.cursorLineBoundaryBackward) ([`selectLineBoundaryBackward`](https://codemirror.net/6/docs/ref/#commands.selectLineBoundaryBackward) with Shift) 1700 - End: [`cursorLineBoundaryForward`](https://codemirror.net/6/docs/ref/#commands.cursorLineBoundaryForward) ([`selectLineBoundaryForward`](https://codemirror.net/6/docs/ref/#commands.selectLineBoundaryForward) with Shift) 1701 - Ctrl-Home (Cmd-Home on macOS): [`cursorDocStart`](https://codemirror.net/6/docs/ref/#commands.cursorDocStart) ([`selectDocStart`](https://codemirror.net/6/docs/ref/#commands.selectDocStart) with Shift) 1702 - Ctrl-End (Cmd-Home on macOS): [`cursorDocEnd`](https://codemirror.net/6/docs/ref/#commands.cursorDocEnd) ([`selectDocEnd`](https://codemirror.net/6/docs/ref/#commands.selectDocEnd) with Shift) 1703 - Enter and Shift-Enter: [`insertNewlineAndIndent`](https://codemirror.net/6/docs/ref/#commands.insertNewlineAndIndent) 1704 - Ctrl-a (Cmd-a on macOS): [`selectAll`](https://codemirror.net/6/docs/ref/#commands.selectAll) 1705 - Backspace: [`deleteCharBackward`](https://codemirror.net/6/docs/ref/#commands.deleteCharBackward) 1706 - Delete: [`deleteCharForward`](https://codemirror.net/6/docs/ref/#commands.deleteCharForward) 1707 - Ctrl-Backspace (Alt-Backspace on macOS): [`deleteGroupBackward`](https://codemirror.net/6/docs/ref/#commands.deleteGroupBackward) 1708 - Ctrl-Delete (Alt-Delete on macOS): [`deleteGroupForward`](https://codemirror.net/6/docs/ref/#commands.deleteGroupForward) 1709 - Cmd-Backspace (macOS): [`deleteLineBoundaryBackward`](https://codemirror.net/6/docs/ref/#commands.deleteLineBoundaryBackward). 1710 - Cmd-Delete (macOS): [`deleteLineBoundaryForward`](https://codemirror.net/6/docs/ref/#commands.deleteLineBoundaryForward). 1711*/ 1712const standardKeymap = /*@__PURE__*/[ 1713 { key: "ArrowLeft", run: cursorCharLeft, shift: selectCharLeft, preventDefault: true }, 1714 { key: "Mod-ArrowLeft", mac: "Alt-ArrowLeft", run: cursorGroupLeft, shift: selectGroupLeft, preventDefault: true }, 1715 { mac: "Cmd-ArrowLeft", run: cursorLineBoundaryLeft, shift: selectLineBoundaryLeft, preventDefault: true }, 1716 { key: "ArrowRight", run: cursorCharRight, shift: selectCharRight, preventDefault: true }, 1717 { key: "Mod-ArrowRight", mac: "Alt-ArrowRight", run: cursorGroupRight, shift: selectGroupRight, preventDefault: true }, 1718 { mac: "Cmd-ArrowRight", run: cursorLineBoundaryRight, shift: selectLineBoundaryRight, preventDefault: true }, 1719 { key: "ArrowUp", run: cursorLineUp, shift: selectLineUp, preventDefault: true }, 1720 { mac: "Cmd-ArrowUp", run: cursorDocStart, shift: selectDocStart }, 1721 { mac: "Ctrl-ArrowUp", run: cursorPageUp, shift: selectPageUp }, 1722 { key: "ArrowDown", run: cursorLineDown, shift: selectLineDown, preventDefault: true }, 1723 { mac: "Cmd-ArrowDown", run: cursorDocEnd, shift: selectDocEnd }, 1724 { mac: "Ctrl-ArrowDown", run: cursorPageDown, shift: selectPageDown }, 1725 { key: "PageUp", run: cursorPageUp, shift: selectPageUp }, 1726 { key: "PageDown", run: cursorPageDown, shift: selectPageDown }, 1727 { key: "Home", run: cursorLineBoundaryBackward, shift: selectLineBoundaryBackward, preventDefault: true }, 1728 { key: "Mod-Home", run: cursorDocStart, shift: selectDocStart }, 1729 { key: "End", run: cursorLineBoundaryForward, shift: selectLineBoundaryForward, preventDefault: true }, 1730 { key: "Mod-End", run: cursorDocEnd, shift: selectDocEnd }, 1731 { key: "Enter", run: insertNewlineAndIndent, shift: insertNewlineAndIndent }, 1732 { key: "Mod-a", run: selectAll }, 1733 { key: "Backspace", run: deleteCharBackward, shift: deleteCharBackward, preventDefault: true }, 1734 { key: "Delete", run: deleteCharForward, preventDefault: true }, 1735 { key: "Mod-Backspace", mac: "Alt-Backspace", run: deleteGroupBackward, preventDefault: true }, 1736 { key: "Mod-Delete", mac: "Alt-Delete", run: deleteGroupForward, preventDefault: true }, 1737 { mac: "Mod-Backspace", run: deleteLineBoundaryBackward, preventDefault: true }, 1738 { mac: "Mod-Delete", run: deleteLineBoundaryForward, preventDefault: true } 1739].concat(/*@__PURE__*/emacsStyleKeymap.map(b => ({ mac: b.key, run: b.run, shift: b.shift }))); 1740/** 1741The default keymap. Includes all bindings from 1742[`standardKeymap`](https://codemirror.net/6/docs/ref/#commands.standardKeymap) plus the following: 1743 1744- Alt-ArrowLeft (Ctrl-ArrowLeft on macOS): [`cursorSyntaxLeft`](https://codemirror.net/6/docs/ref/#commands.cursorSyntaxLeft) ([`selectSyntaxLeft`](https://codemirror.net/6/docs/ref/#commands.selectSyntaxLeft) with Shift) 1745- Alt-ArrowRight (Ctrl-ArrowRight on macOS): [`cursorSyntaxRight`](https://codemirror.net/6/docs/ref/#commands.cursorSyntaxRight) ([`selectSyntaxRight`](https://codemirror.net/6/docs/ref/#commands.selectSyntaxRight) with Shift) 1746- Alt-ArrowUp: [`moveLineUp`](https://codemirror.net/6/docs/ref/#commands.moveLineUp) 1747- Alt-ArrowDown: [`moveLineDown`](https://codemirror.net/6/docs/ref/#commands.moveLineDown) 1748- Shift-Alt-ArrowUp: [`copyLineUp`](https://codemirror.net/6/docs/ref/#commands.copyLineUp) 1749- Shift-Alt-ArrowDown: [`copyLineDown`](https://codemirror.net/6/docs/ref/#commands.copyLineDown) 1750- Ctrl-Alt-ArrowUp (Cmd-Alt-ArrowUp on macOS): [`addCursorAbove`](https://codemirror.net/6/docs/ref/#commands.addCursorAbove). 1751- Ctrl-Alt-ArrowDown (Cmd-Alt-ArrowDown on macOS): [`addCursorBelow`](https://codemirror.net/6/docs/ref/#commands.addCursorBelow). 1752- Escape: [`simplifySelection`](https://codemirror.net/6/docs/ref/#commands.simplifySelection) 1753- Ctrl-Enter (Cmd-Enter on macOS): [`insertBlankLine`](https://codemirror.net/6/docs/ref/#commands.insertBlankLine) 1754- Alt-l (Ctrl-l on macOS): [`selectLine`](https://codemirror.net/6/docs/ref/#commands.selectLine) 1755- Ctrl-i (Cmd-i on macOS): [`selectParentSyntax`](https://codemirror.net/6/docs/ref/#commands.selectParentSyntax) 1756- Ctrl-[ (Cmd-[ on macOS): [`indentLess`](https://codemirror.net/6/docs/ref/#commands.indentLess) 1757- Ctrl-] (Cmd-] on macOS): [`indentMore`](https://codemirror.net/6/docs/ref/#commands.indentMore) 1758- Ctrl-Alt-\\ (Cmd-Alt-\\ on macOS): [`indentSelection`](https://codemirror.net/6/docs/ref/#commands.indentSelection) 1759- Shift-Ctrl-k (Shift-Cmd-k on macOS): [`deleteLine`](https://codemirror.net/6/docs/ref/#commands.deleteLine) 1760- Shift-Ctrl-\\ (Shift-Cmd-\\ on macOS): [`cursorMatchingBracket`](https://codemirror.net/6/docs/ref/#commands.cursorMatchingBracket) 1761- Ctrl-/ (Cmd-/ on macOS): [`toggleComment`](https://codemirror.net/6/docs/ref/#commands.toggleComment). 1762- Shift-Alt-a: [`toggleBlockComment`](https://codemirror.net/6/docs/ref/#commands.toggleBlockComment). 1763- Ctrl-m (Alt-Shift-m on macOS): [`toggleTabFocusMode`](https://codemirror.net/6/docs/ref/#commands.toggleTabFocusMode). 1764*/ 1765const defaultKeymap = /*@__PURE__*/[ 1766 { key: "Alt-ArrowLeft", mac: "Ctrl-ArrowLeft", run: cursorSyntaxLeft, shift: selectSyntaxLeft }, 1767 { key: "Alt-ArrowRight", mac: "Ctrl-ArrowRight", run: cursorSyntaxRight, shift: selectSyntaxRight }, 1768 { key: "Alt-ArrowUp", run: moveLineUp }, 1769 { key: "Shift-Alt-ArrowUp", run: copyLineUp }, 1770 { key: "Alt-ArrowDown", run: moveLineDown }, 1771 { key: "Shift-Alt-ArrowDown", run: copyLineDown }, 1772 { key: "Mod-Alt-ArrowUp", run: addCursorAbove }, 1773 { key: "Mod-Alt-ArrowDown", run: addCursorBelow }, 1774 { key: "Escape", run: simplifySelection }, 1775 { key: "Mod-Enter", run: insertBlankLine }, 1776 { key: "Alt-l", mac: "Ctrl-l", run: selectLine }, 1777 { key: "Mod-i", run: selectParentSyntax, preventDefault: true }, 1778 { key: "Mod-[", run: indentLess }, 1779 { key: "Mod-]", run: indentMore }, 1780 { key: "Mod-Alt-\\", run: indentSelection }, 1781 { key: "Shift-Mod-k", run: deleteLine }, 1782 { key: "Shift-Mod-\\", run: cursorMatchingBracket }, 1783 { key: "Mod-/", run: toggleComment }, 1784 { key: "Alt-A", run: toggleBlockComment }, 1785 { key: "Ctrl-m", mac: "Shift-Alt-m", run: toggleTabFocusMode }, 1786].concat(standardKeymap); 1787/** 1788A binding that binds Tab to [`indentMore`](https://codemirror.net/6/docs/ref/#commands.indentMore) and 1789Shift-Tab to [`indentLess`](https://codemirror.net/6/docs/ref/#commands.indentLess). 1790Please see the [Tab example](../../examples/tab/) before using 1791this. 1792*/ 1793const indentWithTab = { key: "Tab", run: indentMore, shift: indentLess }; 1794 1795export { addCursorAbove, addCursorBelow, blockComment, blockUncomment, copyLineDown, copyLineUp, cursorCharBackward, cursorCharBackwardLogical, cursorCharForward, cursorCharForwardLogical, cursorCharLeft, cursorCharRight, cursorDocEnd, cursorDocStart, cursorGroupBackward, cursorGroupForward, cursorGroupForwardWin, cursorGroupLeft, cursorGroupRight, cursorLineBoundaryBackward, cursorLineBoundaryForward, cursorLineBoundaryLeft, cursorLineBoundaryRight, cursorLineDown, cursorLineEnd, cursorLineStart, cursorLineUp, cursorMatchingBracket, cursorPageDown, cursorPageUp, cursorSubwordBackward, cursorSubwordForward, cursorSyntaxLeft, cursorSyntaxRight, defaultKeymap, deleteCharBackward, deleteCharBackwardStrict, deleteCharForward, deleteGroupBackward, deleteGroupForward, deleteGroupForwardWin, deleteLine, deleteLineBoundaryBackward, deleteLineBoundaryForward, deleteToLineEnd, deleteToLineStart, deleteTrailingWhitespace, emacsStyleKeymap, history, historyField, historyKeymap, indentLess, indentMore, indentSelection, indentWithTab, insertBlankLine, insertNewline, insertNewlineAndIndent, insertNewlineKeepIndent, insertTab, invertedEffects, isolateHistory, lineComment, lineUncomment, moveLineDown, moveLineUp, redo, redoDepth, redoSelection, selectAll, selectCharBackward, selectCharBackwardLogical, selectCharForward, selectCharForwardLogical, selectCharLeft, selectCharRight, selectDocEnd, selectDocStart, selectGroupBackward, selectGroupForward, selectGroupForwardWin, selectGroupLeft, selectGroupRight, selectLine, selectLineBoundaryBackward, selectLineBoundaryForward, selectLineBoundaryLeft, selectLineBoundaryRight, selectLineDown, selectLineEnd, selectLineStart, selectLineUp, selectMatchingBracket, selectPageDown, selectPageUp, selectParentSyntax, selectSubwordBackward, selectSubwordForward, selectSyntaxLeft, selectSyntaxRight, simplifySelection, splitLine, standardKeymap, temporarilySetTabFocusMode, toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment, toggleTabFocusMode, transposeChars, undo, undoDepth, undoSelection };