diff --git a/.gitignore b/.gitignore index 8e1b451..bb9546d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ node_modules package-lock.json -/dist +.yarn/install-state.gz /test/*.js /test/*.d.ts /test/*.d.ts.map diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..02d28e6 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,3 @@ +enableScripts: true + +nodeLinker: node-modules diff --git a/dev/cm5/package.json b/dev/cm5/package.json index e4ef59a..5538f2e 100644 --- a/dev/cm5/package.json +++ b/dev/cm5/package.json @@ -1,6 +1,6 @@ { "name": "cm5-vim", - "version": "0.0.6", + "version": "6.2.1", "description": "Vim keybindings for CodeMirror 5", "files": [ "vim.js", diff --git a/dev/cm5/yarn.lock b/dev/cm5/yarn.lock new file mode 100644 index 0000000..d05d946 --- /dev/null +++ b/dev/cm5/yarn.lock @@ -0,0 +1,21 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 9 + cacheKey: 10c0 + +"cm5-vim@workspace:.": + version: 0.0.0-use.local + resolution: "cm5-vim@workspace:." + dependencies: + codemirror: "npm:5.*" + languageName: unknown + linkType: soft + +"codemirror@npm:5.*": + version: 5.65.21 + resolution: "codemirror@npm:5.65.21" + checksum: 10c0/0884fc54ad32cbfa7605b23c8e9c0085d71856178f88448e6912335dddce321edab1326202b3778647658e7debdff07329f30e30b32f8fb4ba501582f798495a + languageName: node + linkType: hard diff --git a/dist/index.cjs b/dist/index.cjs new file mode 100644 index 0000000..8cceff0 --- /dev/null +++ b/dist/index.cjs @@ -0,0 +1,8502 @@ +'use strict'; + +var state = require('@codemirror/state'); +var language = require('@codemirror/language'); +var View = require('@codemirror/view'); +var search = require('@codemirror/search'); +var commands = require('@codemirror/commands'); + +function _interopNamespaceDefault(e) { + var n = Object.create(null); + if (e) { + Object.keys(e).forEach(function (k) { + if (k !== 'default') { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { return e[k]; } + }); + } + }); + } + n.default = e; + return Object.freeze(n); +} + +var View__namespace = /*#__PURE__*/_interopNamespaceDefault(View); + +//@ts-check + +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/LICENSE + +/** + * Supported keybindings: + * Too many to list. Refer to defaultKeymap below. + * + * Supported Ex commands: + * Refer to defaultExCommandMap below. + * + * Registers: unnamed, -, ., :, /, _, a-z, A-Z, 0-9 + * (Does not respect the special case for number registers when delete + * operator is made with these commands: %, (, ), , /, ?, n, N, {, } ) + * TODO: Implement the remaining registers. + * + * Marks: a-z, A-Z, and 0-9 + * TODO: Implement the remaining special marks. They have more complex + * behavior. + * + * Events: + * 'vim-mode-change' - raised on the editor anytime the current mode changes, + * Event object: {mode: "visual", subMode: "linewise"} + * + * Code structure: + * 1. Default keymap + * 2. Variable declarations and short basic helpers + * 3. Instance (External API) implementation + * 4. Internal state tracking objects (input state, counter) implementation + * and instantiation + * 5. Key handler (the main command dispatcher) implementation + * 6. Motion, operator, and action implementations + * 7. Helper functions for the key handler, motions, operators, and actions + * 8. Set up Vim to work as a keymap for CodeMirror. + * 9. Ex command implementations. + */ + +/** + * @typedef { import("./cm_adapter").CodeMirror } CodeMirror + * @typedef { import("./types").CodeMirrorV} CodeMirrorV + * @typedef { import("./types").Pos } Pos + * @typedef { import("./types").CM5Range } CM5Range + * @typedef { import("./types").vimState } vimState + * @typedef { import("./types").ExFn } ExFn + * @typedef { import("./types").MotionArgs } MotionArgs + * @typedef { import("./types").ActionArgs } ActionArgs + * @typedef { import("./types").OperatorArgs } OperatorArgs + * @typedef { import("./types").vimKey } vimKey + * @typedef { import("./types").InputStateInterface } InputStateInterface + */ + +/** @arg {typeof import("./cm_adapter").CodeMirror} CodeMirror */ +function initVim(CodeMirror) { + + var Pos = CodeMirror.Pos; + + /** @arg {CodeMirror} cm @arg {Pos} curStart @arg {Pos} curEnd */ + function updateSelectionForSurrogateCharacters(cm, curStart, curEnd) { + // start and character position when no selection + // is the same in visual mode, and differs in 1 character in normal mode + if (curStart.line === curEnd.line && curStart.ch >= curEnd.ch - 1) { + var text = cm.getLine(curStart.line); + var charCode = text.charCodeAt(curStart.ch); + if (0xD800 <= charCode && charCode <= 0xD8FF) { + curEnd.ch += 1; + } + } + + return {start: curStart, end: curEnd}; + } + /** @type {import("./types").vimKeyMap} */ + var defaultKeymap = [ + // Key to key mapping. This goes first to make it possible to override + // existing mappings. + { keys: '', type: 'keyToKey', toKeys: 'h' }, + { keys: '', type: 'keyToKey', toKeys: 'l' }, + { keys: '', type: 'keyToKey', toKeys: 'k' }, + { keys: '', type: 'keyToKey', toKeys: 'j' }, + { keys: 'g', type: 'keyToKey', toKeys: 'gk' }, + { keys: 'g', type: 'keyToKey', toKeys: 'gj' }, + { keys: '', type: 'keyToKey', toKeys: 'l' }, + { keys: '', type: 'keyToKey', toKeys: 'h'}, + { keys: '', type: 'keyToKey', toKeys: 'x' }, + { keys: '', type: 'keyToKey', toKeys: 'W' }, + { keys: '', type: 'keyToKey', toKeys: 'B' }, + { keys: '', type: 'keyToKey', toKeys: 'w' }, + { keys: '', type: 'keyToKey', toKeys: 'b' }, + { keys: '', type: 'keyToKey', toKeys: 'j' }, + { keys: '', type: 'keyToKey', toKeys: 'k' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, + { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, + { keys: '', type: 'keyToKey', toKeys: '' }, // ipad keyboard sends C-Esc instead of C-[ + { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, + { keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' }, + { keys: 's', type: 'keyToKey', toKeys: 'c', context: 'visual'}, + { keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' }, + { keys: 'S', type: 'keyToKey', toKeys: 'VdO', context: 'visual' }, + { keys: '', type: 'keyToKey', toKeys: '0' }, + { keys: '', type: 'keyToKey', toKeys: '$' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: 'j^', context: 'normal' }, + { keys: '', type: 'keyToKey', toKeys: 'i', context: 'normal'}, + { keys: '', type: 'action', action: 'toggleOverwrite', context: 'insert' }, + // Motions + { keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }}, + { keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }}, + { keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }}, + { keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }}, + { keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }}, + { keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }}, + { keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }}, + { keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }}, + { keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }}, + { keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }}, + { keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }}, + { keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }}, + { keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }}, + { keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }}, + { keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }}, + { keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }}, + { keys: '(', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: false }}, + { keys: ')', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: true }}, + { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }}, + { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }}, + { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }}, + { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }}, + { keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }}, + { keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }}, + {keys: "g$", type: "motion", motion: "moveToEndOfDisplayLine"}, + {keys: "g^", type: "motion", motion: "moveToStartOfDisplayLine"}, + {keys: "g0", type: "motion", motion: "moveToStartOfDisplayLine"}, + { keys: '0', type: 'motion', motion: 'moveToStartOfLine' }, + { keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }}, + { keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }}, + { keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }}, + { keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }}, + { keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }}, + { keys: 'f', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }}, + { keys: 'F', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }}, + { keys: 't', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }}, + { keys: 'T', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }}, + { keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }}, + { keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }}, + { keys: '\'', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}}, + { keys: '`', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}}, + { keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } }, + { keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } }, + { keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } }, + { keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } }, + // the next two aren't motions but must come before more general motion declarations + { keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}}, + { keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}}, + { keys: ']', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}}, + { keys: '[', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}}, + { keys: '|', type: 'motion', motion: 'moveToColumn'}, + { keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'}, + { keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'}, + // Operators + { keys: 'd', type: 'operator', operator: 'delete' }, + { keys: 'y', type: 'operator', operator: 'yank' }, + { keys: 'c', type: 'operator', operator: 'change' }, + { keys: '=', type: 'operator', operator: 'indentAuto' }, + { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }}, + { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }}, + { keys: 'g~', type: 'operator', operator: 'changeCase' }, + { keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true }, + { keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true }, + { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }}, + { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }}, + { keys: 'gn', type: 'motion', motion: 'findAndSelectNextInclusive', motionArgs: { forward: true }}, + { keys: 'gN', type: 'motion', motion: 'findAndSelectNextInclusive', motionArgs: { forward: false }}, + { keys: 'gq', type: 'operator', operator: 'hardWrap' }, + { keys: 'gw', type: 'operator', operator: 'hardWrap', operatorArgs: {keepCursor: true}}, + // Operator-Motion dual commands + { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }}, + { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }}, + { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, + { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'expandToLine', motionArgs: { linewise: true }, context: 'normal'}, + { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, + { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'}, + { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'}, + { keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveToStartOfLine', context: 'insert' }, + { keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' }, + //ignore C-w in normal mode + { keys: '', type: 'idle', context: 'normal' }, + // Actions + { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }}, + { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }}, + { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }}, + { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }}, + { keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' }, + { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' }, + { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' }, + { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' }, + { keys: 'gi', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'lastEdit' }, context: 'normal' }, + { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' }, + { keys: 'gI', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'bol'}, context: 'normal' }, + { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' }, + { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' }, + { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' }, + { keys: 'v', type: 'action', action: 'toggleVisualMode' }, + { keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }}, + { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }}, + { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }}, + { keys: 'gv', type: 'action', action: 'reselectLastSelection' }, + { keys: 'J', type: 'action', action: 'joinLines', isEdit: true }, + { keys: 'gJ', type: 'action', action: 'joinLines', actionArgs: { keepSpaces: true }, isEdit: true }, + { keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }}, + { keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }}, + { keys: 'r', type: 'action', action: 'replace', isEdit: true }, + { keys: '@', type: 'action', action: 'replayMacro' }, + { keys: 'q', type: 'action', action: 'enterMacroRecordMode' }, + // Handle Replace-mode as a special case of insert mode. + { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }, context: 'normal'}, + { keys: 'R', type: 'operator', operator: 'change', operatorArgs: { linewise: true, fullLine: true }, context: 'visual', exitVisualBlock: true}, + { keys: 'u', type: 'action', action: 'undo', context: 'normal' }, + { keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true }, + { keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true }, + { keys: '', type: 'action', action: 'redo' }, + { keys: 'm', type: 'action', action: 'setMark' }, + { keys: '"', type: 'action', action: 'setRegister' }, + { keys: '', type: 'action', action: 'insertRegister', context: 'insert', isEdit: true }, + { keys: '', type: 'action', action: 'oneNormalCommand', context: 'insert' }, + { keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }}, + { keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }}, + { keys: 'z', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }}, + { keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: '.', type: 'action', action: 'repeatLastEdit' }, + { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}}, + { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}}, + { keys: '', type: 'action', action: 'indent', actionArgs: { indentRight: true }, context: 'insert' }, + { keys: '', type: 'action', action: 'indent', actionArgs: { indentRight: false }, context: 'insert' }, + // Text object motions + { keys: 'a', type: 'motion', motion: 'textObjectManipulation' }, + { keys: 'i', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }}, + // Search + { keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }}, + { keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }}, + { keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }}, + { keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }}, + { keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }}, + { keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }}, + // Ex command + { keys: ':', type: 'ex' } + ]; + var defaultKeymapLength = defaultKeymap.length; + + /** + * Ex commands + * Care must be taken when adding to the default Ex command map. For any + * pair of commands that have a shared prefix, at least one of their + * shortNames must not match the prefix of the other command. + */ + var defaultExCommandMap = [ + { name: 'colorscheme', shortName: 'colo' }, + { name: 'map' }, + { name: 'imap', shortName: 'im' }, + { name: 'nmap', shortName: 'nm' }, + { name: 'vmap', shortName: 'vm' }, + { name: 'omap', shortName: 'om' }, + { name: 'noremap', shortName: 'no' }, + { name: 'nnoremap', shortName: 'nn' }, + { name: 'vnoremap', shortName: 'vn' }, + { name: 'inoremap', shortName: 'ino' }, + { name: 'onoremap', shortName: 'ono' }, + { name: 'unmap' }, + { name: 'mapclear', shortName: 'mapc' }, + { name: 'nmapclear', shortName: 'nmapc' }, + { name: 'vmapclear', shortName: 'vmapc' }, + { name: 'imapclear', shortName: 'imapc' }, + { name: 'omapclear', shortName: 'omapc' }, + { name: 'write', shortName: 'w' }, + { name: 'undo', shortName: 'u' }, + { name: 'redo', shortName: 'red' }, + { name: 'set', shortName: 'se' }, + { name: 'setlocal', shortName: 'setl' }, + { name: 'setglobal', shortName: 'setg' }, + { name: 'sort', shortName: 'sor' }, + { name: 'substitute', shortName: 's', possiblyAsync: true }, + { name: 'startinsert', shortName: 'start' }, + { name: 'nohlsearch', shortName: 'noh' }, + { name: 'yank', shortName: 'y' }, + { name: 'delmarks', shortName: 'delm' }, + { name: 'marks', excludeFromCommandHistory: true }, + { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true }, + { name: 'vglobal', shortName: 'v' }, + { name: 'delete', shortName: 'd' }, + { name: 'join', shortName: 'j' }, + { name: 'normal', shortName: 'norm' }, + { name: 'global', shortName: 'g' } + ]; + + /** + * Langmap + * Determines how to interpret keystrokes in Normal and Visual mode. + * Useful for people who use a different keyboard layout than QWERTY + */ + var langmap = parseLangmap(''); + + /** @arg {CodeMirror} cm */ + function enterVimMode(cm) { + cm.setOption('disableInput', true); + cm.setOption('showCursorWhenSelecting', false); + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + cm.on('cursorActivity', onCursorActivity); + maybeInitVimState(cm); + // @ts-ignore + CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm)); + } + + /** @arg {CodeMirror} cm */ + function leaveVimMode(cm) { + cm.setOption('disableInput', false); + cm.off('cursorActivity', onCursorActivity); + // @ts-ignore + CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm)); + cm.state.vim = null; + vimGlobalState.jumpList = createCircularJumpList(); + if (highlightTimeout) clearTimeout(highlightTimeout); + } + + /** @arg {CodeMirrorV} cm */ + function getOnPasteFn(cm) { + var vim = cm.state.vim; + if (!vim.onPasteFn) { + vim.onPasteFn = function() { + if (!vim.insertMode) { + cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); + actions.enterInsertMode(cm, {}, vim); + } + }; + } + return vim.onPasteFn; + } + + var numberRegex = /[\d]/; + var wordCharTest = [CodeMirror.isWordChar, function(ch) { + return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch); + }], bigWordCharTest = [function(ch) { + return /\S/.test(ch); + }]; + var validMarks = ['<', '>']; + var validRegisters = ['-', '"', '.', ':', '_', '/', '+']; + var latinCharRegex = /^\w$/; + var upperCaseChars; + try { upperCaseChars = new RegExp("^[\\p{Lu}]$", "u"); } + catch (_) { upperCaseChars = /^[A-Z]$/; } + + /** @arg {CodeMirror} cm @arg {number} line */ + function isLine(cm, line) { + return line >= cm.firstLine() && line <= cm.lastLine(); + } + /** @arg {string} k */ + function isLowerCase(k) { + return (/^[a-z]$/).test(k); + } + /** @arg {string} k */ + function isMatchableSymbol(k) { + return '()[]{}'.indexOf(k) != -1; + } + /** @arg {string} k */ + function isNumber(k) { + return numberRegex.test(k); + } + /** @arg {string} k */ + function isUpperCase(k) { + return upperCaseChars.test(k); + } + /** @arg {string} k */ + function isWhiteSpaceString(k) { + return (/^\s*$/).test(k); + } + /** @arg {string} k */ + function isEndOfSentenceSymbol(k) { + return '.?!'.indexOf(k) != -1; + } + /** @arg {any} val @arg {string | any[]} arr */ + function inArray(val, arr) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] == val) { + return true; + } + } + return false; + } + + + /** @typedef {import("./types").optionCallback} optionCallback */ + /** @typedef {import("./types").vimOption} vimOption */ + /** @type {Object} */ + var options = {}; + /** + * @arg {string} name + * @arg {any} defaultValue + * @arg {string} type + * @arg {string[] } [aliases] + * @arg {optionCallback} [callback] + * */ + function defineOption(name, defaultValue, type, aliases, callback) { + if (defaultValue === undefined && !callback) { + throw Error('defaultValue is required unless callback is provided'); + } + if (!type) { type = 'string'; } + options[name] = { + type: type, + defaultValue: defaultValue, + callback: callback + }; + if (aliases) { + for (var i = 0; i < aliases.length; i++) { + options[aliases[i]] = options[name]; + } + } + if (defaultValue) { + setOption(name, defaultValue); + } + } + + /** + * @arg {string} name + * @arg {any} value + * @arg {CodeMirrorV} [cm] + * @arg {{ scope?: any; } | undefined} [cfg] */ + function setOption(name, value, cm, cfg) { + var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; + if (!option) { + return new Error('Unknown option: ' + name); + } + if (option.type == 'boolean') { + if (value && value !== true) { + return new Error('Invalid argument: ' + name + '=' + value); + } else if (value !== false) { + // Boolean options are set to true if value is not defined. + value = true; + } + } + if (option.callback) { + if (scope !== 'local') { + option.callback(value, undefined); + } + if (scope !== 'global' && cm) { + option.callback(value, cm); + } + } else { + if (scope !== 'local') { + option.value = option.type == 'boolean' ? !!value : value; + } + if (scope !== 'global' && cm) { + cm.state.vim.options[name] = {value: value}; + } + } + } + + /** + * @arg {string} name + * @arg {CodeMirrorV} [cm] + * @arg {{ scope?: any; } | undefined} [cfg] */ + function getOption(name, cm, cfg) { + var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; + if (!option) { + return new Error('Unknown option: ' + name); + } + if (option.callback) { + let local = cm && option.callback(undefined, cm); + if (scope !== 'global' && local !== undefined) { + return local; + } + if (scope !== 'local') { + return option.callback(); + } + return; + } else { + let local = (scope !== 'global') && (cm && cm.state.vim.options[name]); + return (local || (scope !== 'local') && option || {}).value; + } + } + /** @arg {string|undefined} name @arg {CodeMirrorV} [cm] */ + defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) { + // Option is local. Do nothing for global. + if (cm === undefined) { + return; + } + // The 'filetype' option proxies to the CodeMirror 'mode' option. + if (name === undefined) { + let mode = cm.getOption('mode'); + return mode == 'null' ? '' : mode; + } else { + let mode = name == '' ? 'null' : name; + cm.setOption('mode', mode); + } + }); + defineOption('textwidth', 80, 'number', ['tw'], function(width, cm) { + // Option is local. Do nothing for global. + if (cm === undefined) { + return; + } + // The 'filetype' option proxies to the CodeMirror 'mode' option. + if (width === undefined) { + var value = cm.getOption('textwidth'); + return value; + } else { + var column = Math.round(/**@type {any}*/(width)); + if (column > 1) { + cm.setOption('textwidth', column); + } + } + }); + + var createCircularJumpList = function() { + var size = 100; + var pointer = -1; + var head = 0; + var tail = 0; + var buffer = new Array(size); + /** @arg {CodeMirror} cm @arg {any} oldCur @arg {any} newCur */ + function add(cm, oldCur, newCur) { + var current = pointer % size; + var curMark = buffer[current]; + /** @arg {Pos} cursor */ + function useNextSlot(cursor) { + var next = ++pointer % size; + var trashMark = buffer[next]; + if (trashMark) { + trashMark.clear(); + } + buffer[next] = cm.setBookmark(cursor); + } + if (curMark) { + var markPos = curMark.find(); + // avoid recording redundant cursor position + if (markPos && !cursorEqual(markPos, oldCur)) { + useNextSlot(oldCur); + } + } else { + useNextSlot(oldCur); + } + useNextSlot(newCur); + head = pointer; + tail = pointer - size + 1; + if (tail < 0) { + tail = 0; + } + } + /** @arg {CodeMirror} cm @arg {number} offset */ + function move(cm, offset) { + pointer += offset; + if (pointer > head) { + pointer = head; + } else if (pointer < tail) { + pointer = tail; + } + var mark = buffer[(size + pointer) % size]; + // skip marks that are temporarily removed from text buffer + if (mark && !mark.find()) { + var inc = offset > 0 ? 1 : -1; + var newCur; + var oldCur = cm.getCursor(); + do { + pointer += inc; + mark = buffer[(size + pointer) % size]; + // skip marks that are the same as current position + if (mark && + (newCur = mark.find()) && + !cursorEqual(oldCur, newCur)) { + break; + } + } while (pointer < head && pointer > tail); + } + return mark; + } + /** @arg {CodeMirror} cm @arg {number} offset */ + function find(cm, offset) { + var oldPointer = pointer; + var mark = move(cm, offset); + pointer = oldPointer; + return mark && mark.find(); + } + return { + cachedCursor: undefined, //used for # and * jumps + add: add, + find: find, + move: move + }; + }; + + /** + * Returns an object to track the changes associated insert mode. It + * clones the object that is passed in, or creates an empty object one if + * none is provided. + * @arg {import("./types").InsertModeChanges | undefined} [c] + * @returns {import("./types").InsertModeChanges} + */ + var createInsertModeChanges = function(c) { + if (c) { + // Copy construction + return { + changes: c.changes, + expectCursorActivityForChange: c.expectCursorActivityForChange + }; + } + return { + // Change list + changes: [], + // Set to true on change, false on cursorActivity. + expectCursorActivityForChange: false + }; + }; + + class MacroModeState { + constructor() { + this.latestRegister = undefined; + this.isPlaying = false; + this.isRecording = false; + this.replaySearchQueries = []; + this.onRecordingDone = undefined; + this.lastInsertModeChanges = createInsertModeChanges(); + } + exitMacroRecordMode() { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.onRecordingDone) { + macroModeState.onRecordingDone(); // close dialog + } + macroModeState.onRecordingDone = undefined; + macroModeState.isRecording = false; + } + enterMacroRecordMode(cm, registerName) { + var register = vimGlobalState.registerController.getRegister(registerName); + if (register) { + register.clear(); + this.latestRegister = registerName; + if (cm.openDialog) { + var template = dom('span', {class: 'cm-vim-message'}, 'recording @' + registerName); + this.onRecordingDone = cm.openDialog(template, null, {bottom:true}); + } + this.isRecording = true; + } + } + } + /** + * @arg Codemirror + * @return {vimState} + */ + function maybeInitVimState(cm) { + if (!cm.state.vim) { + // Store instance state in the CodeMirror object. + cm.state.vim = { + inputState: new InputState(), + // Vim's input state that triggered the last edit, used to repeat + // motions and operators with '.'. + lastEditInputState: undefined, + // Vim's action command before the last edit, used to repeat actions + // with '.' and insert mode repeat. + lastEditActionCommand: undefined, + // When using jk for navigation, if you move from a longer line to a + // shorter line, the cursor may clip to the end of the shorter line. + // If j is pressed again and cursor goes to the next line, the + // cursor should go back to its horizontal position on the longer + // line if it can. This is to keep track of the horizontal position. + lastHPos: -1, + // Doing the same with screen-position for gj/gk + lastHSPos: -1, + // The last motion command run. Cleared if a non-motion command gets + // executed in between. + lastMotion: null, + marks: {}, + insertMode: false, + insertModeReturn: false, + // Repeat count for changes made in insert mode, triggered by key + // sequences like 3,i. Only exists when insertMode is true. + insertModeRepeat: undefined, + visualMode: false, + // If we are in visual line mode. No effect if visualMode is false. + visualLine: false, + visualBlock: false, + lastSelection: null, + lastPastedText: null, + sel: {}, + // Buffer-local/window-local values of vim options. + options: {}, + // Whether the next character should be interpreted literally + // Necassary for correct implementation of f, r etc. + // in terms of langmaps. + expectLiteralNext: false + }; + } + return cm.state.vim; + } + /** + * @type { + { + macroModeState: MacroModeState; + registerController: RegisterController; + searchHistoryController: HistoryController; + jumpList: any; + exCommandHistoryController: HistoryController; + lastCharacterSearch: any; + query?: any; + isReversed?: any; + lastSubstituteReplacePart: any; + searchQuery?: null; + searchIsReversed?: boolean; + } + } + */ + var vimGlobalState; + function resetVimGlobalState() { + vimGlobalState = { + // The current search query. + searchQuery: null, + // Whether we are searching backwards. + searchIsReversed: false, + // Replace part of the last substituted pattern + lastSubstituteReplacePart: undefined, + jumpList: createCircularJumpList(), + macroModeState: new MacroModeState(), + // Recording latest f, t, F or T motion command. + lastCharacterSearch: {increment:0, forward:true, selectedCharacter:''}, + registerController: new RegisterController({}), + // search history buffer + searchHistoryController: new HistoryController(), + // ex Command history buffer + exCommandHistoryController : new HistoryController() + }; + for (var optionName in options) { + var option = options[optionName]; + option.value = option.defaultValue; + } + } + + /** @type {number | undefined|false} */ + var lastInsertModeKeyTimer; + var vimApi = { + enterVimMode: enterVimMode, + leaveVimMode: leaveVimMode, + buildKeyMap: function() { + // TODO: Convert keymap into dictionary format for fast lookup. + }, + // Testing hook, though it might be useful to expose the register + // controller anyway. + getRegisterController: function() { + return vimGlobalState.registerController; + }, + // Testing hook. + resetVimGlobalState_: resetVimGlobalState, + + // Testing hook. + getVimGlobalState_: function() { + return vimGlobalState; + }, + + // Testing hook. + maybeInitVimState_: maybeInitVimState, + + suppressErrorLogging: false, + + InsertModeKey: InsertModeKey, + /**@type {(lhs: string, rhs: string, ctx: string) => void} */ + map: function(lhs, rhs, ctx) { + // Add user defined key bindings. + exCommandDispatcher.map(lhs, rhs, ctx); + }, + /**@type {(lhs: string, ctx: string) => any} */ + unmap: function(lhs, ctx) { + return exCommandDispatcher.unmap(lhs, ctx); + }, + // Non-recursive map function. + // NOTE: This will not create mappings to key maps that aren't present + // in the default key map. See TODO at bottom of function. + /**@type {(lhs: string, rhs: string, ctx: string) => void} */ + noremap: function(lhs, rhs, ctx) { + exCommandDispatcher.map(lhs, rhs, ctx, true); + }, + // Remove all user-defined mappings for the provided context. + /**@arg {string} [ctx]} */ + mapclear: function(ctx) { + // Partition the existing keymap into user-defined and true defaults. + var actualLength = defaultKeymap.length, + origLength = defaultKeymapLength; + var userKeymap = defaultKeymap.slice(0, actualLength - origLength); + defaultKeymap = defaultKeymap.slice(actualLength - origLength); + if (ctx) { + // If a specific context is being cleared, we need to keep mappings + // from all other contexts. + for (var i = userKeymap.length - 1; i >= 0; i--) { + var mapping = userKeymap[i]; + if (ctx !== mapping.context) { + if (mapping.context) { + this._mapCommand(mapping); + } else { + // `mapping` applies to all contexts so create keymap copies + // for each context except the one being cleared. + var contexts = ['normal', 'insert', 'visual']; + for (var j in contexts) { + if (contexts[j] !== ctx) { + var newMapping = Object.assign({}, mapping); + newMapping.context = contexts[j]; + this._mapCommand(newMapping); + } + } + } + } + } + } + }, + langmap: updateLangmap, + vimKeyFromEvent: vimKeyFromEvent, + // TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace + // them, or somehow make them work with the existing CodeMirror setOption/getOption API. + setOption: setOption, + getOption: getOption, + defineOption: defineOption, + /**@type {(name: string, prefix: string|undefined, func: ExFn) => void} */ + defineEx: function(name, prefix, func){ + if (!prefix) { + prefix = name; + } else if (name.indexOf(prefix) !== 0) { + throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered'); + } + exCommands[name]=func; + exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'}; + }, + /**@type {(cm: CodeMirror, key: string, origin: string) => undefined | boolean} */ + handleKey: function (cm, key, origin) { + var command = this.findKey(cm, key, origin); + if (typeof command === 'function') { + return command(); + } + }, + multiSelectHandleKey: multiSelectHandleKey, + + /** + * This is the outermost function called by CodeMirror, after keys have + * been mapped to their Vim equivalents. + * + * Finds a command based on the key (and cached keys if there is a + * multi-key sequence). Returns `undefined` if no key is matched, a noop + * function if a partial match is found (multi-key), and a function to + * execute the bound command if a a key is matched. The function always + * returns true. + */ + /**@type {(cm_: CodeMirror, key: string, origin?: string| undefined) => (() => boolean) | undefined} */ + findKey: function(cm_, key, origin) { + var vim = maybeInitVimState(cm_); + var cm = /**@type {CodeMirrorV}*/(cm_); + + function handleMacroRecording() { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isRecording) { + if (key == 'q') { + macroModeState.exitMacroRecordMode(); + clearInputState(cm); + return true; + } + if (origin != 'mapping') { + logKey(macroModeState, key); + } + } + } + function handleEsc() { + if (key == '') { + if (vim.visualMode) { + // Get back to normal mode. + exitVisualMode(cm); + } else if (vim.insertMode) { + // Get back to normal mode. + exitInsertMode(cm); + } else { + // We're already in normal mode. Let '' be handled normally. + return; + } + clearInputState(cm); + return true; + } + } + + function handleKeyInsertMode() { + if (handleEsc()) { return true; } + vim.inputState.keyBuffer.push(key); + var keys = vim.inputState.keyBuffer.join(""); + var keysAreChars = key.length == 1; + var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert'); + var changeQueue = vim.inputState.changeQueue; + + if (match.type == 'none') { clearInputState(cm); return false; } + else if (match.type == 'partial') { + if (match.expectLiteralNext) vim.expectLiteralNext = true; + if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } + lastInsertModeKeyTimer = keysAreChars && window.setTimeout( + function() { if (vim.insertMode && vim.inputState.keyBuffer.length) { clearInputState(cm); } }, + getOption('insertModeEscKeysTimeout')); + if (keysAreChars) { + var selections = cm.listSelections(); + if (!changeQueue || changeQueue.removed.length != selections.length) + changeQueue = vim.inputState.changeQueue = new ChangeQueue; + changeQueue.inserted += key; + for (var i = 0; i < selections.length; i++) { + var from = cursorMin(selections[i].anchor, selections[i].head); + var to = cursorMax(selections[i].anchor, selections[i].head); + var text = cm.getRange(from, cm.state.overwrite ? offsetCursor(to, 0, 1) : to); + changeQueue.removed[i] = (changeQueue.removed[i] || "") + text; + } + } + return !keysAreChars; + } + vim.expectLiteralNext = false; + + if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } + if (match.command && changeQueue) { + var selections = cm.listSelections(); + for (var i = 0; i < selections.length; i++) { + var here = selections[i].head; + cm.replaceRange(changeQueue.removed[i] || "", + offsetCursor(here, 0, -changeQueue.inserted.length), here, '+input'); + } + vimGlobalState.macroModeState.lastInsertModeChanges.changes.pop(); + } + if (!match.command) clearInputState(cm); + return match.command; + } + + function handleKeyNonInsertMode() { + if (handleMacroRecording() || handleEsc()) { return true; } + + vim.inputState.keyBuffer.push(key); + var keys = vim.inputState.keyBuffer.join(""); + if (/^[1-9]\d*$/.test(keys)) { return true; } + + var keysMatcher = /^(\d*)(.*)$/.exec(keys); + if (!keysMatcher) { clearInputState(cm); return false; } + var context = vim.visualMode ? 'visual' : + 'normal'; + var mainKey = keysMatcher[2] || keysMatcher[1]; + if (vim.inputState.operatorShortcut && vim.inputState.operatorShortcut.slice(-1) == mainKey) { + // multikey operators act linewise by repeating only the last character + mainKey = vim.inputState.operatorShortcut; + } + var match = commandDispatcher.matchCommand(mainKey, defaultKeymap, vim.inputState, context); + if (match.type == 'none') { clearInputState(cm); return false; } + else if (match.type == 'partial') { + if (match.expectLiteralNext) vim.expectLiteralNext = true; + return true; + } + else if (match.type == 'clear') { clearInputState(cm); return true; } + vim.expectLiteralNext = false; + + vim.inputState.keyBuffer.length = 0; + keysMatcher = /^(\d*)(.*)$/.exec(keys); + if (keysMatcher && keysMatcher[1] && keysMatcher[1] != '0') { + vim.inputState.pushRepeatDigit(keysMatcher[1]); + } + return match.command; + } + + var command; + if (vim.insertMode) { command = handleKeyInsertMode(); } + else { command = handleKeyNonInsertMode(); } + if (command === false) { + return !vim.insertMode && key.length === 1 ? function() { return true; } : undefined; + } else if (command === true) { + // TODO: Look into using CodeMirror's multi-key handling. + // Return no-op since we are caching the key. Counts as handled, but + // don't want act on it just yet. + return function() { return true; }; + } else { + return function() { + return cm.operation(function() { + // @ts-ignore + cm.curOp.isVimOp = true; + try { + if (command.type == 'keyToKey') { + doKeyToKey(cm, command.toKeys, command); + } else { + commandDispatcher.processCommand(cm, vim, command); + } + } catch (e) { + // clear VIM state in case it's in a bad state. + // @ts-ignore + cm.state.vim = undefined; + maybeInitVimState(cm); + if (!vimApi.suppressErrorLogging) { + console['log'](e); + } + throw e; + } + return true; + }); + }; + } + }, + handleEx: function(cm, input) { + exCommandDispatcher.processCommand(cm, input); + }, + + defineMotion: defineMotion, + defineAction: defineAction, + defineOperator: defineOperator, + mapCommand: mapCommand, + _mapCommand: _mapCommand, + + defineRegister: defineRegister, + + exitVisualMode: exitVisualMode, + exitInsertMode: exitInsertMode + }; + + var keyToKeyStack = []; + var noremap = false; + var virtualPrompt; + function sendKeyToPrompt(key) { + if (key[0] == "<") { + var lowerKey = key.toLowerCase().slice(1, -1); + var parts = lowerKey.split('-'); + lowerKey = parts.pop() || ''; + if (lowerKey == 'lt') key = '<'; + else if (lowerKey == 'space') key = ' '; + else if (lowerKey == 'cr') key = '\n'; + else if (vimToCmKeyMap[lowerKey]) { + var value = virtualPrompt.value; + var event = { + key: vimToCmKeyMap[lowerKey], + target: { + value: value, + selectionEnd: value.length, + selectionStart: value.length + } + }; + if (virtualPrompt.onKeyDown) { + virtualPrompt.onKeyDown(event, virtualPrompt.value, close); + } + if (virtualPrompt && virtualPrompt.onKeyUp) { + virtualPrompt.onKeyUp(event, virtualPrompt.value, close); + } + return; + } + } + if (key == '\n') { + var prompt = virtualPrompt; + virtualPrompt = null; + prompt.onClose && prompt.onClose(prompt.value); + } else { + virtualPrompt.value = (virtualPrompt.value || '') + key; + } + + function close(value) { + if (typeof value == 'string') { virtualPrompt.value = value; } + else { virtualPrompt = null; } + } + } + function doKeyToKey(cm, keys, fromKey) { + var noremapBefore = noremap; + // prevent infinite recursion. + if (fromKey) { + if (keyToKeyStack.indexOf(fromKey) != -1) return; + keyToKeyStack.push(fromKey); + noremap = fromKey.noremap != false; + } + + try { + var vim = maybeInitVimState(cm); + var keyRe = /<(?:[CSMA]-)*\w+>|./gi; + + var match; + // Pull off one command key, which is either a single character + // or a special sequence wrapped in '<' and '>', e.g. ''. + while ((match = keyRe.exec(keys))) { + var key = match[0]; + var wasInsert = vim.insertMode; + if (virtualPrompt) { + sendKeyToPrompt(key); + continue; + } + + var result = vimApi.handleKey(cm, key, 'mapping'); + + if (!result && wasInsert && vim.insertMode) { + if (key[0] == "<") { + var lowerKey = key.toLowerCase().slice(1, -1); + var parts = lowerKey.split('-'); + lowerKey = parts.pop() || ''; + if (lowerKey == 'lt') key = '<'; + else if (lowerKey == 'space') key = ' '; + else if (lowerKey == 'cr') key = '\n'; + else if (vimToCmKeyMap.hasOwnProperty(lowerKey)) { + // todo support codemirror keys in insertmode vimToCmKeyMap + key = vimToCmKeyMap[lowerKey]; + sendCmKey(cm, key); + continue; + } else { + key = key[0]; + keyRe.lastIndex = match.index + 1; + } + } + cm.replaceSelection(key); + } + } + } finally { + keyToKeyStack.pop(); + noremap = keyToKeyStack.length ? noremapBefore : false; + if (!keyToKeyStack.length && virtualPrompt) { + var promptOptions = virtualPrompt; + virtualPrompt = null; + showPrompt(cm, promptOptions); + } + } + } + + var specialKey = { + Return: 'CR', Backspace: 'BS', 'Delete': 'Del', Escape: 'Esc', Insert: 'Ins', + ArrowLeft: 'Left', ArrowRight: 'Right', ArrowUp: 'Up', ArrowDown: 'Down', + Enter: 'CR', ' ': 'Space' + }; + var ignoredKeys = { Shift: 1, Alt: 1, Command: 1, Control: 1, + CapsLock: 1, AltGraph: 1, Dead: 1, Unidentified: 1 }; + + var vimToCmKeyMap = {}; + 'Left|Right|Up|Down|End|Home'.split('|').concat(Object.keys(specialKey)).forEach(function(x) { + vimToCmKeyMap[(specialKey[x] || '').toLowerCase()] + = vimToCmKeyMap[x.toLowerCase()] = x; + }); + + function vimKeyFromEvent(e, vim) { + var key = e.key; + if (ignoredKeys[key]) return; + if (key.length > 1 && key[0] == "n") { + key = key.replace("Numpad", ""); + } + key = specialKey[key] || key; + + var name = ''; + if (e.ctrlKey) { name += 'C-'; } + if (e.altKey) { name += 'A-'; } + if (e.metaKey) { name += 'M-'; } + // on mac many characters are entered as option- combos + // (e.g. on swiss keyboard { is option-8) + // so we ignore lonely A- modifier for keypress event on mac + if (CodeMirror.isMac && e.altKey && !e.metaKey && !e.ctrlKey) { + name = name.slice(2); + } + if ((name || key.length > 1) && e.shiftKey) { name += 'S-'; } + + if (vim && !vim.expectLiteralNext && key.length == 1) { + if (langmap.keymap && key in langmap.keymap) { + if (langmap.remapCtrl != false || !name) + key = langmap.keymap[key]; + } else if (key.charCodeAt(0) > 255) { + var code = e.code?.slice(-1) || ""; + if (!e.shiftKey) code = code.toLowerCase(); + if (code) key = code; + } + } + + name += key; + if (name.length > 1) { name = '<' + name + '>'; } + return name; + } + // langmap support + function updateLangmap(langmapString, remapCtrl) { + if (langmap.string !== langmapString) { + langmap = parseLangmap(langmapString); + } + langmap.remapCtrl = remapCtrl; + } + /** + * From :help langmap + * The 'langmap' option is a list of parts, separated with commas. Each + * part can be in one of two forms: + * 1. A list of pairs. Each pair is a "from" character immediately + * followed by the "to" character. Examples: "aA", "aAbBcC". + * 2. A list of "from" characters, a semi-colon and a list of "to" + * characters. Example: "abc;ABC" + * @arg {string} langmapString + * @returns {{string: string, keymap: Record, remapCtrl?: boolean}} + */ + function parseLangmap(langmapString) { + let keymap = ({})/**@type {Record}*/; + if (!langmapString) return { keymap: keymap, string: '' }; + + function getEscaped(list) { + return list.split(/\\?(.)/).filter(Boolean); + } + langmapString.split(/((?:[^\\,]|\\.)+),/).map(part => { + if (!part) return; + const semicolon = part.split(/((?:[^\\;]|\\.)+);/); + if (semicolon.length == 3) { + const from = getEscaped(semicolon[1]); + const to = getEscaped(semicolon[2]); + if (from.length !== to.length) return; // skip over malformed part + for (let i = 0; i < from.length; ++i) keymap[from[i]] = to[i]; + } else if (semicolon.length == 1) { + const pairs = getEscaped(part); + if (pairs.length % 2 !== 0) return; // skip over malformed part + for (let i = 0; i < pairs.length; i += 2) keymap[pairs[i]] = pairs[i + 1]; + } + }); + + return { keymap: keymap, string: langmapString }; + } + + defineOption('langmap', undefined, 'string', ['lmap'], function(name, cm) { + // The 'filetype' option proxies to the CodeMirror 'mode' option. + if (name === undefined) { + return langmap.string; + } else { + updateLangmap(name); + } + }); + + // Represents the current input state. + class InputState { + constructor() { + this.prefixRepeat = []; + this.motionRepeat = []; + + this.operator = null; + this.operatorArgs = null; + this.motion = null; + this.motionArgs = null; + this.keyBuffer = []; // For matching multi-key commands. + this.registerName = null; // Defaults to the unnamed register. + this.changeQueue = null; // For restoring text used by insert mode keybindings + } + pushRepeatDigit(n) { + if (!this.operator) { + this.prefixRepeat = this.prefixRepeat.concat(n); + } else { + this.motionRepeat = this.motionRepeat.concat(n); + } + } + getRepeat() { + var repeat = 0; + if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) { + repeat = 1; + if (this.prefixRepeat.length > 0) { + repeat *= parseInt(this.prefixRepeat.join(''), 10); + } + if (this.motionRepeat.length > 0) { + repeat *= parseInt(this.motionRepeat.join(''), 10); + } + } + return repeat; + } + } + + /** @arg {CodeMirrorV} cm @arg {string} [reason] */ + function clearInputState(cm, reason) { + cm.state.vim.inputState = new InputState(); + cm.state.vim.expectLiteralNext = false; + CodeMirror.signal(cm, 'vim-command-done', reason); + } + + function ChangeQueue() { + this.removed = []; + this.inserted = ""; + } + + /** + * Register stores information about copy and paste registers. Besides + * text, a register must store whether it is linewise (i.e., when it is + * pasted, should it insert itself into a new line, or should the text be + * inserted at the cursor position.) + */ + class Register { + constructor(text, linewise, blockwise) { + this.clear(); + this.keyBuffer = [text || '']; + this.insertModeChanges = []; + this.searchQueries = []; + this.linewise = !!linewise; + this.blockwise = !!blockwise; + } + setText(text, linewise, blockwise) { + this.keyBuffer = [text || '']; + this.linewise = !!linewise; + this.blockwise = !!blockwise; + } + pushText(text, linewise) { + // if this register has ever been set to linewise, use linewise. + if (linewise) { + if (!this.linewise) { + this.keyBuffer.push('\n'); + } + this.linewise = true; + } + this.keyBuffer.push(text); + } + pushInsertModeChanges(changes) { + this.insertModeChanges.push(createInsertModeChanges(changes)); + } + pushSearchQuery(query) { + this.searchQueries.push(query); + } + clear() { + this.keyBuffer = []; + this.insertModeChanges = []; + this.searchQueries = []; + this.linewise = false; + } + toString() { + return this.keyBuffer.join(''); + } + } + + /** + * Defines an external register. + * + * The name should be a single character that will be used to reference the register. + * The register should support setText, pushText, clear, and toString(). See Register + * for a reference implementation. + * @arg {string} name + * @arg {Register} register + */ + function defineRegister(name, register) { + var registers = vimGlobalState.registerController.registers; + if (!name || name.length != 1) { + throw Error('Register name must be 1 character'); + } + if (registers[name]) { + throw Error('Register already defined ' + name); + } + registers[name] = register; + validRegisters.push(name); + } + + /** + * vim registers allow you to keep many independent copy and paste buffers. + * See http://usevim.com/2012/04/13/registers/ for an introduction. + * + * RegisterController keeps the state of all the registers. An initial + * state may be passed in. The unnamed register '"' will always be + * overridden. + */ + class RegisterController { + /** @arg {Object} registers */ + constructor(registers) { + this.registers = registers; + this.unnamedRegister = registers['"'] = new Register(); + registers['.'] = new Register(); + registers[':'] = new Register(); + registers['/'] = new Register(); + registers['+'] = new Register(); + } + pushText(registerName, operator, text, linewise, blockwise) { + // The black hole register, "_, means delete/yank to nowhere. + if (registerName === '_') return; + if (linewise && text.charAt(text.length - 1) !== '\n') { + text += '\n'; + } + // Lowercase and uppercase registers refer to the same register. + // Uppercase just means append. + var register = this.isValidRegister(registerName) ? + this.getRegister(registerName) : null; + // if no register/an invalid register was specified, things go to the + // default registers + if (!register) { + switch (operator) { + case 'yank': + // The 0 register contains the text from the most recent yank. + this.registers['0'] = new Register(text, linewise, blockwise); + break; + case 'delete': + case 'change': + if (text.indexOf('\n') == -1) { + // Delete less than 1 line. Update the small delete register. + this.registers['-'] = new Register(text, linewise); + } else { + // Shift down the contents of the numbered registers and put the + // deleted text into register 1. + this.shiftNumericRegisters_(); + this.registers['1'] = new Register(text, linewise); + } + break; + } + // Make sure the unnamed register is set to what just happened + this.unnamedRegister.setText(text, linewise, blockwise); + return; + } + + // If we've gotten to this point, we've actually specified a register + var append = isUpperCase(registerName); + if (append) { + register.pushText(text, linewise); + } else { + register.setText(text, linewise, blockwise); + } + if (registerName === '+') { + navigator.clipboard.writeText(text); + } + // The unnamed register always has the same value as the last used + // register. + this.unnamedRegister.setText(register.toString(), linewise); + } + /** + * Gets the register named @name. If one of @name doesn't already exist, + * create it. If @name is invalid, return the unnamedRegister. + * @arg {string} [name] + */ + getRegister(name) { + if (!this.isValidRegister(name)) { + return this.unnamedRegister; + } + name = name.toLowerCase(); + if (!this.registers[name]) { + this.registers[name] = new Register(); + } + return this.registers[name]; + } + /**@type {{(name: any): name is string}} */ + isValidRegister(name) { + return name && (inArray(name, validRegisters) || latinCharRegex.test(name)); + } + shiftNumericRegisters_() { + for (var i = 9; i >= 2; i--) { + this.registers[i] = this.getRegister('' + (i - 1)); + } + } + } + class HistoryController { + constructor() { + this.historyBuffer = []; + this.iterator = 0; + this.initialPrefix = null; + } + // the input argument here acts a user entered prefix for a small time + // until we start autocompletion in which case it is the autocompleted. + nextMatch(input, up) { + var historyBuffer = this.historyBuffer; + var dir = up ? -1 : 1; + if (this.initialPrefix === null) this.initialPrefix = input; + for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i += dir) { + var element = historyBuffer[i]; + for (var j = 0; j <= element.length; j++) { + if (this.initialPrefix == element.substring(0, j)) { + this.iterator = i; + return element; + } + } + } + // should return the user input in case we reach the end of buffer. + if (i >= historyBuffer.length) { + this.iterator = historyBuffer.length; + return this.initialPrefix; + } + // return the last autocompleted query or exCommand as it is. + if (i < 0) return input; + } + pushInput(input) { + var index = this.historyBuffer.indexOf(input); + if (index > -1) this.historyBuffer.splice(index, 1); + if (input.length) this.historyBuffer.push(input); + } + reset() { + this.initialPrefix = null; + this.iterator = this.historyBuffer.length; + } + } + var commandDispatcher = { + matchCommand: function(keys, keyMap, inputState, context) { + var matches = commandMatches(keys, keyMap, context, inputState); + if (!matches.full && !matches.partial) { + return {type: 'none'}; + } else if (!matches.full && matches.partial) { + return { + type: 'partial', + expectLiteralNext: matches.partial.length == 1 && matches.partial[0].keys.slice(-11) == '' // langmap literal logic + }; + } + + var bestMatch; + // @ts-ignore + for (var i = 0; i < matches.full.length; i++) { + var match = matches.full[i]; + if (!bestMatch) { + bestMatch = match; + } + } + if (bestMatch.keys.slice(-11) == '' || bestMatch.keys.slice(-10) == '') { + var character = lastChar(keys); + if (!character || character.length > 1) return {type: 'clear'}; + inputState.selectedCharacter = character; + } + return {type: 'full', command: bestMatch}; + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {vimKey} command + */ + processCommand: function(cm, vim, command) { + vim.inputState.repeatOverride = command.repeatOverride; + switch (command.type) { + case 'motion': + this.processMotion(cm, vim, command); + break; + case 'operator': + this.processOperator(cm, vim, command); + break; + case 'operatorMotion': + this.processOperatorMotion(cm, vim, command); + break; + case 'action': + this.processAction(cm, vim, command); + break; + case 'search': + this.processSearch(cm, vim, command); + break; + case 'ex': + case 'keyToEx': + this.processEx(cm, vim, command); + break; + } + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {import("./types").motionCommand|import("./types").operatorMotionCommand} command + */ + processMotion: function(cm, vim, command) { + vim.inputState.motion = command.motion; + vim.inputState.motionArgs = /**@type {MotionArgs}*/(copyArgs(command.motionArgs)); + this.evalInput(cm, vim); + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {import("./types").operatorCommand|import("./types").operatorMotionCommand} command + */ + processOperator: function(cm, vim, command) { + var inputState = vim.inputState; + if (inputState.operator) { + if (inputState.operator == command.operator) { + // Typing an operator twice like 'dd' makes the operator operate + // linewise + inputState.motion = 'expandToLine'; + inputState.motionArgs = { linewise: true, repeat: 1 }; + this.evalInput(cm, vim); + return; + } else { + // 2 different operators in a row doesn't make sense. + clearInputState(cm); + } + } + inputState.operator = command.operator; + inputState.operatorArgs = copyArgs(command.operatorArgs); + if (command.keys.length > 1) { + inputState.operatorShortcut = command.keys; + } + if (command.exitVisualBlock) { + vim.visualBlock = false; + updateCmSelection(cm); + } + if (vim.visualMode) { + // Operating on a selection in visual mode. We don't need a motion. + this.evalInput(cm, vim); + } + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {import("./types").operatorMotionCommand} command + */ + processOperatorMotion: function(cm, vim, command) { + var visualMode = vim.visualMode; + var operatorMotionArgs = copyArgs(command.operatorMotionArgs); + if (operatorMotionArgs) { + // Operator motions may have special behavior in visual mode. + if (visualMode && operatorMotionArgs.visualLine) { + vim.visualLine = true; + } + } + this.processOperator(cm, vim, command); + if (!visualMode) { + this.processMotion(cm, vim, command); + } + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {import("./types").actionCommand} command + */ + processAction: function(cm, vim, command) { + var inputState = vim.inputState; + var repeat = inputState.getRepeat(); + var repeatIsExplicit = !!repeat; + var actionArgs = /**@type {ActionArgs}*/(copyArgs(command.actionArgs) || {repeat: 1}); + if (inputState.selectedCharacter) { + actionArgs.selectedCharacter = inputState.selectedCharacter; + } + // Actions may or may not have motions and operators. Do these first. + if (command.operator) { + // @ts-ignore + this.processOperator(cm, vim, command); + } + if (command.motion) { + // @ts-ignore + this.processMotion(cm, vim, command); + } + if (command.motion || command.operator) { + this.evalInput(cm, vim); + } + actionArgs.repeat = repeat || 1; + actionArgs.repeatIsExplicit = repeatIsExplicit; + actionArgs.registerName = inputState.registerName; + clearInputState(cm); + vim.lastMotion = null; + if (command.isEdit) { + this.recordLastEdit(vim, inputState, command); + } + actions[command.action](cm, actionArgs, vim); + }, + /** @arg {CodeMirrorV} cm @arg {vimState} vim @arg {import("./types").searchCommand} command*/ + processSearch: function(cm, vim, command) { + if (!cm.getSearchCursor) { + // Search depends on SearchCursor. + return; + } + var forward = command.searchArgs.forward; + var wholeWordOnly = command.searchArgs.wholeWordOnly; + getSearchState(cm).setReversed(!forward); + var promptPrefix = (forward) ? '/' : '?'; + var originalQuery = getSearchState(cm).getQuery(); + var originalScrollPos = cm.getScrollInfo(); + /** @arg {string} query @arg {boolean} ignoreCase @arg {boolean} smartCase */ + function handleQuery(query, ignoreCase, smartCase) { + vimGlobalState.searchHistoryController.pushInput(query); + vimGlobalState.searchHistoryController.reset(); + try { + updateSearchQuery(cm, query, ignoreCase, smartCase); + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + query); + clearInputState(cm); + return; + } + commandDispatcher.processMotion(cm, vim, { + keys: '', + type: 'motion', + motion: 'findNext', + motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist } + }); + } + /** @arg {string} query */ + function onPromptClose(query) { + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + handleQuery(query, true /** ignoreCase */, true /** smartCase */); + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isRecording) { + logSearchQuery(macroModeState, query); + } + } + /** + * @arg {KeyboardEvent&{target:HTMLInputElement}} e + * @arg {any} query + * @arg {(arg0: any) => void} close + */ + function onPromptKeyUp(e, query, close) { + var keyName = vimKeyFromEvent(e), up, offset; + if (keyName == '' || keyName == '') { + up = keyName == '' ? true : false; + offset = e.target ? e.target.selectionEnd : 0; + query = vimGlobalState.searchHistoryController.nextMatch(query, up) || ''; + close(query); + if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length); + } else if (keyName && keyName != '' && keyName != '') { + vimGlobalState.searchHistoryController.reset(); + } + var parsedQuery; + try { + parsedQuery = updateSearchQuery(cm, query, + true /** ignoreCase */, true /** smartCase */); + } catch (e) { + // Swallow bad regexes for incremental search. + } + if (parsedQuery) { + cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30); + } else { + clearSearchHighlight(cm); + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + } + } + /** @arg {KeyboardEvent} e @arg {string} query @arg {(arg0?: string) => void} close */ + function onPromptKeyDown(e, query, close) { + var keyName = vimKeyFromEvent(e); + if (keyName == '' || keyName == '' || keyName == '' || + (keyName == '' && query == '')) { + vimGlobalState.searchHistoryController.pushInput(query); + vimGlobalState.searchHistoryController.reset(); + updateSearchQuery(cm, originalQuery); + clearSearchHighlight(cm); + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + CodeMirror.e_stop(e); + clearInputState(cm); + close(); + cm.focus(); + } else if (keyName == '' || keyName == '') { + CodeMirror.e_stop(e); + } else if (keyName == '') { + // Ctrl-U clears input. + CodeMirror.e_stop(e); + close(''); + } + } + switch (command.searchArgs.querySrc) { + case 'prompt': + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { + let query = macroModeState.replaySearchQueries.shift(); + handleQuery(query, true /** ignoreCase */, false /** smartCase */); + } else { + showPrompt(cm, { + onClose: onPromptClose, + prefix: promptPrefix, + desc: '(JavaScript regexp)', + onKeyUp: onPromptKeyUp, + onKeyDown: onPromptKeyDown + }); + } + break; + case 'wordUnderCursor': + var word = expandWordUnderCursor(cm, {noSymbol: true}); + var isKeyword = true; + if (!word) { + word = expandWordUnderCursor(cm, {noSymbol: false}); + isKeyword = false; + } + if (!word) { + showConfirm(cm, 'No word under cursor'); + clearInputState(cm); + return; + } + let query = cm.getLine(word.start.line).substring(word.start.ch, + word.end.ch); + if (isKeyword && wholeWordOnly) { + query = '\\b' + query + '\\b'; + } else { + query = escapeRegex(query); + } + + // cachedCursor is used to save the old position of the cursor + // when * or # causes vim to seek for the nearest word and shift + // the cursor before entering the motion. + vimGlobalState.jumpList.cachedCursor = cm.getCursor(); + cm.setCursor(word.start); + + handleQuery(query, true /** ignoreCase */, false /** smartCase */); + break; + } + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {import("./types").exCommand | import("./types").keyToExCommand} command + */ + processEx: function(cm, vim, command) { + /**@arg {string} input*/ + function onPromptClose(input) { + // Give the prompt some time to close so that if processCommand shows + // an error, the elements don't overlap. + vimGlobalState.exCommandHistoryController.pushInput(input); + vimGlobalState.exCommandHistoryController.reset(); + exCommandDispatcher.processCommand(cm, input); + if (cm.state.vim) clearInputState(cm); + clearSearchHighlight(cm); + } + /** + * @arg {KeyboardEvent&{target:HTMLInputElement}} e + * @arg {string} input + * @arg {(arg0?: string) => void} close + */ + function onPromptKeyDown(e, input, close) { + var keyName = vimKeyFromEvent(e), up, offset; + if (keyName == '' || keyName == '' || keyName == '' || + (keyName == '' && input == '')) { + vimGlobalState.exCommandHistoryController.pushInput(input); + vimGlobalState.exCommandHistoryController.reset(); + CodeMirror.e_stop(e); + clearInputState(cm); + clearSearchHighlight(cm); + close(); + cm.focus(); + } + if (keyName == '' || keyName == '') { + CodeMirror.e_stop(e); + up = keyName == '' ? true : false; + offset = e.target ? e.target.selectionEnd : 0; + input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || ''; + close(input); + if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length); + } else if (keyName == '') { + // Ctrl-U clears input. + CodeMirror.e_stop(e); + close(''); + } else if (keyName && keyName != '' && keyName != '') { + vimGlobalState.exCommandHistoryController.reset(); + } + } + /** + * @arg {KeyboardEvent&{target:HTMLInputElement}} e + * @arg {any} query + */ + function onPromptKeyUp(e, query) { + var inputStream = new CodeMirror.StringStream(query); + var params = {}; + try { + exCommandDispatcher.parseInput_(cm, inputStream, params); + if (params.commandName != "s") { + clearSearchHighlight(cm); + return; + } + command = exCommandDispatcher.matchCommand_(params.commandName); + exCommandDispatcher.parseCommandArgs_(inputStream, params, command); + if (!params.argString) return; + var regex = parseQuery(params.argString.slice(1), true, true); + if (regex) highlightSearchMatches(cm, regex); + } catch(e) { + } + } + if (command.type == 'keyToEx') { + // Handle user defined Ex to Ex mappings + exCommandDispatcher.processCommand(cm, command.exArgs.input); + } else { + var promptOptions = { + onClose: onPromptClose, + onKeyDown: onPromptKeyDown, + onKeyUp: onPromptKeyUp, + prefix: ':', + }; + if (vim.visualMode) { + promptOptions.value = '\'<,\'>'; + promptOptions.selectValueOnOpen = false; + } + showPrompt(cm, promptOptions); + } + }, + /**@arg {CodeMirrorV} cm @arg {vimState} vim */ + evalInput: function(cm, vim) { + // If the motion command is set, execute both the operator and motion. + // Otherwise return. + var inputState = vim.inputState; + var motion = inputState.motion; + /** @type {MotionArgs}*/ + var motionArgs = inputState.motionArgs || { repeat: 1}; + var operator = inputState.operator; + /** @type {OperatorArgs}*/ + var operatorArgs = inputState.operatorArgs || {}; + var registerName = inputState.registerName; + var sel = vim.sel; + // TODO: Make sure cm and vim selections are identical outside visual mode. + var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head')); + var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor')); + var oldHead = copyCursor(origHead); + var oldAnchor = copyCursor(origAnchor); + var newHead, newAnchor; + var repeat; + if (operator) { + this.recordLastEdit(vim, inputState); + } + if (inputState.repeatOverride !== undefined) { + // If repeatOverride is specified, that takes precedence over the + // input state's repeat. Used by Ex mode and can be user defined. + repeat = inputState.repeatOverride; + } else { + repeat = inputState.getRepeat(); + } + if (repeat > 0 && motionArgs.explicitRepeat) { + motionArgs.repeatIsExplicit = true; + } else if (motionArgs.noRepeat || + (!motionArgs.explicitRepeat && repeat === 0)) { + repeat = 1; + motionArgs.repeatIsExplicit = false; + } + if (inputState.selectedCharacter) { + // If there is a character input, stick it in all of the arg arrays. + motionArgs.selectedCharacter = operatorArgs.selectedCharacter = + inputState.selectedCharacter; + } + motionArgs.repeat = repeat; + clearInputState(cm); + if (motion) { + var motionResult = motions[motion](cm, origHead, motionArgs, vim, inputState); + vim.lastMotion = motions[motion]; + if (!motionResult) { + return; + } + if (motionArgs.toJumplist) { + var jumpList = vimGlobalState.jumpList; + // if the current motion is # or *, use cachedCursor + var cachedCursor = jumpList.cachedCursor; + if (cachedCursor) { + // @ts-ignore + recordJumpPosition(cm, cachedCursor, motionResult); + delete jumpList.cachedCursor; + } else { + // @ts-ignore + recordJumpPosition(cm, origHead, motionResult); + } + } + if (motionResult instanceof Array) { + newAnchor = motionResult[0]; + newHead = motionResult[1]; + } else { + newHead = motionResult; + } + // TODO: Handle null returns from motion commands better. + if (!newHead) { + newHead = copyCursor(origHead); + } + if (vim.visualMode) { + if (!(vim.visualBlock && newHead.ch === Infinity)) { + newHead = clipCursorToContent(cm, newHead, oldHead); + } + if (newAnchor) { + newAnchor = clipCursorToContent(cm, newAnchor); + } + newAnchor = newAnchor || oldAnchor; + sel.anchor = newAnchor; + sel.head = newHead; + updateCmSelection(cm); + updateMark(cm, vim, '<', + cursorIsBefore(newAnchor, newHead) ? newAnchor + : newHead); + updateMark(cm, vim, '>', + cursorIsBefore(newAnchor, newHead) ? newHead + : newAnchor); + } else if (!operator) { + newHead = clipCursorToContent(cm, newHead, oldHead); + cm.setCursor(newHead.line, newHead.ch); + } + } + if (operator) { + if (operatorArgs.lastSel) { + // Replaying a visual mode operation + newAnchor = oldAnchor; + var lastSel = operatorArgs.lastSel; + var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line); + var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch); + if (lastSel.visualLine) { + // Linewise Visual mode: The same number of lines. + newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch); + } else if (lastSel.visualBlock) { + // Blockwise Visual mode: The same number of lines and columns. + newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset); + } else if (lastSel.head.line == lastSel.anchor.line) { + // Normal Visual mode within one line: The same number of characters. + newHead = new Pos(oldAnchor.line, oldAnchor.ch + chOffset); + } else { + // Normal Visual mode with several lines: The same number of lines, in the + // last line the same number of characters as in the last line the last time. + newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch); + } + vim.visualMode = true; + vim.visualLine = lastSel.visualLine; + vim.visualBlock = lastSel.visualBlock; + sel = vim.sel = { + anchor: newAnchor, + head: newHead + }; + updateCmSelection(cm); + } else if (vim.visualMode) { + operatorArgs.lastSel = { + anchor: copyCursor(sel.anchor), + head: copyCursor(sel.head), + visualBlock: vim.visualBlock, + visualLine: vim.visualLine + }; + } + var curStart, curEnd, linewise; + /** @type {'block'|'line'|'char'}*/ var mode; + var cmSel; + if (vim.visualMode) { + // Init visual op + curStart = cursorMin(sel.head, sel.anchor); + curEnd = cursorMax(sel.head, sel.anchor); + linewise = vim.visualLine || operatorArgs.linewise; + mode = vim.visualBlock ? 'block' : + linewise ? 'line' : + 'char'; + var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd); + cmSel = makeCmSelection(cm, { + anchor: newPositions.start, + head: newPositions.end + }, mode); + if (linewise) { + var ranges = cmSel.ranges; + if (mode == 'block') { + // Linewise operators in visual block mode extend to end of line + for (var i = 0; i < ranges.length; i++) { + ranges[i].head.ch = lineLength(cm, ranges[i].head.line); + } + } else if (mode == 'line') { + ranges[0].head = new Pos(ranges[0].head.line + 1, 0); + } + } + } else { + // Init motion op + curStart = copyCursor(newAnchor || oldAnchor); + curEnd = copyCursor(newHead || oldHead); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curStart; + curStart = curEnd; + curEnd = tmp; + } + linewise = motionArgs.linewise || operatorArgs.linewise; + if (linewise) { + // Expand selection to entire line. + expandSelectionToLine(cm, curStart, curEnd); + } else if (motionArgs.forward) { + // Clip to trailing newlines only if the motion goes forward. + clipToLine(cm, curStart, curEnd); + } + mode = 'char'; + var exclusive = !motionArgs.inclusive || linewise; + var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd); + cmSel = makeCmSelection(cm, { + anchor: newPositions.start, + head: newPositions.end + }, mode, exclusive); + } + cm.setSelections(cmSel.ranges, cmSel.primary); + vim.lastMotion = null; + operatorArgs.repeat = repeat; // For indent in visual mode. + operatorArgs.registerName = registerName; + // Keep track of linewise as it affects how paste and change behave. + operatorArgs.linewise = linewise; + var operatorMoveTo = operators[operator]( + cm, operatorArgs, cmSel.ranges, oldAnchor, newHead); + if (vim.visualMode) { + exitVisualMode(cm, operatorMoveTo != null); + } + if (operatorMoveTo) { + cm.setCursor(operatorMoveTo); + } + } + }, + /**@arg {vimState} vim @arg {InputStateInterface} inputState, @arg {import("./types").actionCommand} [actionCommand] */ + recordLastEdit: function(vim, inputState, actionCommand) { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { return; } + vim.lastEditInputState = inputState; + vim.lastEditActionCommand = actionCommand; + macroModeState.lastInsertModeChanges.changes = []; + macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false; + macroModeState.lastInsertModeChanges.visualBlock = vim.visualBlock ? vim.sel.head.line - vim.sel.anchor.line : 0; + } + }; + + /** + * All of the functions below return Cursor objects. + * @type {import("./types").vimMotions}} + */ + var motions = { + moveToTopLine: function(cm, _head, motionArgs) { + var line = getUserVisibleLines(cm).top + motionArgs.repeat -1; + return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + moveToMiddleLine: function(cm) { + var range = getUserVisibleLines(cm); + var line = Math.floor((range.top + range.bottom) * 0.5); + return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + moveToBottomLine: function(cm, _head, motionArgs) { + var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1; + return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + expandToLine: function(_cm, head, motionArgs) { + // Expands forward to end of line, and then to next line if repeat is + // >1. Does not handle backward motion! + var cur = head; + return new Pos(cur.line + motionArgs.repeat - 1, Infinity); + }, + findNext: function(cm, _head, motionArgs) { + var state = getSearchState(cm); + var query = state.getQuery(); + if (!query) { + return; + } + var prev = !motionArgs.forward; + // If search is initiated with ? instead of /, negate direction. + prev = (state.isReversed()) ? !prev : prev; + highlightSearchMatches(cm, query); + return findNext(cm, prev/** prev */, query, motionArgs.repeat); + }, + /** + * Find and select the next occurrence of the search query. If the cursor is currently + * within a match, then find and select the current match. Otherwise, find the next occurrence in the + * appropriate direction. + * + * This differs from `findNext` in the following ways: + * + * 1. Instead of only returning the "from", this returns a "from", "to" range. + * 2. If the cursor is currently inside a search match, this selects the current match + * instead of the next match. + * 3. If there is no associated operator, this will turn on visual mode. + */ + findAndSelectNextInclusive: function(cm, _head, motionArgs, vim, prevInputState) { + var state = getSearchState(cm); + var query = state.getQuery(); + + if (!query) { + return; + } + + var prev = !motionArgs.forward; + prev = (state.isReversed()) ? !prev : prev; + + // next: [from, to] | null + var next = findNextFromAndToInclusive(cm, prev, query, motionArgs.repeat, vim); + + // No matches. + if (!next) { + return; + } + + // If there's an operator that will be executed, return the selection. + if (prevInputState.operator) { + return next; + } + + // At this point, we know that there is no accompanying operator -- let's + // deal with visual mode in order to select an appropriate match. + + var from = next[0]; + // For whatever reason, when we use the "to" as returned by searchcursor.js directly, + // the resulting selection is extended by 1 char. Let's shrink it so that only the + // match is selected. + var to = new Pos(next[1].line, next[1].ch - 1); + + if (vim.visualMode) { + // If we were in visualLine or visualBlock mode, get out of it. + if (vim.visualLine || vim.visualBlock) { + vim.visualLine = false; + vim.visualBlock = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: ""}); + } + + // If we're currently in visual mode, we should extend the selection to include + // the search result. + var anchor = vim.sel.anchor; + if (anchor) { + if (state.isReversed()) { + if (motionArgs.forward) { + return [anchor, from]; + } + + return [anchor, to]; + } else { + if (motionArgs.forward) { + return [anchor, to]; + } + + return [anchor, from]; + } + } + } else { + // Let's turn visual mode on. + vim.visualMode = true; + vim.visualLine = false; + vim.visualBlock = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: ""}); + } + + return prev ? [to, from] : [from, to]; + }, + goToMark: function(cm, _head, motionArgs, vim) { + var pos = getMarkPos(cm, vim, motionArgs.selectedCharacter || ""); + if (pos) { + return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos; + } + return null; + }, + moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) { + if (vim.visualBlock && motionArgs.sameLine) { + var sel = vim.sel; + return [ + clipCursorToContent(cm, new Pos(sel.anchor.line, sel.head.ch)), + clipCursorToContent(cm, new Pos(sel.head.line, sel.anchor.ch)) + ]; + } else { + return ([vim.sel.head, vim.sel.anchor]); + } + }, + jumpToMark: function(cm, head, motionArgs, vim) { + var best = head; + for (var i = 0; i < motionArgs.repeat; i++) { + var cursor = best; + for (var key in vim.marks) { + if (!isLowerCase(key)) { + continue; + } + var mark = vim.marks[key].find(); + var isWrongDirection = (motionArgs.forward) ? + // @ts-ignore + cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark); + + if (isWrongDirection) { + continue; + } + // @ts-ignore + if (motionArgs.linewise && (mark.line == cursor.line)) { + continue; + } + + var equal = cursorEqual(cursor, best); + var between = (motionArgs.forward) ? + // @ts-ignore + cursorIsBetween(cursor, mark, best) : + // @ts-ignore + cursorIsBetween(best, mark, cursor); + + if (equal || between) { + // @ts-ignore + best = mark; + } + } + } + + if (motionArgs.linewise) { + // Vim places the cursor on the first non-whitespace character of + // the line if there is one, else it places the cursor at the end + // of the line, regardless of whether a mark was found. + best = new Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line))); + } + return best; + }, + moveByCharacters: function(_cm, head, motionArgs) { + var cur = head; + var repeat = motionArgs.repeat; + var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat; + return new Pos(cur.line, ch); + }, + moveByLines: function(cm, head, motionArgs, vim) { + var cur = head; + var endCh = cur.ch; + // Depending what our last motion was, we may want to do different + // things. If our last motion was moving vertically, we want to + // preserve the HPos from our last horizontal move. If our last motion + // was going to the end of a line, moving vertically we should go to + // the end of the line, etc. + switch (vim.lastMotion) { + case this.moveByLines: + case this.moveByDisplayLines: + case this.moveByScroll: + case this.moveToColumn: + case this.moveToEol: + endCh = vim.lastHPos; + break; + default: + vim.lastHPos = endCh; + } + var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0); + var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat; + var first = cm.firstLine(); + var last = cm.lastLine(); + var posV = cm.findPosV(cur, (motionArgs.forward ? repeat : -repeat), 'line', vim.lastHSPos); + var hasMarkedText = motionArgs.forward ? posV.line > line : posV.line < line; + if (hasMarkedText) { + line = posV.line; + endCh = posV.ch; + } + // Vim go to line begin or line end when cursor at first/last line and + // move to previous/next line is triggered. + if (line < first && cur.line == first){ + return this.moveToStartOfLine(cm, head, motionArgs, vim); + } else if (line > last && cur.line == last){ + return moveToEol(cm, head, motionArgs, vim, true); + } + if (motionArgs.toFirstChar){ + endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line)); + vim.lastHPos = endCh; + } + vim.lastHSPos = cm.charCoords(new Pos(line, endCh),'div').left; + return new Pos(line, endCh); + }, + moveByDisplayLines: function(cm, head, motionArgs, vim) { + var cur = head; + switch (vim.lastMotion) { + case this.moveByDisplayLines: + case this.moveByScroll: + case this.moveByLines: + case this.moveToColumn: + case this.moveToEol: + break; + default: + vim.lastHSPos = cm.charCoords(cur,'div').left; + } + var repeat = motionArgs.repeat; + var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos); + if (res.hitSide) { + if (motionArgs.forward) { + var lastCharCoords = cm.charCoords(res, 'div'); + var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos }; + res = cm.coordsChar(goalCoords, 'div'); + } else { + var resCoords = cm.charCoords(new Pos(cm.firstLine(), 0), 'div'); + resCoords.left = vim.lastHSPos; + res = cm.coordsChar(resCoords, 'div'); + } + } + vim.lastHPos = res.ch; + return res; + }, + moveByPage: function(cm, head, motionArgs) { + // CodeMirror only exposes functions that move the cursor page down, so + // doing this bad hack to move the cursor and move it back. evalInput + // will move the cursor to where it should be in the end. + var curStart = head; + var repeat = motionArgs.repeat; + return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page'); + }, + moveByParagraph: function(cm, head, motionArgs) { + var dir = motionArgs.forward ? 1 : -1; + return findParagraph(cm, head, motionArgs.repeat, dir).start; + }, + moveBySentence: function(cm, head, motionArgs) { + var dir = motionArgs.forward ? 1 : -1; + return findSentence(cm, head, motionArgs.repeat, dir); + }, + moveByScroll: function(cm, head, motionArgs, vim) { + var scrollbox = cm.getScrollInfo(); + var curEnd = null; + var repeat = motionArgs.repeat; + if (!repeat) { + repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight()); + } + var orig = cm.charCoords(head, 'local'); + motionArgs.repeat = repeat; + curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim); + if (!curEnd) { + return null; + } + var dest = cm.charCoords(curEnd, 'local'); + cm.scrollTo(null, scrollbox.top + dest.top - orig.top); + return curEnd; + }, + moveByWords: function(cm, head, motionArgs) { + return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward, + !!motionArgs.wordEnd, !!motionArgs.bigWord); + }, + moveTillCharacter: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + var curEnd = moveToCharacter(cm, repeat, motionArgs.forward, + motionArgs.selectedCharacter, head); + var increment = motionArgs.forward ? -1 : 1; + recordLastCharacterSearch(increment, motionArgs); + if (!curEnd) return null; + curEnd.ch += increment; + return curEnd; + }, + moveToCharacter: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + recordLastCharacterSearch(0, motionArgs); + return moveToCharacter(cm, repeat, motionArgs.forward, + motionArgs.selectedCharacter, head) || head; + }, + moveToSymbol: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + return motionArgs.selectedCharacter + && findSymbol(cm, repeat, motionArgs.forward, + motionArgs.selectedCharacter) || head; + }, + moveToColumn: function(cm, head, motionArgs, vim) { + var repeat = motionArgs.repeat; + // repeat is equivalent to which column we want to move to! + vim.lastHPos = repeat - 1; + vim.lastHSPos = cm.charCoords(head,'div').left; + return moveToColumn(cm, repeat); + }, + moveToEol: function(cm, head, motionArgs, vim) { + return moveToEol(cm, head, motionArgs, vim, false); + }, + moveToFirstNonWhiteSpaceCharacter: function(cm, head) { + // Go to the start of the line where the text begins, or the end for + // whitespace-only lines + var cursor = head; + return new Pos(cursor.line, + findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line))); + }, + moveToMatchedSymbol: function(cm, head) { + var cursor = head; + var line = cursor.line; + var ch = cursor.ch; + var lineText = cm.getLine(line); + var symbol; + for (; ch < lineText.length; ch++) { + symbol = lineText.charAt(ch); + if (symbol && isMatchableSymbol(symbol)) { + var style = cm.getTokenTypeAt(new Pos(line, ch + 1)); + if (style !== "string" && style !== "comment") { + break; + } + } + } + if (ch < lineText.length) { + // Only include angle brackets in analysis if they are being matched. + var re = (symbol === '<' || symbol === '>') ? /[(){}[\]<>]/ : /[(){}[\]]/; + var matched = cm.findMatchingBracket(new Pos(line, ch), {bracketRegex: re}); + return matched.to; + } else { + return cursor; + } + }, + moveToStartOfLine: function(_cm, head) { + return new Pos(head.line, 0); + }, + moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) { + var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine(); + if (motionArgs.repeatIsExplicit) { + lineNum = motionArgs.repeat - cm.getOption('firstLineNumber'); + } + return new Pos(lineNum, + findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum))); + }, + moveToStartOfDisplayLine: function(cm) { + cm.execCommand("goLineLeft"); + return cm.getCursor(); + }, + moveToEndOfDisplayLine: function(cm) { + cm.execCommand("goLineRight"); + var head = cm.getCursor(); + if (head.sticky == "before") head.ch--; + return head; + }, + textObjectManipulation: function(cm, head, motionArgs, vim) { + // TODO: lots of possible exceptions that can be thrown here. Try da( + // outside of a () block. + /** @type{Object} */ + var mirroredPairs = {'(': ')', ')': '(', + '{': '}', '}': '{', + '[': ']', ']': '[', + '<': '>', '>': '<'}; + /** @type{Object} */ + var selfPaired = {'\'': true, '"': true, '`': true}; + + var character = motionArgs.selectedCharacter || ""; + // 'b' refers to '()' block. + // 'B' refers to '{}' block. + if (character == 'b') { + character = '('; + } else if (character == 'B') { + character = '{'; + } + + // Inclusive is the difference between a and i + // TODO: Instead of using the additional text object map to perform text + // object operations, merge the map into the defaultKeyMap and use + // motionArgs to define behavior. Define separate entries for 'aw', + // 'iw', 'a[', 'i[', etc. + var inclusive = !motionArgs.textObjectInner; + + var tmp, move; + if (mirroredPairs[character]) { + move = true; + tmp = selectCompanionObject(cm, head, character, inclusive); + if (!tmp) { + var sc = cm.getSearchCursor(new RegExp("\\" + character, "g"), head); + if (sc.find()) { + // @ts-ignore + tmp = selectCompanionObject(cm, sc.from(), character, inclusive); + } + } + } else if (selfPaired[character]) { + move = true; + tmp = findBeginningAndEnd(cm, head, character, inclusive); + } else if (character === 'W' || character === 'w') { + var repeat = motionArgs.repeat || 1; + while (repeat-- > 0) { + var repeated = expandWordUnderCursor(cm, { + inclusive, + innerWord: !inclusive, + bigWord: character === 'W', + noSymbol: character === 'W', + multiline: true + }, tmp && tmp.end); + if (repeated) { + if (!tmp) tmp = repeated; + tmp.end = repeated.end; + } + } + } else if (character === 'p') { + tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive); + motionArgs.linewise = true; + if (vim.visualMode) { + if (!vim.visualLine) { vim.visualLine = true; } + } else { + var operatorArgs = vim.inputState.operatorArgs; + if (operatorArgs) { operatorArgs.linewise = true; } + tmp.end.line--; + } + } else if (character === 't') { + tmp = expandTagUnderCursor(cm, head, inclusive); + } else if (character === 's') { + // account for cursor on end of sentence symbol + var content = cm.getLine(head.line); + if (head.ch > 0 && isEndOfSentenceSymbol(content[head.ch])) { + head.ch -= 1; + } + var end = getSentence(cm, head, motionArgs.repeat, 1, inclusive); + var start = getSentence(cm, head, motionArgs.repeat, -1, inclusive); + // closer vim behaviour, 'a' only takes the space after the sentence if there is one before and after + if (isWhiteSpaceString(cm.getLine(start.line)[start.ch]) + && isWhiteSpaceString(cm.getLine(end.line)[end.ch -1])) { + start = {line: start.line, ch: start.ch + 1}; + } + tmp = {start: start, end: end}; + } + + if (!tmp) { + // No valid text object, don't move. + return null; + } + + if (!cm.state.vim.visualMode) { + return [tmp.start, tmp.end]; + } else { + return expandSelection(cm, tmp.start, tmp.end, move); + } + }, + + repeatLastCharacterSearch: function(cm, head, motionArgs) { + var lastSearch = vimGlobalState.lastCharacterSearch; + var repeat = motionArgs.repeat; + var forward = motionArgs.forward === lastSearch.forward; + var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1); + cm.moveH(-increment, 'char'); + motionArgs.inclusive = forward ? true : false; + var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter); + if (!curEnd) { + cm.moveH(increment, 'char'); + return head; + } + curEnd.ch += increment; + return curEnd; + } + }; + + /** @arg {string} name @arg {import("./types").MotionFn} fn */ + function defineMotion(name, fn) { + motions[name] = fn; + } + + /** @arg {string} val @arg {number} times */ + function fillArray(val, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(val); + } + return arr; + } + /** + * An operator acts on a text selection. It receives the list of selections + * as input. The corresponding CodeMirror selection is guaranteed to + * match the input selection. + */ + /** @type {import("./types").vimOperators} */ + var operators = { + change: function(cm, args, ranges) { + var finalHead, text; + var vim = cm.state.vim; + var anchor = ranges[0].anchor, + head = ranges[0].head; + if (!vim.visualMode) { + text = cm.getRange(anchor, head); + var lastState = vim.lastEditInputState; + if (lastState?.motion == "moveByWords" && !isWhiteSpaceString(text)) { + // Exclude trailing whitespace if the range is not all whitespace. + var match = (/\s+$/).exec(text); + if (match && lastState.motionArgs && lastState.motionArgs.forward) { + head = offsetCursor(head, 0, - match[0].length); + text = text.slice(0, - match[0].length); + } + } + if (args.linewise) { + anchor = new Pos(anchor.line, findFirstNonWhiteSpaceCharacter(cm.getLine(anchor.line))); + if (head.line > anchor.line) { + head = new Pos(head.line - 1, Number.MAX_VALUE); + } + } + cm.replaceRange('', anchor, head); + finalHead = anchor; + } else if (args.fullLine) { + head.ch = Number.MAX_VALUE; + head.line--; + cm.setSelection(anchor, head); + text = cm.getSelection(); + cm.replaceSelection(""); + finalHead = anchor; + } else { + text = cm.getSelection(); + var replacement = fillArray('', ranges.length); + cm.replaceSelections(replacement); + finalHead = cursorMin(ranges[0].head, ranges[0].anchor); + } + vimGlobalState.registerController.pushText( + args.registerName, 'change', text, + args.linewise, ranges.length > 1); + actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim); + }, + delete: function(cm, args, ranges) { + var finalHead, text; + var vim = cm.state.vim; + if (!vim.visualBlock) { + var anchor = ranges[0].anchor, + head = ranges[0].head; + if (args.linewise && + head.line != cm.firstLine() && + anchor.line == cm.lastLine() && + anchor.line == head.line - 1) { + // Special case for dd on last line (and first line). + if (anchor.line == cm.firstLine()) { + anchor.ch = 0; + } else { + anchor = new Pos(anchor.line - 1, lineLength(cm, anchor.line - 1)); + } + } + text = cm.getRange(anchor, head); + cm.replaceRange('', anchor, head); + finalHead = anchor; + if (args.linewise) { + finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor); + } + } else { + text = cm.getSelection(); + var replacement = fillArray('', ranges.length); + cm.replaceSelections(replacement); + finalHead = cursorMin(ranges[0].head, ranges[0].anchor); + } + vimGlobalState.registerController.pushText( + args.registerName, 'delete', text, + args.linewise, vim.visualBlock); + return clipCursorToContent(cm, finalHead); + }, + indent: function(cm, args, ranges) { + var vim = cm.state.vim; + var repeat = (vim.visualMode) ? (args.repeat || 0) : 1; + if (cm.indentMore) { + for (var j = 0; j < repeat; j++) { + if (args.indentRight) cm.indentMore(); + else cm.indentLess(); + } + } else { + var startLine = ranges[0].anchor.line; + var endLine = vim.visualBlock ? + ranges[ranges.length - 1].anchor.line : + ranges[0].head.line; + // In visual mode, n> shifts the selection right n times, instead of + // shifting n lines right once. + if (args.linewise) { + // The only way to delete a newline is to delete until the start of + // the next line, so in linewise mode evalInput will include the next + // line. We don't want this in indent, so we go back a line. + endLine--; + } + for (var i = startLine; i <= endLine; i++) { + for (var j = 0; j < repeat; j++) { + cm.indentLine(i, args.indentRight); + } + } + } + return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor); + }, + indentAuto: function(cm, _args, ranges) { + cm.execCommand("indentAuto"); + return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor); + }, + hardWrap: function(cm, operatorArgs, ranges, oldAnchor) { + if (!cm.hardWrap) return; + var from = ranges[0].anchor.line; + var to = ranges[0].head.line; + if (operatorArgs.linewise) to--; + var endRow = cm.hardWrap({from: from, to: to}); + if (endRow > from && operatorArgs.linewise) endRow--; + return operatorArgs.keepCursor ? oldAnchor : new Pos(endRow, 0); + }, + changeCase: function(cm, args, ranges, oldAnchor, newHead) { + var selections = cm.getSelections(); + var swapped = []; + var toLower = args.toLower; + for (var j = 0; j < selections.length; j++) { + var toSwap = selections[j]; + var text = ''; + if (toLower === true) { + text = toSwap.toLowerCase(); + } else if (toLower === false) { + text = toSwap.toUpperCase(); + } else { + for (var i = 0; i < toSwap.length; i++) { + var character = toSwap.charAt(i); + text += isUpperCase(character) ? character.toLowerCase() : + character.toUpperCase(); + } + } + swapped.push(text); + } + cm.replaceSelections(swapped); + if (args.shouldMoveCursor){ + return newHead; + } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) { + return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor); + } else if (args.linewise){ + return oldAnchor; + } else { + return cursorMin(ranges[0].anchor, ranges[0].head); + } + }, + yank: function(cm, args, ranges, oldAnchor) { + var vim = cm.state.vim; + var text = cm.getSelection(); + var endPos = vim.visualMode + ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor) + : oldAnchor; + vimGlobalState.registerController.pushText( + args.registerName, 'yank', + text, args.linewise, vim.visualBlock); + return endPos; + } + }; + + /** @arg {string} name @arg {import("./types").OperatorFn} fn */ + function defineOperator(name, fn) { + operators[name] = fn; + } + + /** @type {import("./types").vimActions} */ + var actions = { + jumpListWalk: function(cm, actionArgs, vim) { + if (vim.visualMode) { + return; + } + var repeat = actionArgs.repeat || 1; + var forward = actionArgs.forward; + var jumpList = vimGlobalState.jumpList; + + var mark = jumpList.move(cm, forward ? repeat : -repeat); + var markPos = mark ? mark.find() : undefined; + markPos = markPos ? markPos : cm.getCursor(); + cm.setCursor(markPos); + }, + scroll: function(cm, actionArgs, vim) { + if (vim.visualMode) { + return; + } + var repeat = actionArgs.repeat || 1; + var lineHeight = cm.defaultTextHeight(); + var top = cm.getScrollInfo().top; + var delta = lineHeight * repeat; + var newPos = actionArgs.forward ? top + delta : top - delta; + var cursor = copyCursor(cm.getCursor()); + var cursorCoords = cm.charCoords(cursor, 'local'); + if (actionArgs.forward) { + if (newPos > cursorCoords.top) { + cursor.line += (newPos - cursorCoords.top) / lineHeight; + cursor.line = Math.ceil(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, 'local'); + cm.scrollTo(null, cursorCoords.top); + } else { + // Cursor stays within bounds. Just reposition the scroll window. + cm.scrollTo(null, newPos); + } + } else { + var newBottom = newPos + cm.getScrollInfo().clientHeight; + if (newBottom < cursorCoords.bottom) { + cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight; + cursor.line = Math.floor(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, 'local'); + cm.scrollTo( + null, cursorCoords.bottom - cm.getScrollInfo().clientHeight); + } else { + // Cursor stays within bounds. Just reposition the scroll window. + cm.scrollTo(null, newPos); + } + } + }, + scrollToCursor: function(cm, actionArgs) { + var lineNum = cm.getCursor().line; + var charCoords = cm.charCoords(new Pos(lineNum, 0), 'local'); + var height = cm.getScrollInfo().clientHeight; + var y = charCoords.top; + switch (actionArgs.position) { + case 'center': y = charCoords.bottom - height / 2; + break; + case 'bottom': + var lineLastCharPos = new Pos(lineNum, cm.getLine(lineNum).length - 1); + var lineLastCharCoords = cm.charCoords(lineLastCharPos, 'local'); + var lineHeight = lineLastCharCoords.bottom - y; + y = y - height + lineHeight; + break; + } + cm.scrollTo(null, y); + }, + replayMacro: function(cm, actionArgs, vim) { + var registerName = actionArgs.selectedCharacter || ""; + var repeat = actionArgs.repeat || 1; + var macroModeState = vimGlobalState.macroModeState; + if (registerName == '@') { + registerName = macroModeState.latestRegister; + } else { + macroModeState.latestRegister = registerName; + } + while(repeat--){ + executeMacroRegister(cm, vim, macroModeState, registerName); + } + }, + enterMacroRecordMode: function(cm, actionArgs) { + var macroModeState = vimGlobalState.macroModeState; + var registerName = actionArgs.selectedCharacter; + if (vimGlobalState.registerController.isValidRegister(registerName)) { + macroModeState.enterMacroRecordMode(cm, registerName); + } + }, + toggleOverwrite: function(cm) { + if (!cm.state.overwrite) { + cm.toggleOverwrite(true); + cm.setOption('keyMap', 'vim-replace'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"}); + } else { + cm.toggleOverwrite(false); + cm.setOption('keyMap', 'vim-insert'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"}); + } + }, + enterInsertMode: function(cm, actionArgs, vim) { + if (cm.getOption('readOnly')) { return; } + vim.insertMode = true; + vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1; + var insertAt = (actionArgs) ? actionArgs.insertAt : null; + var sel = vim.sel; + var head = actionArgs.head || cm.getCursor('head'); + var height = cm.listSelections().length; + if (insertAt == 'eol') { + head = new Pos(head.line, lineLength(cm, head.line)); + } else if (insertAt == 'bol') { + head = new Pos(head.line, 0); + } else if (insertAt == 'charAfter') { + var newPosition = updateSelectionForSurrogateCharacters(cm, head, offsetCursor(head, 0, 1)); + head = newPosition.end; + } else if (insertAt == 'firstNonBlank') { + var newPosition = updateSelectionForSurrogateCharacters(cm, head, motions.moveToFirstNonWhiteSpaceCharacter(cm, head)); + head = newPosition.end; + } else if (insertAt == 'startOfSelectedArea') { + if (!vim.visualMode) + return; + if (!vim.visualBlock) { + if (sel.head.line < sel.anchor.line) { + head = sel.head; + } else { + head = new Pos(sel.anchor.line, 0); + } + } else { + head = new Pos( + Math.min(sel.head.line, sel.anchor.line), + Math.min(sel.head.ch, sel.anchor.ch)); + height = Math.abs(sel.head.line - sel.anchor.line) + 1; + } + } else if (insertAt == 'endOfSelectedArea') { + if (!vim.visualMode) + return; + if (!vim.visualBlock) { + if (sel.head.line >= sel.anchor.line) { + head = offsetCursor(sel.head, 0, 1); + } else { + head = new Pos(sel.anchor.line, 0); + } + } else { + head = new Pos( + Math.min(sel.head.line, sel.anchor.line), + Math.max(sel.head.ch, sel.anchor.ch) + 1); + height = Math.abs(sel.head.line - sel.anchor.line) + 1; + } + } else if (insertAt == 'inplace') { + if (vim.visualMode){ + return; + } + } else if (insertAt == 'lastEdit') { + head = getLastEditPos(cm) || head; + } + cm.setOption('disableInput', false); + if (actionArgs && actionArgs.replace) { + // Handle Replace-mode as a special case of insert mode. + cm.toggleOverwrite(true); + cm.setOption('keyMap', 'vim-replace'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"}); + } else { + cm.toggleOverwrite(false); + cm.setOption('keyMap', 'vim-insert'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"}); + } + if (!vimGlobalState.macroModeState.isPlaying) { + // Only record if not replaying. + cm.on('change', onChange); + if (vim.insertEnd) vim.insertEnd.clear(); + vim.insertEnd = cm.setBookmark(head, {insertLeft: true}); + CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); + } + if (vim.visualMode) { + exitVisualMode(cm); + } + selectForInsert(cm, head, height); + }, + toggleVisualMode: function(cm, actionArgs, vim) { + var repeat = actionArgs.repeat; + var anchor = cm.getCursor(); + var head; + // TODO: The repeat should actually select number of characters/lines + // equal to the repeat times the size of the previous visual + // operation. + if (!vim.visualMode) { + // Entering visual mode + vim.visualMode = true; + vim.visualLine = !!actionArgs.linewise; + vim.visualBlock = !!actionArgs.blockwise; + head = clipCursorToContent( + cm, new Pos(anchor.line, anchor.ch + repeat - 1)); + var newPosition = updateSelectionForSurrogateCharacters(cm, anchor, head); + vim.sel = { + anchor: newPosition.start, + head: newPosition.end + }; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""}); + updateCmSelection(cm); + updateMark(cm, vim, '<', cursorMin(anchor, head)); + updateMark(cm, vim, '>', cursorMax(anchor, head)); + } else if (vim.visualLine != !!actionArgs.linewise || + vim.visualBlock != !!actionArgs.blockwise) { + // Toggling between modes + vim.visualLine = !!actionArgs.linewise; + vim.visualBlock = !!actionArgs.blockwise; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""}); + updateCmSelection(cm); + } else { + exitVisualMode(cm); + } + }, + reselectLastSelection: function(cm, _actionArgs, vim) { + var lastSelection = vim.lastSelection; + if (vim.visualMode) { + updateLastSelection(cm, vim); + } + if (lastSelection) { + var anchor = lastSelection.anchorMark.find(); + var head = lastSelection.headMark.find(); + if (!anchor || !head) { + // If the marks have been destroyed due to edits, do nothing. + return; + } + vim.sel = { + anchor: anchor, + head: head + }; + vim.visualMode = true; + vim.visualLine = lastSelection.visualLine; + vim.visualBlock = lastSelection.visualBlock; + updateCmSelection(cm); + updateMark(cm, vim, '<', cursorMin(anchor, head)); + updateMark(cm, vim, '>', cursorMax(anchor, head)); + CodeMirror.signal(cm, 'vim-mode-change', { + mode: 'visual', + subMode: vim.visualLine ? 'linewise' : + vim.visualBlock ? 'blockwise' : ''}); + } + }, + joinLines: function(cm, actionArgs, vim) { + var curStart, curEnd; + if (vim.visualMode) { + curStart = cm.getCursor('anchor'); + curEnd = cm.getCursor('head'); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curEnd; + curEnd = curStart; + curStart = tmp; + } + curEnd.ch = lineLength(cm, curEnd.line) - 1; + } else { + // Repeat is the number of lines to join. Minimum 2 lines. + var repeat = Math.max(actionArgs.repeat, 2); + curStart = cm.getCursor(); + curEnd = clipCursorToContent(cm, new Pos(curStart.line + repeat - 1, + Infinity)); + } + var finalCh = 0; + for (var i = curStart.line; i < curEnd.line; i++) { + finalCh = lineLength(cm, curStart.line); + var text = ''; + var nextStartCh = 0; + if (!actionArgs.keepSpaces) { + var nextLine = cm.getLine(curStart.line + 1); + nextStartCh = nextLine.search(/\S/); + if (nextStartCh == -1) { + nextStartCh = nextLine.length; + } else { + text = " "; + } + } + cm.replaceRange(text, + new Pos(curStart.line, finalCh), + new Pos(curStart.line + 1, nextStartCh)); + } + var curFinalPos = clipCursorToContent(cm, new Pos(curStart.line, finalCh)); + if (vim.visualMode) { + exitVisualMode(cm, false); + } + cm.setCursor(curFinalPos); + }, + newLineAndEnterInsertMode: function(cm, actionArgs, vim) { + vim.insertMode = true; + var insertAt = copyCursor(cm.getCursor()); + if (insertAt.line === cm.firstLine() && !actionArgs.after) { + // Special case for inserting newline before start of document. + cm.replaceRange('\n', new Pos(cm.firstLine(), 0)); + cm.setCursor(cm.firstLine(), 0); + } else { + insertAt.line = (actionArgs.after) ? insertAt.line : + insertAt.line - 1; + insertAt.ch = lineLength(cm, insertAt.line); + cm.setCursor(insertAt); + var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment || + CodeMirror.commands.newlineAndIndent; + newlineFn(cm); + } + this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim); + }, + paste: function(cm, actionArgs, vim) { + var register = vimGlobalState.registerController.getRegister( + actionArgs.registerName); + if (actionArgs.registerName === '+') { + navigator.clipboard.readText().then((value) => { + this.continuePaste(cm, actionArgs, vim, value, register); + }); + } else { + var text = register.toString(); + this.continuePaste(cm, actionArgs, vim, text, register); + } + }, + continuePaste: function(cm, actionArgs, vim, text, register) { + var cur = copyCursor(cm.getCursor()); + if (!text) { + return; + } + if (actionArgs.matchIndent) { + var tabSize = cm.getOption("tabSize"); + // length that considers tabs and tabSize + var whitespaceLength = function(/** @type {string} */ str) { + var tabs = (str.split("\t").length - 1); + var spaces = (str.split(" ").length - 1); + return tabs * tabSize + spaces * 1; + }; + var currentLine = cm.getLine(cm.getCursor().line); + // @ts-ignore + var indent = whitespaceLength(currentLine.match(/^\s*/)[0]); + // chomp last newline b/c don't want it to match /^\s*/gm + var chompedText = text.replace(/\n$/, ''); + var wasChomped = text !== chompedText; + // @ts-ignore + var firstIndent = whitespaceLength(text.match(/^\s*/)[0]); + var text = chompedText.replace(/^\s*/gm, function(wspace) { + var newIndent = indent + (whitespaceLength(wspace) - firstIndent); + if (newIndent < 0) { + return ""; + } + else if (cm.getOption("indentWithTabs")) { + var quotient = Math.floor(newIndent / tabSize); + return Array(quotient + 1).join('\t'); + } + else { + return Array(newIndent + 1).join(' '); + } + }); + text += wasChomped ? "\n" : ""; + } + if (actionArgs.repeat > 1) { + var text = Array(actionArgs.repeat + 1).join(text); + } + var linewise = register.linewise; + var blockwise = register.blockwise; + if (blockwise) { + // @ts-ignore + text = text.split('\n'); + if (linewise) { + // @ts-ignore + text.pop(); + } + for (var i = 0; i < text.length; i++) { + // @ts-ignore + text[i] = (text[i] == '') ? ' ' : text[i]; + } + cur.ch += actionArgs.after ? 1 : 0; + cur.ch = Math.min(lineLength(cm, cur.line), cur.ch); + } else if (linewise) { + if(vim.visualMode) { + text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n'; + } else if (actionArgs.after) { + // Move the newline at the end to the start instead, and paste just + // before the newline character of the line we are on right now. + text = '\n' + text.slice(0, text.length - 1); + cur.ch = lineLength(cm, cur.line); + } else { + cur.ch = 0; + } + } else { + cur.ch += actionArgs.after ? 1 : 0; + } + var curPosFinal; + if (vim.visualMode) { + // save the pasted text for reselection if the need arises + vim.lastPastedText = text; + var lastSelectionCurEnd; + var selectedArea = getSelectedAreaRange(cm, vim); + var selectionStart = selectedArea[0]; + var selectionEnd = selectedArea[1]; + var selectedText = cm.getSelection(); + var selections = cm.listSelections(); + var emptyStrings = new Array(selections.length).join('1').split('1'); + // save the curEnd marker before it get cleared due to cm.replaceRange. + if (vim.lastSelection) { + lastSelectionCurEnd = vim.lastSelection.headMark.find(); + } + // push the previously selected text to unnamed register + vimGlobalState.registerController.unnamedRegister.setText(selectedText); + if (blockwise) { + // first delete the selected text + cm.replaceSelections(emptyStrings); + // Set new selections as per the block length of the yanked text + selectionEnd = new Pos(selectionStart.line + text.length-1, selectionStart.ch); + cm.setCursor(selectionStart); + selectBlock(cm, selectionEnd); + // @ts-ignore + cm.replaceSelections(text); + curPosFinal = selectionStart; + } else if (vim.visualBlock) { + cm.replaceSelections(emptyStrings); + cm.setCursor(selectionStart); + cm.replaceRange(text, selectionStart, selectionStart); + curPosFinal = selectionStart; + } else { + cm.replaceRange(text, selectionStart, selectionEnd); + curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1); + } + // restore the curEnd marker + if(lastSelectionCurEnd) { + vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd); + } + if (linewise) { + curPosFinal.ch=0; + } + } else { + if (blockwise) { + cm.setCursor(cur); + for (var i = 0; i < text.length; i++) { + var line = cur.line+i; + if (line > cm.lastLine()) { + cm.replaceRange('\n', new Pos(line, 0)); + } + var lastCh = lineLength(cm, line); + if (lastCh < cur.ch) { + extendLineToColumn(cm, line, cur.ch); + } + } + cm.setCursor(cur); + selectBlock(cm, new Pos(cur.line + text.length-1, cur.ch)); + // @ts-ignore + cm.replaceSelections(text); + curPosFinal = cur; + } else { + cm.replaceRange(text, cur); + // Now fine tune the cursor to where we want it. + if (linewise) { + var line = actionArgs.after ? cur.line + 1 : cur.line; + curPosFinal = new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + } else { + curPosFinal = copyCursor(cur); + if (!/\n/.test(text)) { + curPosFinal.ch += text.length - (actionArgs.after ? 1 : 0); + } + } + } + } + if (vim.visualMode) { + exitVisualMode(cm, false); + } + cm.setCursor(curPosFinal); + }, + undo: function(cm, actionArgs) { + cm.operation(function() { + repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)(); + cm.setCursor(clipCursorToContent(cm, cm.getCursor('start'))); + }); + }, + redo: function(cm, actionArgs) { + repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)(); + }, + setRegister: function(_cm, actionArgs, vim) { + vim.inputState.registerName = actionArgs.selectedCharacter; + }, + insertRegister: function(cm, actionArgs, vim) { + var registerName = actionArgs.selectedCharacter; + var register = vimGlobalState.registerController.getRegister(registerName); + var text = register && register.toString(); + if (text) { + cm.replaceSelection(text); + } + }, + oneNormalCommand: function(cm, actionArgs, vim) { + exitInsertMode(cm, true); + vim.insertModeReturn = true; + CodeMirror.on(cm, 'vim-command-done', function handler() { + if (vim.visualMode) return; + if (vim.insertModeReturn) { + vim.insertModeReturn = false; + if (!vim.insertMode) { + actions.enterInsertMode(cm, {}, vim); + } + } + CodeMirror.off(cm, 'vim-command-done', handler); + }); + }, + setMark: function(cm, actionArgs, vim) { + var markName = actionArgs.selectedCharacter; + if (markName) updateMark(cm, vim, markName, cm.getCursor()); + }, + replace: function(cm, actionArgs, vim) { + var replaceWith = actionArgs.selectedCharacter || ""; + var curStart = cm.getCursor(); + var replaceTo; + var curEnd; + var selections = cm.listSelections(); + if (vim.visualMode) { + curStart = cm.getCursor('start'); + curEnd = cm.getCursor('end'); + } else { + var line = cm.getLine(curStart.line); + replaceTo = curStart.ch + actionArgs.repeat; + if (replaceTo > line.length) { + replaceTo=line.length; + } + curEnd = new Pos(curStart.line, replaceTo); + } + + var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd); + curStart = newPositions.start; + curEnd = newPositions.end; + if (replaceWith=='\n') { + if (!vim.visualMode) cm.replaceRange('', curStart, curEnd); + // special case, where vim help says to replace by just one line-break + (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm); + } else { + var replaceWithStr = cm.getRange(curStart, curEnd); + // replace all surrogate characters with selected character + replaceWithStr = replaceWithStr.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, replaceWith); + //replace all characters in range by selected, but keep linebreaks + replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith); + if (vim.visualBlock) { + // Tabs are split in visua block before replacing + var spaces = new Array(cm.getOption("tabSize")+1).join(' '); + replaceWithStr = cm.getSelection(); + replaceWithStr = replaceWithStr.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, replaceWith); + var replaceWithStrings = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n'); + cm.replaceSelections(replaceWithStrings); + } else { + cm.replaceRange(replaceWithStr, curStart, curEnd); + } + if (vim.visualMode) { + curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ? + selections[0].anchor : selections[0].head; + cm.setCursor(curStart); + exitVisualMode(cm, false); + } else { + cm.setCursor(offsetCursor(curEnd, 0, -1)); + } + } + }, + incrementNumberToken: function(cm, actionArgs) { + var cur = cm.getCursor(); + var lineStr = cm.getLine(cur.line); + var re = /(-?)(?:(0x)([\da-f]+)|(0b|0|)(\d+))/gi; + var match; + var start; + var end; + var numberStr; + while ((match = re.exec(lineStr)) !== null) { + start = match.index; + end = start + match[0].length; + if (cur.ch < end)break; + } + // @ts-ignore + if (!actionArgs.backtrack && (end <= cur.ch))return; + if (match) { + var baseStr = match[2] || match[4]; + var digits = match[3] || match[5]; + var increment = actionArgs.increase ? 1 : -1; + var base = {'0b': 2, '0': 8, '': 10, '0x': 16}[baseStr.toLowerCase()]; + var number = parseInt(match[1] + digits, base) + (increment * actionArgs.repeat); + numberStr = number.toString(base); + var zeroPadding = baseStr ? new Array(digits.length - numberStr.length + 1 + match[1].length).join('0') : ''; + if (numberStr.charAt(0) === '-') { + numberStr = '-' + baseStr + zeroPadding + numberStr.substr(1); + } else { + numberStr = baseStr + zeroPadding + numberStr; + } + // @ts-ignore + var from = new Pos(cur.line, start); + // @ts-ignore + var to = new Pos(cur.line, end); + cm.replaceRange(numberStr, from, to); + } else { + return; + } + // @ts-ignore + cm.setCursor(new Pos(cur.line, start + numberStr.length - 1)); + }, + repeatLastEdit: function(cm, actionArgs, vim) { + var lastEditInputState = vim.lastEditInputState; + if (!lastEditInputState) { return; } + var repeat = actionArgs.repeat; + if (repeat && actionArgs.repeatIsExplicit) { + lastEditInputState.repeatOverride = repeat; + } else { + repeat = lastEditInputState.repeatOverride || repeat; + } + repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */); + }, + indent: function(cm, actionArgs) { + cm.indentLine(cm.getCursor().line, actionArgs.indentRight); + }, + exitInsertMode: function(cm, actionArgs) { + exitInsertMode(cm); + } + }; + + /** @arg {string } name @arg {import("./types").ActionFn} fn */ + function defineAction(name, fn) { + actions[name] = fn; + } + + /* + * Below are miscellaneous utility functions used by vim.js + */ + + /** + * Clips cursor to ensure that line is within the buffer's range + * and is not inside surrogate pair + * If includeLineBreak is true, then allow cur.ch == lineLength. + * @arg {CodeMirrorV} cm + * @arg {Pos} cur + * @arg {Pos} [oldCur] + * @return {Pos} + */ + function clipCursorToContent(cm, cur, oldCur) { + var vim = cm.state.vim; + var includeLineBreak = vim.insertMode || vim.visualMode; + var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() ); + var text = cm.getLine(line); + var maxCh = text.length - 1 + Number(!!includeLineBreak); + var ch = Math.min(Math.max(0, cur.ch), maxCh); + // prevent cursor from entering surrogate pair + var charCode = text.charCodeAt(ch); + if (0xDC00 <= charCode && charCode <= 0xDFFF) { + var direction = 1; + if (oldCur && oldCur.line == line && oldCur.ch > ch) { + direction = -1; + } + ch +=direction; + if (ch > maxCh) ch -=2; + } + return new Pos(line, ch); + } + function copyArgs(args) { + var ret = {}; + for (var prop in args) { + if (args.hasOwnProperty(prop)) { + ret[prop] = args[prop]; + } + } + return ret; + } + function offsetCursor(cur, offsetLine, offsetCh) { + if (typeof offsetLine === 'object') { + offsetCh = offsetLine.ch; + offsetLine = offsetLine.line; + } + return new Pos(cur.line + offsetLine, cur.ch + offsetCh); + } + function commandMatches(keys, keyMap, context, inputState) { + // Partial matches are not applied. They inform the key handler + // that the current key sequence is a subsequence of a valid key + // sequence, so that the key buffer is not cleared. + if (inputState.operator) context = "operatorPending"; + var match, partial = [], full = []; + // if currently expanded key comes from a noremap, searcg only in default keys + var startIndex = noremap ? keyMap.length - defaultKeymapLength : 0; + for (var i = startIndex; i < keyMap.length; i++) { + var command = keyMap[i]; + if (context == 'insert' && command.context != 'insert' || + (command.context && command.context != context) || + inputState.operator && command.type == 'action' || + !(match = commandMatch(keys, command.keys))) { continue; } + if (match == 'partial') { partial.push(command); } + if (match == 'full') { full.push(command); } + } + return { + partial: partial.length && partial, + full: full.length && full + }; + } + /** @arg {string} pressed @arg {string} mapped @return {'full'|'partial'|false}*/ + function commandMatch(pressed, mapped) { + const isLastCharacter = mapped.slice(-11) == ''; + const isLastRegister = mapped.slice(-10) == ''; + if (isLastCharacter || isLastRegister) { + // Last character matches anything. + var prefixLen = mapped.length - (isLastCharacter ? 11 : 10); + var pressedPrefix = pressed.slice(0, prefixLen); + var mappedPrefix = mapped.slice(0, prefixLen); + return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' : + mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false; + } else { + return pressed == mapped ? 'full' : + mapped.indexOf(pressed) == 0 ? 'partial' : false; + } + } + /** @arg {string} keys */ + function lastChar(keys) { + var match = /^.*(<[^>]+>)$/.exec(keys); + var selectedCharacter = match ? match[1] : keys.slice(-1); + if (selectedCharacter.length > 1){ + switch(selectedCharacter){ + case '': + case '': + selectedCharacter='\n'; + break; + case '': + case '': + selectedCharacter=' '; + break; + default: + selectedCharacter=''; + break; + } + } + return selectedCharacter; + } + /** @arg {CodeMirror} cm @arg {{ (cm: CodeMirror): void }} fn @arg {number} repeat */ + function repeatFn(cm, fn, repeat) { + return function() { + for (var i = 0; i < repeat; i++) { + fn(cm); + } + }; + } + /** @arg {Pos} cur @return {Pos}*/ + function copyCursor(cur) { + return new Pos(cur.line, cur.ch); + } + /** @arg {Pos} cur1 @arg {Pos} cur2 @return {boolean} */ + function cursorEqual(cur1, cur2) { + return cur1.ch == cur2.ch && cur1.line == cur2.line; + } + /** @arg {Pos} cur1 @arg {Pos} cur2 @return {boolean}*/ + function cursorIsBefore(cur1, cur2) { + if (cur1.line < cur2.line) { + return true; + } + if (cur1.line == cur2.line && cur1.ch < cur2.ch) { + return true; + } + return false; + } + /** @arg {Pos} cur1 @arg {Pos} cur2 @return {Pos}*/ + function cursorMin(cur1, cur2) { + if (arguments.length > 2) { + // @ts-ignore + cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1)); + } + return cursorIsBefore(cur1, cur2) ? cur1 : cur2; + } + /** @arg {Pos} cur1 @arg {Pos} cur2 @return {Pos} */ + function cursorMax(cur1, cur2) { + if (arguments.length > 2) { + // @ts-ignore + cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1)); + } + return cursorIsBefore(cur1, cur2) ? cur2 : cur1; + } + /** @arg {Pos} cur1 @arg {Pos} cur2 @arg {Pos} cur3 @return {boolean}*/ + function cursorIsBetween(cur1, cur2, cur3) { + // returns true if cur2 is between cur1 and cur3. + var cur1before2 = cursorIsBefore(cur1, cur2); + var cur2before3 = cursorIsBefore(cur2, cur3); + return cur1before2 && cur2before3; + } + /** @arg {CodeMirror} cm @arg {number} lineNum */ + function lineLength(cm, lineNum) { + return cm.getLine(lineNum).length; + } + /** @arg {string} s */ + function trim(s) { + if (s.trim) { + return s.trim(); + } + return s.replace(/^\s+|\s+$/g, ''); + } + /** @arg {string} s */ + function escapeRegex(s) { + return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1'); + } + /** @arg {CodeMirror} cm @arg {number} lineNum @arg {number} column */ + function extendLineToColumn(cm, lineNum, column) { + var endCh = lineLength(cm, lineNum); + var spaces = new Array(column-endCh+1).join(' '); + cm.setCursor(new Pos(lineNum, endCh)); + cm.replaceRange(spaces, cm.getCursor()); + } + // This functions selects a rectangular block + // of text with selectionEnd as any of its corner + // Height of block: + // Difference in selectionEnd.line and first/last selection.line + // Width of the block: + // Distance between selectionEnd.ch and any(first considered here) selection.ch + /** @arg {CodeMirror} cm @arg {Pos} selectionEnd */ + function selectBlock(cm, selectionEnd) { + var selections = [], ranges = cm.listSelections(); + var head = copyCursor(cm.clipPos(selectionEnd)); + var isClipped = !cursorEqual(selectionEnd, head); + var curHead = cm.getCursor('head'); + var primIndex = getIndex(ranges, curHead); + var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor); + var max = ranges.length - 1; + var index = max - primIndex > primIndex ? max : 0; + var base = ranges[index].anchor; + + var firstLine = Math.min(base.line, head.line); + var lastLine = Math.max(base.line, head.line); + var baseCh = base.ch, headCh = head.ch; + + var dir = ranges[index].head.ch - baseCh; + var newDir = headCh - baseCh; + if (dir > 0 && newDir <= 0) { + baseCh++; + if (!isClipped) { headCh--; } + } else if (dir < 0 && newDir >= 0) { + baseCh--; + if (!wasClipped) { headCh++; } + } else if (dir < 0 && newDir == -1) { + baseCh--; + headCh++; + } + for (var line = firstLine; line <= lastLine; line++) { + var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)}; + selections.push(range); + } + cm.setSelections(selections); + selectionEnd.ch = headCh; + base.ch = baseCh; + return base; + } + /** @arg {CodeMirror} cm @arg {any} head @arg {number} height */ + function selectForInsert(cm, head, height) { + var sel = []; + for (var i = 0; i < height; i++) { + var lineHead = offsetCursor(head, i, 0); + sel.push({anchor: lineHead, head: lineHead}); + } + cm.setSelections(sel, 0); + } + // getIndex returns the index of the cursor in the selections. + /** @arg {string | any[]} ranges @arg {any} cursor @arg {string | undefined} [end] */ + function getIndex(ranges, cursor, end) { + for (var i = 0; i < ranges.length; i++) { + var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor); + var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor); + if (atAnchor || atHead) { + return i; + } + } + return -1; + } + /** @arg {CodeMirror} cm @arg {vimState} vim */ + function getSelectedAreaRange(cm, vim) { + var lastSelection = vim.lastSelection; + /** @return {[Pos,Pos]} */ + var getCurrentSelectedAreaRange = function() { + var selections = cm.listSelections(); + var start = selections[0]; + var end = selections[selections.length-1]; + var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head; + var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; + return [selectionStart, selectionEnd]; + }; + var getLastSelectedAreaRange = function() { + var selectionStart = cm.getCursor(); + var selectionEnd = cm.getCursor(); + var block = lastSelection.visualBlock; + if (block) { + var width = block.width; + var height = block.height; + selectionEnd = new Pos(selectionStart.line + height, selectionStart.ch + width); + var selections = []; + // selectBlock creates a 'proper' rectangular block. + // We do not want that in all cases, so we manually set selections. + for (var i = selectionStart.line; i < selectionEnd.line; i++) { + var anchor = new Pos(i, selectionStart.ch); + var head = new Pos(i, selectionEnd.ch); + var range = {anchor: anchor, head: head}; + selections.push(range); + } + cm.setSelections(selections); + } else { + var start = lastSelection.anchorMark.find(); + var end = lastSelection.headMark.find(); + var line = end.line - start.line; + var ch = end.ch - start.ch; + selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch}; + if (lastSelection.visualLine) { + selectionStart = new Pos(selectionStart.line, 0); + selectionEnd = new Pos(selectionEnd.line, lineLength(cm, selectionEnd.line)); + } + cm.setSelection(selectionStart, selectionEnd); + } + return [selectionStart, selectionEnd]; + }; + if (!vim.visualMode) { + // In case of replaying the action. + return getLastSelectedAreaRange(); + } else { + return getCurrentSelectedAreaRange(); + } + } + // Updates the previous selection with the current selection's values. This + // should only be called in visual mode. + /** @arg {CodeMirror} cm @arg {vimState} vim */ + function updateLastSelection(cm, vim) { + var anchor = vim.sel.anchor; + var head = vim.sel.head; + // To accommodate the effect of lastPastedText in the last selection + if (vim.lastPastedText) { + head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length); + vim.lastPastedText = null; + } + vim.lastSelection = {'anchorMark': cm.setBookmark(anchor), + 'headMark': cm.setBookmark(head), + 'anchor': copyCursor(anchor), + 'head': copyCursor(head), + 'visualMode': vim.visualMode, + 'visualLine': vim.visualLine, + 'visualBlock': vim.visualBlock}; + } + /** @arg {CodeMirrorV} cm @arg {Pos} start @arg {Pos} end @returns {[Pos, Pos]} */ + function expandSelection(cm, start, end, move) { + var sel = cm.state.vim.sel; + var head = move ? start: sel.head; + var anchor = move ? start: sel.anchor; + var tmp; + if (cursorIsBefore(end, start)) { + tmp = end; + end = start; + start = tmp; + } + if (cursorIsBefore(head, anchor)) { + head = cursorMin(start, head); + anchor = cursorMax(anchor, end); + } else { + anchor = cursorMin(start, anchor); + head = cursorMax(head, end); + head = offsetCursor(head, 0, -1); + if (head.ch == -1 && head.line != cm.firstLine()) { + head = new Pos(head.line - 1, lineLength(cm, head.line - 1)); + } + } + return [anchor, head]; + } + /** + * Updates the CodeMirror selection to match the provided vim selection. + * If no arguments are given, it uses the current vim selection state. + * @arg {CodeMirrorV} cm + * @arg {vimState["sel"]} [sel] + * @arg {"char"|"line"|"block" | undefined} [mode] + */ + function updateCmSelection(cm, sel, mode) { + var vim = cm.state.vim; + sel = sel || vim.sel; + if (!mode) { + mode = vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char'; + } + var cmSel = makeCmSelection(cm, sel, mode); + cm.setSelections(cmSel.ranges, cmSel.primary); + } + /** + * @arg {CodeMirror} cm + * @arg {import("./types").CM5RangeInterface} sel + * @arg {"char"|"line"|"block"} mode + * @arg {boolean|undefined} [exclusive] + * @return {{ranges: any, primary: number}} + */ + function makeCmSelection(cm, sel, mode, exclusive) { + var head = copyCursor(sel.head); + var anchor = copyCursor(sel.anchor); + if (mode == 'char') { + var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; + var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; + head = offsetCursor(sel.head, 0, headOffset); + anchor = offsetCursor(sel.anchor, 0, anchorOffset); + return { + ranges: [{anchor: anchor, head: head}], + primary: 0 + }; + } else if (mode == 'line') { + if (!cursorIsBefore(sel.head, sel.anchor)) { + anchor.ch = 0; + + var lastLine = cm.lastLine(); + if (head.line > lastLine) { + head.line = lastLine; + } + head.ch = lineLength(cm, head.line); + } else { + head.ch = 0; + anchor.ch = lineLength(cm, anchor.line); + } + return { + ranges: [{anchor: anchor, head: head}], + primary: 0 + }; + } else if (mode == 'block') { + var top = Math.min(anchor.line, head.line), + fromCh = anchor.ch, + bottom = Math.max(anchor.line, head.line), + toCh = head.ch; + if (fromCh < toCh) { toCh += 1; } + else { fromCh += 1; } var height = bottom - top + 1; + var primary = head.line == top ? 0 : height - 1; + var ranges = []; + for (var i = 0; i < height; i++) { + ranges.push({ + anchor: new Pos(top + i, fromCh), + head: new Pos(top + i, toCh) + }); + } + return { + ranges: ranges, + primary: primary + }; + } + throw "never happens"; + } + /** @arg {CodeMirror} cm */ + function getHead(cm) { + var cur = cm.getCursor('head'); + if (cm.getSelection().length == 1) { + // Small corner case when only 1 character is selected. The "real" + // head is the left of head and anchor. + cur = cursorMin(cur, cm.getCursor('anchor')); + } + return cur; + } + + /** + * If moveHead is set to false, the CodeMirror selection will not be + * touched. The caller assumes the responsibility of putting the cursor + * in the right place. + * @arg {CodeMirrorV} cm + * @arg {boolean} [moveHead] + */ + function exitVisualMode(cm, moveHead) { + var vim = cm.state.vim; + if (moveHead !== false) { + cm.setCursor(clipCursorToContent(cm, vim.sel.head)); + } + updateLastSelection(cm, vim); + vim.visualMode = false; + vim.visualLine = false; + vim.visualBlock = false; + if (!vim.insertMode) CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + } + + /** + * Remove any trailing newlines from the selection. For + * example, with the caret at the start of the last word on the line, + * 'dw' should word, but not the newline, while 'w' should advance the + * caret to the first character of the next line. + * @arg {CodeMirror} cm + * @arg {Pos} curStart + * @arg {Pos} curEnd + */ + function clipToLine(cm, curStart, curEnd) { + var selection = cm.getRange(curStart, curEnd); + // Only clip if the selection ends with trailing newline + whitespace + if (/\n\s*$/.test(selection)) { + var lines = selection.split('\n'); + // We know this is all whitespace. + lines.pop(); + + // Cases: + // 1. Last word is an empty line - do not clip the trailing '\n' + // 2. Last word is not an empty line - clip the trailing '\n' + // Find the line containing the last word, and clip all whitespace up + // to it. + for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) { + curEnd.line--; + curEnd.ch = 0; + } + // If the last word is not an empty line, clip an additional newline + if (line) { + curEnd.line--; + curEnd.ch = lineLength(cm, curEnd.line); + } else { + curEnd.ch = 0; + } + } + } + + // Expand the selection to line ends. + /** @arg {CodeMirror} _cm @arg {Pos} curStart @arg {Pos} curEnd */ + function expandSelectionToLine(_cm, curStart, curEnd) { + curStart.ch = 0; + curEnd.ch = 0; + curEnd.line++; + } + + /** @arg {string} [text] */ + function findFirstNonWhiteSpaceCharacter(text) { + if (!text) { + return 0; + } + var firstNonWS = text.search(/\S/); + return firstNonWS == -1 ? text.length : firstNonWS; + } + + /** + * @arg {CodeMirror} cm + * @arg {{inclusive?: boolean, innerWord?: boolean, bigWord?: boolean, noSymbol?: boolean, multiline?: boolean}} options + * @arg {Pos} [cursor] + **/ + function expandWordUnderCursor(cm, {inclusive, innerWord, bigWord, noSymbol, multiline}, cursor) { + var cur = cursor || getHead(cm); + var line = cm.getLine(cur.line); + var endLine = line; + var startLineNumber = cur.line; + var endLineNumber = startLineNumber; + var idx = cur.ch; + + var wordOnNextLine; + // Seek to first word or non-whitespace character, depending on if + // noSymbol is true. + var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0]; + if (innerWord && /\s/.test(line.charAt(idx))) { + test = function(/** @type {string} */ ch) { return /\s/.test(ch); }; + } else { + while (!test(line.charAt(idx))) { + idx++; + if (idx >= line.length) { + if (!multiline) return null; + idx--; + wordOnNextLine = findWord(cm, cur, true, bigWord, true); + break + } + } + + if (bigWord) { + test = bigWordCharTest[0]; + } else { + test = wordCharTest[0]; + if (!test(line.charAt(idx))) { + test = wordCharTest[1]; + } + } + } + + var end = idx, start = idx; + while (test(line.charAt(start)) && start >= 0) { start--; } + start++; + if (wordOnNextLine) { + end = wordOnNextLine.to; + endLineNumber = wordOnNextLine.line; + endLine = cm.getLine(endLineNumber); + if (!endLine && end == 0) end++; + } else { + while (test(line.charAt(end)) && end < line.length) { end++; } + } + + if (inclusive) { + // If present, include all whitespace after word. + // Otherwise, include all whitespace before word, except indentation. + var wordEnd = end; + var startsWithSpace = cur.ch <= start && /\s/.test(line.charAt(cur.ch)); + if (!startsWithSpace) { + while (/\s/.test(endLine.charAt(end)) && end < endLine.length) { end++; } + } + if (wordEnd == end || startsWithSpace) { + var wordStart = start; + while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; } + if (!start && !startsWithSpace) { start = wordStart; } + } + } + + return { start: new Pos(startLineNumber, start), end: new Pos(endLineNumber, end) }; + } + + /** + * Depends on the following: + * + * - editor mode should be htmlmixedmode / xml + * - mode/xml/xml.js should be loaded + * - addon/fold/xml-fold.js should be loaded + * + * If any of the above requirements are not true, this function noops. + * + * This is _NOT_ a 100% accurate implementation of vim tag text objects. + * The following caveats apply (based off cursory testing, I'm sure there + * are other discrepancies): + * + * - Does not work inside comments: + * ``` + * + * ``` + * - Does not work when tags have different cases: + * ``` + *
broken
+ * ``` + * - Does not work when cursor is inside a broken tag: + * ``` + *
+ * ``` + * @arg {CodeMirror} cm + * @arg {Pos} head + * @arg {boolean} [inclusive] + */ + function expandTagUnderCursor(cm, head, inclusive) { + var cur = head; + if (!CodeMirror.findMatchingTag || !CodeMirror.findEnclosingTag) { + return { start: cur, end: cur }; + } + + var tags = CodeMirror.findMatchingTag(cm, head) || CodeMirror.findEnclosingTag(cm, head); + if (!tags || !tags.open || !tags.close) { + return { start: cur, end: cur }; + } + + if (inclusive) { + return { start: tags.open.from, end: tags.close.to }; + } + return { start: tags.open.to, end: tags.close.from }; + } + + /** @arg {CodeMirror} cm @arg {Pos} oldCur @arg {Pos} newCur */ + function recordJumpPosition(cm, oldCur, newCur) { + if (!cursorEqual(oldCur, newCur)) { + vimGlobalState.jumpList.add(cm, oldCur, newCur); + } + } + + /** @arg {number} increment @arg {{ forward?: any; selectedCharacter?: any; }} args */ + function recordLastCharacterSearch(increment, args) { + vimGlobalState.lastCharacterSearch.increment = increment; + vimGlobalState.lastCharacterSearch.forward = args.forward; + vimGlobalState.lastCharacterSearch.selectedCharacter = args.selectedCharacter; + } + + var symbolToMode = { + '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket', + '[': 'section', ']': 'section', + '*': 'comment', '/': 'comment', + 'm': 'method', 'M': 'method', + '#': 'preprocess' + }; + var findSymbolModes = { + bracket: { + isComplete: function(state) { + if (state.nextCh === state.symb) { + state.depth++; + if (state.depth >= 1)return true; + } else if (state.nextCh === state.reverseSymb) { + state.depth--; + } + return false; + } + }, + section: { + init: function(state) { + state.curMoveThrough = true; + state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}'; + }, + isComplete: function(state) { + return state.index === 0 && state.nextCh === state.symb; + } + }, + comment: { + isComplete: function(state) { + var found = state.lastCh === '*' && state.nextCh === '/'; + state.lastCh = state.nextCh; + return found; + } + }, + // TODO: The original Vim implementation only operates on level 1 and 2. + // The current implementation doesn't check for code block level and + // therefore it operates on any levels. + method: { + init: function(state) { + state.symb = (state.symb === 'm' ? '{' : '}'); + state.reverseSymb = state.symb === '{' ? '}' : '{'; + }, + isComplete: function(state) { + if (state.nextCh === state.symb)return true; + return false; + } + }, + preprocess: { + init: function(state) { + state.index = 0; + }, + isComplete: function(state) { + if (state.nextCh === '#') { + var token = state.lineText.match(/^#(\w+)/)[1]; + if (token === 'endif') { + if (state.forward && state.depth === 0) { + return true; + } + state.depth++; + } else if (token === 'if') { + if (!state.forward && state.depth === 0) { + return true; + } + state.depth--; + } + if (token === 'else' && state.depth === 0)return true; + } + return false; + } + } + }; + /** @arg {CodeMirrorV} cm @arg {number} repeat @arg {boolean|undefined} forward @arg {string} symb */ + function findSymbol(cm, repeat, forward, symb) { + var cur = copyCursor(cm.getCursor()); + var increment = forward ? 1 : -1; + var endLine = forward ? cm.lineCount() : -1; + var curCh = cur.ch; + var line = cur.line; + var lineText = cm.getLine(line); + var state = { + lineText: lineText, + nextCh: lineText.charAt(curCh), + lastCh: null, + index: curCh, + symb: symb, + reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb], + forward: forward, + depth: 0, + curMoveThrough: false + }; + var mode = symbolToMode[symb]; + if (!mode)return cur; + var init = findSymbolModes[mode].init; + var isComplete = findSymbolModes[mode].isComplete; + if (init) { init(state); } + while (line !== endLine && repeat) { + state.index += increment; + state.nextCh = state.lineText.charAt(state.index); + if (!state.nextCh) { + line += increment; + state.lineText = cm.getLine(line) || ''; + if (increment > 0) { + state.index = 0; + } else { + var lineLen = state.lineText.length; + state.index = (lineLen > 0) ? (lineLen-1) : 0; + } + state.nextCh = state.lineText.charAt(state.index); + } + if (isComplete(state)) { + cur.line = line; + cur.ch = state.index; + repeat--; + } + } + if (state.nextCh || state.curMoveThrough) { + return new Pos(line, state.index); + } + return cur; + } + + /* + * Returns the boundaries of the next word. If the cursor in the middle of + * the word, then returns the boundaries of the current word, starting at + * the cursor. If the cursor is at the start/end of a word, and we are going + * forward/backward, respectively, find the boundaries of the next word. + * + * @arg {CodeMirror} cm CodeMirror object. + * @arg {Cursor} cur The cursor position. + * @arg {boolean} forward True to search forward. False to search + * backward. + * @arg {boolean} bigWord True if punctuation count as part of the word. + * False if only [a-zA-Z0-9] characters count as part of the word. + * @arg {boolean} emptyLineIsWord True if empty lines should be treated + * as words. + * @return {Object{from:number, to:number, line: number}} The boundaries of + * the word, or null if there are no more words. + */ + function findWord(cm, cur, forward, bigWord, emptyLineIsWord) { + var lineNum = cur.line; + var pos = cur.ch; + var line = cm.getLine(lineNum); + var dir = forward ? 1 : -1; + var charTests = bigWord ? bigWordCharTest: wordCharTest; + + if (emptyLineIsWord && line == '') { + lineNum += dir; + line = cm.getLine(lineNum); + if (!isLine(cm, lineNum)) { + return null; + } + pos = (forward) ? 0 : line.length; + } + + while (true) { + if (emptyLineIsWord && line == '') { + return { from: 0, to: 0, line: lineNum }; + } + var stop = (dir > 0) ? line.length : -1; + var wordStart = stop, wordEnd = stop; + // Find bounds of next word. + while (pos != stop) { + var foundWord = false; + for (var i = 0; i < charTests.length && !foundWord; ++i) { + if (charTests[i](line.charAt(pos))) { + wordStart = pos; + // Advance to end of word. + while (pos != stop && charTests[i](line.charAt(pos))) { + pos += dir; + } + wordEnd = pos; + foundWord = wordStart != wordEnd; + if (wordStart == cur.ch && lineNum == cur.line && + wordEnd == wordStart + dir) { + // We started at the end of a word. Find the next one. + continue; + } else { + return { + from: Math.min(wordStart, wordEnd + 1), + to: Math.max(wordStart, wordEnd), + line: lineNum }; + } + } + } + if (!foundWord) { + pos += dir; + } + } + // Advance to next/prev line. + lineNum += dir; + if (!isLine(cm, lineNum)) { + return null; + } + line = cm.getLine(lineNum); + pos = (dir > 0) ? 0 : line.length; + } + } + + /** + * @arg {CodeMirror} cm CodeMirror object. + * @arg {Pos} cur The position to start from. + * @arg {number} repeat Number of words to move past. + * @arg {boolean} forward True to search forward. False to search + * backward. + * @arg {boolean} wordEnd True to move to end of word. False to move to + * beginning of word. + * @arg {boolean} bigWord True if punctuation count as part of the word. + * False if only alphabet characters count as part of the word. + * @return {Pos|undefined} The position the cursor should move to. + */ + function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) { + var curStart = copyCursor(cur); + var words = []; + if (forward && !wordEnd || !forward && wordEnd) { + repeat++; + } + // For 'e', empty lines are not considered words, go figure. + var emptyLineIsWord = !(forward && wordEnd); + for (var i = 0; i < repeat; i++) { + var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord); + if (!word) { + var eodCh = lineLength(cm, cm.lastLine()); + words.push(forward + ? {line: cm.lastLine(), from: eodCh, to: eodCh} + : {line: 0, from: 0, to: 0}); + break; + } + words.push(word); + cur = new Pos(word.line, forward ? (word.to - 1) : word.from); + } + var shortCircuit = words.length != repeat; + var firstWord = words[0]; + var lastWord = words.pop(); + if (forward && !wordEnd) { + // w + if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) { + // We did not start in the middle of a word. Discard the extra word at the end. + lastWord = words.pop(); + } + return lastWord && new Pos(lastWord.line, lastWord.from); + } else if (forward && wordEnd) { + return lastWord && new Pos(lastWord.line, lastWord.to - 1); + } else if (!forward && wordEnd) { + // ge + if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) { + // We did not start in the middle of a word. Discard the extra word at the end. + lastWord = words.pop(); + } + return lastWord && new Pos(lastWord.line, lastWord.to); + } else { + // b + return lastWord && new Pos(lastWord.line, lastWord.from); + } + } + + /** + * @arg {CodeMirror} cm + * @arg {Pos} head + * @arg {MotionArgs} motionArgs + * @arg {vimState} vim + * @arg {boolean} keepHPos */ + function moveToEol(cm, head, motionArgs, vim, keepHPos) { + var cur = head; + var retval= new Pos(cur.line + motionArgs.repeat - 1, Infinity); + var end=cm.clipPos(retval); + end.ch--; + if (!keepHPos) { + vim.lastHPos = Infinity; + vim.lastHSPos = cm.charCoords(end,'div').left; + } + return retval; + } + + /** + * @arg {CodeMirror} cm + * @arg {number} repeat + * @arg {boolean} [forward] + * @arg {string} [character] + * @arg {Pos} [head] + */ + function moveToCharacter(cm, repeat, forward, character, head) { + if (!character) return; + var cur = head || cm.getCursor(); + var start = cur.ch; + var idx; + for (var i = 0; i < repeat; i ++) { + var line = cm.getLine(cur.line); + idx = charIdxInLine(start, line, character, forward, true); + if (idx == -1) { + return undefined; + } + start = idx; + } + if (idx != undefined) + return new Pos(cm.getCursor().line, idx); + } + + /** @arg {CodeMirrorV} cm @arg {number} repeat */ + function moveToColumn(cm, repeat) { + // repeat is always >= 1, so repeat - 1 always corresponds + // to the column we want to go to. + var line = cm.getCursor().line; + return clipCursorToContent(cm, new Pos(line, repeat - 1)); + } + + /** + * @arg {CodeMirror} cm + * @arg {vimState} vim + * @arg {string} markName + * @arg {Pos} pos */ + function updateMark(cm, vim, markName, pos) { + if (!inArray(markName, validMarks) && !latinCharRegex.test(markName)) { + return; + } + if (vim.marks[markName]) { + vim.marks[markName].clear(); + } + vim.marks[markName] = cm.setBookmark(pos); + } + + /** + * @arg {number} start + * @arg {string | any[]} line + * @arg {any} character + * @arg {boolean} [forward] + * @arg {boolean} [includeChar] */ + function charIdxInLine(start, line, character, forward, includeChar) { + // Search for char in line. + // motion_options: {forward, includeChar} + // If includeChar = true, include it too. + // If forward = true, search forward, else search backwards. + // If char is not found on this line, do nothing + var idx; + if (forward) { + idx = line.indexOf(character, start + 1); + if (idx != -1 && !includeChar) { + idx -= 1; + } + } else { + idx = line.lastIndexOf(character, start - 1); + if (idx != -1 && !includeChar) { + idx += 1; + } + } + return idx; + } + + /** @arg {CodeMirrorV} cm + * @arg {Pos} head + * @arg {number} repeat + * @arg {number} dir + * @arg {boolean} [inclusive] */ + function findParagraph(cm, head, repeat, dir, inclusive) { + var line = head.line; + var min = cm.firstLine(); + var max = cm.lastLine(); + var start, end, i = line; + /** @arg {number} i */ + function isEmpty(i) { return !cm.getLine(i); } + /** @arg {number} i @arg {number} dir @arg {boolean} [any] */ + function isBoundary(i, dir, any) { + if (any) { return isEmpty(i) != isEmpty(i + dir); } + return !isEmpty(i) && isEmpty(i + dir); + } + if (dir) { + while (min <= i && i <= max && repeat > 0) { + if (isBoundary(i, dir)) { repeat--; } + i += dir; + } + return {start: new Pos(i, 0), end: head}; + } + + var vim = cm.state.vim; + if (vim.visualLine && isBoundary(line, 1, true)) { + var anchor = vim.sel.anchor; + if (isBoundary(anchor.line, -1, true)) { + if (!inclusive || anchor.line != line) { + line += 1; + } + } + } + var startState = isEmpty(line); + for (i = line; i <= max && repeat; i++) { + if (isBoundary(i, 1, true)) { + if (!inclusive || isEmpty(i) != startState) { + repeat--; + } + } + } + end = new Pos(i, 0); + // select boundary before paragraph for the last one + if (i > max && !startState) { startState = true; } + else { inclusive = false; } + for (i = line; i > min; i--) { + if (!inclusive || isEmpty(i) == startState || i == line) { + if (isBoundary(i, -1, true)) { break; } + } + } + start = new Pos(i, 0); + return { start: start, end: end }; + } + + /** + * Based on {@link findSentence}. The internal functions have the same names, + * but their behaviour is different. findSentence() crosses line breaks and + * is used for jumping to sentence beginnings before or after the current cursor position, + * whereas getSentence() is for getting the beginning or end of the sentence + * at the current cursor position, either including (a) or excluding (i) whitespace. + * @arg {CodeMirror} cm + * @arg {Pos} cur + * @arg {number} repeat + * @arg {number} dir + * @arg {boolean} inclusive + */ + function getSentence(cm, cur, repeat, dir, inclusive /*includes whitespace*/) { + + /* + Takes an index object + { + line: the line string, + ln: line number, + pos: index in line, + dir: direction of traversal (-1 or 1) + } + and modifies the pos member to represent the + next valid position or sets the line to null if there are + no more valid positions. + */ + function nextChar(curr) { + if (curr.pos + curr.dir < 0 || curr.pos + curr.dir >= curr.line.length) { + curr.line = null; + } + else { + curr.pos += curr.dir; + } + } + /* + Performs one iteration of traversal in forward direction + Returns an index object of the sentence end + */ + function forward(cm, ln, pos, dir) { + var line = cm.getLine(ln); + + var curr = { + line: line, + ln: ln, + pos: pos, + dir: dir, + }; + + if (curr.line === "") { + return { ln: curr.ln, pos: curr.pos }; + } + + var lastSentencePos = curr.pos; + + // Move one step to skip character we start on + nextChar(curr); + + while (curr.line !== null) { + lastSentencePos = curr.pos; + if (isEndOfSentenceSymbol(curr.line[curr.pos])) { + if (!inclusive) { + return { ln: curr.ln, pos: curr.pos + 1 }; + } + else { + nextChar(curr); + while (curr.line !== null ) { + if (isWhiteSpaceString(curr.line[curr.pos])) { + lastSentencePos = curr.pos; + nextChar(curr); + } + else { + break; + } + } + return { ln: curr.ln, pos: lastSentencePos + 1 }; + } + } + nextChar(curr); + } + return { ln: curr.ln, pos: lastSentencePos + 1 }; + } + + /** + * Performs one iteration of traversal in reverse direction + * Returns an index object of the sentence start + * @arg {CodeMirror} cm + * @arg {number} ln + * @arg {number} pos + * @arg {number} dir + */ + function reverse(cm, ln, pos, dir) { + var line = cm.getLine(ln); + + var curr = { + line: line, + ln: ln, + pos: pos, + dir: dir, + }; + + if (curr.line === "") { + return { ln: curr.ln, pos: curr.pos }; + } + + var lastSentencePos = curr.pos; + + // Move one step to skip character we start on + nextChar(curr); + + while (curr.line !== null) { + if (!isWhiteSpaceString(curr.line[curr.pos]) && !isEndOfSentenceSymbol(curr.line[curr.pos])) { + lastSentencePos = curr.pos; + } + + else if (isEndOfSentenceSymbol(curr.line[curr.pos]) ) { + if (!inclusive) { + return { ln: curr.ln, pos: lastSentencePos }; + } + else { + if (isWhiteSpaceString(curr.line[curr.pos + 1])) { + return { ln: curr.ln, pos: curr.pos + 1 }; + } + else { + return { ln: curr.ln, pos: lastSentencePos }; + } + } + } + + nextChar(curr); + } + curr.line = line; + if (inclusive && isWhiteSpaceString(curr.line[curr.pos])) { + return { ln: curr.ln, pos: curr.pos }; + } + else { + return { ln: curr.ln, pos: lastSentencePos }; + } + + } + + var curr_index = { + ln: cur.line, + pos: cur.ch, + }; + + while (repeat > 0) { + if (dir < 0) { + curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir); + } + else { + curr_index = forward(cm, curr_index.ln, curr_index.pos, dir); + } + repeat--; + } + + return new Pos(curr_index.ln, curr_index.pos); + } + + function findSentence(cm, cur, repeat, dir) { + + /* + Takes an index object + { + line: the line string, + ln: line number, + pos: index in line, + dir: direction of traversal (-1 or 1) + } + and modifies the line, ln, and pos members to represent the + next valid position or sets them to null if there are + no more valid positions. + */ + function nextChar(cm, idx) { + if (idx.pos + idx.dir < 0 || idx.pos + idx.dir >= idx.line.length) { + idx.ln += idx.dir; + if (!isLine(cm, idx.ln)) { + idx.line = null; + idx.ln = null; + idx.pos = null; + return; + } + idx.line = cm.getLine(idx.ln); + idx.pos = (idx.dir > 0) ? 0 : idx.line.length - 1; + } + else { + idx.pos += idx.dir; + } + } + + /* + Performs one iteration of traversal in forward direction + Returns an index object of the new location + */ + /** @arg {CodeMirror} cm @arg {number} ln @arg {number} pos @arg {number} dir */ + function forward(cm, ln, pos, dir) { + var line = cm.getLine(ln); + var stop = (line === ""); + + var curr = { + line: line, + ln: ln, + pos: pos, + dir: dir, + }; + + var last_valid = { + ln: curr.ln, + pos: curr.pos, + }; + + var skip_empty_lines = (curr.line === ""); + + // Move one step to skip character we start on + nextChar(cm, curr); + + while (curr.line !== null) { + last_valid.ln = curr.ln; + last_valid.pos = curr.pos; + + if (curr.line === "" && !skip_empty_lines) { + return { ln: curr.ln, pos: curr.pos, }; + } + else if (stop && curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) { + return { ln: curr.ln, pos: curr.pos, }; + } + else if (isEndOfSentenceSymbol(curr.line[curr.pos]) + && !stop + && (curr.pos === curr.line.length - 1 + || isWhiteSpaceString(curr.line[curr.pos + 1]))) { + stop = true; + } + + nextChar(cm, curr); + } + + /* + Set the position to the last non whitespace character on the last + valid line in the case that we reach the end of the document. + */ + var line = cm.getLine(last_valid.ln); + last_valid.pos = 0; + for(var i = line.length - 1; i >= 0; --i) { + if (!isWhiteSpaceString(line[i])) { + last_valid.pos = i; + break; + } + } + + return last_valid; + + } + + /* + Performs one iteration of traversal in reverse direction + Returns an index object of the new location + */ + /** @arg {CodeMirror} cm @arg {number} ln @arg {number} pos @arg {number} dir */ + function reverse(cm, ln, pos, dir) { + var line = cm.getLine(ln); + + var curr = { + line: line, + ln: ln, + pos: pos, + dir: dir, + }; + + /** @type {{ln: number, pos: number|null}} */ + var last_valid = { + ln: curr.ln, + pos: null, + }; + + var skip_empty_lines = (curr.line === ""); + + // Move one step to skip character we start on + nextChar(cm, curr); + + while (curr.line !== null) { + + if (curr.line === "" && !skip_empty_lines) { + if (last_valid.pos !== null) { + return last_valid; + } + else { + return { ln: curr.ln, pos: curr.pos }; + } + } + else if (isEndOfSentenceSymbol(curr.line[curr.pos]) + && last_valid.pos !== null + && !(curr.ln === last_valid.ln && curr.pos + 1 === last_valid.pos)) { + return last_valid; + } + else if (curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) { + skip_empty_lines = false; + last_valid = { ln: curr.ln, pos: curr.pos }; + } + + nextChar(cm, curr); + } + + /* + Set the position to the first non whitespace character on the last + valid line in the case that we reach the beginning of the document. + */ + var line = cm.getLine(last_valid.ln); + last_valid.pos = 0; + for(var i = 0; i < line.length; ++i) { + if (!isWhiteSpaceString(line[i])) { + last_valid.pos = i; + break; + } + } + return last_valid; + } + + var curr_index = { + ln: cur.line, + pos: cur.ch, + }; + + while (repeat > 0) { + if (dir < 0) { + curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir); + } + else { + curr_index = forward(cm, curr_index.ln, curr_index.pos, dir); + } + repeat--; + } + + return new Pos(curr_index.ln, curr_index.pos); + } + + // TODO: perhaps this finagling of start and end positions belongs + // in codemirror/replaceRange? + /** @arg {CodeMirror} cm @arg {Pos} head @arg {string | number} symb @arg {boolean} inclusive */ + function selectCompanionObject(cm, head, symb, inclusive) { + var cur = head; + + var bracketRegexp = ({ + '(': /[()]/, ')': /[()]/, + '[': /[[\]]/, ']': /[[\]]/, + '{': /[{}]/, '}': /[{}]/, + '<': /[<>]/, '>': /[<>]/})[symb]; + var openSym = ({ + '(': '(', ')': '(', + '[': '[', ']': '[', + '{': '{', '}': '{', + '<': '<', '>': '<'})[symb]; + var curChar = cm.getLine(cur.line).charAt(cur.ch); + // Due to the behavior of scanForBracket, we need to add an offset if the + // cursor is on a matching open bracket. + var offset = curChar === openSym ? 1 : 0; + + var startBracket = cm.scanForBracket(new Pos(cur.line, cur.ch + offset), -1, undefined, {'bracketRegex': bracketRegexp}); + var endBracket = cm.scanForBracket(new Pos(cur.line, cur.ch + offset), 1, undefined, {'bracketRegex': bracketRegexp}); + + if (!startBracket || !endBracket) return null; + + var start = startBracket.pos; + var end = endBracket.pos; + + if ((start.line == end.line && start.ch > end.ch) + || (start.line > end.line)) { + var tmp = start; + start = end; + end = tmp; + } + + if (inclusive) { + end.ch += 1; + } else { + start.ch += 1; + } + + return { start: start, end: end }; + } + + // Takes in a symbol and a cursor and tries to simulate text objects that + // have identical opening and closing symbols + // TODO support across multiple lines + /** @arg {CodeMirror} cm @arg {Pos} head @arg {string} symb @arg {boolean} inclusive */ + function findBeginningAndEnd(cm, head, symb, inclusive) { + var cur = copyCursor(head); + var line = cm.getLine(cur.line); + var chars = line.split(''); + var start, end, i, len; + var firstIndex = chars.indexOf(symb); + + // the decision tree is to always look backwards for the beginning first, + // but if the cursor is in front of the first instance of the symb, + // then move the cursor forward + if (cur.ch < firstIndex) { + cur.ch = firstIndex; + } + // otherwise if the cursor is currently on the closing symbol + else if (firstIndex < cur.ch && chars[cur.ch] == symb) { + var stringAfter = /string/.test(cm.getTokenTypeAt(offsetCursor(head, 0, 1))); + var stringBefore = /string/.test(cm.getTokenTypeAt(head)); + var isStringStart = stringAfter && !stringBefore; + if (!isStringStart) { + end = cur.ch; // assign end to the current cursor + --cur.ch; // make sure to look backwards + } + } + + // if we're currently on the symbol, we've got a start + if (chars[cur.ch] == symb && !end) { + start = cur.ch + 1; // assign start to ahead of the cursor + } else { + // go backwards to find the start + for (i = cur.ch; i > -1 && !start; i--) { + if (chars[i] == symb) { + start = i + 1; + } + } + } + + // look forwards for the end symbol + if (start && !end) { + for (i = start, len = chars.length; i < len && !end; i++) { + if (chars[i] == symb) { + end = i; + } + } + } + + // nothing found + if (!start || !end) { + return { start: cur, end: cur }; + } + + // include the symbols + if (inclusive) { + --start; ++end; + } + + return { + start: new Pos(cur.line, start), + end: new Pos(cur.line, end) + }; + } + + // Search functions + defineOption('pcre', true, 'boolean'); + + class SearchState { + getQuery() { + return vimGlobalState.query; + }; + setQuery(query) { + vimGlobalState.query = query; + }; + getOverlay() { + return this.searchOverlay; + }; + setOverlay(overlay) { + this.searchOverlay = overlay; + }; + isReversed() { + return vimGlobalState.isReversed; + }; + setReversed(reversed) { + vimGlobalState.isReversed = reversed; + }; + getScrollbarAnnotate() { + return this.annotate; + }; + setScrollbarAnnotate(annotate) { + this.annotate = annotate; + }; + } /** @arg {CodeMirrorV} cm */ + function getSearchState(cm) { + var vim = cm.state.vim; + return vim.searchState_ || (vim.searchState_ = new SearchState()); + } + /** @arg {string} argString */ + function splitBySlash(argString) { + return splitBySeparator(argString, '/'); + } + + /** @arg {string} argString */ + function findUnescapedSlashes(argString) { + return findUnescapedSeparators(argString, '/'); + } + + /** @arg {string} argString @arg {string} separator */ + function splitBySeparator(argString, separator) { + var slashes = findUnescapedSeparators(argString, separator) || []; + if (!slashes.length) return []; + var tokens = []; + // in case of strings like foo/bar + if (slashes[0] !== 0) return; + for (var i = 0; i < slashes.length; i++) { + if (typeof slashes[i] == 'number') + tokens.push(argString.substring(slashes[i] + 1, slashes[i+1])); + } + return tokens; + } + + /** @arg {string} str @arg {string} separator */ + function findUnescapedSeparators(str, separator) { + if (!separator) + separator = '/'; + + var escapeNextChar = false; + var slashes = []; + for (var i = 0; i < str.length; i++) { + var c = str.charAt(i); + if (!escapeNextChar && c == separator) { + slashes.push(i); + } + escapeNextChar = !escapeNextChar && (c == '\\'); + } + return slashes; + } + + // Translates a search string from ex (vim) syntax into javascript form. + /** @arg {string} str */ + function translateRegex(str) { + // When these match, add a '\' if unescaped or remove one if escaped. + var specials = '|(){'; + // Remove, but never add, a '\' for these. + var unescape = '}'; + var escapeNextChar = false; + var out = []; + for (var i = -1; i < str.length; i++) { + var c = str.charAt(i) || ''; + var n = str.charAt(i+1) || ''; + var specialComesNext = (n && specials.indexOf(n) != -1); + if (escapeNextChar) { + if (c !== '\\' || !specialComesNext) { + out.push(c); + } + escapeNextChar = false; + } else { + if (c === '\\') { + escapeNextChar = true; + // Treat the unescape list as special for removing, but not adding '\'. + if (n && unescape.indexOf(n) != -1) { + specialComesNext = true; + } + // Not passing this test means removing a '\'. + if (!specialComesNext || n === '\\') { + out.push(c); + } + } else { + out.push(c); + if (specialComesNext && n !== '\\') { + out.push('\\'); + } + } + } + } + return out.join(''); + } + + // Translates the replace part of a search and replace from ex (vim) syntax into + // javascript form. Similar to translateRegex, but additionally fixes back references + // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'. + /** @type{Object} */ + var charUnescapes = {'\\n': '\n', '\\r': '\r', '\\t': '\t'}; + /** @arg {string} str */ + function translateRegexReplace(str) { + var escapeNextChar = false; + var out = []; + for (var i = -1; i < str.length; i++) { + var c = str.charAt(i) || ''; + var n = str.charAt(i+1) || ''; + if (charUnescapes[c + n]) { + out.push(charUnescapes[c+n]); + i++; + } else if (escapeNextChar) { + // At any point in the loop, escapeNextChar is true if the previous + // character was a '\' and was not escaped. + out.push(c); + escapeNextChar = false; + } else { + if (c === '\\') { + escapeNextChar = true; + if ((isNumber(n) || n === '$')) { + out.push('$'); + } else if (n !== '/' && n !== '\\') { + out.push('\\'); + } + } else { + if (c === '$') { + out.push('$'); + } + out.push(c); + if (n === '/') { + out.push('\\'); + } + } + } + } + return out.join(''); + } + + // Unescape \ and / in the replace part, for PCRE mode. + /** @type{Record} */ + var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t', '\\&':'&'}; + /** @arg {string} str */ + function unescapeRegexReplace(str) { + var stream = new CodeMirror.StringStream(str); + var output = []; + while (!stream.eol()) { + // Search for \. + while (stream.peek() && stream.peek() != '\\') { + output.push(stream.next()); + } + var matched = false; + for (var matcher in unescapes) { + if (stream.match(matcher, true)) { + matched = true; + output.push(unescapes[matcher]); + break; + } + } + if (!matched) { + // Don't change anything + output.push(stream.next()); + } + } + return output.join(''); + } + + /** + * Extract the regular expression from the query and return a Regexp object. + * Returns null if the query is blank. + * If ignoreCase is passed in, the Regexp object will have the 'i' flag set. + * If smartCase is passed in, and the query contains upper case letters, + * then ignoreCase is overridden, and the 'i' flag will not be set. + * If the query contains the /i in the flag part of the regular expression, + * then both ignoreCase and smartCase are ignored, and 'i' will be passed + * through to the Regex object. + * @arg {string|RegExp} query + * @arg {boolean} ignoreCase + * @arg {boolean} smartCase + */ + function parseQuery(query, ignoreCase, smartCase) { + // First update the last search register + var lastSearchRegister = vimGlobalState.registerController.getRegister('/'); + lastSearchRegister.setText(query); + // Check if the query is already a regex. + if (query instanceof RegExp) { return query; } + // First try to extract regex + flags from the input. If no flags found, + // extract just the regex. IE does not accept flags directly defined in + // the regex string in the form /regex/flags + var slashes = findUnescapedSlashes(query); + var regexPart; + var forceIgnoreCase; + if (!slashes.length) { + // Query looks like 'regexp' + regexPart = query; + } else { + // Query looks like 'regexp/...' + regexPart = query.substring(0, slashes[0]); + var flagsPart = query.substring(slashes[0]); + forceIgnoreCase = (flagsPart.indexOf('i') != -1); + } + if (!regexPart) { + return null; + } + if (!getOption('pcre')) { + regexPart = translateRegex(regexPart); + } + if (smartCase) { + ignoreCase = (/^[^A-Z]*$/).test(regexPart); + } + var regexp = new RegExp(regexPart, + (ignoreCase || forceIgnoreCase) ? 'im' : 'm'); + return regexp; + } + + /** + * dom - Document Object Manipulator + * Usage: + * dom(''|[, ...{|<$styles>}||'']) + * Examples: + * dom('div', {id:'xyz'}, dom('p', 'CM rocks!', {$color:'red'})) + * dom(document.head, dom('script', 'alert("hello!")')) + * Not supported: + * dom('p', ['arrays are objects'], Error('objects specify attributes')) + * @arg {string | HTMLElement } n + */ + function dom(n) { + if (typeof n === 'string') n = document.createElement(n); + for (var a, i = 1; i < arguments.length; i++) { + if (!(a = arguments[i])) continue; + if (typeof a !== 'object') a = document.createTextNode(a); + if (a.nodeType) n.appendChild(a); + else for (var key in a) { + if (!Object.prototype.hasOwnProperty.call(a, key)) continue; + if (key[0] === '$') n.style[key.slice(1)] = a[key]; + else n.setAttribute(key, a[key]); + } + } + return n; + } + + /** @arg {CodeMirror} cm @arg {any} template @arg {boolean} [long]*/ + function showConfirm(cm, template, long) { + var pre = dom('div', {$color: 'red', $whiteSpace: 'pre', class: 'cm-vim-message'}, template); + if (cm.openNotification) { + if (long) { + pre = dom('div', {}, pre, dom('div', {}, 'Press ENTER or type command to continue')); + if (cm.state.closeVimNotification) { + cm.state.closeVimNotification(); + } + cm.state.closeVimNotification = cm.openNotification(pre, {bottom: true, duration: 0}); + } else { + cm.openNotification(pre, {bottom: true, duration: 5000}); + } + } else { + alert(pre.innerText); + } + } + /** @arg {string} prefix @arg {string} desc */ + function makePrompt(prefix, desc) { + return dom('div', {$display: 'flex'}, + dom('span', {$fontFamily: 'monospace', $whiteSpace: 'pre', $flex: 1, $display: 'flex'}, + prefix, + dom('input', {type: 'text', autocorrect: 'off', + autocapitalize: 'off', spellcheck: 'false', $flex: 1})), + desc && dom('span', {$color: '#888'}, desc)); + } + /** + * @arg {CodeMirror} cm + * @arg {{ onClose?: any; prefix: any; desc?: any; onKeyUp?: any; onKeyDown: any; value?: any; selectValueOnOpen?: boolean; }} options + */ + function showPrompt(cm, options) { + if (keyToKeyStack.length) { + if (!options.value) options.value = ''; + virtualPrompt = options; + return; + } + var template = makePrompt(options.prefix, options.desc); + if (cm.openDialog) { + cm.openDialog(template, options.onClose, { + onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp, + bottom: true, selectValueOnOpen: false, value: options.value + }); + } + else { + var shortText = ''; + if (typeof options.prefix != "string" && options.prefix) shortText += options.prefix.textContent; + if (options.desc) shortText += " " + options.desc; + options.onClose(prompt(shortText, '')); + } + } + + /** @arg {RegExp|unknown} r1 @arg {RegExp|unknown} r2 */ + function regexEqual(r1, r2) { + if (r1 instanceof RegExp && r2 instanceof RegExp) { + var props = ['global', 'multiline', 'ignoreCase', 'source']; + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + if (r1[prop] !== r2[prop]) { + return false; + } + } + return true; + } + return false; + } + // Returns true if the query is valid. + /** + * @arg {CodeMirrorV} cm + * @arg {string | RegExp} rawQuery + * @arg {boolean | undefined} [ignoreCase] + * @arg {boolean | undefined} [smartCase] + */ + function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) { + if (!rawQuery) { + return; + } + var state = getSearchState(cm); + var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase); + if (!query) { + return; + } + highlightSearchMatches(cm, query); + if (regexEqual(query, state.getQuery())) { + return query; + } + state.setQuery(query); + return query; + } + /** @arg {RegExp} query */ + function searchOverlay(query) { + if (query.source.charAt(0) == '^') { + var matchSol = true; + } + return { + token: function(stream) { + if (matchSol && !stream.sol()) { + stream.skipToEnd(); + return; + } + var match = stream.match(query, false); + if (match) { + if (match[0].length == 0) { + // Matched empty string, skip to next. + stream.next(); + return 'searching'; + } + if (!stream.sol()) { + // Backtrack 1 to match \b + stream.backUp(1); + if (!query.exec(stream.next() + match[0])) { + stream.next(); + return null; + } + } + stream.match(query); + return 'searching'; + } + while (!stream.eol()) { + stream.next(); + if (stream.match(query, false)) break; + } + }, + query: query + }; + } + var highlightTimeout = 0; + /** @arg {CodeMirrorV} cm @arg {RegExp} query */ + function highlightSearchMatches(cm, query) { + clearTimeout(highlightTimeout); + var searchState = getSearchState(cm); + searchState.highlightTimeout = highlightTimeout; + highlightTimeout = setTimeout(function() { + if (!cm.state.vim) return; + var searchState = getSearchState(cm); + searchState.highlightTimeout = null; + var overlay = searchState.getOverlay(); + if (!overlay || query != overlay.query) { + if (overlay) { + cm.removeOverlay(overlay); + } + overlay = searchOverlay(query); + cm.addOverlay(overlay); + if (cm.showMatchesOnScrollbar) { + if (searchState.getScrollbarAnnotate()) { + searchState.getScrollbarAnnotate().clear(); + } + searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query)); + } + searchState.setOverlay(overlay); + } + }, 50); + } + /** @arg {CodeMirror} cm @arg {boolean} prev @arg {RegExp} query @arg {number | undefined} [repeat] */ + function findNext(cm, prev, query, repeat) { + return cm.operation(function() { + if (repeat === undefined) { repeat = 1; } + var pos = cm.getCursor(); + var cursor = cm.getSearchCursor(query, pos); + for (var i = 0; i < repeat; i++) { + var found = cursor.find(prev); + // @ts-ignore + if (i == 0 && found && cursorEqual(cursor.from(), pos)) { + var lastEndPos = prev ? cursor.from() : cursor.to(); + found = cursor.find(prev); + // @ts-ignore + if (found && !found[0] && cursorEqual(cursor.from(), lastEndPos)) { + // @ts-ignore + if (cm.getLine(lastEndPos.line).length == lastEndPos.ch) + found = cursor.find(prev); + } + } + if (!found) { + // SearchCursor may have returned null because it hit EOF, wrap + // around and try again. + cursor = cm.getSearchCursor(query, + // @ts-ignore + (prev) ? new Pos(cm.lastLine()) : new Pos(cm.firstLine(), 0) ); + if (!cursor.find(prev)) { + return; + } + } + } + return cursor.from(); + }); + } + /** + * Pretty much the same as `findNext`, except for the following differences: + * + * 1. Before starting the search, move to the previous search. This way if our cursor is + * already inside a match, we should return the current match. + * 2. Rather than only returning the cursor's from, we return the cursor's from and to as a tuple. + * @arg {CodeMirror} cm + * @arg {boolean} prev + * @arg {any} query + * @arg {number | undefined} repeat + * @arg {vimState} vim + */ + function findNextFromAndToInclusive(cm, prev, query, repeat, vim) { + return cm.operation(function() { + if (repeat === undefined) { repeat = 1; } + var pos = cm.getCursor(); + var cursor = cm.getSearchCursor(query, pos); + + // Go back one result to ensure that if the cursor is currently a match, we keep it. + var found = cursor.find(!prev); + + // If we haven't moved, go back one more (similar to if i==0 logic in findNext). + // @ts-ignore + if (!vim.visualMode && found && cursorEqual(cursor.from(), pos)) { + cursor.find(!prev); + } + + for (var i = 0; i < repeat; i++) { + found = cursor.find(prev); + if (!found) { + // SearchCursor may have returned null because it hit EOF, wrap + // around and try again. + cursor = cm.getSearchCursor(query, + // @ts-ignore + (prev) ? new Pos(cm.lastLine()) : new Pos(cm.firstLine(), 0) ); + if (!cursor.find(prev)) { + return; + } + } + } + return [cursor.from(), cursor.to()]; + }); + } + /** @arg {CodeMirrorV} cm */ + function clearSearchHighlight(cm) { + var state = getSearchState(cm); + if (state.highlightTimeout) { + clearTimeout(state.highlightTimeout); + state.highlightTimeout = null; + } + cm.removeOverlay(getSearchState(cm).getOverlay()); + state.setOverlay(null); + if (state.getScrollbarAnnotate()) { + state.getScrollbarAnnotate().clear(); + state.setScrollbarAnnotate(null); + } + } + /** + * Check if pos is in the specified range, INCLUSIVE. + * Range can be specified with 1 or 2 arguments. + * If the first range argument is an array, treat it as an array of line + * numbers. Match pos against any of the lines. + * If the first range argument is a number, + * if there is only 1 range argument, check if pos has the same line + * number + * if there are 2 range arguments, then check if pos is in between the two + * range arguments. + * @arg {number|Pos} pos + * @arg {number|number[]} start + * @arg {number} end + */ + function isInRange(pos, start, end) { + if (typeof pos != 'number') { + // Assume it is a cursor position. Get the line number. + pos = pos.line; + } + if (start instanceof Array) { + return inArray(pos, start); + } else { + if (typeof end == 'number') { + return (pos >= start && pos <= end); + } else { + return pos == start; + } + } + } + /** @arg {CodeMirror} cm */ + function getUserVisibleLines(cm) { + var scrollInfo = cm.getScrollInfo(); + var occludeToleranceTop = 6; + var occludeToleranceBottom = 10; + var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local'); + var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top; + var to = cm.coordsChar({left:0, top: bottomY}, 'local'); + return {top: from.line, bottom: to.line}; + } + + /** @arg {CodeMirror} cm @arg {vimState} vim @arg {string} markName */ + function getMarkPos(cm, vim, markName) { + if (markName == '\'' || markName == '`') { + return vimGlobalState.jumpList.find(cm, -1) || new Pos(0, 0); + } else if (markName == '.') { + return getLastEditPos(cm); + } + + var mark = vim.marks[markName]; + return mark && mark.find(); + } + + /** @arg {CodeMirror} cm */ + function getLastEditPos(cm) { + if (cm.getLastEditEnd) { + return cm.getLastEditEnd(); + } + // for old cm + var done = /**@type{any}*/(cm).doc.history.done; + for (var i = done.length; i--;) { + if (done[i].changes) { + return copyCursor(done[i].changes[0].to); + } + } + } + + class ExCommandDispatcher { + constructor() { + /**@type {Record} */ + this.commandMap_; + this.buildCommandMap_(); + } + processCommand(cm, input, opt_params) { + var that = this; + cm.operation(function () { + cm.curOp.isVimOp = true; + that._processCommand(cm, input, opt_params); + }); + } + _processCommand(cm, input, opt_params) { + var vim = cm.state.vim; + var commandHistoryRegister = vimGlobalState.registerController.getRegister(':'); + var previousCommand = commandHistoryRegister.toString(); + var inputStream = new CodeMirror.StringStream(input); + // update ": with the latest command whether valid or invalid + commandHistoryRegister.setText(input); + var params = opt_params || {}; + params.input = input; + try { + this.parseInput_(cm, inputStream, params); + } catch(e) { + showConfirm(cm, e + ""); + throw e; + } + + if (vim.visualMode) { + exitVisualMode(cm); + } + + var command; + var commandName; + if (!params.commandName) { + // If only a line range is defined, move to the line. + if (params.line !== undefined) { + commandName = 'move'; + } + } else { + command = this.matchCommand_(params.commandName); + if (command) { + commandName = command.name; + if (command.excludeFromCommandHistory) { + commandHistoryRegister.setText(previousCommand); + } + this.parseCommandArgs_(inputStream, params, command); + if (command.type == 'exToKey') { + // Handle Ex to Key mapping. + doKeyToKey(cm, command.toKeys, command); + return; + } else if (command.type == 'exToEx') { + // Handle Ex to Ex mapping. + this.processCommand(cm, command.toInput); + return; + } + } + } + if (!commandName) { + showConfirm(cm, 'Not an editor command ":' + input + '"'); + return; + } + try { + exCommands[commandName](cm, params); + // Possibly asynchronous commands (e.g. substitute, which might have a + // user confirmation), are responsible for calling the callback when + // done. All others have it taken care of for them here. + if ((!command || !command.possiblyAsync) && params.callback) { + params.callback(); + } + } catch(e) { + showConfirm(cm, e + ""); + throw e; + } + } + parseInput_(cm, inputStream, result) { + inputStream.eatWhile(':'); + // Parse range. + if (inputStream.eat('%')) { + result.line = cm.firstLine(); + result.lineEnd = cm.lastLine(); + } else { + result.line = this.parseLineSpec_(cm, inputStream); + if (result.line !== undefined && inputStream.eat(',')) { + result.lineEnd = this.parseLineSpec_(cm, inputStream); + } + } + + if (result.line == undefined) { + if (cm.state.vim.visualMode) { + result.selectionLine = getMarkPos(cm, cm.state.vim, '<')?.line; + result.selectionLineEnd = getMarkPos(cm, cm.state.vim, '>')?.line; + } else { + result.selectionLine = cm.getCursor().line; + } + } else { + result.selectionLine = result.line; + result.selectionLineEnd = result.lineEnd; + } + + // Parse command name. + var commandMatch = inputStream.match(/^(\w+|!!|@@|[!#&*<=>@~])/); + if (commandMatch) { + result.commandName = commandMatch[1]; + } else { + result.commandName = inputStream.match(/.*/)[0]; + } + + return result; + } + parseLineSpec_(cm, inputStream) { + var numberMatch = inputStream.match(/^(\d+)/); + if (numberMatch) { + // Absolute line number plus offset (N+M or N-M) is probably a typo, + // not something the user actually wanted. (NB: vim does allow this.) + return parseInt(numberMatch[1], 10) - 1; + } + switch (inputStream.next()) { + case '.': + return this.parseLineSpecOffset_(inputStream, cm.getCursor().line); + case '$': + return this.parseLineSpecOffset_(inputStream, cm.lastLine()); + case '\'': + var markName = inputStream.next(); + var markPos = getMarkPos(cm, cm.state.vim, markName); + if (!markPos) throw new Error('Mark not set'); + return this.parseLineSpecOffset_(inputStream, markPos.line); + case '-': + case '+': + inputStream.backUp(1); + // Offset is relative to current line if not otherwise specified. + return this.parseLineSpecOffset_(inputStream, cm.getCursor().line); + default: + inputStream.backUp(1); + return undefined; + } + } + parseLineSpecOffset_(inputStream, line) { + var offsetMatch = inputStream.match(/^([+-])?(\d+)/); + if (offsetMatch) { + var offset = parseInt(offsetMatch[2], 10); + if (offsetMatch[1] == "-") { + line -= offset; + } else { + line += offset; + } + } + return line; + } + parseCommandArgs_(inputStream, params, command) { + if (inputStream.eol()) { + return; + } + params.argString = inputStream.match(/.*/)[0]; + // Parse command-line arguments + var delim = command.argDelimiter || /\s+/; + var args = trim(params.argString).split(delim); + if (args.length && args[0]) { + params.args = args; + } + } + matchCommand_(commandName) { + // Return the command in the command map that matches the shortest + // prefix of the passed in command name. The match is guaranteed to be + // unambiguous if the defaultExCommandMap's shortNames are set up + // correctly. (see @code{defaultExCommandMap}). + for (var i = commandName.length; i > 0; i--) { + var prefix = commandName.substring(0, i); + if (this.commandMap_[prefix]) { + var command = this.commandMap_[prefix]; + if (command.name.indexOf(commandName) === 0) { + return command; + } + } + } + return null; + } + buildCommandMap_() { + this.commandMap_ = {}; + for (var i = 0; i < defaultExCommandMap.length; i++) { + var command = defaultExCommandMap[i]; + var key = command.shortName || command.name; + this.commandMap_[key] = command; + } + } + /**@type {(lhs: string, rhs: string, ctx: string, noremap?: boolean) => void} */ + map(lhs, rhs, ctx, noremap) { + if (lhs != ':' && lhs.charAt(0) == ':') { + if (ctx) { throw Error('Mode not supported for ex mappings'); } + var commandName = lhs.substring(1); + if (rhs != ':' && rhs.charAt(0) == ':') { + // Ex to Ex mapping + this.commandMap_[commandName] = { + name: commandName, + type: 'exToEx', + toInput: rhs.substring(1), + user: true + }; + } else { + // Ex to key mapping + this.commandMap_[commandName] = { + name: commandName, + type: 'exToKey', + toKeys: rhs, + user: true + }; + } + } else { + // Key to key or ex mapping + var mapping = { + keys: lhs, + type: 'keyToKey', + toKeys: rhs, + noremap: !!noremap + }; + if (ctx) { mapping.context = ctx; } + // @ts-ignore + defaultKeymap.unshift(mapping); + } + } + /**@type {(lhs: string, ctx: string) => boolean|void} */ + unmap(lhs, ctx) { + if (lhs != ':' && lhs.charAt(0) == ':') { + // Ex to Ex or Ex to key mapping + if (ctx) { throw Error('Mode not supported for ex mappings'); } + var commandName = lhs.substring(1); + if (this.commandMap_[commandName] && this.commandMap_[commandName].user) { + delete this.commandMap_[commandName]; + return true; + } + } else { + // Key to Ex or key to key mapping + var keys = lhs; + for (var i = 0; i < defaultKeymap.length; i++) { + if (keys == defaultKeymap[i].keys + && defaultKeymap[i].context === ctx) { + defaultKeymap.splice(i, 1); + return true; + } + } + } + } + } + + /** @typedef { import("./types").ExParams} ExParams */ + var exCommands = { + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + colorscheme: function(cm, params) { + if (!params.args || params.args.length < 1) { + showConfirm(cm, cm.getOption('theme')); + return; + } + cm.setOption('theme', params.args[0]); + }, + map: function(cm, params, ctx, defaultOnly) { + var mapArgs = params.args; + if (!mapArgs || mapArgs.length < 2) { + if (cm) { + showConfirm(cm, 'Invalid mapping: ' + params.input); + } + return; + } + exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx, defaultOnly); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + imap: function(cm, params) { this.map(cm, params, 'insert'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + nmap: function(cm, params) { this.map(cm, params, 'normal'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + vmap: function(cm, params) { this.map(cm, params, 'visual'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + omap: function(cm, params) { this.map(cm, params, 'operatorPending'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + noremap: function(cm, params) { this.map(cm, params, undefined, true); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + inoremap: function(cm, params) { this.map(cm, params, 'insert', true); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + nnoremap: function(cm, params) { this.map(cm, params, 'normal', true); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + vnoremap: function(cm, params) { this.map(cm, params, 'visual', true); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + onoremap: function(cm, params) { this.map(cm, params, 'operatorPending', true); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params @arg {string} ctx*/ + unmap: function(cm, params, ctx) { + var mapArgs = params.args; + if (!mapArgs || mapArgs.length < 1 || !exCommandDispatcher.unmap(mapArgs[0], ctx)) { + if (cm) { + showConfirm(cm, 'No such mapping: ' + params.input); + } + } + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + mapclear: function(cm, params) { vimApi.mapclear(); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + imapclear: function(cm, params) { vimApi.mapclear('insert'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + nmapclear: function(cm, params) { vimApi.mapclear('normal'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + vmapclear: function(cm, params) { vimApi.mapclear('visual'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + omapclear: function(cm, params) { vimApi.mapclear('operatorPending'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + move: function(cm, params) { + commandDispatcher.processCommand(cm, cm.state.vim, { + keys: "", + type: 'motion', + motion: 'moveToLineOrEdgeOfDocument', + motionArgs: { forward: false, explicitRepeat: true, linewise: true }, + repeatOverride: params.line+1 + }); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + set: function(cm, params) { + var setArgs = params.args; + // Options passed through to the setOption/getOption calls. May be passed in by the + // local/global versions of the set command + var setCfg = params.setCfg || {}; + if (!setArgs || setArgs.length < 1) { + if (cm) { + showConfirm(cm, 'Invalid mapping: ' + params.input); + } + return; + } + var expr = setArgs[0].split('='); + var optionName = expr.shift() || ""; + /**@type {string|boolean|undefined} */ + var value = expr.length > 0 ? expr.join('=') : undefined; + var forceGet = false; + var forceToggle = false; + + if (optionName.charAt(optionName.length - 1) == '?') { + // If post-fixed with ?, then the set is actually a get. + if (value) { throw Error('Trailing characters: ' + params.argString); } + optionName = optionName.substring(0, optionName.length - 1); + forceGet = true; + } else if (optionName.charAt(optionName.length - 1) == '!') { + optionName = optionName.substring(0, optionName.length - 1); + forceToggle = true; + } + if (value === undefined && optionName.substring(0, 2) == 'no') { + // To set boolean options to false, the option name is prefixed with + // 'no'. + optionName = optionName.substring(2); + value = false; + } + + var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean'; + if (optionIsBoolean) { + if (forceToggle) { + value = !getOption(optionName, cm, setCfg); + } else if (value == undefined) { + // Calling set with a boolean option sets it to true. + value = true; + } + } + // If no value is provided, then we assume this is a get. + if (!optionIsBoolean && value === undefined || forceGet) { + var oldValue = getOption(optionName, cm, setCfg); + if (oldValue instanceof Error) { + showConfirm(cm, oldValue.message); + } else if (oldValue === true || oldValue === false) { + showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName); + } else { + showConfirm(cm, ' ' + optionName + '=' + oldValue); + } + } else { + var setOptionReturn = setOption(optionName, value, cm, setCfg); + if (setOptionReturn instanceof Error) { + showConfirm(cm, setOptionReturn.message); + } + } + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + setlocal: function (cm, params) { + // setCfg is passed through to setOption + params.setCfg = {scope: 'local'}; + this.set(cm, params); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + setglobal: function (cm, params) { + // setCfg is passed through to setOption + params.setCfg = {scope: 'global'}; + this.set(cm, params); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + registers: function(cm, params) { + var regArgs = params.args; + var registers = vimGlobalState.registerController.registers; + var regInfo = '----------Registers----------\n\n'; + if (!regArgs) { + for (var registerName in registers) { + var text = registers[registerName].toString(); + if (text.length) { + regInfo += '"' + registerName + ' ' + text + '\n'; + } + } + } else { + var registerNames = regArgs.join(''); + for (var i = 0; i < registerNames.length; i++) { + var registerName = registerNames.charAt(i); + if (!vimGlobalState.registerController.isValidRegister(registerName)) { + continue; + } + var register = registers[registerName] || new Register(); + regInfo += '"' + registerName + ' ' + register.toString() + '\n'; + } + } + showConfirm(cm, regInfo, true); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + marks: function(cm, params) { + var filterArgs = params.args; + var marks = cm.state.vim.marks; + var regInfo = '-----------Marks-----------\nmark\tline\tcol\n\n'; + if (!filterArgs) { + for (var name in marks) { + var marker = marks[name] && marks[name].find(); + if (marker) { + regInfo += name + '\t' + marker.line + '\t' + marker.ch + '\n'; + } + } + } else { + var registerNames = filterArgs.join(''); + for (var i = 0; i < registerNames.length; i++) { + var name = registerNames.charAt(i); + var marker = marks[name] && marks[name].find(); + if (marker) { + regInfo += name + '\t' + marker.line + '\t' + marker.ch + '\n'; + } + } + } + showConfirm(cm, regInfo, true); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + sort: function(cm, params) { + var reverse, ignoreCase, unique, number, pattern; + function parseArgs() { + if (params.argString) { + var args = new CodeMirror.StringStream(params.argString); + if (args.eat('!')) { reverse = true; } + if (args.eol()) { return; } + if (!args.eatSpace()) { return 'Invalid arguments'; } + var opts = args.match(/([dinuox]+)?\s*(\/.+\/)?\s*/); + if (!opts || !args.eol()) { return 'Invalid arguments'; } + if (opts[1]) { + ignoreCase = opts[1].indexOf('i') != -1; + unique = opts[1].indexOf('u') != -1; + var decimal = opts[1].indexOf('d') != -1 || opts[1].indexOf('n') != -1; + var hex = opts[1].indexOf('x') != -1; + var octal = opts[1].indexOf('o') != -1; + if (Number(decimal) + Number(hex) + Number(octal) > 1) { return 'Invalid arguments'; } + number = decimal && 'decimal' || hex && 'hex' || octal && 'octal'; + } + if (opts[2]) { + pattern = new RegExp(opts[2].substr(1, opts[2].length - 2), ignoreCase ? 'i' : ''); + } + } + } + var err = parseArgs(); + if (err) { + showConfirm(cm, err + ': ' + params.argString); + return; + } + var lineStart = params.line || cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + if (lineStart == lineEnd) { return; } + var curStart = new Pos(lineStart, 0); + var curEnd = new Pos(lineEnd, lineLength(cm, lineEnd)); + var text = cm.getRange(curStart, curEnd).split('\n'); + var numberRegex = + (number == 'decimal') ? /(-?)([\d]+)/ : + (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i : + (number == 'octal') ? /([0-7]+)/ : null; + var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : undefined; + var numPart = [], textPart = []; + if (number || pattern) { + for (var i = 0; i < text.length; i++) { + var matchPart = pattern ? text[i].match(pattern) : null; + if (matchPart && matchPart[0] != '') { + numPart.push(matchPart); + } else if (numberRegex && numberRegex.exec(text[i])) { + numPart.push(text[i]); + } else { + textPart.push(text[i]); + } + } + } else { + textPart = text; + } + /** @arg {string} a @arg {string} b */ + function compareFn(a, b) { + if (reverse) { var tmp; tmp = a; a = b; b = tmp; } + if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); } + var amatch = numberRegex && numberRegex.exec(a); + var bmatch = numberRegex && numberRegex.exec(b); + if (!amatch || !bmatch) { return a < b ? -1 : 1; } + var anum = parseInt((amatch[1] + amatch[2]).toLowerCase(), radix); + var bnum = parseInt((bmatch[1] + bmatch[2]).toLowerCase(), radix); + return anum - bnum; + } + /** @arg {string[]} a @arg {string[]} b */ + function comparePatternFn(a, b) { + if (reverse) { var tmp; tmp = a; a = b; b = tmp; } + if (ignoreCase) { a[0] = a[0].toLowerCase(); b[0] = b[0].toLowerCase(); } + return (a[0] < b[0]) ? -1 : 1; + } + // @ts-ignore + numPart.sort(pattern ? comparePatternFn : compareFn); + if (pattern) { + for (var i = 0; i < numPart.length; i++) { + // @ts-ignore + numPart[i] = numPart[i].input; + } + } else if (!number) { textPart.sort(compareFn); } + text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart); + if (unique) { // Remove duplicate lines + var textOld = text; + var lastLine; + text = []; + for (var i = 0; i < textOld.length; i++) { + if (textOld[i] != lastLine) { + text.push(textOld[i]); + } + lastLine = textOld[i]; + } + } + cm.replaceRange(text.join('\n'), curStart, curEnd); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + vglobal: function(cm, params) { + // global inspects params.commandName + this.global(cm, params); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + normal: function(cm, params) { + var argString = params.argString; + if (argString && argString[0] == '!') { + argString = argString.slice(1); + noremap = true; + } + argString = argString.trimStart(); + if (!argString) { + showConfirm(cm, 'Argument is required.'); + return; + } + var line = params.line; + if (typeof line == 'number') { + var lineEnd = isNaN(params.lineEnd) ? line : params.lineEnd; + for (var i = line; i <= lineEnd; i++) { + cm.setCursor(i, 0); + doKeyToKey(cm, params.argString.trimStart()); + if (cm.state.vim.insertMode) { + exitInsertMode(cm, true); + } + } + } else { + doKeyToKey(cm, params.argString.trimStart()); + if (cm.state.vim.insertMode) { + exitInsertMode(cm, true); + } + } + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + global: function(cm, params) { + // a global command is of the form + // :[range]g/pattern/[cmd] + // argString holds the string /pattern/[cmd] + var argString = params.argString; + if (!argString) { + showConfirm(cm, 'Regular Expression missing from global'); + return; + } + var inverted = params.commandName[0] === 'v'; + if (argString[0] === '!' && params.commandName[0] === 'g') { + inverted = true; + argString = argString.slice(1); + } + // range is specified here + var lineStart = (params.line !== undefined) ? params.line : cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + // get the tokens from argString + var tokens = splitBySlash(argString); + var regexPart = argString, cmd = ""; + if (tokens && tokens.length) { + regexPart = tokens[0]; + cmd = tokens.slice(1, tokens.length).join('/'); + } + if (regexPart) { + // If regex part is empty, then use the previous query. Otherwise + // use the regex part as the new query. + try { + updateSearchQuery(cm, regexPart, true /** ignoreCase */, + true /** smartCase */); + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + regexPart); + return; + } + } + // now that we have the regexPart, search for regex matches in the + // specified range of lines + var query = getSearchState(cm).getQuery(); + /**@type {(string|import("./types").LineHandle)[]}*/ + var matchedLines = []; + for (var i = lineStart; i <= lineEnd; i++) { + var line = cm.getLine(i); + var matched = query.test(line); + if (matched !== inverted) { + matchedLines.push(cmd ? cm.getLineHandle(i) : line); + } + } + // if there is no [cmd], just display the list of matched lines + if (!cmd) { + showConfirm(cm, matchedLines.join('\n')); + return; + } + var index = 0; + var nextCommand = function() { + if (index < matchedLines.length) { + var lineHandle = matchedLines[index++]; + var lineNum = cm.getLineNumber(lineHandle); + if (lineNum == null) { + nextCommand(); + return; + } + var command = (lineNum + 1) + cmd; + exCommandDispatcher.processCommand(cm, command, { + callback: nextCommand + }); + } else if (cm.releaseLineHandles) { + cm.releaseLineHandles(); + } + }; + nextCommand(); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + substitute: function(cm, params) { + if (!cm.getSearchCursor) { + throw new Error('Search feature not available. Requires searchcursor.js or ' + + 'any other getSearchCursor implementation.'); + } + var argString = params.argString; + var tokens = argString ? splitBySeparator(argString, argString[0]) : []; + var regexPart = '', replacePart = '', trailing, flagsPart, count; + var confirm = false; // Whether to confirm each replace. + var global = false; // True to replace all instances on a line, false to replace only 1. + if (tokens && tokens.length) { + regexPart = tokens[0]; + if (getOption('pcre') && regexPart !== '') { + regexPart = new RegExp(regexPart).source; //normalize not escaped characters + } + replacePart = tokens[1]; + if (replacePart !== undefined) { + if (getOption('pcre')) { + replacePart = unescapeRegexReplace(replacePart.replace(/([^\\])&/g,"$1$$&")); + } else { + replacePart = translateRegexReplace(replacePart); + } + vimGlobalState.lastSubstituteReplacePart = replacePart; + } + trailing = tokens[2] ? tokens[2].split(' ') : []; + } else { + // either the argString is empty or its of the form ' hello/world' + // actually splitBySlash returns a list of tokens + // only if the string starts with a '/' + if (argString && argString.length) { + showConfirm(cm, 'Substitutions should be of the form ' + + ':s/pattern/replace/'); + return; + } + } + // After the 3rd slash, we can have flags followed by a space followed + // by count. + if (trailing) { + flagsPart = trailing[0]; + count = parseInt(trailing[1]); + if (flagsPart) { + if (flagsPart.indexOf('c') != -1) { + confirm = true; + } + if (flagsPart.indexOf('g') != -1) { + global = true; + } + if (getOption('pcre')) { + regexPart = regexPart + '/' + flagsPart; + } else { + regexPart = regexPart.replace(/\//g, "\\/") + '/' + flagsPart; + } + } + } + if (regexPart) { + // If regex part is empty, then use the previous query. Otherwise use + // the regex part as the new query. + try { + updateSearchQuery(cm, regexPart, true /** ignoreCase */, + true /** smartCase */); + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + regexPart); + return; + } + } + replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart; + if (replacePart === undefined) { + showConfirm(cm, 'No previous substitute regular expression'); + return; + } + var state = getSearchState(cm); + var query = state.getQuery(); + var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line; + var lineEnd = params.lineEnd || lineStart; + if (lineStart == cm.firstLine() && lineEnd == cm.lastLine()) { + lineEnd = Infinity; + } + if (count) { + lineStart = lineEnd; + lineEnd = lineStart + count - 1; + } + var startPos = clipCursorToContent(cm, new Pos(lineStart, 0)); + var cursor = cm.getSearchCursor(query, startPos); + doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + startinsert: function(cm, params) { + doKeyToKey(cm, params.argString == '!' ? 'A' : 'i', {}); + }, + redo: CodeMirror.commands.redo, + undo: CodeMirror.commands.undo, + /** @arg {CodeMirrorV} cm */ + write: function(cm) { + if (CodeMirror.commands.save) { + CodeMirror.commands.save(cm); + } else if (cm.save) { + // Saves to text area if no save command is defined and cm.save() is available. + cm.save(); + } + }, + /** @arg {CodeMirrorV} cm */ + nohlsearch: function(cm) { + clearSearchHighlight(cm); + }, + /** @arg {CodeMirrorV} cm */ + yank: function (cm) { + var cur = copyCursor(cm.getCursor()); + var line = cur.line; + var lineText = cm.getLine(line); + vimGlobalState.registerController.pushText( + '0', 'yank', lineText, true, true); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + delete: function(cm, params) { + var line = params.selectionLine; + var lineEnd = isNaN(params.selectionLineEnd) ? line : params.selectionLineEnd; + operators.delete(cm, {linewise: true}, [ + { anchor: new Pos(line, 0), + head: new Pos(lineEnd + 1, 0) } + ]); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + join: function(cm, params) { + var line = params.selectionLine; + var lineEnd = isNaN(params.selectionLineEnd) ? line : params.selectionLineEnd; + cm.setCursor(new Pos(line, 0)); + actions.joinLines(cm, {repeat: lineEnd - line}, cm.state.vim); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + delmarks: function(cm, params) { + if (!params.argString || !trim(params.argString)) { + showConfirm(cm, 'Argument required'); + return; + } + + var state = cm.state.vim; + var stream = new CodeMirror.StringStream(trim(params.argString)); + while (!stream.eol()) { + stream.eatSpace(); + + // Record the streams position at the beginning of the loop for use + // in error messages. + var count = stream.pos; + + if (!stream.match(/[a-zA-Z]/, false)) { + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); + return; + } + + var sym = stream.next(); + // Check if this symbol is part of a range + if (stream.match('-', true)) { + // This symbol is part of a range. + + // The range must terminate at an alphabetic character. + if (!stream.match(/[a-zA-Z]/, false)) { + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); + return; + } + + var startMark = sym; + var finishMark = stream.next(); + // The range must terminate at an alphabetic character which + // shares the same case as the start of the range. + if (startMark && finishMark && isLowerCase(startMark) == isLowerCase(finishMark)) { + var start = startMark.charCodeAt(0); + var finish = finishMark.charCodeAt(0); + if (start >= finish) { + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); + return; + } + + // Because marks are always ASCII values, and we have + // determined that they are the same case, we can use + // their char codes to iterate through the defined range. + for (var j = 0; j <= finish - start; j++) { + var mark = String.fromCharCode(start + j); + delete state.marks[mark]; + } + } else { + showConfirm(cm, 'Invalid argument: ' + startMark + '-'); + return; + } + } else if (sym) { + // This symbol is a valid mark, and is not part of a range. + delete state.marks[sym]; + } + } + } + }; + + var exCommandDispatcher = new ExCommandDispatcher(); + +/** + * @arg {CodeMirrorV} cm CodeMirror instance we are in. + * @arg {boolean} confirm Whether to confirm each replace. + * @arg {boolean} global + * @arg {number} lineStart Line to start replacing from. + * @arg {number} lineEnd Line to stop replacing at. + * @arg {RegExp} query Query for performing matches with. + * @arg {string} replaceWith Text to replace matches with. May contain $1, + * $2, etc for replacing captured groups using JavaScript replace. + * @arg {function} [callback] A callback for when the replace is done. + */ + function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query, + replaceWith, callback) { + // Set up all the functions. + cm.state.vim.exMode = true; + var done = false; + + /** @type {Pos}*/ var lastPos; + /** @type {number}*/ var modifiedLineNumber; + /** @type {boolean}*/var joined; + function replaceAll() { + cm.operation(function() { + while (!done) { + replace(); + next(); + } + stop(); + }); + } + function replace() { + var text = cm.getRange(searchCursor.from(), searchCursor.to()); + var newText = text.replace(query, replaceWith); + var unmodifiedLineNumber = searchCursor.to().line; + searchCursor.replace(newText); + modifiedLineNumber = searchCursor.to().line; + lineEnd += modifiedLineNumber - unmodifiedLineNumber; + joined = modifiedLineNumber < unmodifiedLineNumber; + } + function findNextValidMatch() { + var lastMatchTo = lastPos && copyCursor(searchCursor.to()); + var match = searchCursor.findNext(); + if (match && !match[0] && lastMatchTo && cursorEqual(searchCursor.from(), lastMatchTo)) { + match = searchCursor.findNext(); + } + return match; + } + function next() { + // The below only loops to skip over multiple occurrences on the same + // line when 'global' is not true. + while(findNextValidMatch() && + isInRange(searchCursor.from(), lineStart, lineEnd)) { + if (!global && searchCursor.from().line == modifiedLineNumber && !joined) { + continue; + } + cm.scrollIntoView(searchCursor.from(), 30); + cm.setSelection(searchCursor.from(), searchCursor.to()); + lastPos = searchCursor.from(); + done = false; + return; + } + done = true; + } + /** @arg {(() => void) | undefined} [close] */ + function stop(close) { + if (close) { close(); } + cm.focus(); + if (lastPos) { + cm.setCursor(lastPos); + var vim = cm.state.vim; + vim.exMode = false; + vim.lastHPos = vim.lastHSPos = lastPos.ch; + } + if (callback) { callback(); } + } + /** @arg {KeyboardEvent} e @arg {any} _value @arg {any} close */ + function onPromptKeyDown(e, _value, close) { + // Swallow all keys. + CodeMirror.e_stop(e); + var keyName = vimKeyFromEvent(e); + switch (keyName) { + case 'y': + replace(); next(); break; + case 'n': + next(); break; + case 'a': + // replaceAll contains a call to close of its own. We don't want it + // to fire too early or multiple times. + var savedCallback = callback; + callback = undefined; + cm.operation(replaceAll); + callback = savedCallback; + break; + case 'l': + replace(); + // fall through and exit. + case 'q': + case '': + case '': + case '': + stop(close); + break; + } + if (done) { stop(close); } + return true; + } + + // Actually do replace. + next(); + if (done) { + showConfirm(cm, 'No matches for ' + query.source); + return; + } + if (!confirm) { + replaceAll(); + if (callback) { callback(); } + return; + } + showPrompt(cm, { + prefix: dom('span', 'replace with ', dom('strong', replaceWith), ' (y/n/a/q/l)'), + onKeyDown: onPromptKeyDown + }); + } + + /** @arg {CodeMirrorV} cm @arg {boolean} [keepCursor] */ + function exitInsertMode(cm, keepCursor) { + var vim = cm.state.vim; + var macroModeState = vimGlobalState.macroModeState; + var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.'); + var isPlaying = macroModeState.isPlaying; + var lastChange = macroModeState.lastInsertModeChanges; + if (!isPlaying) { + cm.off('change', onChange); + if (vim.insertEnd) vim.insertEnd.clear(); + vim.insertEnd = undefined; + CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); + } + if (!isPlaying && vim.insertModeRepeat > 1) { + // Perform insert mode repeat for commands like 3,a and 3,o. + repeatLastEdit(cm, vim, vim.insertModeRepeat - 1, + true /** repeatForInsert */); + // @ts-ignore + vim.lastEditInputState.repeatOverride = vim.insertModeRepeat; + } + delete vim.insertModeRepeat; + vim.insertMode = false; + if (!keepCursor) { + cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1); + } + cm.setOption('keyMap', 'vim'); + cm.setOption('disableInput', true); + cm.toggleOverwrite(false); // exit replace mode if we were in it. + // update the ". register before exiting insert mode + insertModeChangeRegister.setText(lastChange.changes.join('')); + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + if (macroModeState.isRecording) { + logInsertModeChange(macroModeState); + } + } + + /** @arg {vimKey} command*/ + function _mapCommand(command) { + defaultKeymap.unshift(command); + } + + /** + * @arg {string} keys + * @arg {string} type + * @arg {string} name + * @arg {any} args + * @arg {{ [x: string]: any; }} extra + **/ + function mapCommand(keys, type, name, args, extra) { + /**@type{any} */ + var command = {keys: keys, type: type}; + command[type] = name; + command[type + "Args"] = args; + for (var key in extra) + command[key] = extra[key]; + _mapCommand(command); + } + + // The timeout in milliseconds for the two-character ESC keymap should be + // adjusted according to your typing speed to prevent false positives. + defineOption('insertModeEscKeysTimeout', 200, 'number'); + + + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {MacroModeState} macroModeState + * @arg {string} registerName + */ + function executeMacroRegister(cm, vim, macroModeState, registerName) { + var register = vimGlobalState.registerController.getRegister(registerName); + if (registerName == ':') { + // Read-only register containing last Ex command. + if (register.keyBuffer[0]) { + exCommandDispatcher.processCommand(cm, register.keyBuffer[0]); + } + macroModeState.isPlaying = false; + return; + } + var keyBuffer = register.keyBuffer; + var imc = 0; + macroModeState.isPlaying = true; + macroModeState.replaySearchQueries = register.searchQueries.slice(0); + for (var i = 0; i < keyBuffer.length; i++) { + var text = keyBuffer[i]; + var match, key; + var keyRe = /<(?:[CSMA]-)*\w+>|./gi; + while ((match = keyRe.exec(text))) { + // Pull off one command key, which is either a single character + // or a special sequence wrapped in '<' and '>', e.g. ''. + key = match[0]; + vimApi.handleKey(cm, key, 'macro'); + if (vim.insertMode) { + var changes = register.insertModeChanges[imc++].changes; + vimGlobalState.macroModeState.lastInsertModeChanges.changes = changes; + repeatInsertModeChanges(cm, changes, 1); + exitInsertMode(cm); + } + } + } + macroModeState.isPlaying = false; + } + + /** @arg {MacroModeState} macroModeState @arg {any} key */ + function logKey(macroModeState, key) { + if (macroModeState.isPlaying) { return; } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register) { + register.pushText(key); + } + } + + /** @arg {MacroModeState} macroModeState */ + function logInsertModeChange(macroModeState) { + if (macroModeState.isPlaying) { return; } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register && register.pushInsertModeChanges) { + register.pushInsertModeChanges(macroModeState.lastInsertModeChanges); + } + } + + /** @arg {MacroModeState} macroModeState @arg {string} query */ + function logSearchQuery(macroModeState, query) { + if (macroModeState.isPlaying) { return; } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register && register.pushSearchQuery) { + register.pushSearchQuery(query); + } + } + + /** + * Listens for changes made in insert mode. + * Should only be active in insert mode. + * @arg {CodeMirror} cm + * @arg {{ origin: string | undefined; text: any[]; next: any; }} changeObj + */ + function onChange(cm, changeObj) { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + if (!macroModeState.isPlaying) { + var vim = cm.state.vim; + while(changeObj) { + lastChange.expectCursorActivityForChange = true; + // @ts-ignore + if (lastChange.ignoreCount > 1) { + // @ts-ignore + lastChange.ignoreCount--; + } else if (changeObj.origin == '+input' || changeObj.origin == 'paste' + || changeObj.origin === undefined /* only in testing */) { + var selectionCount = cm.listSelections().length; + if (selectionCount > 1) + lastChange.ignoreCount = selectionCount; + var text = changeObj.text.join('\n'); + if (lastChange.maybeReset) { + lastChange.changes = []; + lastChange.maybeReset = false; + } + if (text) { + if (cm.state.overwrite && !/\n/.test(text)) { + lastChange.changes.push([text]); + } else { + if (text.length > 1) { + var insertEnd = vim && vim.insertEnd && vim.insertEnd.find(); + var cursor = cm.getCursor(); + if (insertEnd && insertEnd.line == cursor.line) { + var offset = insertEnd.ch - cursor.ch; + if (offset > 0 && offset < text.length) { + lastChange.changes.push([text, offset]); + text = ''; + } + } + } + if (text) lastChange.changes.push(text); + } + } + } + // Change objects may be chained with next. + changeObj = changeObj.next; + } + } + } + + /** + * Listens for any kind of cursor activity on CodeMirror. + * @arg {CodeMirrorV} cm + */ + function onCursorActivity(cm) { + var vim = cm.state.vim; + if (vim.insertMode) { + // Tracking cursor activity in insert mode (for macro support). + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { return; } + var lastChange = macroModeState.lastInsertModeChanges; + if (lastChange.expectCursorActivityForChange) { + lastChange.expectCursorActivityForChange = false; + } else { + // Cursor moved outside the context of an edit. Reset the change. + lastChange.maybeReset = true; + if (vim.insertEnd) vim.insertEnd.clear(); + vim.insertEnd = cm.setBookmark(cm.getCursor(), {insertLeft: true}); + } + } else if (!cm.curOp?.isVimOp) { + handleExternalSelection(cm, vim); + } + } + /** @arg {CodeMirrorV} cm @arg {vimState} vim */ + function handleExternalSelection(cm, vim) { + var anchor = cm.getCursor('anchor'); + var head = cm.getCursor('head'); + // Enter or exit visual mode to match mouse selection. + if (vim.visualMode && !cm.somethingSelected()) { + exitVisualMode(cm, false); + } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) { + vim.visualMode = true; + vim.visualLine = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); + } + if (vim.visualMode) { + // Bind CodeMirror selection model to vim selection model. + // Mouse selections are considered visual characterwise. + var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0; + var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0; + head = offsetCursor(head, 0, headOffset); + anchor = offsetCursor(anchor, 0, anchorOffset); + vim.sel = { + anchor: anchor, + head: head + }; + updateMark(cm, vim, '<', cursorMin(head, anchor)); + updateMark(cm, vim, '>', cursorMax(head, anchor)); + } else if (!vim.insertMode) { + // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse. + vim.lastHPos = cm.getCursor().ch; + } + } + + /** + * Wrapper for special keys pressed in insert mode + * @arg {string} keyName + */ + function InsertModeKey(keyName, e) { + this.keyName = keyName; + this.key = e.key; + this.ctrlKey = e.ctrlKey; + this.altKey = e.altKey; + this.metaKey = e.metaKey; + this.shiftKey = e.shiftKey; + } + + /** + * Handles raw key down events from the text area. + * - Should only be active in insert mode. + * - For recording deletes in insert mode. + * @arg {KeyboardEvent} e + */ + function onKeyEventTargetKeyDown(e) { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + var keyName = CodeMirror.keyName ? CodeMirror.keyName(e) : e.key; + if (!keyName) { return; } + + if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) { + if (lastChange.maybeReset) { + lastChange.changes = []; + lastChange.maybeReset = false; + } + lastChange.changes.push(new InsertModeKey(keyName, e)); + } + } + + /** + * Repeats the last edit, which includes exactly 1 command and at most 1 + * insert. Operator and motion commands are read from lastEditInputState, + * while action commands are read from lastEditActionCommand. + * + * If repeatForInsert is true, then the function was called by + * exitInsertMode to repeat the insert mode changes the user just made. The + * corresponding enterInsertMode call was made with a count. + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {number} repeat + * @arg {boolean} repeatForInsert + */ + function repeatLastEdit(cm, vim, repeat, repeatForInsert) { + var macroModeState = vimGlobalState.macroModeState; + macroModeState.isPlaying = true; + var lastAction = vim.lastEditActionCommand; + var cachedInputState = vim.inputState; + function repeatCommand() { + if (lastAction) { + commandDispatcher.processAction(cm, vim, lastAction); + } else { + commandDispatcher.evalInput(cm, vim); + } + } + /** @arg {number} repeat */ + function repeatInsert(repeat) { + if (macroModeState.lastInsertModeChanges.changes.length > 0) { + // For some reason, repeat cw in desktop VIM does not repeat + // insert mode changes. Will conform to that behavior. + repeat = !vim.lastEditActionCommand ? 1 : repeat; + var changeObject = macroModeState.lastInsertModeChanges; + repeatInsertModeChanges(cm, changeObject.changes, repeat); + } + } + // @ts-ignore + vim.inputState = vim.lastEditInputState; + if (lastAction && lastAction.interlaceInsertRepeat) { + // o and O repeat have to be interlaced with insert repeats so that the + // insertions appear on separate lines instead of the last line. + for (var i = 0; i < repeat; i++) { + repeatCommand(); + repeatInsert(1); + } + } else { + if (!repeatForInsert) { + // Hack to get the cursor to end up at the right place. If I is + // repeated in insert mode repeat, cursor will be 1 insert + // change set left of where it should be. + repeatCommand(); + } + repeatInsert(repeat); + } + vim.inputState = cachedInputState; + if (vim.insertMode && !repeatForInsert) { + // Don't exit insert mode twice. If repeatForInsert is set, then we + // were called by an exitInsertMode call lower on the stack. + exitInsertMode(cm); + } + macroModeState.isPlaying = false; + } + /**@arg {CodeMirrorV} cm, @arg {string} key */ + function sendCmKey(cm, key) { + CodeMirror.lookupKey(key, 'vim-insert', function keyHandler(binding) { + if (typeof binding == 'string') { + CodeMirror.commands[binding](cm); + } else { + binding(cm); + } + return true; + }); + } + function repeatInsertModeChanges(cm, changes, repeat) { + var head = cm.getCursor('head'); + var visualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.visualBlock; + if (visualBlock) { + // Set up block selection again for repeating the changes. + selectForInsert(cm, head, visualBlock + 1); + repeat = cm.listSelections().length; + cm.setCursor(head); + } + for (var i = 0; i < repeat; i++) { + if (visualBlock) { + cm.setCursor(offsetCursor(head, i, 0)); + } + for (var j = 0; j < changes.length; j++) { + var change = changes[j]; + if (change instanceof InsertModeKey) { + sendCmKey(cm, change.keyName); + } else if (typeof change == "string") { + cm.replaceSelection(change); + } else { + var start = cm.getCursor(); + var end = offsetCursor(start, 0, change[0].length - (change[1] || 0)); + cm.replaceRange(change[0], start, change[1] ? start: end); + cm.setCursor(end); + } + } + } + if (visualBlock) { + cm.setCursor(offsetCursor(head, 0, 1)); + } + } + + // multiselect support + /** @arg {vimState} state */ + function cloneVimState(state) { + var n = new state.constructor(); + Object.keys(state).forEach(function(key) { + if (key == "insertEnd") return; + var o = state[key]; + if (Array.isArray(o)) + o = o.slice(); + else if (o && typeof o == "object" && o.constructor != Object) + o = cloneVimState(o); + n[key] = o; + }); + if (state.sel) { + n.sel = { + head: state.sel.head && copyCursor(state.sel.head), + anchor: state.sel.anchor && copyCursor(state.sel.anchor) + }; + } + return n; + } + /** @arg {CodeMirror} cm_ @arg {string} key @arg {string} origin */ + function multiSelectHandleKey(cm_, key, origin) { + var vim = maybeInitVimState(cm_); + var cm = /**@type {CodeMirrorV}*/(cm_); + /** @type {boolean | undefined} */ + var isHandled = false; + var vim = vimApi.maybeInitVimState_(cm); + var visualBlock = vim.visualBlock || vim.wasInVisualBlock; + + if (cm.state.closeVimNotification) { + var close = cm.state.closeVimNotification; + cm.state.closeVimNotification = null; + close(); + if (key == '') { + clearInputState(cm); + return true; + } + } + + var wasMultiselect = cm.isInMultiSelectMode(); + if (vim.wasInVisualBlock && !wasMultiselect) { + vim.wasInVisualBlock = false; + } else if (wasMultiselect && vim.visualBlock) { + vim.wasInVisualBlock = true; + } + + if (key == '' && !vim.insertMode && !vim.visualMode && wasMultiselect && vim.status == "") { + // allow editor to exit multiselect + clearInputState(cm); + // @ts-ignore + } else if (visualBlock || !wasMultiselect || cm.inVirtualSelectionMode) { + isHandled = vimApi.handleKey(cm, key, origin); + } else { + var old = cloneVimState(vim); + var changeQueueList = vim.inputState.changeQueueList || []; + + cm.operation(function() { + if (cm.curOp) + cm.curOp.isVimOp = true; + var index = 0; + cm.forEachSelection(function() { + cm.state.vim.inputState.changeQueue = changeQueueList[index]; + var head = cm.getCursor("head"); + var anchor = cm.getCursor("anchor"); + var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0; + var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0; + head = offsetCursor(head, 0, headOffset); + anchor = offsetCursor(anchor, 0, anchorOffset); + cm.state.vim.sel.head = head; + cm.state.vim.sel.anchor = anchor; + + isHandled = vimApi.handleKey(cm, key, origin); + if (cm.virtualSelection) { + changeQueueList[index] = cm.state.vim.inputState.changeQueue; + cm.state.vim = cloneVimState(old); + } + index++; + }); + if (cm.curOp?.cursorActivity && !isHandled) + cm.curOp.cursorActivity = false; + cm.state.vim = vim; + vim.inputState.changeQueueList = changeQueueList; + vim.inputState.changeQueue = null; + }, true); + } + // some commands may bring visualMode and selection out of sync + if (isHandled && !vim.visualMode && !vim.insert && vim.visualMode != cm.somethingSelected()) { + handleExternalSelection(cm, vim); + } + return isHandled; + } + resetVimGlobalState(); + + return vimApi; +} + +function indexFromPos(doc, pos) { + var ch = pos.ch; + var lineNumber = pos.line + 1; + if (lineNumber < 1) { + lineNumber = 1; + ch = 0; + } + if (lineNumber > doc.lines) { + lineNumber = doc.lines; + ch = Number.MAX_VALUE; + } + var line = doc.line(lineNumber); + return Math.min(line.from + Math.max(0, ch), line.to); +} +function posFromIndex(doc, offset) { + let line = doc.lineAt(offset); + return { line: line.number - 1, ch: offset - line.from }; +} +class Pos { + constructor(line, ch) { + this.line = line; + this.ch = ch; + } +} +function on(emitter, type, f) { + if (emitter.addEventListener) { + emitter.addEventListener(type, f, false); + } + else { + var map = emitter._handlers || (emitter._handlers = {}); + map[type] = (map[type] || []).concat(f); + } +} +function off(emitter, type, f) { + if (emitter.removeEventListener) { + emitter.removeEventListener(type, f, false); + } + else { + var map = emitter._handlers, arr = map && map[type]; + if (arr) { + var index = arr.indexOf(f); + if (index > -1) { + map[type] = arr.slice(0, index).concat(arr.slice(index + 1)); + } + } + } +} +function signal(emitter, type, ...args) { + var _a; + var handlers = (_a = emitter._handlers) === null || _a === void 0 ? void 0 : _a[type]; + if (!handlers) + return; + for (var i = 0; i < handlers.length; ++i) { + handlers[i](...args); + } +} +function signalTo(handlers, ...args) { + if (!handlers) + return; + for (var i = 0; i < handlers.length; ++i) { + handlers[i](...args); + } +} +let wordChar; +try { + wordChar = new RegExp("[\\w\\p{Alphabetic}\\p{Number}_]", "u"); +} +catch (_) { + wordChar = /[\w]/; +} +// workaround for missing api for merging transactions +function dispatchChange(cm, transaction) { + var view = cm.cm6; + if (view.state.readOnly) + return; + var type = "input.type.compose"; + if (cm.curOp) { + if (!cm.curOp.lastChange) + type = "input.type.compose.start"; + } + if (transaction.annotations) { + try { + transaction.annotations.some(function (note) { + if (note.value == "input") + note.value = type; + }); + } + catch (e) { + console.error(e); + } + } + else { + transaction.userEvent = type; + } + return view.dispatch(transaction); +} +function runHistoryCommand(cm, revert) { + var _a; + if (cm.curOp) { + cm.curOp.$changeStart = undefined; + } + (revert ? commands.undo : commands.redo)(cm.cm6); + let changeStartIndex = (_a = cm.curOp) === null || _a === void 0 ? void 0 : _a.$changeStart; + // vim mode expects the changed text to be either selected or cursor placed at the start + if (changeStartIndex != null) { + cm.cm6.dispatch({ selection: { anchor: changeStartIndex } }); + } +} +var keys = {}; +"Left|Right|Up|Down|Backspace|Delete".split("|").forEach(key => { + keys[key] = (cm) => View.runScopeHandlers(cm.cm6, { key: key }, "editor"); +}); +class CodeMirror { + // -------------------------- + openDialog(template, callback, options) { + return openDialog(this, template, callback, options); + } + ; + openNotification(template, options) { + return openNotification(this, template, options); + } + ; + constructor(cm6) { + this.state = {}; + this.marks = Object.create(null); + this.$mid = 0; // marker id counter + this.options = {}; + this._handlers = {}; + this.$lastChangeEndOffset = 0; + this.virtualSelection = null; + this.cm6 = cm6; + this.onChange = this.onChange.bind(this); + this.onSelectionChange = this.onSelectionChange.bind(this); + } + ; + on(type, f) { on(this, type, f); } + off(type, f) { off(this, type, f); } + signal(type, e, handlers) { signal(this, type, e, handlers); } + indexFromPos(pos) { + return indexFromPos(this.cm6.state.doc, pos); + } + ; + posFromIndex(offset) { + return posFromIndex(this.cm6.state.doc, offset); + } + ; + foldCode(pos) { + let view = this.cm6; + let ranges = view.state.selection.ranges; + let doc = this.cm6.state.doc; + let index = indexFromPos(doc, pos); + let tmpRanges = state.EditorSelection.create([state.EditorSelection.range(index, index)], 0).ranges; + view.state.selection.ranges = tmpRanges; + language.foldCode(view); + view.state.selection.ranges = ranges; + } + firstLine() { return 0; } + ; + lastLine() { return this.cm6.state.doc.lines - 1; } + ; + lineCount() { return this.cm6.state.doc.lines; } + ; + setCursor(line, ch) { + if (typeof line === 'object') { + ch = line.ch; + line = line.line; + } + var offset = indexFromPos(this.cm6.state.doc, { line, ch: ch || 0 }); + this.cm6.dispatch({ selection: { anchor: offset } }, { scrollIntoView: !this.curOp }); + if (this.curOp && !this.curOp.isVimOp) + this.onBeforeEndOperation(); + } + ; + getCursor(p) { + var sel = this.cm6.state.selection.main; + var offset = p == "head" || !p + ? sel.head + : p == "anchor" + ? sel.anchor + : p == "start" + ? sel.from + : p == "end" + ? sel.to + : null; + if (offset == null) + throw new Error("Invalid cursor type"); + return this.posFromIndex(offset); + } + ; + listSelections() { + var doc = this.cm6.state.doc; + return this.cm6.state.selection.ranges.map(r => { + return { + anchor: posFromIndex(doc, r.anchor), + head: posFromIndex(doc, r.head), + }; + }); + } + ; + setSelections(p, primIndex) { + var doc = this.cm6.state.doc; + var ranges = p.map(x => { + var head = indexFromPos(doc, x.head); + var anchor = indexFromPos(doc, x.anchor); + // workaround for codemirror bug, see https://github.com/replit/codemirror-vim/issues/169 + if (head == anchor) + return state.EditorSelection.cursor(head, 1); + return state.EditorSelection.range(anchor, head); + }); + this.cm6.dispatch({ + selection: state.EditorSelection.create(ranges, primIndex) + }); + } + ; + setSelection(anchor, head, options) { + this.setSelections([{ anchor, head }], 0); + if (options && options.origin == '*mouse') { + this.onBeforeEndOperation(); + } + } + ; + getLine(row) { + var doc = this.cm6.state.doc; + if (row < 0 || row >= doc.lines) + return ""; + return this.cm6.state.doc.line(row + 1).text; + } + ; + getLineHandle(row) { + if (!this.$lineHandleChanges) + this.$lineHandleChanges = []; + return { row: row, index: this.indexFromPos(new Pos(row, 0)) }; + } + getLineNumber(handle) { + var updates = this.$lineHandleChanges; + if (!updates) + return null; + var offset = handle.index; + for (var i = 0; i < updates.length; i++) { + offset = updates[i].changes.mapPos(offset, 1, state.MapMode.TrackAfter); + if (offset == null) + return null; + } + var pos = this.posFromIndex(offset); + return pos.ch == 0 ? pos.line : null; + } + releaseLineHandles() { + this.$lineHandleChanges = undefined; + } + getRange(s, e) { + var doc = this.cm6.state.doc; + return this.cm6.state.sliceDoc(indexFromPos(doc, s), indexFromPos(doc, e)); + } + ; + replaceRange(text, s, e, source) { + if (!e) + e = s; + var doc = this.cm6.state.doc; + var from = indexFromPos(doc, s); + var to = indexFromPos(doc, e); + dispatchChange(this, { changes: { from, to, insert: text } }); + } + ; + replaceSelection(text) { + dispatchChange(this, this.cm6.state.replaceSelection(text)); + } + ; + replaceSelections(replacements) { + var ranges = this.cm6.state.selection.ranges; + var changes = ranges.map((r, i) => { + return { from: r.from, to: r.to, insert: replacements[i] || "" }; + }); + dispatchChange(this, { changes }); + } + ; + getSelection() { + return this.getSelections().join("\n"); + } + ; + getSelections() { + var cm = this.cm6; + return cm.state.selection.ranges.map(r => cm.state.sliceDoc(r.from, r.to)); + } + ; + somethingSelected() { + return this.cm6.state.selection.ranges.some(r => !r.empty); + } + ; + getInputField() { + return this.cm6.contentDOM; + } + ; + clipPos(p) { + var doc = this.cm6.state.doc; + var ch = p.ch; + var lineNumber = p.line + 1; + if (lineNumber < 1) { + lineNumber = 1; + ch = 0; + } + if (lineNumber > doc.lines) { + lineNumber = doc.lines; + ch = Number.MAX_VALUE; + } + var line = doc.line(lineNumber); + ch = Math.min(Math.max(0, ch), line.to - line.from); + return new Pos(lineNumber - 1, ch); + } + ; + getValue() { + return this.cm6.state.doc.toString(); + } + ; + setValue(text) { + var cm = this.cm6; + return cm.dispatch({ + changes: { from: 0, to: cm.state.doc.length, insert: text }, + selection: state.EditorSelection.range(0, 0) + }); + } + ; + focus() { + return this.cm6.focus(); + } + ; + blur() { + return this.cm6.contentDOM.blur(); + } + ; + defaultTextHeight() { + return this.cm6.defaultLineHeight; + } + ; + findMatchingBracket(pos, _options) { + var state = this.cm6.state; + var offset = indexFromPos(state.doc, pos); + var m = language.matchBrackets(state, offset + 1, -1); + if (m && m.end) { + return { to: posFromIndex(state.doc, m.end.from) }; + } + m = language.matchBrackets(state, offset, 1); + if (m && m.end) { + return { to: posFromIndex(state.doc, m.end.from) }; + } + return { to: undefined }; + } + ; + scanForBracket(pos, dir, style, config) { + return scanForBracket(this, pos, dir, style, config); + } + ; + indentLine(line, more) { + // todo how to indent only one line instead of selection + if (more) + this.indentMore(); + else + this.indentLess(); + } + ; + indentMore() { + commands.indentMore(this.cm6); + } + ; + indentLess() { + commands.indentLess(this.cm6); + } + ; + execCommand(name) { + if (name == "indentAuto") + CodeMirror.commands.indentAuto(this); + else if (name == "goLineLeft") + commands.cursorLineBoundaryBackward(this.cm6); + else if (name == "goLineRight") { + commands.cursorLineBoundaryForward(this.cm6); + let state = this.cm6.state; + let cur = state.selection.main.head; + if (cur < state.doc.length && state.sliceDoc(cur, cur + 1) !== "\n") { + commands.cursorCharBackward(this.cm6); + } + } + else + console.log(name + " is not implemented"); + } + ; + setBookmark(cursor, options) { + var assoc = (options === null || options === void 0 ? void 0 : options.insertLeft) ? 1 : -1; + var offset = this.indexFromPos(cursor); + var bm = new Marker(this, offset, assoc); + return bm; + } + ; + addOverlay({ query }) { + let cm6Query = new search.SearchQuery({ + regexp: true, + search: query.source, + caseSensitive: !/i/.test(query.flags), + }); + if (cm6Query.valid) { + cm6Query.forVim = true; + this.cm6Query = cm6Query; + let effect = search.setSearchQuery.of(cm6Query); + this.cm6.dispatch({ effects: effect }); + return cm6Query; + } + } + ; + removeOverlay(overlay) { + if (!this.cm6Query) + return; + this.cm6Query.forVim = false; + let effect = search.setSearchQuery.of(this.cm6Query); + this.cm6.dispatch({ effects: effect }); + } + ; + getSearchCursor(query, pos) { + var cm = this; + var last = null; + var lastCM5Result = null; + if (pos.ch == undefined) + pos.ch = Number.MAX_VALUE; + var firstOffset = indexFromPos(cm.cm6.state.doc, pos); + var source = query.source.replace(/(\\.|{(?:\d+(?:,\d*)?|,\d+)})|[{}]/g, function (a, b) { + if (!b) + return "\\" + a; + return b; + }); + function rCursor(doc, from = 0, to = doc.length) { + return new search.RegExpCursor(doc, source, { ignoreCase: query.ignoreCase }, from, to); + } + function nextMatch(from) { + var doc = cm.cm6.state.doc; + if (from > doc.length) + return null; + let res = rCursor(doc, from).next(); + return res.done ? null : res.value; + } + var ChunkSize = 10000; + function prevMatchInRange(from, to) { + var doc = cm.cm6.state.doc; + for (let size = 1;; size++) { + let start = Math.max(from, to - size * ChunkSize); + let cursor = rCursor(doc, start, to), range = null; + while (!cursor.next().done) + range = cursor.value; + if (range && (start == from || range.from > start + 10)) + return range; + if (start == from) + return null; + } + } + return { + findNext: function () { return this.find(false); }, + findPrevious: function () { return this.find(true); }, + find: function (back) { + var doc = cm.cm6.state.doc; + if (back) { + let endAt = last ? (last.from == last.to ? last.to - 1 : last.from) : firstOffset; + last = prevMatchInRange(0, endAt); + } + else { + let startFrom = last ? (last.from == last.to ? last.to + 1 : last.to) : firstOffset; + last = nextMatch(startFrom); + } + lastCM5Result = last && { + from: posFromIndex(doc, last.from), + to: posFromIndex(doc, last.to), + match: last.match, + }; + return last && last.match; + }, + from: function () { return lastCM5Result === null || lastCM5Result === void 0 ? void 0 : lastCM5Result.from; }, + to: function () { return lastCM5Result === null || lastCM5Result === void 0 ? void 0 : lastCM5Result.to; }, + replace: function (text) { + if (last) { + dispatchChange(cm, { + changes: { from: last.from, to: last.to, insert: text } + }); + last.to = last.from + text.length; + if (lastCM5Result) { + lastCM5Result.to = posFromIndex(cm.cm6.state.doc, last.to); + } + } + } + }; + } + ; + findPosV(start, amount, unit, goalColumn) { + let { cm6 } = this; + const doc = cm6.state.doc; + let pixels = unit == 'page' ? cm6.dom.clientHeight : 0; + const startOffset = indexFromPos(doc, start); + let range = state.EditorSelection.cursor(startOffset, 1, undefined, goalColumn); + let count = Math.round(Math.abs(amount)); + for (let i = 0; i < count; i++) { + if (unit == 'page') { + range = cm6.moveVertically(range, amount > 0, pixels); + } + else if (unit == 'line') { + range = cm6.moveVertically(range, amount > 0); + } + } + let pos = posFromIndex(doc, range.head); + // set hitside to true if there was no place to move and cursor was clipped to the edge + // of document. Needed for gj/gk + if ((amount < 0 && + range.head == 0 && goalColumn != 0 && + start.line == 0 && start.ch != 0) || (amount > 0 && + range.head == doc.length && pos.ch != goalColumn + && start.line == pos.line)) { + pos.hitSide = true; + } + return pos; + } + ; + charCoords(pos, mode) { + var rect = this.cm6.contentDOM.getBoundingClientRect(); + var offset = indexFromPos(this.cm6.state.doc, pos); + var coords = this.cm6.coordsAtPos(offset); + var d = -rect.top; + return { left: ((coords === null || coords === void 0 ? void 0 : coords.left) || 0) - rect.left, top: ((coords === null || coords === void 0 ? void 0 : coords.top) || 0) + d, bottom: ((coords === null || coords === void 0 ? void 0 : coords.bottom) || 0) + d }; + } + ; + coordsChar(coords, mode) { + var rect = this.cm6.contentDOM.getBoundingClientRect(); + var offset = this.cm6.posAtCoords({ x: coords.left + rect.left, y: coords.top + rect.top }) || 0; + return posFromIndex(this.cm6.state.doc, offset); + } + ; + getScrollInfo() { + var scroller = this.cm6.scrollDOM; + return { + left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight, + width: scroller.scrollWidth, + clientHeight: scroller.clientHeight, clientWidth: scroller.clientWidth + }; + } + ; + scrollTo(x, y) { + if (x != null) + this.cm6.scrollDOM.scrollLeft = x; + if (y != null) + this.cm6.scrollDOM.scrollTop = y; + } + ; + scrollIntoView(pos, margin) { + if (pos) { + var offset = this.indexFromPos(pos); + this.cm6.dispatch({ + effects: View.EditorView.scrollIntoView(offset) + }); + } + else { + this.cm6.dispatch({ scrollIntoView: true, userEvent: "scroll" }); + } + } + ; + getWrapperElement() { + return this.cm6.dom; + } + ; + // for tests + getMode() { + return { name: this.getOption("mode") }; + } + ; + setSize(w, h) { + this.cm6.dom.style.width = w + 4 + "px"; + this.cm6.dom.style.height = h + "px"; + this.refresh(); + } + refresh() { + this.cm6.measure(); + } + // event listeners + destroy() { + this.removeOverlay(); + } + ; + getLastEditEnd() { + return this.posFromIndex(this.$lastChangeEndOffset); + } + ; + onChange(update) { + if (this.$lineHandleChanges) { + this.$lineHandleChanges.push(update); + } + for (let i in this.marks) { + let m = this.marks[i]; + m.update(update.changes); + } + if (this.virtualSelection) { + this.virtualSelection.ranges = this.virtualSelection.ranges.map(range => range.map(update.changes)); + } + var curOp = this.curOp = this.curOp || {}; + update.changes.iterChanges((fromA, toA, fromB, toB, text) => { + if (curOp.$changeStart == null || curOp.$changeStart > fromB) + curOp.$changeStart = fromB; + this.$lastChangeEndOffset = toB; + var change = { text: text.toJSON() }; + if (!curOp.lastChange) { + curOp.lastChange = curOp.change = change; + } + else { + curOp.lastChange.next = curOp.lastChange = change; + } + }, true); + if (!curOp.changeHandlers) + curOp.changeHandlers = this._handlers["change"] && this._handlers["change"].slice(); + } + ; + onSelectionChange() { + var curOp = this.curOp = this.curOp || {}; + if (!curOp.cursorActivityHandlers) + curOp.cursorActivityHandlers = this._handlers["cursorActivity"] && this._handlers["cursorActivity"].slice(); + this.curOp.cursorActivity = true; + } + ; + operation(fn, force) { + if (!this.curOp) + this.curOp = { $d: 0 }; + this.curOp.$d++; + try { + var result = fn(); + } + finally { + if (this.curOp) { + this.curOp.$d--; + if (!this.curOp.$d) + this.onBeforeEndOperation(); + } + } + return result; + } + ; + onBeforeEndOperation() { + var op = this.curOp; + var scrollIntoView = false; + if (op) { + if (op.change) { + signalTo(op.changeHandlers, this, op.change); + } + if (op && op.cursorActivity) { + signalTo(op.cursorActivityHandlers, this, null); + if (op.isVimOp) + scrollIntoView = true; + } + this.curOp = null; + } + if (scrollIntoView) + this.scrollIntoView(); + } + ; + moveH(increment, unit) { + if (unit == 'char') { + // todo + var cur = this.getCursor(); + this.setCursor(cur.line, cur.ch + increment); + } + } + ; + setOption(name, val) { + switch (name) { + case "keyMap": + this.state.keyMap = val; + break; + case "textwidth": + this.state.textwidth = val; + break; + } + } + ; + getOption(name) { + switch (name) { + case "firstLineNumber": return 1; + case "tabSize": return this.cm6.state.tabSize || 4; + case "readOnly": return this.cm6.state.readOnly; + case "indentWithTabs": return this.cm6.state.facet(language.indentUnit) == "\t"; // TODO + case "indentUnit": return this.cm6.state.facet(language.indentUnit).length || 2; + case "textwidth": return this.state.textwidth; + // for tests + case "keyMap": return this.state.keyMap || "vim"; + } + } + ; + toggleOverwrite(on) { + this.state.overwrite = on; + } + ; + getTokenTypeAt(pos) { + var _a; + // only comment|string are needed + var offset = this.indexFromPos(pos); + var tree = language.ensureSyntaxTree(this.cm6.state, offset); + var node = tree === null || tree === void 0 ? void 0 : tree.resolve(offset); + var type = ((_a = node === null || node === void 0 ? void 0 : node.type) === null || _a === void 0 ? void 0 : _a.name) || ""; + if (/comment/i.test(type)) + return "comment"; + if (/string/i.test(type)) + return "string"; + return ""; + } + ; + overWriteSelection(text) { + var doc = this.cm6.state.doc; + var sel = this.cm6.state.selection; + var ranges = sel.ranges.map(x => { + if (x.empty) { + var ch = x.to < doc.length ? doc.sliceString(x.from, x.to + 1) : ""; + if (ch && !/\n/.test(ch)) + return state.EditorSelection.range(x.from, x.to + 1); + } + return x; + }); + this.cm6.dispatch({ + selection: state.EditorSelection.create(ranges, sel.mainIndex) + }); + this.replaceSelection(text); + } + /*** multiselect ****/ + isInMultiSelectMode() { + return this.cm6.state.selection.ranges.length > 1; + } + virtualSelectionMode() { + return !!this.virtualSelection; + } + forEachSelection(command) { + var selection = this.cm6.state.selection; + this.virtualSelection = state.EditorSelection.create(selection.ranges, selection.mainIndex); + for (var i = 0; i < this.virtualSelection.ranges.length; i++) { + var range = this.virtualSelection.ranges[i]; + if (!range) + continue; + this.cm6.dispatch({ selection: state.EditorSelection.create([range]) }); + command(); + this.virtualSelection.ranges[i] = this.cm6.state.selection.ranges[0]; + } + this.cm6.dispatch({ selection: this.virtualSelection }); + this.virtualSelection = null; + } + hardWrap(options) { + return hardWrap(this, options); + } +} +CodeMirror.isMac = typeof navigator != "undefined" && /Mac/.test(navigator.platform); +// -------------------------- +CodeMirror.Pos = Pos; +CodeMirror.StringStream = language.StringStream; +CodeMirror.commands = { + cursorCharLeft: function (cm) { commands.cursorCharLeft(cm.cm6); }, + redo: function (cm) { runHistoryCommand(cm, false); }, + undo: function (cm) { runHistoryCommand(cm, true); }, + newlineAndIndent: function (cm) { + commands.insertNewlineAndIndent({ + state: cm.cm6.state, + dispatch: (tr) => { + return dispatchChange(cm, tr); + } + }); + }, + indentAuto: function (cm) { + commands.indentSelection(cm.cm6); + }, + newlineAndIndentContinueComment: undefined, + save: undefined, +}; +CodeMirror.isWordChar = function (ch) { + return wordChar.test(ch); +}; +CodeMirror.keys = keys; +CodeMirror.addClass = function (el, str) { }; +CodeMirror.rmClass = function (el, str) { }; +CodeMirror.e_preventDefault = function (e) { + e.preventDefault(); +}; +CodeMirror.e_stop = function (e) { + var _a, _b; + (_a = e === null || e === void 0 ? void 0 : e.stopPropagation) === null || _a === void 0 ? void 0 : _a.call(e); + (_b = e === null || e === void 0 ? void 0 : e.preventDefault) === null || _b === void 0 ? void 0 : _b.call(e); +}; +CodeMirror.lookupKey = function lookupKey(key, map, handle) { + var result = CodeMirror.keys[key]; + if (result) + handle(result); +}; +CodeMirror.on = on; +CodeMirror.off = off; +CodeMirror.signal = signal; +CodeMirror.findMatchingTag = findMatchingTag; +CodeMirror.findEnclosingTag = findEnclosingTag; +CodeMirror.keyName = undefined; +/************* dialog *************/ +function dialogDiv(cm, template, bottom) { + var dialog = document.createElement("div"); + dialog.appendChild(template); + return dialog; +} +function closeNotification(cm, newVal) { + if (cm.state.currentNotificationClose) + cm.state.currentNotificationClose(); + cm.state.currentNotificationClose = newVal; +} +function openNotification(cm, template, options) { + closeNotification(cm, close); + var dialog = dialogDiv(cm, template, options && options.bottom); + var closed = false; + var doneTimer; + var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000; + function close() { + if (closed) + return; + closed = true; + clearTimeout(doneTimer); + dialog.remove(); + hideDialog(cm, dialog); + } + dialog.onclick = function (e) { + e.preventDefault(); + close(); + }; + showDialog(cm, dialog); + if (duration) + doneTimer = setTimeout(close, duration); + return close; +} +function showDialog(cm, dialog) { + var oldDialog = cm.state.dialog; + cm.state.dialog = dialog; + if (dialog && oldDialog !== dialog) { + if (oldDialog && oldDialog.contains(document.activeElement)) + cm.focus(); + if (oldDialog && oldDialog.parentElement) { + oldDialog.parentElement.replaceChild(dialog, oldDialog); + } + else if (oldDialog) { + oldDialog.remove(); + } + CodeMirror.signal(cm, "dialog"); + } +} +function hideDialog(cm, dialog) { + if (cm.state.dialog == dialog) { + cm.state.dialog = null; + CodeMirror.signal(cm, "dialog"); + } +} +function openDialog(me, template, callback, options) { + if (!options) + options = {}; + closeNotification(me, undefined); + var dialog = dialogDiv(me, template, options.bottom); + var closed = false; + showDialog(me, dialog); + function close(newVal) { + if (typeof newVal == 'string') { + inp.value = newVal; + } + else { + if (closed) + return; + closed = true; + hideDialog(me, dialog); + if (!me.state.dialog) + me.focus(); + if (options.onClose) + options.onClose(dialog); + } + } + var inp = dialog.getElementsByTagName("input")[0]; + if (inp) { + if (options.value) { + inp.value = options.value; + if (options.selectValueOnOpen !== false) + inp.select(); + } + if (options.onInput) + CodeMirror.on(inp, "input", function (e) { options.onInput(e, inp.value, close); }); + if (options.onKeyUp) + CodeMirror.on(inp, "keyup", function (e) { options.onKeyUp(e, inp.value, close); }); + CodeMirror.on(inp, "keydown", function (e) { + if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { + return; + } + if (e.keyCode == 13) + callback(inp.value); + if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) { + inp.blur(); + CodeMirror.e_stop(e); + close(); + } + }); + if (options.closeOnBlur !== false) + CodeMirror.on(inp, "blur", function () { + setTimeout(function () { + if (document.activeElement === inp) + return; + close(); + }); + }); + inp.focus(); + } + return close; +} +var matching = { "(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<", "<": ">>", ">": "<<" }; +function bracketRegex(config) { + return config && config.bracketRegex || /[(){}[\]]/; +} +function scanForBracket(cm, where, dir, style, config) { + var maxScanLen = (config && config.maxScanLineLength) || 10000; + var maxScanLines = (config && config.maxScanLines) || 1000; + var stack = []; + var re = bracketRegex(config); + var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) + : Math.max(cm.firstLine() - 1, where.line - maxScanLines); + for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { + var line = cm.getLine(lineNo); + if (!line) + continue; + var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; + if (line.length > maxScanLen) + continue; + if (lineNo == where.line) + pos = where.ch - (dir < 0 ? 1 : 0); + for (; pos != end; pos += dir) { + var ch = line.charAt(pos); + if (re.test(ch) /*&& (style === undefined || + (cm.getTokenTypeAt(new Pos(lineNo, pos + 1)) || "") == (style || ""))*/) { + var match = matching[ch]; + if (match && (match.charAt(1) == ">") == (dir > 0)) + stack.push(ch); + else if (!stack.length) + return { pos: new Pos(lineNo, pos), ch: ch }; + else + stack.pop(); + } + } + } + return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; +} +function findMatchingTag(cm, pos) { +} +function findEnclosingTag(cm, pos) { + var _a, _b; + var state = cm.cm6.state; + var offset = cm.indexFromPos(pos); + if (offset < state.doc.length) { + var text = state.sliceDoc(offset, offset + 1); + if (text == "<") + offset++; + } + var tree = language.ensureSyntaxTree(state, offset); + var node = (tree === null || tree === void 0 ? void 0 : tree.resolve(offset)) || null; + while (node) { + if (((_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.type.name) == 'OpenTag' + && ((_b = node.lastChild) === null || _b === void 0 ? void 0 : _b.type.name) == 'CloseTag') { + return { + open: convertRange(state.doc, node.firstChild), + close: convertRange(state.doc, node.lastChild), + }; + } + node = node.parent; + } +} +function convertRange(doc, cm6Range) { + return { + from: posFromIndex(doc, cm6Range.from), + to: posFromIndex(doc, cm6Range.to) + }; +} +class Marker { + constructor(cm, offset, assoc) { + this.cm = cm; + this.id = cm.$mid++; + this.offset = offset; + this.assoc = assoc; + cm.marks[this.id] = this; + } + ; + clear() { delete this.cm.marks[this.id]; } + ; + find() { + if (this.offset == null) + return null; + return this.cm.posFromIndex(this.offset); + } + ; + update(change) { + if (this.offset != null) + this.offset = change.mapPos(this.offset, this.assoc, state.MapMode.TrackDel); + } +} +function hardWrap(cm, options) { + var _a; + var max = options.column || cm.getOption('textwidth') || 80; + var allowMerge = options.allowMerge != false; + var row = Math.min(options.from, options.to); + var endRow = Math.max(options.from, options.to); + while (row <= endRow) { + var line = cm.getLine(row); + if (line.length > max) { + var space = findSpace(line, max, 5); + if (space) { + var indentation = (_a = /^\s*/.exec(line)) === null || _a === void 0 ? void 0 : _a[0]; + cm.replaceRange("\n" + indentation, new Pos(row, space.start), new Pos(row, space.end)); + } + endRow++; + } + else if (allowMerge && /\S/.test(line) && row != endRow) { + var nextLine = cm.getLine(row + 1); + if (nextLine && /\S/.test(nextLine)) { + var trimmedLine = line.replace(/\s+$/, ""); + var trimmedNextLine = nextLine.replace(/^\s+/, ""); + var mergedLine = trimmedLine + " " + trimmedNextLine; + var space = findSpace(mergedLine, max, 5); + if (space && space.start > trimmedLine.length || mergedLine.length < max) { + cm.replaceRange(" ", new Pos(row, trimmedLine.length), new Pos(row + 1, nextLine.length - trimmedNextLine.length)); + row--; + endRow--; + } + else if (trimmedLine.length < line.length) { + cm.replaceRange("", new Pos(row, trimmedLine.length), new Pos(row, line.length)); + } + } + } + row++; + } + return row; + function findSpace(line, max, min) { + if (line.length < max) + return; + var before = line.slice(0, max); + var after = line.slice(max); + var spaceAfter = /^(?:(\s+)|(\S+)(\s+))/.exec(after); + var spaceBefore = /(?:(\s+)|(\s+)(\S+))$/.exec(before); + var start = 0; + var end = 0; + if (spaceBefore && !spaceBefore[2]) { + start = max - spaceBefore[1].length; + end = max; + } + if (spaceAfter && !spaceAfter[2]) { + if (!start) + start = max; + end = max + spaceAfter[1].length; + } + if (start) { + return { + start: start, + end: end + }; + } + if (spaceBefore && spaceBefore[2] && spaceBefore.index > min) { + return { + start: spaceBefore.index, + end: spaceBefore.index + spaceBefore[2].length + }; + } + if (spaceAfter && spaceAfter[2]) { + start = max + spaceAfter[2].length; + return { + start: start, + end: start + spaceAfter[3].length + }; + } + } +} + +// backwards compatibility for old versions not supporting getDrawSelectionConfig +let getDrawSelectionConfig = View__namespace.getDrawSelectionConfig || function () { + let defaultConfig = { cursorBlinkRate: 1200 }; + return function () { + return defaultConfig; + }; +}(); +class Piece { + constructor(left, top, height, fontFamily, fontSize, fontWeight, color, className, letter, partial) { + this.left = left; + this.top = top; + this.height = height; + this.fontFamily = fontFamily; + this.fontSize = fontSize; + this.fontWeight = fontWeight; + this.color = color; + this.className = className; + this.letter = letter; + this.partial = partial; + } + draw() { + let elt = document.createElement("div"); + elt.className = this.className; + this.adjust(elt); + return elt; + } + adjust(elt) { + elt.style.left = this.left + "px"; + elt.style.top = this.top + "px"; + elt.style.height = this.height + "px"; + elt.style.lineHeight = this.height + "px"; + elt.style.fontFamily = this.fontFamily; + elt.style.fontSize = this.fontSize; + elt.style.fontWeight = this.fontWeight; + elt.style.color = this.partial ? "transparent" : this.color; + elt.className = this.className; + elt.textContent = this.letter; + } + eq(p) { + return this.left == p.left && this.top == p.top && this.height == p.height && + this.fontFamily == p.fontFamily && this.fontSize == p.fontSize && + this.fontWeight == p.fontWeight && this.color == p.color && + this.className == p.className && + this.letter == p.letter; + } +} +class BlockCursorPlugin { + constructor(view, cm) { + this.view = view; + this.rangePieces = []; + this.cursors = []; + this.cm = cm; + this.measureReq = { read: this.readPos.bind(this), write: this.drawSel.bind(this) }; + this.cursorLayer = view.scrollDOM.appendChild(document.createElement("div")); + this.cursorLayer.className = "cm-cursorLayer cm-vimCursorLayer"; + this.cursorLayer.setAttribute("aria-hidden", "true"); + view.requestMeasure(this.measureReq); + this.setBlinkRate(); + } + setBlinkRate() { + let config = getDrawSelectionConfig(this.cm.cm6.state); + let blinkRate = config.cursorBlinkRate; + this.cursorLayer.style.animationDuration = blinkRate + "ms"; + } + update(update) { + if (update.selectionSet || update.geometryChanged || update.viewportChanged) { + this.view.requestMeasure(this.measureReq); + this.cursorLayer.style.animationName = this.cursorLayer.style.animationName == "cm-blink" ? "cm-blink2" : "cm-blink"; + } + if (configChanged(update)) + this.setBlinkRate(); + } + scheduleRedraw() { + this.view.requestMeasure(this.measureReq); + } + readPos() { + let { state } = this.view; + let cursors = []; + for (let r of state.selection.ranges) { + let prim = r == state.selection.main; + let piece = measureCursor(this.cm, this.view, r, prim); + if (piece) + cursors.push(piece); + } + return { cursors }; + } + drawSel({ cursors }) { + if (cursors.length != this.cursors.length || cursors.some((c, i) => !c.eq(this.cursors[i]))) { + let oldCursors = this.cursorLayer.children; + if (oldCursors.length !== cursors.length) { + this.cursorLayer.textContent = ""; + for (const c of cursors) + this.cursorLayer.appendChild(c.draw()); + } + else { + cursors.forEach((c, idx) => c.adjust(oldCursors[idx])); + } + this.cursors = cursors; + } + } + destroy() { + this.cursorLayer.remove(); + } +} +function configChanged(update) { + return getDrawSelectionConfig(update.startState) != getDrawSelectionConfig(update.state); +} +const themeSpec = { + ".cm-vimMode .cm-line": { + "& ::selection": { backgroundColor: "transparent !important" }, + "&::selection": { backgroundColor: "transparent !important" }, + caretColor: "transparent !important", + }, + ".cm-fat-cursor": { + position: "absolute", + background: "#ff9696", + border: "none", + whiteSpace: "pre", + }, + "&:not(.cm-focused) .cm-fat-cursor": { + background: "none", + outline: "solid 1px #ff9696", + color: "transparent !important", + }, +}; +const hideNativeSelection = state.Prec.highest(View.EditorView.theme(themeSpec)); +function getBase(view) { + let rect = view.scrollDOM.getBoundingClientRect(); + let left = view.textDirection == View.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth; + return { left: left - view.scrollDOM.scrollLeft, top: rect.top - view.scrollDOM.scrollTop }; +} +function measureCursor(cm, view, cursor, primary) { + var _a, _b; + let head = cursor.head; + let fatCursor = false; + let hCoeff = 1; + let vim = cm.state.vim; + if (vim && (!vim.insertMode || cm.state.overwrite)) { + fatCursor = true; + if (vim.visualBlock && !primary) + return null; + if (cursor.anchor < cursor.head) { + let letter = head < view.state.doc.length && view.state.sliceDoc(head, head + 1); + if (letter != "\n") + head--; + } + if (cm.state.overwrite) + hCoeff = 0.2; + else if (vim.status) + hCoeff = 0.5; + } + if (fatCursor) { + let letter = head < view.state.doc.length && view.state.sliceDoc(head, head + 1); + if (letter && (/[\uDC00-\uDFFF]/.test(letter) && head > 1)) { + // step back if cursor is on the second half of a surrogate pair + head--; + letter = view.state.sliceDoc(head, head + 1); + } + let pos = view.coordsAtPos(head, 1); + if (!pos) + return null; + let base = getBase(view); + let domAtPos = view.domAtPos(head); + let node = domAtPos ? domAtPos.node : view.contentDOM; + while (domAtPos && domAtPos.node instanceof HTMLElement) { + node = domAtPos.node; + domAtPos = { node: domAtPos.node.childNodes[domAtPos.offset], offset: 0 }; + } + if (!(node instanceof HTMLElement)) { + if (!node.parentNode) + return null; + node = node.parentNode; + } + let style = getComputedStyle(node); + let left = pos.left; + // TODO remove coordsAtPos when all supported versions of codemirror have coordsForChar api + let charCoords = (_b = (_a = view).coordsForChar) === null || _b === void 0 ? void 0 : _b.call(_a, head); + if (charCoords) { + left = charCoords.left; + } + if (!letter || letter == "\n" || letter == "\r") { + letter = "\xa0"; + } + else if (letter == "\t") { + letter = "\xa0"; + var nextPos = view.coordsAtPos(head + 1, -1); + if (nextPos) { + left = nextPos.left - (nextPos.left - pos.left) / parseInt(style.tabSize); + } + } + else if ((/[\uD800-\uDBFF]/.test(letter) && head < view.state.doc.length - 1)) { + // include the second half of a surrogate pair in cursor + letter += view.state.sliceDoc(head + 1, head + 2); + } + let h = (pos.bottom - pos.top); + return new Piece(left - base.left, pos.top - base.top + h * (1 - hCoeff), h * hCoeff, style.fontFamily, style.fontSize, style.fontWeight, style.color, primary ? "cm-fat-cursor cm-cursor-primary" : "cm-fat-cursor cm-cursor-secondary", letter, hCoeff != 1); + } + else { + return null; + } +} + +var FIREFOX_LINUX = typeof navigator != "undefined" + && /linux/i.test(navigator.platform) + && / Gecko\/\d+/.exec(navigator.userAgent); +const Vim = initVim(CodeMirror); +const HighlightMargin = 250; +const vimStyle = View.EditorView.baseTheme({ + ".cm-vimMode .cm-cursorLayer:not(.cm-vimCursorLayer)": { + display: "none", + }, + ".cm-vim-panel": { + padding: "0px 10px", + fontFamily: "monospace", + minHeight: "1.3em", + }, + ".cm-vim-panel input": { + border: "none", + outline: "none", + backgroundColor: "inherit", + }, + "&light .cm-searchMatch": { backgroundColor: "#ffff0054" }, + "&dark .cm-searchMatch": { backgroundColor: "#00ffff8a" }, +}); +const vimPlugin = View.ViewPlugin.fromClass(class { + constructor(view) { + this.status = ""; + this.query = null; + this.decorations = View.Decoration.none; + this.waitForCopy = false; + this.lastKeydown = ''; + this.useNextTextInput = false; + this.compositionText = ''; + this.view = view; + const cm = (this.cm = new CodeMirror(view)); + Vim.enterVimMode(this.cm); + this.view.cm = this.cm; + this.cm.state.vimPlugin = this; + this.blockCursor = new BlockCursorPlugin(view, cm); + this.updateClass(); + this.cm.on("vim-command-done", () => { + if (cm.state.vim) + cm.state.vim.status = ""; + this.blockCursor.scheduleRedraw(); + this.updateStatus(); + }); + this.cm.on("vim-mode-change", (e) => { + if (!cm.state.vim) + return; + cm.state.vim.mode = e.mode; + if (e.subMode) { + cm.state.vim.mode += " block"; + } + cm.state.vim.status = ""; + this.blockCursor.scheduleRedraw(); + this.updateClass(); + this.updateStatus(); + }); + this.cm.on("dialog", () => { + if (this.cm.state.statusbar) { + this.updateStatus(); + } + else { + view.dispatch({ + effects: showVimPanel.of(!!this.cm.state.dialog), + }); + } + }); + this.dom = document.createElement("span"); + this.dom.style.cssText = "position: absolute; right: 10px; top: 1px"; + this.statusButton = document.createElement("span"); + this.statusButton.onclick = (e) => { + Vim.handleKey(this.cm, "", "user"); + this.cm.focus(); + }; + this.statusButton.style.cssText = "cursor: pointer"; + } + update(update) { + var _a; + if ((update.viewportChanged || update.docChanged) && this.query) { + this.highlight(this.query); + } + if (update.docChanged) { + this.cm.onChange(update); + } + if (update.selectionSet) { + this.cm.onSelectionChange(); + } + if (update.viewportChanged) ; + if (this.cm.curOp && !this.cm.curOp.isVimOp) { + this.cm.onBeforeEndOperation(); + } + if (update.transactions) { + for (let tr of update.transactions) + for (let effect of tr.effects) { + if (effect.is(search.setSearchQuery)) { + let forVim = (_a = effect.value) === null || _a === void 0 ? void 0 : _a.forVim; + if (!forVim) { + this.highlight(null); + } + else { + let query = effect.value.create(); + this.highlight(query); + } + } + } + } + this.blockCursor.update(update); + } + updateClass() { + const state = this.cm.state; + if (!state.vim || (state.vim.insertMode && !state.overwrite)) + this.view.scrollDOM.classList.remove("cm-vimMode"); + else + this.view.scrollDOM.classList.add("cm-vimMode"); + } + updateStatus() { + let dom = this.cm.state.statusbar; + let vim = this.cm.state.vim; + if (!dom || !vim) + return; + let dialog = this.cm.state.dialog; + if (dialog) { + if (dialog.parentElement != dom) { + dom.textContent = ""; + dom.appendChild(dialog); + } + } + else { + dom.textContent = ""; + var status = (vim.mode || "normal").toUpperCase(); + if (vim.insertModeReturn) + status += "(C-O)"; + this.statusButton.textContent = `--${status}--`; + dom.appendChild(this.statusButton); + } + this.dom.textContent = vim.status; + dom.appendChild(this.dom); + } + destroy() { + Vim.leaveVimMode(this.cm); + this.updateClass(); + this.blockCursor.destroy(); + delete this.view.cm; + } + highlight(query) { + this.query = query; + if (!query) + return (this.decorations = View.Decoration.none); + let { view } = this; + let builder = new state.RangeSetBuilder(); + for (let i = 0, ranges = view.visibleRanges, l = ranges.length; i < l; i++) { + let { from, to } = ranges[i]; + while (i < l - 1 && to > ranges[i + 1].from - 2 * HighlightMargin) + to = ranges[++i].to; + query.highlight(view.state, from, to, (from, to) => { + builder.add(from, to, matchMark); + }); + } + return (this.decorations = builder.finish()); + } + handleKey(e, view) { + const cm = this.cm; + let vim = cm.state.vim; + if (!vim) + return; + const key = Vim.vimKeyFromEvent(e, vim); + CodeMirror.signal(this.cm, 'inputEvent', { type: "handleKey", key }); + if (!key) + return; + // clear search highlight + if (key == "" && + !vim.insertMode && + !vim.visualMode && + this.query /* && !cm.inMultiSelectMode*/) { + const searchState = vim.searchState_; + if (searchState) { + cm.removeOverlay(searchState.getOverlay()); + searchState.setOverlay(null); + } + } + let isCopy = key === "" && !CodeMirror.isMac; + if (isCopy && cm.somethingSelected()) { + this.waitForCopy = true; + return true; + } + vim.status = (vim.status || "") + key; + let result = Vim.multiSelectHandleKey(cm, key, "user"); + vim = Vim.maybeInitVimState_(cm); // the object can change if there is an exception in handleKey + // insert mode + if (!result && vim.insertMode && cm.state.overwrite) { + if (e.key && e.key.length == 1 && !/\n/.test(e.key)) { + result = true; + cm.overWriteSelection(e.key); + } + else if (e.key == "Backspace") { + result = true; + CodeMirror.commands.cursorCharLeft(cm); + } + } + if (result) { + CodeMirror.signal(this.cm, 'vim-keypress', key); + e.preventDefault(); + e.stopPropagation(); + this.blockCursor.scheduleRedraw(); + } + this.updateStatus(); + return !!result; + } +}, { + eventHandlers: { + copy: function (e, view) { + if (!this.waitForCopy) + return; + this.waitForCopy = false; + Promise.resolve().then(() => { + var cm = this.cm; + var vim = cm.state.vim; + if (!vim) + return; + if (vim.insertMode) { + cm.setSelection(cm.getCursor(), cm.getCursor()); + } + else { + cm.operation(() => { + if (cm.curOp) + cm.curOp.isVimOp = true; + Vim.handleKey(cm, '', 'user'); + }); + } + }); + }, + compositionstart: function (e, view) { + this.useNextTextInput = true; + CodeMirror.signal(this.cm, 'inputEvent', e); + }, + compositionupdate: function (e, view) { + CodeMirror.signal(this.cm, 'inputEvent', e); + }, + compositionend: function (e, view) { + CodeMirror.signal(this.cm, 'inputEvent', e); + }, + keypress: function (e, view) { + CodeMirror.signal(this.cm, 'inputEvent', e); + if (this.lastKeydown == "Dead") + this.handleKey(e, view); + }, + keydown: function (e, view) { + CodeMirror.signal(this.cm, 'inputEvent', e); + this.lastKeydown = e.key; + if (this.lastKeydown == "Unidentified" + || this.lastKeydown == "Process" + || this.lastKeydown == "Dead") { + this.useNextTextInput = true; + } + else { + this.useNextTextInput = false; + this.handleKey(e, view); + } + }, + }, + provide: () => { + return [ + View.EditorView.inputHandler.of((view, from, to, text) => { + var _a, _b; + var cm = getCM(view); + if (!cm) + return false; + var vim = (_a = cm.state) === null || _a === void 0 ? void 0 : _a.vim; + var vimPlugin = cm.state.vimPlugin; + if (vim && !vim.insertMode && !((_b = cm.curOp) === null || _b === void 0 ? void 0 : _b.isVimOp)) { + if (text === "\0\0") { + return true; + } + CodeMirror.signal(cm, 'inputEvent', { + type: "text", + text, + from, + to, + }); + if (text.length == 1 && vimPlugin.useNextTextInput) { + if (vim.expectLiteralNext && view.composing) { + vimPlugin.compositionText = text; + return false; + } + if (vimPlugin.compositionText) { + var toRemove = vimPlugin.compositionText; + vimPlugin.compositionText = ''; + var head = view.state.selection.main.head; + var textInDoc = view.state.sliceDoc(head - toRemove.length, head); + if (toRemove === textInDoc) { + var pos = cm.getCursor(); + cm.replaceRange('', cm.posFromIndex(head - toRemove.length), pos); + } + } + vimPlugin.handleKey({ + key: text, + preventDefault: () => { }, + stopPropagation: () => { } + }); + forceEndComposition(view); + return true; + } + } + return false; + }) + ]; + }, + decorations: (v) => v.decorations, +}); +/** + * removes contenteditable element and adds it back to end + * IME composition in normal mode + * this method works on all browsers except for Firefox on Linux + * where we need to reset textContent of editor + * (which doesn't work on other browsers) + */ +function forceEndComposition(view) { + var parent = view.scrollDOM.parentElement; + if (!parent) + return; + if (FIREFOX_LINUX) { + view.contentDOM.textContent = "\0\0"; + view.contentDOM.dispatchEvent(new CustomEvent("compositionend")); + return; + } + var sibling = view.scrollDOM.nextSibling; + var selection = window.getSelection(); + var savedSelection = selection && { + anchorNode: selection.anchorNode, + anchorOffset: selection.anchorOffset, + focusNode: selection.focusNode, + focusOffset: selection.focusOffset + }; + view.scrollDOM.remove(); + parent.insertBefore(view.scrollDOM, sibling); + try { + if (savedSelection && selection) { + selection.setPosition(savedSelection.anchorNode, savedSelection.anchorOffset); + if (savedSelection.focusNode) { + selection.extend(savedSelection.focusNode, savedSelection.focusOffset); + } + } + } + catch (e) { + console.error(e); + } + view.focus(); + view.contentDOM.dispatchEvent(new CustomEvent("compositionend")); +} +const matchMark = View.Decoration.mark({ class: "cm-searchMatch" }); +const showVimPanel = state.StateEffect.define(); +const vimPanelState = state.StateField.define({ + create: () => false, + update(value, tr) { + for (let e of tr.effects) + if (e.is(showVimPanel)) + value = e.value; + return value; + }, + provide: (f) => { + return View.showPanel.from(f, (on) => (on ? createVimPanel : null)); + }, +}); +function createVimPanel(view) { + let dom = document.createElement("div"); + dom.className = "cm-vim-panel"; + let cm = view.cm; + if (cm.state.dialog) { + dom.appendChild(cm.state.dialog); + } + return { top: false, dom }; +} +function statusPanel(view) { + let dom = document.createElement("div"); + dom.className = "cm-vim-panel"; + let cm = view.cm; + cm.state.statusbar = dom; + cm.state.vimPlugin.updateStatus(); + return { dom }; +} +function vim(options = {}) { + return [ + vimStyle, + vimPlugin, + hideNativeSelection, + options.status ? View.showPanel.of(statusPanel) : vimPanelState, + ]; +} +function getCM(view) { + return view.cm || null; +} + +exports.CodeMirror = CodeMirror; +exports.Vim = Vim; +exports.getCM = getCM; +exports.vim = vim; diff --git a/dist/index.d.cts b/dist/index.d.cts new file mode 100644 index 0000000..930aaf2 --- /dev/null +++ b/dist/index.d.cts @@ -0,0 +1,373 @@ +import { EditorSelection, ChangeDesc, Extension } from '@codemirror/state'; +import { StringStream } from '@codemirror/language'; +import { EditorView, ViewUpdate } from '@codemirror/view'; +import { SearchQuery } from '@codemirror/search'; + +type vimState = { + onPasteFn?: any, + sel: {head: Pos$1, anchor: Pos$1}, + insertModeReturn: boolean, + visualBlock: boolean, + marks: {[mark: string]: Marker$1}, + visualMode: boolean, + insertMode: boolean, + pasteFn: any, + lastSelection: any, + searchState_: any, + lastEditActionCommand: actionCommand|void, + lastPastedText: any, + lastMotion: any, + options: {[optionName: string]: vimOption}, + lastEditInputState: InputStateInterface|void, + inputState: InputStateInterface, + visualLine: boolean, + insertModeRepeat: any, + lastHSPos: number, + lastHPos: number, + wasInVisualBlock?: boolean, + insert?: any, + insertEnd?: Marker$1, + status: string, + exMode?: boolean, + mode?: any, + expectLiteralNext?: boolean, + constructor(): void; +} +type Marker$1 = ReturnType +type Pos$1 = { line: number, ch: number, sticky?: string } +interface CM5RangeInterface { + anchor: Pos$1, + head: Pos$1, +} + +type OperatorArgs = { + repeat?: number, + forward?: boolean, + linewise?: boolean, + fullLine?: boolean, + registerName?: string|null, + indentRight?: boolean, + toLower?: boolean, + shouldMoveCursor?: boolean, + selectedCharacter?: string, + lastSel?: any; + keepCursor?: boolean; +} + +type ActionArgsPartial = { + repeat?: number, + forward?: boolean, + head?: Pos$1, + position?: string + backtrack?: boolean, + increase?: boolean, + repeatIsExplicit?: boolean, + indentRight?: boolean, + selectedCharacter?: string, + after?: boolean, + matchIndent?: boolean, + registerName?: string, + isEdit?: boolean + linewise?: boolean, + insertAt?: string, + blockwise?: boolean, + keepSpaces?: boolean, + replace?: boolean, + keepCursor?: boolean +} + +type MotionArgsPartial = { + repeat?: number, + forward?: boolean, + selectedCharacter?: string, + linewise?: boolean, + textObjectInner?: boolean, + sameLine?: boolean, + repeatOffset?: number, + toJumplist?: boolean, + inclusive?: boolean, + wordEnd?: boolean, + toFirstChar?:boolean, + explicitRepeat?: boolean, + bigWord?: boolean, + repeatIsExplicit?: boolean, + noRepeat?: boolean +}; + +type MotionArgs = MotionArgsPartial & {repeat: number}; + + + +type optionCallback = (value?: string|undefined, cm?: CodeMirror) => any +type vimOption = { + type?: string, + defaultValue?: unknown, + callback?: optionCallback, + value?: unknown +} + +type allCommands = { + keys: string, + context?: string, + interlaceInsertRepeat?: boolean, + exitVisualBlock?: boolean, + isEdit?: boolean, + repeatOverride?: number +} +type actionCommand = allCommands & { + type: 'action', + action: string, + actionArgs?: ActionArgsPartial, + motion?: string, + operator?: string, + interlaceInsertRepeat?: boolean +} + +interface InputStateInterface { + prefixRepeat: string[]; + motionRepeat: any[]; + operator: any| undefined | null; + operatorArgs: OperatorArgs | undefined | null; + motion: string | undefined | null; + motionArgs: MotionArgs | null; + keyBuffer: any[]; + registerName?: string; + changeQueue: any; + operatorShortcut?: string; + selectedCharacter?: string; + repeatOverride?: number; + changeQueueList?: any[]; + pushRepeatDigit(n: string): void; + getRepeat(): number; +} + + +declare global { + function isNaN(v: any): v is Exclude; + interface String { + trimStart(): string + } +} + +declare class Pos { + line: number; + ch: number; + sticky?: string; + constructor(line: number, ch: number); +} +declare function on(emitter: any, type: string, f: Function): void; +declare function off(emitter: any, type: string, f: Function): void; +declare function signal(emitter: any, type: string, ...args: any[]): void; +interface Operation { + $d: number; + isVimOp?: boolean; + cursorActivityHandlers?: Function[]; + cursorActivity?: boolean; + lastChange?: any; + change?: any; + changeHandlers?: Function[]; + $changeStart?: number; +} +declare class CodeMirror { + static isMac: boolean; + static Pos: typeof Pos; + static StringStream: StringStream & (new (_: string) => StringStream); + static commands: { + cursorCharLeft: (cm: CodeMirror) => void; + redo: (cm: CodeMirror) => void; + undo: (cm: CodeMirror) => void; + newlineAndIndent: (cm: CodeMirror) => void; + indentAuto: (cm: CodeMirror) => void; + newlineAndIndentContinueComment: any; + save: any; + }; + static isWordChar: (ch: string) => boolean; + static keys: any; + static addClass: (el: any, str: any) => void; + static rmClass: (el: any, str: any) => void; + static e_preventDefault: (e: Event) => void; + static e_stop: (e: Event) => void; + static lookupKey: (key: string, map: string, handle: Function) => void; + static on: typeof on; + static off: typeof off; + static signal: typeof signal; + openDialog(template: Element, callback: Function, options: any): (newVal?: string | undefined) => void; + openNotification(template: Node, options: NotificationOptions): () => void; + static findMatchingTag: typeof findMatchingTag; + static findEnclosingTag: typeof findEnclosingTag; + cm6: EditorView; + state: { + statusbar?: Element | null; + dialog?: Element | null; + vimPlugin?: any; + vim?: vimState | null; + currentNotificationClose?: Function | null; + closeVimNotification?: Function | null; + keyMap?: string; + overwrite?: boolean; + textwidth?: number; + }; + marks: Record; + $mid: number; + curOp: Operation | null | undefined; + options: any; + _handlers: any; + constructor(cm6: EditorView); + on(type: string, f: Function): void; + off(type: string, f: Function): void; + signal(type: string, e: any, handlers?: any): void; + indexFromPos(pos: Pos): number; + posFromIndex(offset: number): Pos; + foldCode(pos: Pos): void; + firstLine(): number; + lastLine(): number; + lineCount(): number; + setCursor(line: number, ch: number): void; + setCursor(line: Pos): void; + getCursor(p?: "head" | "anchor" | "start" | "end"): Pos; + listSelections(): { + anchor: Pos; + head: Pos; + }[]; + setSelections(p: CM5RangeInterface[], primIndex?: number): void; + setSelection(anchor: Pos, head: Pos, options?: any): void; + getLine(row: number): string; + getLineHandle(row: number): { + row: number; + index: number; + }; + getLineNumber(handle: any): number | null; + releaseLineHandles(): void; + getRange(s: Pos, e: Pos): string; + replaceRange(text: string, s: Pos, e?: Pos, source?: string): void; + replaceSelection(text: string): void; + replaceSelections(replacements: string[]): void; + getSelection(): string; + getSelections(): string[]; + somethingSelected(): boolean; + getInputField(): HTMLElement; + clipPos(p: Pos): Pos; + getValue(): string; + setValue(text: string): void; + focus(): void; + blur(): void; + defaultTextHeight(): number; + findMatchingBracket(pos: Pos, _options?: any): { + to: Pos; + } | { + to: undefined; + }; + scanForBracket(pos: Pos, dir: 1 | -1, style: any, config: any): false | { + pos: Pos; + ch: string; + } | null; + indentLine(line: number, more?: boolean): void; + indentMore(): void; + indentLess(): void; + execCommand(name: string): void; + setBookmark(cursor: Pos, options?: { + insertLeft: boolean; + }): Marker; + cm6Query?: SearchQuery; + addOverlay({ query }: { + query: RegExp; + }): SearchQuery | undefined; + removeOverlay(overlay?: any): void; + getSearchCursor(query: RegExp, pos: Pos): { + findNext: () => string[] | null | undefined; + findPrevious: () => string[] | null | undefined; + find: (back?: boolean) => string[] | null | undefined; + from: () => Pos | undefined; + to: () => Pos | undefined; + replace: (text: string) => void; + }; + findPosV(start: Pos, amount: number, unit: "page" | "line", goalColumn?: number): Pos & { + hitSide?: boolean | undefined; + }; + charCoords(pos: Pos, mode: "div" | "local"): { + left: number; + top: number; + bottom: number; + }; + coordsChar(coords: { + left: number; + top: number; + }, mode: "div" | "local"): Pos; + getScrollInfo(): { + left: number; + top: number; + height: number; + width: number; + clientHeight: number; + clientWidth: number; + }; + scrollTo(x?: number | null, y?: number | null): void; + scrollIntoView(pos?: Pos, margin?: number): void; + getWrapperElement(): HTMLElement; + getMode(): { + name: string | number | boolean | undefined; + }; + setSize(w: number, h: number): void; + refresh(): void; + destroy(): void; + getLastEditEnd(): Pos; + $lastChangeEndOffset: number; + $lineHandleChanges: undefined | ViewUpdate[]; + onChange(update: ViewUpdate): void; + onSelectionChange(): void; + operation(fn: Function, force?: boolean): any; + onBeforeEndOperation(): void; + moveH(increment: number, unit: string): void; + setOption(name: string, val: any): void; + getOption(name: "firstLineNumber" | "tabSize"): number; + getOption(name: string): number | boolean | string | undefined; + toggleOverwrite(on: boolean): void; + getTokenTypeAt(pos: Pos): "" | "string" | "comment"; + overWriteSelection(text: string): void; + /*** multiselect ****/ + isInMultiSelectMode(): boolean; + virtualSelectionMode(): boolean; + virtualSelection: Mutable | null; + forEachSelection(command: Function): void; + hardWrap(options: any): number; + showMatchesOnScrollbar?: Function; + save?: Function; + static keyName?: Function; +} +type Mutable = { + -readonly [Key in keyof Type]: Type[Key]; +}; +interface NotificationOptions { + bottom?: boolean; + duration?: number; +} +declare function findMatchingTag(cm: CodeMirror, pos: Pos): undefined; +declare function findEnclosingTag(cm: CodeMirror, pos: Pos): { + open: { + from: Pos; + to: Pos; + }; + close: { + from: Pos; + to: Pos; + }; +} | undefined; +declare class Marker { + cm: CodeMirror; + id: number; + offset: number | null; + assoc: number; + constructor(cm: CodeMirror, offset: number, assoc: number); + clear(): void; + find(): Pos | null; + update(change: ChangeDesc): void; +} + +declare const Vim: any; +declare function vim(options?: { + status?: boolean; +}): Extension; + +declare function getCM(view: EditorView): CodeMirror | null; + +export { CodeMirror, Vim, getCM, vim }; diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..930aaf2 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,373 @@ +import { EditorSelection, ChangeDesc, Extension } from '@codemirror/state'; +import { StringStream } from '@codemirror/language'; +import { EditorView, ViewUpdate } from '@codemirror/view'; +import { SearchQuery } from '@codemirror/search'; + +type vimState = { + onPasteFn?: any, + sel: {head: Pos$1, anchor: Pos$1}, + insertModeReturn: boolean, + visualBlock: boolean, + marks: {[mark: string]: Marker$1}, + visualMode: boolean, + insertMode: boolean, + pasteFn: any, + lastSelection: any, + searchState_: any, + lastEditActionCommand: actionCommand|void, + lastPastedText: any, + lastMotion: any, + options: {[optionName: string]: vimOption}, + lastEditInputState: InputStateInterface|void, + inputState: InputStateInterface, + visualLine: boolean, + insertModeRepeat: any, + lastHSPos: number, + lastHPos: number, + wasInVisualBlock?: boolean, + insert?: any, + insertEnd?: Marker$1, + status: string, + exMode?: boolean, + mode?: any, + expectLiteralNext?: boolean, + constructor(): void; +} +type Marker$1 = ReturnType +type Pos$1 = { line: number, ch: number, sticky?: string } +interface CM5RangeInterface { + anchor: Pos$1, + head: Pos$1, +} + +type OperatorArgs = { + repeat?: number, + forward?: boolean, + linewise?: boolean, + fullLine?: boolean, + registerName?: string|null, + indentRight?: boolean, + toLower?: boolean, + shouldMoveCursor?: boolean, + selectedCharacter?: string, + lastSel?: any; + keepCursor?: boolean; +} + +type ActionArgsPartial = { + repeat?: number, + forward?: boolean, + head?: Pos$1, + position?: string + backtrack?: boolean, + increase?: boolean, + repeatIsExplicit?: boolean, + indentRight?: boolean, + selectedCharacter?: string, + after?: boolean, + matchIndent?: boolean, + registerName?: string, + isEdit?: boolean + linewise?: boolean, + insertAt?: string, + blockwise?: boolean, + keepSpaces?: boolean, + replace?: boolean, + keepCursor?: boolean +} + +type MotionArgsPartial = { + repeat?: number, + forward?: boolean, + selectedCharacter?: string, + linewise?: boolean, + textObjectInner?: boolean, + sameLine?: boolean, + repeatOffset?: number, + toJumplist?: boolean, + inclusive?: boolean, + wordEnd?: boolean, + toFirstChar?:boolean, + explicitRepeat?: boolean, + bigWord?: boolean, + repeatIsExplicit?: boolean, + noRepeat?: boolean +}; + +type MotionArgs = MotionArgsPartial & {repeat: number}; + + + +type optionCallback = (value?: string|undefined, cm?: CodeMirror) => any +type vimOption = { + type?: string, + defaultValue?: unknown, + callback?: optionCallback, + value?: unknown +} + +type allCommands = { + keys: string, + context?: string, + interlaceInsertRepeat?: boolean, + exitVisualBlock?: boolean, + isEdit?: boolean, + repeatOverride?: number +} +type actionCommand = allCommands & { + type: 'action', + action: string, + actionArgs?: ActionArgsPartial, + motion?: string, + operator?: string, + interlaceInsertRepeat?: boolean +} + +interface InputStateInterface { + prefixRepeat: string[]; + motionRepeat: any[]; + operator: any| undefined | null; + operatorArgs: OperatorArgs | undefined | null; + motion: string | undefined | null; + motionArgs: MotionArgs | null; + keyBuffer: any[]; + registerName?: string; + changeQueue: any; + operatorShortcut?: string; + selectedCharacter?: string; + repeatOverride?: number; + changeQueueList?: any[]; + pushRepeatDigit(n: string): void; + getRepeat(): number; +} + + +declare global { + function isNaN(v: any): v is Exclude; + interface String { + trimStart(): string + } +} + +declare class Pos { + line: number; + ch: number; + sticky?: string; + constructor(line: number, ch: number); +} +declare function on(emitter: any, type: string, f: Function): void; +declare function off(emitter: any, type: string, f: Function): void; +declare function signal(emitter: any, type: string, ...args: any[]): void; +interface Operation { + $d: number; + isVimOp?: boolean; + cursorActivityHandlers?: Function[]; + cursorActivity?: boolean; + lastChange?: any; + change?: any; + changeHandlers?: Function[]; + $changeStart?: number; +} +declare class CodeMirror { + static isMac: boolean; + static Pos: typeof Pos; + static StringStream: StringStream & (new (_: string) => StringStream); + static commands: { + cursorCharLeft: (cm: CodeMirror) => void; + redo: (cm: CodeMirror) => void; + undo: (cm: CodeMirror) => void; + newlineAndIndent: (cm: CodeMirror) => void; + indentAuto: (cm: CodeMirror) => void; + newlineAndIndentContinueComment: any; + save: any; + }; + static isWordChar: (ch: string) => boolean; + static keys: any; + static addClass: (el: any, str: any) => void; + static rmClass: (el: any, str: any) => void; + static e_preventDefault: (e: Event) => void; + static e_stop: (e: Event) => void; + static lookupKey: (key: string, map: string, handle: Function) => void; + static on: typeof on; + static off: typeof off; + static signal: typeof signal; + openDialog(template: Element, callback: Function, options: any): (newVal?: string | undefined) => void; + openNotification(template: Node, options: NotificationOptions): () => void; + static findMatchingTag: typeof findMatchingTag; + static findEnclosingTag: typeof findEnclosingTag; + cm6: EditorView; + state: { + statusbar?: Element | null; + dialog?: Element | null; + vimPlugin?: any; + vim?: vimState | null; + currentNotificationClose?: Function | null; + closeVimNotification?: Function | null; + keyMap?: string; + overwrite?: boolean; + textwidth?: number; + }; + marks: Record; + $mid: number; + curOp: Operation | null | undefined; + options: any; + _handlers: any; + constructor(cm6: EditorView); + on(type: string, f: Function): void; + off(type: string, f: Function): void; + signal(type: string, e: any, handlers?: any): void; + indexFromPos(pos: Pos): number; + posFromIndex(offset: number): Pos; + foldCode(pos: Pos): void; + firstLine(): number; + lastLine(): number; + lineCount(): number; + setCursor(line: number, ch: number): void; + setCursor(line: Pos): void; + getCursor(p?: "head" | "anchor" | "start" | "end"): Pos; + listSelections(): { + anchor: Pos; + head: Pos; + }[]; + setSelections(p: CM5RangeInterface[], primIndex?: number): void; + setSelection(anchor: Pos, head: Pos, options?: any): void; + getLine(row: number): string; + getLineHandle(row: number): { + row: number; + index: number; + }; + getLineNumber(handle: any): number | null; + releaseLineHandles(): void; + getRange(s: Pos, e: Pos): string; + replaceRange(text: string, s: Pos, e?: Pos, source?: string): void; + replaceSelection(text: string): void; + replaceSelections(replacements: string[]): void; + getSelection(): string; + getSelections(): string[]; + somethingSelected(): boolean; + getInputField(): HTMLElement; + clipPos(p: Pos): Pos; + getValue(): string; + setValue(text: string): void; + focus(): void; + blur(): void; + defaultTextHeight(): number; + findMatchingBracket(pos: Pos, _options?: any): { + to: Pos; + } | { + to: undefined; + }; + scanForBracket(pos: Pos, dir: 1 | -1, style: any, config: any): false | { + pos: Pos; + ch: string; + } | null; + indentLine(line: number, more?: boolean): void; + indentMore(): void; + indentLess(): void; + execCommand(name: string): void; + setBookmark(cursor: Pos, options?: { + insertLeft: boolean; + }): Marker; + cm6Query?: SearchQuery; + addOverlay({ query }: { + query: RegExp; + }): SearchQuery | undefined; + removeOverlay(overlay?: any): void; + getSearchCursor(query: RegExp, pos: Pos): { + findNext: () => string[] | null | undefined; + findPrevious: () => string[] | null | undefined; + find: (back?: boolean) => string[] | null | undefined; + from: () => Pos | undefined; + to: () => Pos | undefined; + replace: (text: string) => void; + }; + findPosV(start: Pos, amount: number, unit: "page" | "line", goalColumn?: number): Pos & { + hitSide?: boolean | undefined; + }; + charCoords(pos: Pos, mode: "div" | "local"): { + left: number; + top: number; + bottom: number; + }; + coordsChar(coords: { + left: number; + top: number; + }, mode: "div" | "local"): Pos; + getScrollInfo(): { + left: number; + top: number; + height: number; + width: number; + clientHeight: number; + clientWidth: number; + }; + scrollTo(x?: number | null, y?: number | null): void; + scrollIntoView(pos?: Pos, margin?: number): void; + getWrapperElement(): HTMLElement; + getMode(): { + name: string | number | boolean | undefined; + }; + setSize(w: number, h: number): void; + refresh(): void; + destroy(): void; + getLastEditEnd(): Pos; + $lastChangeEndOffset: number; + $lineHandleChanges: undefined | ViewUpdate[]; + onChange(update: ViewUpdate): void; + onSelectionChange(): void; + operation(fn: Function, force?: boolean): any; + onBeforeEndOperation(): void; + moveH(increment: number, unit: string): void; + setOption(name: string, val: any): void; + getOption(name: "firstLineNumber" | "tabSize"): number; + getOption(name: string): number | boolean | string | undefined; + toggleOverwrite(on: boolean): void; + getTokenTypeAt(pos: Pos): "" | "string" | "comment"; + overWriteSelection(text: string): void; + /*** multiselect ****/ + isInMultiSelectMode(): boolean; + virtualSelectionMode(): boolean; + virtualSelection: Mutable | null; + forEachSelection(command: Function): void; + hardWrap(options: any): number; + showMatchesOnScrollbar?: Function; + save?: Function; + static keyName?: Function; +} +type Mutable = { + -readonly [Key in keyof Type]: Type[Key]; +}; +interface NotificationOptions { + bottom?: boolean; + duration?: number; +} +declare function findMatchingTag(cm: CodeMirror, pos: Pos): undefined; +declare function findEnclosingTag(cm: CodeMirror, pos: Pos): { + open: { + from: Pos; + to: Pos; + }; + close: { + from: Pos; + to: Pos; + }; +} | undefined; +declare class Marker { + cm: CodeMirror; + id: number; + offset: number | null; + assoc: number; + constructor(cm: CodeMirror, offset: number, assoc: number); + clear(): void; + find(): Pos | null; + update(change: ChangeDesc): void; +} + +declare const Vim: any; +declare function vim(options?: { + status?: boolean; +}): Extension; + +declare function getCM(view: EditorView): CodeMirror | null; + +export { CodeMirror, Vim, getCM, vim }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..2254792 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,8479 @@ +import { EditorSelection, MapMode, Prec, RangeSetBuilder, StateEffect, StateField } from '@codemirror/state'; +import { foldCode, matchBrackets, indentUnit, ensureSyntaxTree, StringStream } from '@codemirror/language'; +import * as View from '@codemirror/view'; +import { runScopeHandlers, EditorView, Direction, ViewPlugin, Decoration, showPanel } from '@codemirror/view'; +import { SearchQuery, setSearchQuery, RegExpCursor } from '@codemirror/search'; +import { indentMore, indentLess, cursorLineBoundaryBackward, cursorLineBoundaryForward, cursorCharBackward, cursorCharLeft, insertNewlineAndIndent, indentSelection, undo, redo } from '@codemirror/commands'; + +//@ts-check + +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/LICENSE + +/** + * Supported keybindings: + * Too many to list. Refer to defaultKeymap below. + * + * Supported Ex commands: + * Refer to defaultExCommandMap below. + * + * Registers: unnamed, -, ., :, /, _, a-z, A-Z, 0-9 + * (Does not respect the special case for number registers when delete + * operator is made with these commands: %, (, ), , /, ?, n, N, {, } ) + * TODO: Implement the remaining registers. + * + * Marks: a-z, A-Z, and 0-9 + * TODO: Implement the remaining special marks. They have more complex + * behavior. + * + * Events: + * 'vim-mode-change' - raised on the editor anytime the current mode changes, + * Event object: {mode: "visual", subMode: "linewise"} + * + * Code structure: + * 1. Default keymap + * 2. Variable declarations and short basic helpers + * 3. Instance (External API) implementation + * 4. Internal state tracking objects (input state, counter) implementation + * and instantiation + * 5. Key handler (the main command dispatcher) implementation + * 6. Motion, operator, and action implementations + * 7. Helper functions for the key handler, motions, operators, and actions + * 8. Set up Vim to work as a keymap for CodeMirror. + * 9. Ex command implementations. + */ + +/** + * @typedef { import("./cm_adapter").CodeMirror } CodeMirror + * @typedef { import("./types").CodeMirrorV} CodeMirrorV + * @typedef { import("./types").Pos } Pos + * @typedef { import("./types").CM5Range } CM5Range + * @typedef { import("./types").vimState } vimState + * @typedef { import("./types").ExFn } ExFn + * @typedef { import("./types").MotionArgs } MotionArgs + * @typedef { import("./types").ActionArgs } ActionArgs + * @typedef { import("./types").OperatorArgs } OperatorArgs + * @typedef { import("./types").vimKey } vimKey + * @typedef { import("./types").InputStateInterface } InputStateInterface + */ + +/** @arg {typeof import("./cm_adapter").CodeMirror} CodeMirror */ +function initVim(CodeMirror) { + + var Pos = CodeMirror.Pos; + + /** @arg {CodeMirror} cm @arg {Pos} curStart @arg {Pos} curEnd */ + function updateSelectionForSurrogateCharacters(cm, curStart, curEnd) { + // start and character position when no selection + // is the same in visual mode, and differs in 1 character in normal mode + if (curStart.line === curEnd.line && curStart.ch >= curEnd.ch - 1) { + var text = cm.getLine(curStart.line); + var charCode = text.charCodeAt(curStart.ch); + if (0xD800 <= charCode && charCode <= 0xD8FF) { + curEnd.ch += 1; + } + } + + return {start: curStart, end: curEnd}; + } + /** @type {import("./types").vimKeyMap} */ + var defaultKeymap = [ + // Key to key mapping. This goes first to make it possible to override + // existing mappings. + { keys: '', type: 'keyToKey', toKeys: 'h' }, + { keys: '', type: 'keyToKey', toKeys: 'l' }, + { keys: '', type: 'keyToKey', toKeys: 'k' }, + { keys: '', type: 'keyToKey', toKeys: 'j' }, + { keys: 'g', type: 'keyToKey', toKeys: 'gk' }, + { keys: 'g', type: 'keyToKey', toKeys: 'gj' }, + { keys: '', type: 'keyToKey', toKeys: 'l' }, + { keys: '', type: 'keyToKey', toKeys: 'h'}, + { keys: '', type: 'keyToKey', toKeys: 'x' }, + { keys: '', type: 'keyToKey', toKeys: 'W' }, + { keys: '', type: 'keyToKey', toKeys: 'B' }, + { keys: '', type: 'keyToKey', toKeys: 'w' }, + { keys: '', type: 'keyToKey', toKeys: 'b' }, + { keys: '', type: 'keyToKey', toKeys: 'j' }, + { keys: '', type: 'keyToKey', toKeys: 'k' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, + { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, + { keys: '', type: 'keyToKey', toKeys: '' }, // ipad keyboard sends C-Esc instead of C-[ + { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, + { keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' }, + { keys: 's', type: 'keyToKey', toKeys: 'c', context: 'visual'}, + { keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' }, + { keys: 'S', type: 'keyToKey', toKeys: 'VdO', context: 'visual' }, + { keys: '', type: 'keyToKey', toKeys: '0' }, + { keys: '', type: 'keyToKey', toKeys: '$' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: 'j^', context: 'normal' }, + { keys: '', type: 'keyToKey', toKeys: 'i', context: 'normal'}, + { keys: '', type: 'action', action: 'toggleOverwrite', context: 'insert' }, + // Motions + { keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }}, + { keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }}, + { keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }}, + { keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }}, + { keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }}, + { keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }}, + { keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }}, + { keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }}, + { keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }}, + { keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }}, + { keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }}, + { keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }}, + { keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }}, + { keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }}, + { keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }}, + { keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }}, + { keys: '(', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: false }}, + { keys: ')', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: true }}, + { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }}, + { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }}, + { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }}, + { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }}, + { keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }}, + { keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }}, + {keys: "g$", type: "motion", motion: "moveToEndOfDisplayLine"}, + {keys: "g^", type: "motion", motion: "moveToStartOfDisplayLine"}, + {keys: "g0", type: "motion", motion: "moveToStartOfDisplayLine"}, + { keys: '0', type: 'motion', motion: 'moveToStartOfLine' }, + { keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }}, + { keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }}, + { keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }}, + { keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }}, + { keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }}, + { keys: 'f', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }}, + { keys: 'F', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }}, + { keys: 't', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }}, + { keys: 'T', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }}, + { keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }}, + { keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }}, + { keys: '\'', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}}, + { keys: '`', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}}, + { keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } }, + { keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } }, + { keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } }, + { keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } }, + // the next two aren't motions but must come before more general motion declarations + { keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}}, + { keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}}, + { keys: ']', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}}, + { keys: '[', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}}, + { keys: '|', type: 'motion', motion: 'moveToColumn'}, + { keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'}, + { keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'}, + // Operators + { keys: 'd', type: 'operator', operator: 'delete' }, + { keys: 'y', type: 'operator', operator: 'yank' }, + { keys: 'c', type: 'operator', operator: 'change' }, + { keys: '=', type: 'operator', operator: 'indentAuto' }, + { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }}, + { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }}, + { keys: 'g~', type: 'operator', operator: 'changeCase' }, + { keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true }, + { keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true }, + { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }}, + { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }}, + { keys: 'gn', type: 'motion', motion: 'findAndSelectNextInclusive', motionArgs: { forward: true }}, + { keys: 'gN', type: 'motion', motion: 'findAndSelectNextInclusive', motionArgs: { forward: false }}, + { keys: 'gq', type: 'operator', operator: 'hardWrap' }, + { keys: 'gw', type: 'operator', operator: 'hardWrap', operatorArgs: {keepCursor: true}}, + // Operator-Motion dual commands + { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }}, + { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }}, + { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, + { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'expandToLine', motionArgs: { linewise: true }, context: 'normal'}, + { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, + { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'}, + { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'}, + { keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveToStartOfLine', context: 'insert' }, + { keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' }, + //ignore C-w in normal mode + { keys: '', type: 'idle', context: 'normal' }, + // Actions + { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }}, + { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }}, + { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }}, + { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }}, + { keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' }, + { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' }, + { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' }, + { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' }, + { keys: 'gi', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'lastEdit' }, context: 'normal' }, + { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' }, + { keys: 'gI', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'bol'}, context: 'normal' }, + { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' }, + { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' }, + { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' }, + { keys: 'v', type: 'action', action: 'toggleVisualMode' }, + { keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }}, + { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }}, + { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }}, + { keys: 'gv', type: 'action', action: 'reselectLastSelection' }, + { keys: 'J', type: 'action', action: 'joinLines', isEdit: true }, + { keys: 'gJ', type: 'action', action: 'joinLines', actionArgs: { keepSpaces: true }, isEdit: true }, + { keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }}, + { keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }}, + { keys: 'r', type: 'action', action: 'replace', isEdit: true }, + { keys: '@', type: 'action', action: 'replayMacro' }, + { keys: 'q', type: 'action', action: 'enterMacroRecordMode' }, + // Handle Replace-mode as a special case of insert mode. + { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }, context: 'normal'}, + { keys: 'R', type: 'operator', operator: 'change', operatorArgs: { linewise: true, fullLine: true }, context: 'visual', exitVisualBlock: true}, + { keys: 'u', type: 'action', action: 'undo', context: 'normal' }, + { keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true }, + { keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true }, + { keys: '', type: 'action', action: 'redo' }, + { keys: 'm', type: 'action', action: 'setMark' }, + { keys: '"', type: 'action', action: 'setRegister' }, + { keys: '', type: 'action', action: 'insertRegister', context: 'insert', isEdit: true }, + { keys: '', type: 'action', action: 'oneNormalCommand', context: 'insert' }, + { keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }}, + { keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }}, + { keys: 'z', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }}, + { keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: '.', type: 'action', action: 'repeatLastEdit' }, + { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}}, + { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}}, + { keys: '', type: 'action', action: 'indent', actionArgs: { indentRight: true }, context: 'insert' }, + { keys: '', type: 'action', action: 'indent', actionArgs: { indentRight: false }, context: 'insert' }, + // Text object motions + { keys: 'a', type: 'motion', motion: 'textObjectManipulation' }, + { keys: 'i', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }}, + // Search + { keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }}, + { keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }}, + { keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }}, + { keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }}, + { keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }}, + { keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }}, + // Ex command + { keys: ':', type: 'ex' } + ]; + var defaultKeymapLength = defaultKeymap.length; + + /** + * Ex commands + * Care must be taken when adding to the default Ex command map. For any + * pair of commands that have a shared prefix, at least one of their + * shortNames must not match the prefix of the other command. + */ + var defaultExCommandMap = [ + { name: 'colorscheme', shortName: 'colo' }, + { name: 'map' }, + { name: 'imap', shortName: 'im' }, + { name: 'nmap', shortName: 'nm' }, + { name: 'vmap', shortName: 'vm' }, + { name: 'omap', shortName: 'om' }, + { name: 'noremap', shortName: 'no' }, + { name: 'nnoremap', shortName: 'nn' }, + { name: 'vnoremap', shortName: 'vn' }, + { name: 'inoremap', shortName: 'ino' }, + { name: 'onoremap', shortName: 'ono' }, + { name: 'unmap' }, + { name: 'mapclear', shortName: 'mapc' }, + { name: 'nmapclear', shortName: 'nmapc' }, + { name: 'vmapclear', shortName: 'vmapc' }, + { name: 'imapclear', shortName: 'imapc' }, + { name: 'omapclear', shortName: 'omapc' }, + { name: 'write', shortName: 'w' }, + { name: 'undo', shortName: 'u' }, + { name: 'redo', shortName: 'red' }, + { name: 'set', shortName: 'se' }, + { name: 'setlocal', shortName: 'setl' }, + { name: 'setglobal', shortName: 'setg' }, + { name: 'sort', shortName: 'sor' }, + { name: 'substitute', shortName: 's', possiblyAsync: true }, + { name: 'startinsert', shortName: 'start' }, + { name: 'nohlsearch', shortName: 'noh' }, + { name: 'yank', shortName: 'y' }, + { name: 'delmarks', shortName: 'delm' }, + { name: 'marks', excludeFromCommandHistory: true }, + { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true }, + { name: 'vglobal', shortName: 'v' }, + { name: 'delete', shortName: 'd' }, + { name: 'join', shortName: 'j' }, + { name: 'normal', shortName: 'norm' }, + { name: 'global', shortName: 'g' } + ]; + + /** + * Langmap + * Determines how to interpret keystrokes in Normal and Visual mode. + * Useful for people who use a different keyboard layout than QWERTY + */ + var langmap = parseLangmap(''); + + /** @arg {CodeMirror} cm */ + function enterVimMode(cm) { + cm.setOption('disableInput', true); + cm.setOption('showCursorWhenSelecting', false); + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + cm.on('cursorActivity', onCursorActivity); + maybeInitVimState(cm); + // @ts-ignore + CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm)); + } + + /** @arg {CodeMirror} cm */ + function leaveVimMode(cm) { + cm.setOption('disableInput', false); + cm.off('cursorActivity', onCursorActivity); + // @ts-ignore + CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm)); + cm.state.vim = null; + vimGlobalState.jumpList = createCircularJumpList(); + if (highlightTimeout) clearTimeout(highlightTimeout); + } + + /** @arg {CodeMirrorV} cm */ + function getOnPasteFn(cm) { + var vim = cm.state.vim; + if (!vim.onPasteFn) { + vim.onPasteFn = function() { + if (!vim.insertMode) { + cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); + actions.enterInsertMode(cm, {}, vim); + } + }; + } + return vim.onPasteFn; + } + + var numberRegex = /[\d]/; + var wordCharTest = [CodeMirror.isWordChar, function(ch) { + return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch); + }], bigWordCharTest = [function(ch) { + return /\S/.test(ch); + }]; + var validMarks = ['<', '>']; + var validRegisters = ['-', '"', '.', ':', '_', '/', '+']; + var latinCharRegex = /^\w$/; + var upperCaseChars; + try { upperCaseChars = new RegExp("^[\\p{Lu}]$", "u"); } + catch (_) { upperCaseChars = /^[A-Z]$/; } + + /** @arg {CodeMirror} cm @arg {number} line */ + function isLine(cm, line) { + return line >= cm.firstLine() && line <= cm.lastLine(); + } + /** @arg {string} k */ + function isLowerCase(k) { + return (/^[a-z]$/).test(k); + } + /** @arg {string} k */ + function isMatchableSymbol(k) { + return '()[]{}'.indexOf(k) != -1; + } + /** @arg {string} k */ + function isNumber(k) { + return numberRegex.test(k); + } + /** @arg {string} k */ + function isUpperCase(k) { + return upperCaseChars.test(k); + } + /** @arg {string} k */ + function isWhiteSpaceString(k) { + return (/^\s*$/).test(k); + } + /** @arg {string} k */ + function isEndOfSentenceSymbol(k) { + return '.?!'.indexOf(k) != -1; + } + /** @arg {any} val @arg {string | any[]} arr */ + function inArray(val, arr) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] == val) { + return true; + } + } + return false; + } + + + /** @typedef {import("./types").optionCallback} optionCallback */ + /** @typedef {import("./types").vimOption} vimOption */ + /** @type {Object} */ + var options = {}; + /** + * @arg {string} name + * @arg {any} defaultValue + * @arg {string} type + * @arg {string[] } [aliases] + * @arg {optionCallback} [callback] + * */ + function defineOption(name, defaultValue, type, aliases, callback) { + if (defaultValue === undefined && !callback) { + throw Error('defaultValue is required unless callback is provided'); + } + if (!type) { type = 'string'; } + options[name] = { + type: type, + defaultValue: defaultValue, + callback: callback + }; + if (aliases) { + for (var i = 0; i < aliases.length; i++) { + options[aliases[i]] = options[name]; + } + } + if (defaultValue) { + setOption(name, defaultValue); + } + } + + /** + * @arg {string} name + * @arg {any} value + * @arg {CodeMirrorV} [cm] + * @arg {{ scope?: any; } | undefined} [cfg] */ + function setOption(name, value, cm, cfg) { + var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; + if (!option) { + return new Error('Unknown option: ' + name); + } + if (option.type == 'boolean') { + if (value && value !== true) { + return new Error('Invalid argument: ' + name + '=' + value); + } else if (value !== false) { + // Boolean options are set to true if value is not defined. + value = true; + } + } + if (option.callback) { + if (scope !== 'local') { + option.callback(value, undefined); + } + if (scope !== 'global' && cm) { + option.callback(value, cm); + } + } else { + if (scope !== 'local') { + option.value = option.type == 'boolean' ? !!value : value; + } + if (scope !== 'global' && cm) { + cm.state.vim.options[name] = {value: value}; + } + } + } + + /** + * @arg {string} name + * @arg {CodeMirrorV} [cm] + * @arg {{ scope?: any; } | undefined} [cfg] */ + function getOption(name, cm, cfg) { + var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; + if (!option) { + return new Error('Unknown option: ' + name); + } + if (option.callback) { + let local = cm && option.callback(undefined, cm); + if (scope !== 'global' && local !== undefined) { + return local; + } + if (scope !== 'local') { + return option.callback(); + } + return; + } else { + let local = (scope !== 'global') && (cm && cm.state.vim.options[name]); + return (local || (scope !== 'local') && option || {}).value; + } + } + /** @arg {string|undefined} name @arg {CodeMirrorV} [cm] */ + defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) { + // Option is local. Do nothing for global. + if (cm === undefined) { + return; + } + // The 'filetype' option proxies to the CodeMirror 'mode' option. + if (name === undefined) { + let mode = cm.getOption('mode'); + return mode == 'null' ? '' : mode; + } else { + let mode = name == '' ? 'null' : name; + cm.setOption('mode', mode); + } + }); + defineOption('textwidth', 80, 'number', ['tw'], function(width, cm) { + // Option is local. Do nothing for global. + if (cm === undefined) { + return; + } + // The 'filetype' option proxies to the CodeMirror 'mode' option. + if (width === undefined) { + var value = cm.getOption('textwidth'); + return value; + } else { + var column = Math.round(/**@type {any}*/(width)); + if (column > 1) { + cm.setOption('textwidth', column); + } + } + }); + + var createCircularJumpList = function() { + var size = 100; + var pointer = -1; + var head = 0; + var tail = 0; + var buffer = new Array(size); + /** @arg {CodeMirror} cm @arg {any} oldCur @arg {any} newCur */ + function add(cm, oldCur, newCur) { + var current = pointer % size; + var curMark = buffer[current]; + /** @arg {Pos} cursor */ + function useNextSlot(cursor) { + var next = ++pointer % size; + var trashMark = buffer[next]; + if (trashMark) { + trashMark.clear(); + } + buffer[next] = cm.setBookmark(cursor); + } + if (curMark) { + var markPos = curMark.find(); + // avoid recording redundant cursor position + if (markPos && !cursorEqual(markPos, oldCur)) { + useNextSlot(oldCur); + } + } else { + useNextSlot(oldCur); + } + useNextSlot(newCur); + head = pointer; + tail = pointer - size + 1; + if (tail < 0) { + tail = 0; + } + } + /** @arg {CodeMirror} cm @arg {number} offset */ + function move(cm, offset) { + pointer += offset; + if (pointer > head) { + pointer = head; + } else if (pointer < tail) { + pointer = tail; + } + var mark = buffer[(size + pointer) % size]; + // skip marks that are temporarily removed from text buffer + if (mark && !mark.find()) { + var inc = offset > 0 ? 1 : -1; + var newCur; + var oldCur = cm.getCursor(); + do { + pointer += inc; + mark = buffer[(size + pointer) % size]; + // skip marks that are the same as current position + if (mark && + (newCur = mark.find()) && + !cursorEqual(oldCur, newCur)) { + break; + } + } while (pointer < head && pointer > tail); + } + return mark; + } + /** @arg {CodeMirror} cm @arg {number} offset */ + function find(cm, offset) { + var oldPointer = pointer; + var mark = move(cm, offset); + pointer = oldPointer; + return mark && mark.find(); + } + return { + cachedCursor: undefined, //used for # and * jumps + add: add, + find: find, + move: move + }; + }; + + /** + * Returns an object to track the changes associated insert mode. It + * clones the object that is passed in, or creates an empty object one if + * none is provided. + * @arg {import("./types").InsertModeChanges | undefined} [c] + * @returns {import("./types").InsertModeChanges} + */ + var createInsertModeChanges = function(c) { + if (c) { + // Copy construction + return { + changes: c.changes, + expectCursorActivityForChange: c.expectCursorActivityForChange + }; + } + return { + // Change list + changes: [], + // Set to true on change, false on cursorActivity. + expectCursorActivityForChange: false + }; + }; + + class MacroModeState { + constructor() { + this.latestRegister = undefined; + this.isPlaying = false; + this.isRecording = false; + this.replaySearchQueries = []; + this.onRecordingDone = undefined; + this.lastInsertModeChanges = createInsertModeChanges(); + } + exitMacroRecordMode() { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.onRecordingDone) { + macroModeState.onRecordingDone(); // close dialog + } + macroModeState.onRecordingDone = undefined; + macroModeState.isRecording = false; + } + enterMacroRecordMode(cm, registerName) { + var register = vimGlobalState.registerController.getRegister(registerName); + if (register) { + register.clear(); + this.latestRegister = registerName; + if (cm.openDialog) { + var template = dom('span', {class: 'cm-vim-message'}, 'recording @' + registerName); + this.onRecordingDone = cm.openDialog(template, null, {bottom:true}); + } + this.isRecording = true; + } + } + } + /** + * @arg Codemirror + * @return {vimState} + */ + function maybeInitVimState(cm) { + if (!cm.state.vim) { + // Store instance state in the CodeMirror object. + cm.state.vim = { + inputState: new InputState(), + // Vim's input state that triggered the last edit, used to repeat + // motions and operators with '.'. + lastEditInputState: undefined, + // Vim's action command before the last edit, used to repeat actions + // with '.' and insert mode repeat. + lastEditActionCommand: undefined, + // When using jk for navigation, if you move from a longer line to a + // shorter line, the cursor may clip to the end of the shorter line. + // If j is pressed again and cursor goes to the next line, the + // cursor should go back to its horizontal position on the longer + // line if it can. This is to keep track of the horizontal position. + lastHPos: -1, + // Doing the same with screen-position for gj/gk + lastHSPos: -1, + // The last motion command run. Cleared if a non-motion command gets + // executed in between. + lastMotion: null, + marks: {}, + insertMode: false, + insertModeReturn: false, + // Repeat count for changes made in insert mode, triggered by key + // sequences like 3,i. Only exists when insertMode is true. + insertModeRepeat: undefined, + visualMode: false, + // If we are in visual line mode. No effect if visualMode is false. + visualLine: false, + visualBlock: false, + lastSelection: null, + lastPastedText: null, + sel: {}, + // Buffer-local/window-local values of vim options. + options: {}, + // Whether the next character should be interpreted literally + // Necassary for correct implementation of f, r etc. + // in terms of langmaps. + expectLiteralNext: false + }; + } + return cm.state.vim; + } + /** + * @type { + { + macroModeState: MacroModeState; + registerController: RegisterController; + searchHistoryController: HistoryController; + jumpList: any; + exCommandHistoryController: HistoryController; + lastCharacterSearch: any; + query?: any; + isReversed?: any; + lastSubstituteReplacePart: any; + searchQuery?: null; + searchIsReversed?: boolean; + } + } + */ + var vimGlobalState; + function resetVimGlobalState() { + vimGlobalState = { + // The current search query. + searchQuery: null, + // Whether we are searching backwards. + searchIsReversed: false, + // Replace part of the last substituted pattern + lastSubstituteReplacePart: undefined, + jumpList: createCircularJumpList(), + macroModeState: new MacroModeState(), + // Recording latest f, t, F or T motion command. + lastCharacterSearch: {increment:0, forward:true, selectedCharacter:''}, + registerController: new RegisterController({}), + // search history buffer + searchHistoryController: new HistoryController(), + // ex Command history buffer + exCommandHistoryController : new HistoryController() + }; + for (var optionName in options) { + var option = options[optionName]; + option.value = option.defaultValue; + } + } + + /** @type {number | undefined|false} */ + var lastInsertModeKeyTimer; + var vimApi = { + enterVimMode: enterVimMode, + leaveVimMode: leaveVimMode, + buildKeyMap: function() { + // TODO: Convert keymap into dictionary format for fast lookup. + }, + // Testing hook, though it might be useful to expose the register + // controller anyway. + getRegisterController: function() { + return vimGlobalState.registerController; + }, + // Testing hook. + resetVimGlobalState_: resetVimGlobalState, + + // Testing hook. + getVimGlobalState_: function() { + return vimGlobalState; + }, + + // Testing hook. + maybeInitVimState_: maybeInitVimState, + + suppressErrorLogging: false, + + InsertModeKey: InsertModeKey, + /**@type {(lhs: string, rhs: string, ctx: string) => void} */ + map: function(lhs, rhs, ctx) { + // Add user defined key bindings. + exCommandDispatcher.map(lhs, rhs, ctx); + }, + /**@type {(lhs: string, ctx: string) => any} */ + unmap: function(lhs, ctx) { + return exCommandDispatcher.unmap(lhs, ctx); + }, + // Non-recursive map function. + // NOTE: This will not create mappings to key maps that aren't present + // in the default key map. See TODO at bottom of function. + /**@type {(lhs: string, rhs: string, ctx: string) => void} */ + noremap: function(lhs, rhs, ctx) { + exCommandDispatcher.map(lhs, rhs, ctx, true); + }, + // Remove all user-defined mappings for the provided context. + /**@arg {string} [ctx]} */ + mapclear: function(ctx) { + // Partition the existing keymap into user-defined and true defaults. + var actualLength = defaultKeymap.length, + origLength = defaultKeymapLength; + var userKeymap = defaultKeymap.slice(0, actualLength - origLength); + defaultKeymap = defaultKeymap.slice(actualLength - origLength); + if (ctx) { + // If a specific context is being cleared, we need to keep mappings + // from all other contexts. + for (var i = userKeymap.length - 1; i >= 0; i--) { + var mapping = userKeymap[i]; + if (ctx !== mapping.context) { + if (mapping.context) { + this._mapCommand(mapping); + } else { + // `mapping` applies to all contexts so create keymap copies + // for each context except the one being cleared. + var contexts = ['normal', 'insert', 'visual']; + for (var j in contexts) { + if (contexts[j] !== ctx) { + var newMapping = Object.assign({}, mapping); + newMapping.context = contexts[j]; + this._mapCommand(newMapping); + } + } + } + } + } + } + }, + langmap: updateLangmap, + vimKeyFromEvent: vimKeyFromEvent, + // TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace + // them, or somehow make them work with the existing CodeMirror setOption/getOption API. + setOption: setOption, + getOption: getOption, + defineOption: defineOption, + /**@type {(name: string, prefix: string|undefined, func: ExFn) => void} */ + defineEx: function(name, prefix, func){ + if (!prefix) { + prefix = name; + } else if (name.indexOf(prefix) !== 0) { + throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered'); + } + exCommands[name]=func; + exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'}; + }, + /**@type {(cm: CodeMirror, key: string, origin: string) => undefined | boolean} */ + handleKey: function (cm, key, origin) { + var command = this.findKey(cm, key, origin); + if (typeof command === 'function') { + return command(); + } + }, + multiSelectHandleKey: multiSelectHandleKey, + + /** + * This is the outermost function called by CodeMirror, after keys have + * been mapped to their Vim equivalents. + * + * Finds a command based on the key (and cached keys if there is a + * multi-key sequence). Returns `undefined` if no key is matched, a noop + * function if a partial match is found (multi-key), and a function to + * execute the bound command if a a key is matched. The function always + * returns true. + */ + /**@type {(cm_: CodeMirror, key: string, origin?: string| undefined) => (() => boolean) | undefined} */ + findKey: function(cm_, key, origin) { + var vim = maybeInitVimState(cm_); + var cm = /**@type {CodeMirrorV}*/(cm_); + + function handleMacroRecording() { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isRecording) { + if (key == 'q') { + macroModeState.exitMacroRecordMode(); + clearInputState(cm); + return true; + } + if (origin != 'mapping') { + logKey(macroModeState, key); + } + } + } + function handleEsc() { + if (key == '') { + if (vim.visualMode) { + // Get back to normal mode. + exitVisualMode(cm); + } else if (vim.insertMode) { + // Get back to normal mode. + exitInsertMode(cm); + } else { + // We're already in normal mode. Let '' be handled normally. + return; + } + clearInputState(cm); + return true; + } + } + + function handleKeyInsertMode() { + if (handleEsc()) { return true; } + vim.inputState.keyBuffer.push(key); + var keys = vim.inputState.keyBuffer.join(""); + var keysAreChars = key.length == 1; + var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert'); + var changeQueue = vim.inputState.changeQueue; + + if (match.type == 'none') { clearInputState(cm); return false; } + else if (match.type == 'partial') { + if (match.expectLiteralNext) vim.expectLiteralNext = true; + if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } + lastInsertModeKeyTimer = keysAreChars && window.setTimeout( + function() { if (vim.insertMode && vim.inputState.keyBuffer.length) { clearInputState(cm); } }, + getOption('insertModeEscKeysTimeout')); + if (keysAreChars) { + var selections = cm.listSelections(); + if (!changeQueue || changeQueue.removed.length != selections.length) + changeQueue = vim.inputState.changeQueue = new ChangeQueue; + changeQueue.inserted += key; + for (var i = 0; i < selections.length; i++) { + var from = cursorMin(selections[i].anchor, selections[i].head); + var to = cursorMax(selections[i].anchor, selections[i].head); + var text = cm.getRange(from, cm.state.overwrite ? offsetCursor(to, 0, 1) : to); + changeQueue.removed[i] = (changeQueue.removed[i] || "") + text; + } + } + return !keysAreChars; + } + vim.expectLiteralNext = false; + + if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } + if (match.command && changeQueue) { + var selections = cm.listSelections(); + for (var i = 0; i < selections.length; i++) { + var here = selections[i].head; + cm.replaceRange(changeQueue.removed[i] || "", + offsetCursor(here, 0, -changeQueue.inserted.length), here, '+input'); + } + vimGlobalState.macroModeState.lastInsertModeChanges.changes.pop(); + } + if (!match.command) clearInputState(cm); + return match.command; + } + + function handleKeyNonInsertMode() { + if (handleMacroRecording() || handleEsc()) { return true; } + + vim.inputState.keyBuffer.push(key); + var keys = vim.inputState.keyBuffer.join(""); + if (/^[1-9]\d*$/.test(keys)) { return true; } + + var keysMatcher = /^(\d*)(.*)$/.exec(keys); + if (!keysMatcher) { clearInputState(cm); return false; } + var context = vim.visualMode ? 'visual' : + 'normal'; + var mainKey = keysMatcher[2] || keysMatcher[1]; + if (vim.inputState.operatorShortcut && vim.inputState.operatorShortcut.slice(-1) == mainKey) { + // multikey operators act linewise by repeating only the last character + mainKey = vim.inputState.operatorShortcut; + } + var match = commandDispatcher.matchCommand(mainKey, defaultKeymap, vim.inputState, context); + if (match.type == 'none') { clearInputState(cm); return false; } + else if (match.type == 'partial') { + if (match.expectLiteralNext) vim.expectLiteralNext = true; + return true; + } + else if (match.type == 'clear') { clearInputState(cm); return true; } + vim.expectLiteralNext = false; + + vim.inputState.keyBuffer.length = 0; + keysMatcher = /^(\d*)(.*)$/.exec(keys); + if (keysMatcher && keysMatcher[1] && keysMatcher[1] != '0') { + vim.inputState.pushRepeatDigit(keysMatcher[1]); + } + return match.command; + } + + var command; + if (vim.insertMode) { command = handleKeyInsertMode(); } + else { command = handleKeyNonInsertMode(); } + if (command === false) { + return !vim.insertMode && key.length === 1 ? function() { return true; } : undefined; + } else if (command === true) { + // TODO: Look into using CodeMirror's multi-key handling. + // Return no-op since we are caching the key. Counts as handled, but + // don't want act on it just yet. + return function() { return true; }; + } else { + return function() { + return cm.operation(function() { + // @ts-ignore + cm.curOp.isVimOp = true; + try { + if (command.type == 'keyToKey') { + doKeyToKey(cm, command.toKeys, command); + } else { + commandDispatcher.processCommand(cm, vim, command); + } + } catch (e) { + // clear VIM state in case it's in a bad state. + // @ts-ignore + cm.state.vim = undefined; + maybeInitVimState(cm); + if (!vimApi.suppressErrorLogging) { + console['log'](e); + } + throw e; + } + return true; + }); + }; + } + }, + handleEx: function(cm, input) { + exCommandDispatcher.processCommand(cm, input); + }, + + defineMotion: defineMotion, + defineAction: defineAction, + defineOperator: defineOperator, + mapCommand: mapCommand, + _mapCommand: _mapCommand, + + defineRegister: defineRegister, + + exitVisualMode: exitVisualMode, + exitInsertMode: exitInsertMode + }; + + var keyToKeyStack = []; + var noremap = false; + var virtualPrompt; + function sendKeyToPrompt(key) { + if (key[0] == "<") { + var lowerKey = key.toLowerCase().slice(1, -1); + var parts = lowerKey.split('-'); + lowerKey = parts.pop() || ''; + if (lowerKey == 'lt') key = '<'; + else if (lowerKey == 'space') key = ' '; + else if (lowerKey == 'cr') key = '\n'; + else if (vimToCmKeyMap[lowerKey]) { + var value = virtualPrompt.value; + var event = { + key: vimToCmKeyMap[lowerKey], + target: { + value: value, + selectionEnd: value.length, + selectionStart: value.length + } + }; + if (virtualPrompt.onKeyDown) { + virtualPrompt.onKeyDown(event, virtualPrompt.value, close); + } + if (virtualPrompt && virtualPrompt.onKeyUp) { + virtualPrompt.onKeyUp(event, virtualPrompt.value, close); + } + return; + } + } + if (key == '\n') { + var prompt = virtualPrompt; + virtualPrompt = null; + prompt.onClose && prompt.onClose(prompt.value); + } else { + virtualPrompt.value = (virtualPrompt.value || '') + key; + } + + function close(value) { + if (typeof value == 'string') { virtualPrompt.value = value; } + else { virtualPrompt = null; } + } + } + function doKeyToKey(cm, keys, fromKey) { + var noremapBefore = noremap; + // prevent infinite recursion. + if (fromKey) { + if (keyToKeyStack.indexOf(fromKey) != -1) return; + keyToKeyStack.push(fromKey); + noremap = fromKey.noremap != false; + } + + try { + var vim = maybeInitVimState(cm); + var keyRe = /<(?:[CSMA]-)*\w+>|./gi; + + var match; + // Pull off one command key, which is either a single character + // or a special sequence wrapped in '<' and '>', e.g. ''. + while ((match = keyRe.exec(keys))) { + var key = match[0]; + var wasInsert = vim.insertMode; + if (virtualPrompt) { + sendKeyToPrompt(key); + continue; + } + + var result = vimApi.handleKey(cm, key, 'mapping'); + + if (!result && wasInsert && vim.insertMode) { + if (key[0] == "<") { + var lowerKey = key.toLowerCase().slice(1, -1); + var parts = lowerKey.split('-'); + lowerKey = parts.pop() || ''; + if (lowerKey == 'lt') key = '<'; + else if (lowerKey == 'space') key = ' '; + else if (lowerKey == 'cr') key = '\n'; + else if (vimToCmKeyMap.hasOwnProperty(lowerKey)) { + // todo support codemirror keys in insertmode vimToCmKeyMap + key = vimToCmKeyMap[lowerKey]; + sendCmKey(cm, key); + continue; + } else { + key = key[0]; + keyRe.lastIndex = match.index + 1; + } + } + cm.replaceSelection(key); + } + } + } finally { + keyToKeyStack.pop(); + noremap = keyToKeyStack.length ? noremapBefore : false; + if (!keyToKeyStack.length && virtualPrompt) { + var promptOptions = virtualPrompt; + virtualPrompt = null; + showPrompt(cm, promptOptions); + } + } + } + + var specialKey = { + Return: 'CR', Backspace: 'BS', 'Delete': 'Del', Escape: 'Esc', Insert: 'Ins', + ArrowLeft: 'Left', ArrowRight: 'Right', ArrowUp: 'Up', ArrowDown: 'Down', + Enter: 'CR', ' ': 'Space' + }; + var ignoredKeys = { Shift: 1, Alt: 1, Command: 1, Control: 1, + CapsLock: 1, AltGraph: 1, Dead: 1, Unidentified: 1 }; + + var vimToCmKeyMap = {}; + 'Left|Right|Up|Down|End|Home'.split('|').concat(Object.keys(specialKey)).forEach(function(x) { + vimToCmKeyMap[(specialKey[x] || '').toLowerCase()] + = vimToCmKeyMap[x.toLowerCase()] = x; + }); + + function vimKeyFromEvent(e, vim) { + var key = e.key; + if (ignoredKeys[key]) return; + if (key.length > 1 && key[0] == "n") { + key = key.replace("Numpad", ""); + } + key = specialKey[key] || key; + + var name = ''; + if (e.ctrlKey) { name += 'C-'; } + if (e.altKey) { name += 'A-'; } + if (e.metaKey) { name += 'M-'; } + // on mac many characters are entered as option- combos + // (e.g. on swiss keyboard { is option-8) + // so we ignore lonely A- modifier for keypress event on mac + if (CodeMirror.isMac && e.altKey && !e.metaKey && !e.ctrlKey) { + name = name.slice(2); + } + if ((name || key.length > 1) && e.shiftKey) { name += 'S-'; } + + if (vim && !vim.expectLiteralNext && key.length == 1) { + if (langmap.keymap && key in langmap.keymap) { + if (langmap.remapCtrl != false || !name) + key = langmap.keymap[key]; + } else if (key.charCodeAt(0) > 255) { + var code = e.code?.slice(-1) || ""; + if (!e.shiftKey) code = code.toLowerCase(); + if (code) key = code; + } + } + + name += key; + if (name.length > 1) { name = '<' + name + '>'; } + return name; + } + // langmap support + function updateLangmap(langmapString, remapCtrl) { + if (langmap.string !== langmapString) { + langmap = parseLangmap(langmapString); + } + langmap.remapCtrl = remapCtrl; + } + /** + * From :help langmap + * The 'langmap' option is a list of parts, separated with commas. Each + * part can be in one of two forms: + * 1. A list of pairs. Each pair is a "from" character immediately + * followed by the "to" character. Examples: "aA", "aAbBcC". + * 2. A list of "from" characters, a semi-colon and a list of "to" + * characters. Example: "abc;ABC" + * @arg {string} langmapString + * @returns {{string: string, keymap: Record, remapCtrl?: boolean}} + */ + function parseLangmap(langmapString) { + let keymap = ({})/**@type {Record}*/; + if (!langmapString) return { keymap: keymap, string: '' }; + + function getEscaped(list) { + return list.split(/\\?(.)/).filter(Boolean); + } + langmapString.split(/((?:[^\\,]|\\.)+),/).map(part => { + if (!part) return; + const semicolon = part.split(/((?:[^\\;]|\\.)+);/); + if (semicolon.length == 3) { + const from = getEscaped(semicolon[1]); + const to = getEscaped(semicolon[2]); + if (from.length !== to.length) return; // skip over malformed part + for (let i = 0; i < from.length; ++i) keymap[from[i]] = to[i]; + } else if (semicolon.length == 1) { + const pairs = getEscaped(part); + if (pairs.length % 2 !== 0) return; // skip over malformed part + for (let i = 0; i < pairs.length; i += 2) keymap[pairs[i]] = pairs[i + 1]; + } + }); + + return { keymap: keymap, string: langmapString }; + } + + defineOption('langmap', undefined, 'string', ['lmap'], function(name, cm) { + // The 'filetype' option proxies to the CodeMirror 'mode' option. + if (name === undefined) { + return langmap.string; + } else { + updateLangmap(name); + } + }); + + // Represents the current input state. + class InputState { + constructor() { + this.prefixRepeat = []; + this.motionRepeat = []; + + this.operator = null; + this.operatorArgs = null; + this.motion = null; + this.motionArgs = null; + this.keyBuffer = []; // For matching multi-key commands. + this.registerName = null; // Defaults to the unnamed register. + this.changeQueue = null; // For restoring text used by insert mode keybindings + } + pushRepeatDigit(n) { + if (!this.operator) { + this.prefixRepeat = this.prefixRepeat.concat(n); + } else { + this.motionRepeat = this.motionRepeat.concat(n); + } + } + getRepeat() { + var repeat = 0; + if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) { + repeat = 1; + if (this.prefixRepeat.length > 0) { + repeat *= parseInt(this.prefixRepeat.join(''), 10); + } + if (this.motionRepeat.length > 0) { + repeat *= parseInt(this.motionRepeat.join(''), 10); + } + } + return repeat; + } + } + + /** @arg {CodeMirrorV} cm @arg {string} [reason] */ + function clearInputState(cm, reason) { + cm.state.vim.inputState = new InputState(); + cm.state.vim.expectLiteralNext = false; + CodeMirror.signal(cm, 'vim-command-done', reason); + } + + function ChangeQueue() { + this.removed = []; + this.inserted = ""; + } + + /** + * Register stores information about copy and paste registers. Besides + * text, a register must store whether it is linewise (i.e., when it is + * pasted, should it insert itself into a new line, or should the text be + * inserted at the cursor position.) + */ + class Register { + constructor(text, linewise, blockwise) { + this.clear(); + this.keyBuffer = [text || '']; + this.insertModeChanges = []; + this.searchQueries = []; + this.linewise = !!linewise; + this.blockwise = !!blockwise; + } + setText(text, linewise, blockwise) { + this.keyBuffer = [text || '']; + this.linewise = !!linewise; + this.blockwise = !!blockwise; + } + pushText(text, linewise) { + // if this register has ever been set to linewise, use linewise. + if (linewise) { + if (!this.linewise) { + this.keyBuffer.push('\n'); + } + this.linewise = true; + } + this.keyBuffer.push(text); + } + pushInsertModeChanges(changes) { + this.insertModeChanges.push(createInsertModeChanges(changes)); + } + pushSearchQuery(query) { + this.searchQueries.push(query); + } + clear() { + this.keyBuffer = []; + this.insertModeChanges = []; + this.searchQueries = []; + this.linewise = false; + } + toString() { + return this.keyBuffer.join(''); + } + } + + /** + * Defines an external register. + * + * The name should be a single character that will be used to reference the register. + * The register should support setText, pushText, clear, and toString(). See Register + * for a reference implementation. + * @arg {string} name + * @arg {Register} register + */ + function defineRegister(name, register) { + var registers = vimGlobalState.registerController.registers; + if (!name || name.length != 1) { + throw Error('Register name must be 1 character'); + } + if (registers[name]) { + throw Error('Register already defined ' + name); + } + registers[name] = register; + validRegisters.push(name); + } + + /** + * vim registers allow you to keep many independent copy and paste buffers. + * See http://usevim.com/2012/04/13/registers/ for an introduction. + * + * RegisterController keeps the state of all the registers. An initial + * state may be passed in. The unnamed register '"' will always be + * overridden. + */ + class RegisterController { + /** @arg {Object} registers */ + constructor(registers) { + this.registers = registers; + this.unnamedRegister = registers['"'] = new Register(); + registers['.'] = new Register(); + registers[':'] = new Register(); + registers['/'] = new Register(); + registers['+'] = new Register(); + } + pushText(registerName, operator, text, linewise, blockwise) { + // The black hole register, "_, means delete/yank to nowhere. + if (registerName === '_') return; + if (linewise && text.charAt(text.length - 1) !== '\n') { + text += '\n'; + } + // Lowercase and uppercase registers refer to the same register. + // Uppercase just means append. + var register = this.isValidRegister(registerName) ? + this.getRegister(registerName) : null; + // if no register/an invalid register was specified, things go to the + // default registers + if (!register) { + switch (operator) { + case 'yank': + // The 0 register contains the text from the most recent yank. + this.registers['0'] = new Register(text, linewise, blockwise); + break; + case 'delete': + case 'change': + if (text.indexOf('\n') == -1) { + // Delete less than 1 line. Update the small delete register. + this.registers['-'] = new Register(text, linewise); + } else { + // Shift down the contents of the numbered registers and put the + // deleted text into register 1. + this.shiftNumericRegisters_(); + this.registers['1'] = new Register(text, linewise); + } + break; + } + // Make sure the unnamed register is set to what just happened + this.unnamedRegister.setText(text, linewise, blockwise); + return; + } + + // If we've gotten to this point, we've actually specified a register + var append = isUpperCase(registerName); + if (append) { + register.pushText(text, linewise); + } else { + register.setText(text, linewise, blockwise); + } + if (registerName === '+') { + navigator.clipboard.writeText(text); + } + // The unnamed register always has the same value as the last used + // register. + this.unnamedRegister.setText(register.toString(), linewise); + } + /** + * Gets the register named @name. If one of @name doesn't already exist, + * create it. If @name is invalid, return the unnamedRegister. + * @arg {string} [name] + */ + getRegister(name) { + if (!this.isValidRegister(name)) { + return this.unnamedRegister; + } + name = name.toLowerCase(); + if (!this.registers[name]) { + this.registers[name] = new Register(); + } + return this.registers[name]; + } + /**@type {{(name: any): name is string}} */ + isValidRegister(name) { + return name && (inArray(name, validRegisters) || latinCharRegex.test(name)); + } + shiftNumericRegisters_() { + for (var i = 9; i >= 2; i--) { + this.registers[i] = this.getRegister('' + (i - 1)); + } + } + } + class HistoryController { + constructor() { + this.historyBuffer = []; + this.iterator = 0; + this.initialPrefix = null; + } + // the input argument here acts a user entered prefix for a small time + // until we start autocompletion in which case it is the autocompleted. + nextMatch(input, up) { + var historyBuffer = this.historyBuffer; + var dir = up ? -1 : 1; + if (this.initialPrefix === null) this.initialPrefix = input; + for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i += dir) { + var element = historyBuffer[i]; + for (var j = 0; j <= element.length; j++) { + if (this.initialPrefix == element.substring(0, j)) { + this.iterator = i; + return element; + } + } + } + // should return the user input in case we reach the end of buffer. + if (i >= historyBuffer.length) { + this.iterator = historyBuffer.length; + return this.initialPrefix; + } + // return the last autocompleted query or exCommand as it is. + if (i < 0) return input; + } + pushInput(input) { + var index = this.historyBuffer.indexOf(input); + if (index > -1) this.historyBuffer.splice(index, 1); + if (input.length) this.historyBuffer.push(input); + } + reset() { + this.initialPrefix = null; + this.iterator = this.historyBuffer.length; + } + } + var commandDispatcher = { + matchCommand: function(keys, keyMap, inputState, context) { + var matches = commandMatches(keys, keyMap, context, inputState); + if (!matches.full && !matches.partial) { + return {type: 'none'}; + } else if (!matches.full && matches.partial) { + return { + type: 'partial', + expectLiteralNext: matches.partial.length == 1 && matches.partial[0].keys.slice(-11) == '' // langmap literal logic + }; + } + + var bestMatch; + // @ts-ignore + for (var i = 0; i < matches.full.length; i++) { + var match = matches.full[i]; + if (!bestMatch) { + bestMatch = match; + } + } + if (bestMatch.keys.slice(-11) == '' || bestMatch.keys.slice(-10) == '') { + var character = lastChar(keys); + if (!character || character.length > 1) return {type: 'clear'}; + inputState.selectedCharacter = character; + } + return {type: 'full', command: bestMatch}; + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {vimKey} command + */ + processCommand: function(cm, vim, command) { + vim.inputState.repeatOverride = command.repeatOverride; + switch (command.type) { + case 'motion': + this.processMotion(cm, vim, command); + break; + case 'operator': + this.processOperator(cm, vim, command); + break; + case 'operatorMotion': + this.processOperatorMotion(cm, vim, command); + break; + case 'action': + this.processAction(cm, vim, command); + break; + case 'search': + this.processSearch(cm, vim, command); + break; + case 'ex': + case 'keyToEx': + this.processEx(cm, vim, command); + break; + } + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {import("./types").motionCommand|import("./types").operatorMotionCommand} command + */ + processMotion: function(cm, vim, command) { + vim.inputState.motion = command.motion; + vim.inputState.motionArgs = /**@type {MotionArgs}*/(copyArgs(command.motionArgs)); + this.evalInput(cm, vim); + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {import("./types").operatorCommand|import("./types").operatorMotionCommand} command + */ + processOperator: function(cm, vim, command) { + var inputState = vim.inputState; + if (inputState.operator) { + if (inputState.operator == command.operator) { + // Typing an operator twice like 'dd' makes the operator operate + // linewise + inputState.motion = 'expandToLine'; + inputState.motionArgs = { linewise: true, repeat: 1 }; + this.evalInput(cm, vim); + return; + } else { + // 2 different operators in a row doesn't make sense. + clearInputState(cm); + } + } + inputState.operator = command.operator; + inputState.operatorArgs = copyArgs(command.operatorArgs); + if (command.keys.length > 1) { + inputState.operatorShortcut = command.keys; + } + if (command.exitVisualBlock) { + vim.visualBlock = false; + updateCmSelection(cm); + } + if (vim.visualMode) { + // Operating on a selection in visual mode. We don't need a motion. + this.evalInput(cm, vim); + } + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {import("./types").operatorMotionCommand} command + */ + processOperatorMotion: function(cm, vim, command) { + var visualMode = vim.visualMode; + var operatorMotionArgs = copyArgs(command.operatorMotionArgs); + if (operatorMotionArgs) { + // Operator motions may have special behavior in visual mode. + if (visualMode && operatorMotionArgs.visualLine) { + vim.visualLine = true; + } + } + this.processOperator(cm, vim, command); + if (!visualMode) { + this.processMotion(cm, vim, command); + } + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {import("./types").actionCommand} command + */ + processAction: function(cm, vim, command) { + var inputState = vim.inputState; + var repeat = inputState.getRepeat(); + var repeatIsExplicit = !!repeat; + var actionArgs = /**@type {ActionArgs}*/(copyArgs(command.actionArgs) || {repeat: 1}); + if (inputState.selectedCharacter) { + actionArgs.selectedCharacter = inputState.selectedCharacter; + } + // Actions may or may not have motions and operators. Do these first. + if (command.operator) { + // @ts-ignore + this.processOperator(cm, vim, command); + } + if (command.motion) { + // @ts-ignore + this.processMotion(cm, vim, command); + } + if (command.motion || command.operator) { + this.evalInput(cm, vim); + } + actionArgs.repeat = repeat || 1; + actionArgs.repeatIsExplicit = repeatIsExplicit; + actionArgs.registerName = inputState.registerName; + clearInputState(cm); + vim.lastMotion = null; + if (command.isEdit) { + this.recordLastEdit(vim, inputState, command); + } + actions[command.action](cm, actionArgs, vim); + }, + /** @arg {CodeMirrorV} cm @arg {vimState} vim @arg {import("./types").searchCommand} command*/ + processSearch: function(cm, vim, command) { + if (!cm.getSearchCursor) { + // Search depends on SearchCursor. + return; + } + var forward = command.searchArgs.forward; + var wholeWordOnly = command.searchArgs.wholeWordOnly; + getSearchState(cm).setReversed(!forward); + var promptPrefix = (forward) ? '/' : '?'; + var originalQuery = getSearchState(cm).getQuery(); + var originalScrollPos = cm.getScrollInfo(); + /** @arg {string} query @arg {boolean} ignoreCase @arg {boolean} smartCase */ + function handleQuery(query, ignoreCase, smartCase) { + vimGlobalState.searchHistoryController.pushInput(query); + vimGlobalState.searchHistoryController.reset(); + try { + updateSearchQuery(cm, query, ignoreCase, smartCase); + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + query); + clearInputState(cm); + return; + } + commandDispatcher.processMotion(cm, vim, { + keys: '', + type: 'motion', + motion: 'findNext', + motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist } + }); + } + /** @arg {string} query */ + function onPromptClose(query) { + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + handleQuery(query, true /** ignoreCase */, true /** smartCase */); + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isRecording) { + logSearchQuery(macroModeState, query); + } + } + /** + * @arg {KeyboardEvent&{target:HTMLInputElement}} e + * @arg {any} query + * @arg {(arg0: any) => void} close + */ + function onPromptKeyUp(e, query, close) { + var keyName = vimKeyFromEvent(e), up, offset; + if (keyName == '' || keyName == '') { + up = keyName == '' ? true : false; + offset = e.target ? e.target.selectionEnd : 0; + query = vimGlobalState.searchHistoryController.nextMatch(query, up) || ''; + close(query); + if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length); + } else if (keyName && keyName != '' && keyName != '') { + vimGlobalState.searchHistoryController.reset(); + } + var parsedQuery; + try { + parsedQuery = updateSearchQuery(cm, query, + true /** ignoreCase */, true /** smartCase */); + } catch (e) { + // Swallow bad regexes for incremental search. + } + if (parsedQuery) { + cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30); + } else { + clearSearchHighlight(cm); + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + } + } + /** @arg {KeyboardEvent} e @arg {string} query @arg {(arg0?: string) => void} close */ + function onPromptKeyDown(e, query, close) { + var keyName = vimKeyFromEvent(e); + if (keyName == '' || keyName == '' || keyName == '' || + (keyName == '' && query == '')) { + vimGlobalState.searchHistoryController.pushInput(query); + vimGlobalState.searchHistoryController.reset(); + updateSearchQuery(cm, originalQuery); + clearSearchHighlight(cm); + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + CodeMirror.e_stop(e); + clearInputState(cm); + close(); + cm.focus(); + } else if (keyName == '' || keyName == '') { + CodeMirror.e_stop(e); + } else if (keyName == '') { + // Ctrl-U clears input. + CodeMirror.e_stop(e); + close(''); + } + } + switch (command.searchArgs.querySrc) { + case 'prompt': + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { + let query = macroModeState.replaySearchQueries.shift(); + handleQuery(query, true /** ignoreCase */, false /** smartCase */); + } else { + showPrompt(cm, { + onClose: onPromptClose, + prefix: promptPrefix, + desc: '(JavaScript regexp)', + onKeyUp: onPromptKeyUp, + onKeyDown: onPromptKeyDown + }); + } + break; + case 'wordUnderCursor': + var word = expandWordUnderCursor(cm, {noSymbol: true}); + var isKeyword = true; + if (!word) { + word = expandWordUnderCursor(cm, {noSymbol: false}); + isKeyword = false; + } + if (!word) { + showConfirm(cm, 'No word under cursor'); + clearInputState(cm); + return; + } + let query = cm.getLine(word.start.line).substring(word.start.ch, + word.end.ch); + if (isKeyword && wholeWordOnly) { + query = '\\b' + query + '\\b'; + } else { + query = escapeRegex(query); + } + + // cachedCursor is used to save the old position of the cursor + // when * or # causes vim to seek for the nearest word and shift + // the cursor before entering the motion. + vimGlobalState.jumpList.cachedCursor = cm.getCursor(); + cm.setCursor(word.start); + + handleQuery(query, true /** ignoreCase */, false /** smartCase */); + break; + } + }, + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {import("./types").exCommand | import("./types").keyToExCommand} command + */ + processEx: function(cm, vim, command) { + /**@arg {string} input*/ + function onPromptClose(input) { + // Give the prompt some time to close so that if processCommand shows + // an error, the elements don't overlap. + vimGlobalState.exCommandHistoryController.pushInput(input); + vimGlobalState.exCommandHistoryController.reset(); + exCommandDispatcher.processCommand(cm, input); + if (cm.state.vim) clearInputState(cm); + clearSearchHighlight(cm); + } + /** + * @arg {KeyboardEvent&{target:HTMLInputElement}} e + * @arg {string} input + * @arg {(arg0?: string) => void} close + */ + function onPromptKeyDown(e, input, close) { + var keyName = vimKeyFromEvent(e), up, offset; + if (keyName == '' || keyName == '' || keyName == '' || + (keyName == '' && input == '')) { + vimGlobalState.exCommandHistoryController.pushInput(input); + vimGlobalState.exCommandHistoryController.reset(); + CodeMirror.e_stop(e); + clearInputState(cm); + clearSearchHighlight(cm); + close(); + cm.focus(); + } + if (keyName == '' || keyName == '') { + CodeMirror.e_stop(e); + up = keyName == '' ? true : false; + offset = e.target ? e.target.selectionEnd : 0; + input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || ''; + close(input); + if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length); + } else if (keyName == '') { + // Ctrl-U clears input. + CodeMirror.e_stop(e); + close(''); + } else if (keyName && keyName != '' && keyName != '') { + vimGlobalState.exCommandHistoryController.reset(); + } + } + /** + * @arg {KeyboardEvent&{target:HTMLInputElement}} e + * @arg {any} query + */ + function onPromptKeyUp(e, query) { + var inputStream = new CodeMirror.StringStream(query); + var params = {}; + try { + exCommandDispatcher.parseInput_(cm, inputStream, params); + if (params.commandName != "s") { + clearSearchHighlight(cm); + return; + } + command = exCommandDispatcher.matchCommand_(params.commandName); + exCommandDispatcher.parseCommandArgs_(inputStream, params, command); + if (!params.argString) return; + var regex = parseQuery(params.argString.slice(1), true, true); + if (regex) highlightSearchMatches(cm, regex); + } catch(e) { + } + } + if (command.type == 'keyToEx') { + // Handle user defined Ex to Ex mappings + exCommandDispatcher.processCommand(cm, command.exArgs.input); + } else { + var promptOptions = { + onClose: onPromptClose, + onKeyDown: onPromptKeyDown, + onKeyUp: onPromptKeyUp, + prefix: ':', + }; + if (vim.visualMode) { + promptOptions.value = '\'<,\'>'; + promptOptions.selectValueOnOpen = false; + } + showPrompt(cm, promptOptions); + } + }, + /**@arg {CodeMirrorV} cm @arg {vimState} vim */ + evalInput: function(cm, vim) { + // If the motion command is set, execute both the operator and motion. + // Otherwise return. + var inputState = vim.inputState; + var motion = inputState.motion; + /** @type {MotionArgs}*/ + var motionArgs = inputState.motionArgs || { repeat: 1}; + var operator = inputState.operator; + /** @type {OperatorArgs}*/ + var operatorArgs = inputState.operatorArgs || {}; + var registerName = inputState.registerName; + var sel = vim.sel; + // TODO: Make sure cm and vim selections are identical outside visual mode. + var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head')); + var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor')); + var oldHead = copyCursor(origHead); + var oldAnchor = copyCursor(origAnchor); + var newHead, newAnchor; + var repeat; + if (operator) { + this.recordLastEdit(vim, inputState); + } + if (inputState.repeatOverride !== undefined) { + // If repeatOverride is specified, that takes precedence over the + // input state's repeat. Used by Ex mode and can be user defined. + repeat = inputState.repeatOverride; + } else { + repeat = inputState.getRepeat(); + } + if (repeat > 0 && motionArgs.explicitRepeat) { + motionArgs.repeatIsExplicit = true; + } else if (motionArgs.noRepeat || + (!motionArgs.explicitRepeat && repeat === 0)) { + repeat = 1; + motionArgs.repeatIsExplicit = false; + } + if (inputState.selectedCharacter) { + // If there is a character input, stick it in all of the arg arrays. + motionArgs.selectedCharacter = operatorArgs.selectedCharacter = + inputState.selectedCharacter; + } + motionArgs.repeat = repeat; + clearInputState(cm); + if (motion) { + var motionResult = motions[motion](cm, origHead, motionArgs, vim, inputState); + vim.lastMotion = motions[motion]; + if (!motionResult) { + return; + } + if (motionArgs.toJumplist) { + var jumpList = vimGlobalState.jumpList; + // if the current motion is # or *, use cachedCursor + var cachedCursor = jumpList.cachedCursor; + if (cachedCursor) { + // @ts-ignore + recordJumpPosition(cm, cachedCursor, motionResult); + delete jumpList.cachedCursor; + } else { + // @ts-ignore + recordJumpPosition(cm, origHead, motionResult); + } + } + if (motionResult instanceof Array) { + newAnchor = motionResult[0]; + newHead = motionResult[1]; + } else { + newHead = motionResult; + } + // TODO: Handle null returns from motion commands better. + if (!newHead) { + newHead = copyCursor(origHead); + } + if (vim.visualMode) { + if (!(vim.visualBlock && newHead.ch === Infinity)) { + newHead = clipCursorToContent(cm, newHead, oldHead); + } + if (newAnchor) { + newAnchor = clipCursorToContent(cm, newAnchor); + } + newAnchor = newAnchor || oldAnchor; + sel.anchor = newAnchor; + sel.head = newHead; + updateCmSelection(cm); + updateMark(cm, vim, '<', + cursorIsBefore(newAnchor, newHead) ? newAnchor + : newHead); + updateMark(cm, vim, '>', + cursorIsBefore(newAnchor, newHead) ? newHead + : newAnchor); + } else if (!operator) { + newHead = clipCursorToContent(cm, newHead, oldHead); + cm.setCursor(newHead.line, newHead.ch); + } + } + if (operator) { + if (operatorArgs.lastSel) { + // Replaying a visual mode operation + newAnchor = oldAnchor; + var lastSel = operatorArgs.lastSel; + var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line); + var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch); + if (lastSel.visualLine) { + // Linewise Visual mode: The same number of lines. + newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch); + } else if (lastSel.visualBlock) { + // Blockwise Visual mode: The same number of lines and columns. + newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset); + } else if (lastSel.head.line == lastSel.anchor.line) { + // Normal Visual mode within one line: The same number of characters. + newHead = new Pos(oldAnchor.line, oldAnchor.ch + chOffset); + } else { + // Normal Visual mode with several lines: The same number of lines, in the + // last line the same number of characters as in the last line the last time. + newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch); + } + vim.visualMode = true; + vim.visualLine = lastSel.visualLine; + vim.visualBlock = lastSel.visualBlock; + sel = vim.sel = { + anchor: newAnchor, + head: newHead + }; + updateCmSelection(cm); + } else if (vim.visualMode) { + operatorArgs.lastSel = { + anchor: copyCursor(sel.anchor), + head: copyCursor(sel.head), + visualBlock: vim.visualBlock, + visualLine: vim.visualLine + }; + } + var curStart, curEnd, linewise; + /** @type {'block'|'line'|'char'}*/ var mode; + var cmSel; + if (vim.visualMode) { + // Init visual op + curStart = cursorMin(sel.head, sel.anchor); + curEnd = cursorMax(sel.head, sel.anchor); + linewise = vim.visualLine || operatorArgs.linewise; + mode = vim.visualBlock ? 'block' : + linewise ? 'line' : + 'char'; + var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd); + cmSel = makeCmSelection(cm, { + anchor: newPositions.start, + head: newPositions.end + }, mode); + if (linewise) { + var ranges = cmSel.ranges; + if (mode == 'block') { + // Linewise operators in visual block mode extend to end of line + for (var i = 0; i < ranges.length; i++) { + ranges[i].head.ch = lineLength(cm, ranges[i].head.line); + } + } else if (mode == 'line') { + ranges[0].head = new Pos(ranges[0].head.line + 1, 0); + } + } + } else { + // Init motion op + curStart = copyCursor(newAnchor || oldAnchor); + curEnd = copyCursor(newHead || oldHead); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curStart; + curStart = curEnd; + curEnd = tmp; + } + linewise = motionArgs.linewise || operatorArgs.linewise; + if (linewise) { + // Expand selection to entire line. + expandSelectionToLine(cm, curStart, curEnd); + } else if (motionArgs.forward) { + // Clip to trailing newlines only if the motion goes forward. + clipToLine(cm, curStart, curEnd); + } + mode = 'char'; + var exclusive = !motionArgs.inclusive || linewise; + var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd); + cmSel = makeCmSelection(cm, { + anchor: newPositions.start, + head: newPositions.end + }, mode, exclusive); + } + cm.setSelections(cmSel.ranges, cmSel.primary); + vim.lastMotion = null; + operatorArgs.repeat = repeat; // For indent in visual mode. + operatorArgs.registerName = registerName; + // Keep track of linewise as it affects how paste and change behave. + operatorArgs.linewise = linewise; + var operatorMoveTo = operators[operator]( + cm, operatorArgs, cmSel.ranges, oldAnchor, newHead); + if (vim.visualMode) { + exitVisualMode(cm, operatorMoveTo != null); + } + if (operatorMoveTo) { + cm.setCursor(operatorMoveTo); + } + } + }, + /**@arg {vimState} vim @arg {InputStateInterface} inputState, @arg {import("./types").actionCommand} [actionCommand] */ + recordLastEdit: function(vim, inputState, actionCommand) { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { return; } + vim.lastEditInputState = inputState; + vim.lastEditActionCommand = actionCommand; + macroModeState.lastInsertModeChanges.changes = []; + macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false; + macroModeState.lastInsertModeChanges.visualBlock = vim.visualBlock ? vim.sel.head.line - vim.sel.anchor.line : 0; + } + }; + + /** + * All of the functions below return Cursor objects. + * @type {import("./types").vimMotions}} + */ + var motions = { + moveToTopLine: function(cm, _head, motionArgs) { + var line = getUserVisibleLines(cm).top + motionArgs.repeat -1; + return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + moveToMiddleLine: function(cm) { + var range = getUserVisibleLines(cm); + var line = Math.floor((range.top + range.bottom) * 0.5); + return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + moveToBottomLine: function(cm, _head, motionArgs) { + var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1; + return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + expandToLine: function(_cm, head, motionArgs) { + // Expands forward to end of line, and then to next line if repeat is + // >1. Does not handle backward motion! + var cur = head; + return new Pos(cur.line + motionArgs.repeat - 1, Infinity); + }, + findNext: function(cm, _head, motionArgs) { + var state = getSearchState(cm); + var query = state.getQuery(); + if (!query) { + return; + } + var prev = !motionArgs.forward; + // If search is initiated with ? instead of /, negate direction. + prev = (state.isReversed()) ? !prev : prev; + highlightSearchMatches(cm, query); + return findNext(cm, prev/** prev */, query, motionArgs.repeat); + }, + /** + * Find and select the next occurrence of the search query. If the cursor is currently + * within a match, then find and select the current match. Otherwise, find the next occurrence in the + * appropriate direction. + * + * This differs from `findNext` in the following ways: + * + * 1. Instead of only returning the "from", this returns a "from", "to" range. + * 2. If the cursor is currently inside a search match, this selects the current match + * instead of the next match. + * 3. If there is no associated operator, this will turn on visual mode. + */ + findAndSelectNextInclusive: function(cm, _head, motionArgs, vim, prevInputState) { + var state = getSearchState(cm); + var query = state.getQuery(); + + if (!query) { + return; + } + + var prev = !motionArgs.forward; + prev = (state.isReversed()) ? !prev : prev; + + // next: [from, to] | null + var next = findNextFromAndToInclusive(cm, prev, query, motionArgs.repeat, vim); + + // No matches. + if (!next) { + return; + } + + // If there's an operator that will be executed, return the selection. + if (prevInputState.operator) { + return next; + } + + // At this point, we know that there is no accompanying operator -- let's + // deal with visual mode in order to select an appropriate match. + + var from = next[0]; + // For whatever reason, when we use the "to" as returned by searchcursor.js directly, + // the resulting selection is extended by 1 char. Let's shrink it so that only the + // match is selected. + var to = new Pos(next[1].line, next[1].ch - 1); + + if (vim.visualMode) { + // If we were in visualLine or visualBlock mode, get out of it. + if (vim.visualLine || vim.visualBlock) { + vim.visualLine = false; + vim.visualBlock = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: ""}); + } + + // If we're currently in visual mode, we should extend the selection to include + // the search result. + var anchor = vim.sel.anchor; + if (anchor) { + if (state.isReversed()) { + if (motionArgs.forward) { + return [anchor, from]; + } + + return [anchor, to]; + } else { + if (motionArgs.forward) { + return [anchor, to]; + } + + return [anchor, from]; + } + } + } else { + // Let's turn visual mode on. + vim.visualMode = true; + vim.visualLine = false; + vim.visualBlock = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: ""}); + } + + return prev ? [to, from] : [from, to]; + }, + goToMark: function(cm, _head, motionArgs, vim) { + var pos = getMarkPos(cm, vim, motionArgs.selectedCharacter || ""); + if (pos) { + return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos; + } + return null; + }, + moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) { + if (vim.visualBlock && motionArgs.sameLine) { + var sel = vim.sel; + return [ + clipCursorToContent(cm, new Pos(sel.anchor.line, sel.head.ch)), + clipCursorToContent(cm, new Pos(sel.head.line, sel.anchor.ch)) + ]; + } else { + return ([vim.sel.head, vim.sel.anchor]); + } + }, + jumpToMark: function(cm, head, motionArgs, vim) { + var best = head; + for (var i = 0; i < motionArgs.repeat; i++) { + var cursor = best; + for (var key in vim.marks) { + if (!isLowerCase(key)) { + continue; + } + var mark = vim.marks[key].find(); + var isWrongDirection = (motionArgs.forward) ? + // @ts-ignore + cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark); + + if (isWrongDirection) { + continue; + } + // @ts-ignore + if (motionArgs.linewise && (mark.line == cursor.line)) { + continue; + } + + var equal = cursorEqual(cursor, best); + var between = (motionArgs.forward) ? + // @ts-ignore + cursorIsBetween(cursor, mark, best) : + // @ts-ignore + cursorIsBetween(best, mark, cursor); + + if (equal || between) { + // @ts-ignore + best = mark; + } + } + } + + if (motionArgs.linewise) { + // Vim places the cursor on the first non-whitespace character of + // the line if there is one, else it places the cursor at the end + // of the line, regardless of whether a mark was found. + best = new Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line))); + } + return best; + }, + moveByCharacters: function(_cm, head, motionArgs) { + var cur = head; + var repeat = motionArgs.repeat; + var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat; + return new Pos(cur.line, ch); + }, + moveByLines: function(cm, head, motionArgs, vim) { + var cur = head; + var endCh = cur.ch; + // Depending what our last motion was, we may want to do different + // things. If our last motion was moving vertically, we want to + // preserve the HPos from our last horizontal move. If our last motion + // was going to the end of a line, moving vertically we should go to + // the end of the line, etc. + switch (vim.lastMotion) { + case this.moveByLines: + case this.moveByDisplayLines: + case this.moveByScroll: + case this.moveToColumn: + case this.moveToEol: + endCh = vim.lastHPos; + break; + default: + vim.lastHPos = endCh; + } + var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0); + var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat; + var first = cm.firstLine(); + var last = cm.lastLine(); + var posV = cm.findPosV(cur, (motionArgs.forward ? repeat : -repeat), 'line', vim.lastHSPos); + var hasMarkedText = motionArgs.forward ? posV.line > line : posV.line < line; + if (hasMarkedText) { + line = posV.line; + endCh = posV.ch; + } + // Vim go to line begin or line end when cursor at first/last line and + // move to previous/next line is triggered. + if (line < first && cur.line == first){ + return this.moveToStartOfLine(cm, head, motionArgs, vim); + } else if (line > last && cur.line == last){ + return moveToEol(cm, head, motionArgs, vim, true); + } + if (motionArgs.toFirstChar){ + endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line)); + vim.lastHPos = endCh; + } + vim.lastHSPos = cm.charCoords(new Pos(line, endCh),'div').left; + return new Pos(line, endCh); + }, + moveByDisplayLines: function(cm, head, motionArgs, vim) { + var cur = head; + switch (vim.lastMotion) { + case this.moveByDisplayLines: + case this.moveByScroll: + case this.moveByLines: + case this.moveToColumn: + case this.moveToEol: + break; + default: + vim.lastHSPos = cm.charCoords(cur,'div').left; + } + var repeat = motionArgs.repeat; + var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos); + if (res.hitSide) { + if (motionArgs.forward) { + var lastCharCoords = cm.charCoords(res, 'div'); + var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos }; + res = cm.coordsChar(goalCoords, 'div'); + } else { + var resCoords = cm.charCoords(new Pos(cm.firstLine(), 0), 'div'); + resCoords.left = vim.lastHSPos; + res = cm.coordsChar(resCoords, 'div'); + } + } + vim.lastHPos = res.ch; + return res; + }, + moveByPage: function(cm, head, motionArgs) { + // CodeMirror only exposes functions that move the cursor page down, so + // doing this bad hack to move the cursor and move it back. evalInput + // will move the cursor to where it should be in the end. + var curStart = head; + var repeat = motionArgs.repeat; + return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page'); + }, + moveByParagraph: function(cm, head, motionArgs) { + var dir = motionArgs.forward ? 1 : -1; + return findParagraph(cm, head, motionArgs.repeat, dir).start; + }, + moveBySentence: function(cm, head, motionArgs) { + var dir = motionArgs.forward ? 1 : -1; + return findSentence(cm, head, motionArgs.repeat, dir); + }, + moveByScroll: function(cm, head, motionArgs, vim) { + var scrollbox = cm.getScrollInfo(); + var curEnd = null; + var repeat = motionArgs.repeat; + if (!repeat) { + repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight()); + } + var orig = cm.charCoords(head, 'local'); + motionArgs.repeat = repeat; + curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim); + if (!curEnd) { + return null; + } + var dest = cm.charCoords(curEnd, 'local'); + cm.scrollTo(null, scrollbox.top + dest.top - orig.top); + return curEnd; + }, + moveByWords: function(cm, head, motionArgs) { + return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward, + !!motionArgs.wordEnd, !!motionArgs.bigWord); + }, + moveTillCharacter: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + var curEnd = moveToCharacter(cm, repeat, motionArgs.forward, + motionArgs.selectedCharacter, head); + var increment = motionArgs.forward ? -1 : 1; + recordLastCharacterSearch(increment, motionArgs); + if (!curEnd) return null; + curEnd.ch += increment; + return curEnd; + }, + moveToCharacter: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + recordLastCharacterSearch(0, motionArgs); + return moveToCharacter(cm, repeat, motionArgs.forward, + motionArgs.selectedCharacter, head) || head; + }, + moveToSymbol: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + return motionArgs.selectedCharacter + && findSymbol(cm, repeat, motionArgs.forward, + motionArgs.selectedCharacter) || head; + }, + moveToColumn: function(cm, head, motionArgs, vim) { + var repeat = motionArgs.repeat; + // repeat is equivalent to which column we want to move to! + vim.lastHPos = repeat - 1; + vim.lastHSPos = cm.charCoords(head,'div').left; + return moveToColumn(cm, repeat); + }, + moveToEol: function(cm, head, motionArgs, vim) { + return moveToEol(cm, head, motionArgs, vim, false); + }, + moveToFirstNonWhiteSpaceCharacter: function(cm, head) { + // Go to the start of the line where the text begins, or the end for + // whitespace-only lines + var cursor = head; + return new Pos(cursor.line, + findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line))); + }, + moveToMatchedSymbol: function(cm, head) { + var cursor = head; + var line = cursor.line; + var ch = cursor.ch; + var lineText = cm.getLine(line); + var symbol; + for (; ch < lineText.length; ch++) { + symbol = lineText.charAt(ch); + if (symbol && isMatchableSymbol(symbol)) { + var style = cm.getTokenTypeAt(new Pos(line, ch + 1)); + if (style !== "string" && style !== "comment") { + break; + } + } + } + if (ch < lineText.length) { + // Only include angle brackets in analysis if they are being matched. + var re = (symbol === '<' || symbol === '>') ? /[(){}[\]<>]/ : /[(){}[\]]/; + var matched = cm.findMatchingBracket(new Pos(line, ch), {bracketRegex: re}); + return matched.to; + } else { + return cursor; + } + }, + moveToStartOfLine: function(_cm, head) { + return new Pos(head.line, 0); + }, + moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) { + var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine(); + if (motionArgs.repeatIsExplicit) { + lineNum = motionArgs.repeat - cm.getOption('firstLineNumber'); + } + return new Pos(lineNum, + findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum))); + }, + moveToStartOfDisplayLine: function(cm) { + cm.execCommand("goLineLeft"); + return cm.getCursor(); + }, + moveToEndOfDisplayLine: function(cm) { + cm.execCommand("goLineRight"); + var head = cm.getCursor(); + if (head.sticky == "before") head.ch--; + return head; + }, + textObjectManipulation: function(cm, head, motionArgs, vim) { + // TODO: lots of possible exceptions that can be thrown here. Try da( + // outside of a () block. + /** @type{Object} */ + var mirroredPairs = {'(': ')', ')': '(', + '{': '}', '}': '{', + '[': ']', ']': '[', + '<': '>', '>': '<'}; + /** @type{Object} */ + var selfPaired = {'\'': true, '"': true, '`': true}; + + var character = motionArgs.selectedCharacter || ""; + // 'b' refers to '()' block. + // 'B' refers to '{}' block. + if (character == 'b') { + character = '('; + } else if (character == 'B') { + character = '{'; + } + + // Inclusive is the difference between a and i + // TODO: Instead of using the additional text object map to perform text + // object operations, merge the map into the defaultKeyMap and use + // motionArgs to define behavior. Define separate entries for 'aw', + // 'iw', 'a[', 'i[', etc. + var inclusive = !motionArgs.textObjectInner; + + var tmp, move; + if (mirroredPairs[character]) { + move = true; + tmp = selectCompanionObject(cm, head, character, inclusive); + if (!tmp) { + var sc = cm.getSearchCursor(new RegExp("\\" + character, "g"), head); + if (sc.find()) { + // @ts-ignore + tmp = selectCompanionObject(cm, sc.from(), character, inclusive); + } + } + } else if (selfPaired[character]) { + move = true; + tmp = findBeginningAndEnd(cm, head, character, inclusive); + } else if (character === 'W' || character === 'w') { + var repeat = motionArgs.repeat || 1; + while (repeat-- > 0) { + var repeated = expandWordUnderCursor(cm, { + inclusive, + innerWord: !inclusive, + bigWord: character === 'W', + noSymbol: character === 'W', + multiline: true + }, tmp && tmp.end); + if (repeated) { + if (!tmp) tmp = repeated; + tmp.end = repeated.end; + } + } + } else if (character === 'p') { + tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive); + motionArgs.linewise = true; + if (vim.visualMode) { + if (!vim.visualLine) { vim.visualLine = true; } + } else { + var operatorArgs = vim.inputState.operatorArgs; + if (operatorArgs) { operatorArgs.linewise = true; } + tmp.end.line--; + } + } else if (character === 't') { + tmp = expandTagUnderCursor(cm, head, inclusive); + } else if (character === 's') { + // account for cursor on end of sentence symbol + var content = cm.getLine(head.line); + if (head.ch > 0 && isEndOfSentenceSymbol(content[head.ch])) { + head.ch -= 1; + } + var end = getSentence(cm, head, motionArgs.repeat, 1, inclusive); + var start = getSentence(cm, head, motionArgs.repeat, -1, inclusive); + // closer vim behaviour, 'a' only takes the space after the sentence if there is one before and after + if (isWhiteSpaceString(cm.getLine(start.line)[start.ch]) + && isWhiteSpaceString(cm.getLine(end.line)[end.ch -1])) { + start = {line: start.line, ch: start.ch + 1}; + } + tmp = {start: start, end: end}; + } + + if (!tmp) { + // No valid text object, don't move. + return null; + } + + if (!cm.state.vim.visualMode) { + return [tmp.start, tmp.end]; + } else { + return expandSelection(cm, tmp.start, tmp.end, move); + } + }, + + repeatLastCharacterSearch: function(cm, head, motionArgs) { + var lastSearch = vimGlobalState.lastCharacterSearch; + var repeat = motionArgs.repeat; + var forward = motionArgs.forward === lastSearch.forward; + var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1); + cm.moveH(-increment, 'char'); + motionArgs.inclusive = forward ? true : false; + var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter); + if (!curEnd) { + cm.moveH(increment, 'char'); + return head; + } + curEnd.ch += increment; + return curEnd; + } + }; + + /** @arg {string} name @arg {import("./types").MotionFn} fn */ + function defineMotion(name, fn) { + motions[name] = fn; + } + + /** @arg {string} val @arg {number} times */ + function fillArray(val, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(val); + } + return arr; + } + /** + * An operator acts on a text selection. It receives the list of selections + * as input. The corresponding CodeMirror selection is guaranteed to + * match the input selection. + */ + /** @type {import("./types").vimOperators} */ + var operators = { + change: function(cm, args, ranges) { + var finalHead, text; + var vim = cm.state.vim; + var anchor = ranges[0].anchor, + head = ranges[0].head; + if (!vim.visualMode) { + text = cm.getRange(anchor, head); + var lastState = vim.lastEditInputState; + if (lastState?.motion == "moveByWords" && !isWhiteSpaceString(text)) { + // Exclude trailing whitespace if the range is not all whitespace. + var match = (/\s+$/).exec(text); + if (match && lastState.motionArgs && lastState.motionArgs.forward) { + head = offsetCursor(head, 0, - match[0].length); + text = text.slice(0, - match[0].length); + } + } + if (args.linewise) { + anchor = new Pos(anchor.line, findFirstNonWhiteSpaceCharacter(cm.getLine(anchor.line))); + if (head.line > anchor.line) { + head = new Pos(head.line - 1, Number.MAX_VALUE); + } + } + cm.replaceRange('', anchor, head); + finalHead = anchor; + } else if (args.fullLine) { + head.ch = Number.MAX_VALUE; + head.line--; + cm.setSelection(anchor, head); + text = cm.getSelection(); + cm.replaceSelection(""); + finalHead = anchor; + } else { + text = cm.getSelection(); + var replacement = fillArray('', ranges.length); + cm.replaceSelections(replacement); + finalHead = cursorMin(ranges[0].head, ranges[0].anchor); + } + vimGlobalState.registerController.pushText( + args.registerName, 'change', text, + args.linewise, ranges.length > 1); + actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim); + }, + delete: function(cm, args, ranges) { + var finalHead, text; + var vim = cm.state.vim; + if (!vim.visualBlock) { + var anchor = ranges[0].anchor, + head = ranges[0].head; + if (args.linewise && + head.line != cm.firstLine() && + anchor.line == cm.lastLine() && + anchor.line == head.line - 1) { + // Special case for dd on last line (and first line). + if (anchor.line == cm.firstLine()) { + anchor.ch = 0; + } else { + anchor = new Pos(anchor.line - 1, lineLength(cm, anchor.line - 1)); + } + } + text = cm.getRange(anchor, head); + cm.replaceRange('', anchor, head); + finalHead = anchor; + if (args.linewise) { + finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor); + } + } else { + text = cm.getSelection(); + var replacement = fillArray('', ranges.length); + cm.replaceSelections(replacement); + finalHead = cursorMin(ranges[0].head, ranges[0].anchor); + } + vimGlobalState.registerController.pushText( + args.registerName, 'delete', text, + args.linewise, vim.visualBlock); + return clipCursorToContent(cm, finalHead); + }, + indent: function(cm, args, ranges) { + var vim = cm.state.vim; + var repeat = (vim.visualMode) ? (args.repeat || 0) : 1; + if (cm.indentMore) { + for (var j = 0; j < repeat; j++) { + if (args.indentRight) cm.indentMore(); + else cm.indentLess(); + } + } else { + var startLine = ranges[0].anchor.line; + var endLine = vim.visualBlock ? + ranges[ranges.length - 1].anchor.line : + ranges[0].head.line; + // In visual mode, n> shifts the selection right n times, instead of + // shifting n lines right once. + if (args.linewise) { + // The only way to delete a newline is to delete until the start of + // the next line, so in linewise mode evalInput will include the next + // line. We don't want this in indent, so we go back a line. + endLine--; + } + for (var i = startLine; i <= endLine; i++) { + for (var j = 0; j < repeat; j++) { + cm.indentLine(i, args.indentRight); + } + } + } + return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor); + }, + indentAuto: function(cm, _args, ranges) { + cm.execCommand("indentAuto"); + return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor); + }, + hardWrap: function(cm, operatorArgs, ranges, oldAnchor) { + if (!cm.hardWrap) return; + var from = ranges[0].anchor.line; + var to = ranges[0].head.line; + if (operatorArgs.linewise) to--; + var endRow = cm.hardWrap({from: from, to: to}); + if (endRow > from && operatorArgs.linewise) endRow--; + return operatorArgs.keepCursor ? oldAnchor : new Pos(endRow, 0); + }, + changeCase: function(cm, args, ranges, oldAnchor, newHead) { + var selections = cm.getSelections(); + var swapped = []; + var toLower = args.toLower; + for (var j = 0; j < selections.length; j++) { + var toSwap = selections[j]; + var text = ''; + if (toLower === true) { + text = toSwap.toLowerCase(); + } else if (toLower === false) { + text = toSwap.toUpperCase(); + } else { + for (var i = 0; i < toSwap.length; i++) { + var character = toSwap.charAt(i); + text += isUpperCase(character) ? character.toLowerCase() : + character.toUpperCase(); + } + } + swapped.push(text); + } + cm.replaceSelections(swapped); + if (args.shouldMoveCursor){ + return newHead; + } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) { + return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor); + } else if (args.linewise){ + return oldAnchor; + } else { + return cursorMin(ranges[0].anchor, ranges[0].head); + } + }, + yank: function(cm, args, ranges, oldAnchor) { + var vim = cm.state.vim; + var text = cm.getSelection(); + var endPos = vim.visualMode + ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor) + : oldAnchor; + vimGlobalState.registerController.pushText( + args.registerName, 'yank', + text, args.linewise, vim.visualBlock); + return endPos; + } + }; + + /** @arg {string} name @arg {import("./types").OperatorFn} fn */ + function defineOperator(name, fn) { + operators[name] = fn; + } + + /** @type {import("./types").vimActions} */ + var actions = { + jumpListWalk: function(cm, actionArgs, vim) { + if (vim.visualMode) { + return; + } + var repeat = actionArgs.repeat || 1; + var forward = actionArgs.forward; + var jumpList = vimGlobalState.jumpList; + + var mark = jumpList.move(cm, forward ? repeat : -repeat); + var markPos = mark ? mark.find() : undefined; + markPos = markPos ? markPos : cm.getCursor(); + cm.setCursor(markPos); + }, + scroll: function(cm, actionArgs, vim) { + if (vim.visualMode) { + return; + } + var repeat = actionArgs.repeat || 1; + var lineHeight = cm.defaultTextHeight(); + var top = cm.getScrollInfo().top; + var delta = lineHeight * repeat; + var newPos = actionArgs.forward ? top + delta : top - delta; + var cursor = copyCursor(cm.getCursor()); + var cursorCoords = cm.charCoords(cursor, 'local'); + if (actionArgs.forward) { + if (newPos > cursorCoords.top) { + cursor.line += (newPos - cursorCoords.top) / lineHeight; + cursor.line = Math.ceil(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, 'local'); + cm.scrollTo(null, cursorCoords.top); + } else { + // Cursor stays within bounds. Just reposition the scroll window. + cm.scrollTo(null, newPos); + } + } else { + var newBottom = newPos + cm.getScrollInfo().clientHeight; + if (newBottom < cursorCoords.bottom) { + cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight; + cursor.line = Math.floor(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, 'local'); + cm.scrollTo( + null, cursorCoords.bottom - cm.getScrollInfo().clientHeight); + } else { + // Cursor stays within bounds. Just reposition the scroll window. + cm.scrollTo(null, newPos); + } + } + }, + scrollToCursor: function(cm, actionArgs) { + var lineNum = cm.getCursor().line; + var charCoords = cm.charCoords(new Pos(lineNum, 0), 'local'); + var height = cm.getScrollInfo().clientHeight; + var y = charCoords.top; + switch (actionArgs.position) { + case 'center': y = charCoords.bottom - height / 2; + break; + case 'bottom': + var lineLastCharPos = new Pos(lineNum, cm.getLine(lineNum).length - 1); + var lineLastCharCoords = cm.charCoords(lineLastCharPos, 'local'); + var lineHeight = lineLastCharCoords.bottom - y; + y = y - height + lineHeight; + break; + } + cm.scrollTo(null, y); + }, + replayMacro: function(cm, actionArgs, vim) { + var registerName = actionArgs.selectedCharacter || ""; + var repeat = actionArgs.repeat || 1; + var macroModeState = vimGlobalState.macroModeState; + if (registerName == '@') { + registerName = macroModeState.latestRegister; + } else { + macroModeState.latestRegister = registerName; + } + while(repeat--){ + executeMacroRegister(cm, vim, macroModeState, registerName); + } + }, + enterMacroRecordMode: function(cm, actionArgs) { + var macroModeState = vimGlobalState.macroModeState; + var registerName = actionArgs.selectedCharacter; + if (vimGlobalState.registerController.isValidRegister(registerName)) { + macroModeState.enterMacroRecordMode(cm, registerName); + } + }, + toggleOverwrite: function(cm) { + if (!cm.state.overwrite) { + cm.toggleOverwrite(true); + cm.setOption('keyMap', 'vim-replace'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"}); + } else { + cm.toggleOverwrite(false); + cm.setOption('keyMap', 'vim-insert'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"}); + } + }, + enterInsertMode: function(cm, actionArgs, vim) { + if (cm.getOption('readOnly')) { return; } + vim.insertMode = true; + vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1; + var insertAt = (actionArgs) ? actionArgs.insertAt : null; + var sel = vim.sel; + var head = actionArgs.head || cm.getCursor('head'); + var height = cm.listSelections().length; + if (insertAt == 'eol') { + head = new Pos(head.line, lineLength(cm, head.line)); + } else if (insertAt == 'bol') { + head = new Pos(head.line, 0); + } else if (insertAt == 'charAfter') { + var newPosition = updateSelectionForSurrogateCharacters(cm, head, offsetCursor(head, 0, 1)); + head = newPosition.end; + } else if (insertAt == 'firstNonBlank') { + var newPosition = updateSelectionForSurrogateCharacters(cm, head, motions.moveToFirstNonWhiteSpaceCharacter(cm, head)); + head = newPosition.end; + } else if (insertAt == 'startOfSelectedArea') { + if (!vim.visualMode) + return; + if (!vim.visualBlock) { + if (sel.head.line < sel.anchor.line) { + head = sel.head; + } else { + head = new Pos(sel.anchor.line, 0); + } + } else { + head = new Pos( + Math.min(sel.head.line, sel.anchor.line), + Math.min(sel.head.ch, sel.anchor.ch)); + height = Math.abs(sel.head.line - sel.anchor.line) + 1; + } + } else if (insertAt == 'endOfSelectedArea') { + if (!vim.visualMode) + return; + if (!vim.visualBlock) { + if (sel.head.line >= sel.anchor.line) { + head = offsetCursor(sel.head, 0, 1); + } else { + head = new Pos(sel.anchor.line, 0); + } + } else { + head = new Pos( + Math.min(sel.head.line, sel.anchor.line), + Math.max(sel.head.ch, sel.anchor.ch) + 1); + height = Math.abs(sel.head.line - sel.anchor.line) + 1; + } + } else if (insertAt == 'inplace') { + if (vim.visualMode){ + return; + } + } else if (insertAt == 'lastEdit') { + head = getLastEditPos(cm) || head; + } + cm.setOption('disableInput', false); + if (actionArgs && actionArgs.replace) { + // Handle Replace-mode as a special case of insert mode. + cm.toggleOverwrite(true); + cm.setOption('keyMap', 'vim-replace'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"}); + } else { + cm.toggleOverwrite(false); + cm.setOption('keyMap', 'vim-insert'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"}); + } + if (!vimGlobalState.macroModeState.isPlaying) { + // Only record if not replaying. + cm.on('change', onChange); + if (vim.insertEnd) vim.insertEnd.clear(); + vim.insertEnd = cm.setBookmark(head, {insertLeft: true}); + CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); + } + if (vim.visualMode) { + exitVisualMode(cm); + } + selectForInsert(cm, head, height); + }, + toggleVisualMode: function(cm, actionArgs, vim) { + var repeat = actionArgs.repeat; + var anchor = cm.getCursor(); + var head; + // TODO: The repeat should actually select number of characters/lines + // equal to the repeat times the size of the previous visual + // operation. + if (!vim.visualMode) { + // Entering visual mode + vim.visualMode = true; + vim.visualLine = !!actionArgs.linewise; + vim.visualBlock = !!actionArgs.blockwise; + head = clipCursorToContent( + cm, new Pos(anchor.line, anchor.ch + repeat - 1)); + var newPosition = updateSelectionForSurrogateCharacters(cm, anchor, head); + vim.sel = { + anchor: newPosition.start, + head: newPosition.end + }; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""}); + updateCmSelection(cm); + updateMark(cm, vim, '<', cursorMin(anchor, head)); + updateMark(cm, vim, '>', cursorMax(anchor, head)); + } else if (vim.visualLine != !!actionArgs.linewise || + vim.visualBlock != !!actionArgs.blockwise) { + // Toggling between modes + vim.visualLine = !!actionArgs.linewise; + vim.visualBlock = !!actionArgs.blockwise; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""}); + updateCmSelection(cm); + } else { + exitVisualMode(cm); + } + }, + reselectLastSelection: function(cm, _actionArgs, vim) { + var lastSelection = vim.lastSelection; + if (vim.visualMode) { + updateLastSelection(cm, vim); + } + if (lastSelection) { + var anchor = lastSelection.anchorMark.find(); + var head = lastSelection.headMark.find(); + if (!anchor || !head) { + // If the marks have been destroyed due to edits, do nothing. + return; + } + vim.sel = { + anchor: anchor, + head: head + }; + vim.visualMode = true; + vim.visualLine = lastSelection.visualLine; + vim.visualBlock = lastSelection.visualBlock; + updateCmSelection(cm); + updateMark(cm, vim, '<', cursorMin(anchor, head)); + updateMark(cm, vim, '>', cursorMax(anchor, head)); + CodeMirror.signal(cm, 'vim-mode-change', { + mode: 'visual', + subMode: vim.visualLine ? 'linewise' : + vim.visualBlock ? 'blockwise' : ''}); + } + }, + joinLines: function(cm, actionArgs, vim) { + var curStart, curEnd; + if (vim.visualMode) { + curStart = cm.getCursor('anchor'); + curEnd = cm.getCursor('head'); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curEnd; + curEnd = curStart; + curStart = tmp; + } + curEnd.ch = lineLength(cm, curEnd.line) - 1; + } else { + // Repeat is the number of lines to join. Minimum 2 lines. + var repeat = Math.max(actionArgs.repeat, 2); + curStart = cm.getCursor(); + curEnd = clipCursorToContent(cm, new Pos(curStart.line + repeat - 1, + Infinity)); + } + var finalCh = 0; + for (var i = curStart.line; i < curEnd.line; i++) { + finalCh = lineLength(cm, curStart.line); + var text = ''; + var nextStartCh = 0; + if (!actionArgs.keepSpaces) { + var nextLine = cm.getLine(curStart.line + 1); + nextStartCh = nextLine.search(/\S/); + if (nextStartCh == -1) { + nextStartCh = nextLine.length; + } else { + text = " "; + } + } + cm.replaceRange(text, + new Pos(curStart.line, finalCh), + new Pos(curStart.line + 1, nextStartCh)); + } + var curFinalPos = clipCursorToContent(cm, new Pos(curStart.line, finalCh)); + if (vim.visualMode) { + exitVisualMode(cm, false); + } + cm.setCursor(curFinalPos); + }, + newLineAndEnterInsertMode: function(cm, actionArgs, vim) { + vim.insertMode = true; + var insertAt = copyCursor(cm.getCursor()); + if (insertAt.line === cm.firstLine() && !actionArgs.after) { + // Special case for inserting newline before start of document. + cm.replaceRange('\n', new Pos(cm.firstLine(), 0)); + cm.setCursor(cm.firstLine(), 0); + } else { + insertAt.line = (actionArgs.after) ? insertAt.line : + insertAt.line - 1; + insertAt.ch = lineLength(cm, insertAt.line); + cm.setCursor(insertAt); + var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment || + CodeMirror.commands.newlineAndIndent; + newlineFn(cm); + } + this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim); + }, + paste: function(cm, actionArgs, vim) { + var register = vimGlobalState.registerController.getRegister( + actionArgs.registerName); + if (actionArgs.registerName === '+') { + navigator.clipboard.readText().then((value) => { + this.continuePaste(cm, actionArgs, vim, value, register); + }); + } else { + var text = register.toString(); + this.continuePaste(cm, actionArgs, vim, text, register); + } + }, + continuePaste: function(cm, actionArgs, vim, text, register) { + var cur = copyCursor(cm.getCursor()); + if (!text) { + return; + } + if (actionArgs.matchIndent) { + var tabSize = cm.getOption("tabSize"); + // length that considers tabs and tabSize + var whitespaceLength = function(/** @type {string} */ str) { + var tabs = (str.split("\t").length - 1); + var spaces = (str.split(" ").length - 1); + return tabs * tabSize + spaces * 1; + }; + var currentLine = cm.getLine(cm.getCursor().line); + // @ts-ignore + var indent = whitespaceLength(currentLine.match(/^\s*/)[0]); + // chomp last newline b/c don't want it to match /^\s*/gm + var chompedText = text.replace(/\n$/, ''); + var wasChomped = text !== chompedText; + // @ts-ignore + var firstIndent = whitespaceLength(text.match(/^\s*/)[0]); + var text = chompedText.replace(/^\s*/gm, function(wspace) { + var newIndent = indent + (whitespaceLength(wspace) - firstIndent); + if (newIndent < 0) { + return ""; + } + else if (cm.getOption("indentWithTabs")) { + var quotient = Math.floor(newIndent / tabSize); + return Array(quotient + 1).join('\t'); + } + else { + return Array(newIndent + 1).join(' '); + } + }); + text += wasChomped ? "\n" : ""; + } + if (actionArgs.repeat > 1) { + var text = Array(actionArgs.repeat + 1).join(text); + } + var linewise = register.linewise; + var blockwise = register.blockwise; + if (blockwise) { + // @ts-ignore + text = text.split('\n'); + if (linewise) { + // @ts-ignore + text.pop(); + } + for (var i = 0; i < text.length; i++) { + // @ts-ignore + text[i] = (text[i] == '') ? ' ' : text[i]; + } + cur.ch += actionArgs.after ? 1 : 0; + cur.ch = Math.min(lineLength(cm, cur.line), cur.ch); + } else if (linewise) { + if(vim.visualMode) { + text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n'; + } else if (actionArgs.after) { + // Move the newline at the end to the start instead, and paste just + // before the newline character of the line we are on right now. + text = '\n' + text.slice(0, text.length - 1); + cur.ch = lineLength(cm, cur.line); + } else { + cur.ch = 0; + } + } else { + cur.ch += actionArgs.after ? 1 : 0; + } + var curPosFinal; + if (vim.visualMode) { + // save the pasted text for reselection if the need arises + vim.lastPastedText = text; + var lastSelectionCurEnd; + var selectedArea = getSelectedAreaRange(cm, vim); + var selectionStart = selectedArea[0]; + var selectionEnd = selectedArea[1]; + var selectedText = cm.getSelection(); + var selections = cm.listSelections(); + var emptyStrings = new Array(selections.length).join('1').split('1'); + // save the curEnd marker before it get cleared due to cm.replaceRange. + if (vim.lastSelection) { + lastSelectionCurEnd = vim.lastSelection.headMark.find(); + } + // push the previously selected text to unnamed register + vimGlobalState.registerController.unnamedRegister.setText(selectedText); + if (blockwise) { + // first delete the selected text + cm.replaceSelections(emptyStrings); + // Set new selections as per the block length of the yanked text + selectionEnd = new Pos(selectionStart.line + text.length-1, selectionStart.ch); + cm.setCursor(selectionStart); + selectBlock(cm, selectionEnd); + // @ts-ignore + cm.replaceSelections(text); + curPosFinal = selectionStart; + } else if (vim.visualBlock) { + cm.replaceSelections(emptyStrings); + cm.setCursor(selectionStart); + cm.replaceRange(text, selectionStart, selectionStart); + curPosFinal = selectionStart; + } else { + cm.replaceRange(text, selectionStart, selectionEnd); + curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1); + } + // restore the curEnd marker + if(lastSelectionCurEnd) { + vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd); + } + if (linewise) { + curPosFinal.ch=0; + } + } else { + if (blockwise) { + cm.setCursor(cur); + for (var i = 0; i < text.length; i++) { + var line = cur.line+i; + if (line > cm.lastLine()) { + cm.replaceRange('\n', new Pos(line, 0)); + } + var lastCh = lineLength(cm, line); + if (lastCh < cur.ch) { + extendLineToColumn(cm, line, cur.ch); + } + } + cm.setCursor(cur); + selectBlock(cm, new Pos(cur.line + text.length-1, cur.ch)); + // @ts-ignore + cm.replaceSelections(text); + curPosFinal = cur; + } else { + cm.replaceRange(text, cur); + // Now fine tune the cursor to where we want it. + if (linewise) { + var line = actionArgs.after ? cur.line + 1 : cur.line; + curPosFinal = new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + } else { + curPosFinal = copyCursor(cur); + if (!/\n/.test(text)) { + curPosFinal.ch += text.length - (actionArgs.after ? 1 : 0); + } + } + } + } + if (vim.visualMode) { + exitVisualMode(cm, false); + } + cm.setCursor(curPosFinal); + }, + undo: function(cm, actionArgs) { + cm.operation(function() { + repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)(); + cm.setCursor(clipCursorToContent(cm, cm.getCursor('start'))); + }); + }, + redo: function(cm, actionArgs) { + repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)(); + }, + setRegister: function(_cm, actionArgs, vim) { + vim.inputState.registerName = actionArgs.selectedCharacter; + }, + insertRegister: function(cm, actionArgs, vim) { + var registerName = actionArgs.selectedCharacter; + var register = vimGlobalState.registerController.getRegister(registerName); + var text = register && register.toString(); + if (text) { + cm.replaceSelection(text); + } + }, + oneNormalCommand: function(cm, actionArgs, vim) { + exitInsertMode(cm, true); + vim.insertModeReturn = true; + CodeMirror.on(cm, 'vim-command-done', function handler() { + if (vim.visualMode) return; + if (vim.insertModeReturn) { + vim.insertModeReturn = false; + if (!vim.insertMode) { + actions.enterInsertMode(cm, {}, vim); + } + } + CodeMirror.off(cm, 'vim-command-done', handler); + }); + }, + setMark: function(cm, actionArgs, vim) { + var markName = actionArgs.selectedCharacter; + if (markName) updateMark(cm, vim, markName, cm.getCursor()); + }, + replace: function(cm, actionArgs, vim) { + var replaceWith = actionArgs.selectedCharacter || ""; + var curStart = cm.getCursor(); + var replaceTo; + var curEnd; + var selections = cm.listSelections(); + if (vim.visualMode) { + curStart = cm.getCursor('start'); + curEnd = cm.getCursor('end'); + } else { + var line = cm.getLine(curStart.line); + replaceTo = curStart.ch + actionArgs.repeat; + if (replaceTo > line.length) { + replaceTo=line.length; + } + curEnd = new Pos(curStart.line, replaceTo); + } + + var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd); + curStart = newPositions.start; + curEnd = newPositions.end; + if (replaceWith=='\n') { + if (!vim.visualMode) cm.replaceRange('', curStart, curEnd); + // special case, where vim help says to replace by just one line-break + (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm); + } else { + var replaceWithStr = cm.getRange(curStart, curEnd); + // replace all surrogate characters with selected character + replaceWithStr = replaceWithStr.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, replaceWith); + //replace all characters in range by selected, but keep linebreaks + replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith); + if (vim.visualBlock) { + // Tabs are split in visua block before replacing + var spaces = new Array(cm.getOption("tabSize")+1).join(' '); + replaceWithStr = cm.getSelection(); + replaceWithStr = replaceWithStr.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, replaceWith); + var replaceWithStrings = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n'); + cm.replaceSelections(replaceWithStrings); + } else { + cm.replaceRange(replaceWithStr, curStart, curEnd); + } + if (vim.visualMode) { + curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ? + selections[0].anchor : selections[0].head; + cm.setCursor(curStart); + exitVisualMode(cm, false); + } else { + cm.setCursor(offsetCursor(curEnd, 0, -1)); + } + } + }, + incrementNumberToken: function(cm, actionArgs) { + var cur = cm.getCursor(); + var lineStr = cm.getLine(cur.line); + var re = /(-?)(?:(0x)([\da-f]+)|(0b|0|)(\d+))/gi; + var match; + var start; + var end; + var numberStr; + while ((match = re.exec(lineStr)) !== null) { + start = match.index; + end = start + match[0].length; + if (cur.ch < end)break; + } + // @ts-ignore + if (!actionArgs.backtrack && (end <= cur.ch))return; + if (match) { + var baseStr = match[2] || match[4]; + var digits = match[3] || match[5]; + var increment = actionArgs.increase ? 1 : -1; + var base = {'0b': 2, '0': 8, '': 10, '0x': 16}[baseStr.toLowerCase()]; + var number = parseInt(match[1] + digits, base) + (increment * actionArgs.repeat); + numberStr = number.toString(base); + var zeroPadding = baseStr ? new Array(digits.length - numberStr.length + 1 + match[1].length).join('0') : ''; + if (numberStr.charAt(0) === '-') { + numberStr = '-' + baseStr + zeroPadding + numberStr.substr(1); + } else { + numberStr = baseStr + zeroPadding + numberStr; + } + // @ts-ignore + var from = new Pos(cur.line, start); + // @ts-ignore + var to = new Pos(cur.line, end); + cm.replaceRange(numberStr, from, to); + } else { + return; + } + // @ts-ignore + cm.setCursor(new Pos(cur.line, start + numberStr.length - 1)); + }, + repeatLastEdit: function(cm, actionArgs, vim) { + var lastEditInputState = vim.lastEditInputState; + if (!lastEditInputState) { return; } + var repeat = actionArgs.repeat; + if (repeat && actionArgs.repeatIsExplicit) { + lastEditInputState.repeatOverride = repeat; + } else { + repeat = lastEditInputState.repeatOverride || repeat; + } + repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */); + }, + indent: function(cm, actionArgs) { + cm.indentLine(cm.getCursor().line, actionArgs.indentRight); + }, + exitInsertMode: function(cm, actionArgs) { + exitInsertMode(cm); + } + }; + + /** @arg {string } name @arg {import("./types").ActionFn} fn */ + function defineAction(name, fn) { + actions[name] = fn; + } + + /* + * Below are miscellaneous utility functions used by vim.js + */ + + /** + * Clips cursor to ensure that line is within the buffer's range + * and is not inside surrogate pair + * If includeLineBreak is true, then allow cur.ch == lineLength. + * @arg {CodeMirrorV} cm + * @arg {Pos} cur + * @arg {Pos} [oldCur] + * @return {Pos} + */ + function clipCursorToContent(cm, cur, oldCur) { + var vim = cm.state.vim; + var includeLineBreak = vim.insertMode || vim.visualMode; + var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() ); + var text = cm.getLine(line); + var maxCh = text.length - 1 + Number(!!includeLineBreak); + var ch = Math.min(Math.max(0, cur.ch), maxCh); + // prevent cursor from entering surrogate pair + var charCode = text.charCodeAt(ch); + if (0xDC00 <= charCode && charCode <= 0xDFFF) { + var direction = 1; + if (oldCur && oldCur.line == line && oldCur.ch > ch) { + direction = -1; + } + ch +=direction; + if (ch > maxCh) ch -=2; + } + return new Pos(line, ch); + } + function copyArgs(args) { + var ret = {}; + for (var prop in args) { + if (args.hasOwnProperty(prop)) { + ret[prop] = args[prop]; + } + } + return ret; + } + function offsetCursor(cur, offsetLine, offsetCh) { + if (typeof offsetLine === 'object') { + offsetCh = offsetLine.ch; + offsetLine = offsetLine.line; + } + return new Pos(cur.line + offsetLine, cur.ch + offsetCh); + } + function commandMatches(keys, keyMap, context, inputState) { + // Partial matches are not applied. They inform the key handler + // that the current key sequence is a subsequence of a valid key + // sequence, so that the key buffer is not cleared. + if (inputState.operator) context = "operatorPending"; + var match, partial = [], full = []; + // if currently expanded key comes from a noremap, searcg only in default keys + var startIndex = noremap ? keyMap.length - defaultKeymapLength : 0; + for (var i = startIndex; i < keyMap.length; i++) { + var command = keyMap[i]; + if (context == 'insert' && command.context != 'insert' || + (command.context && command.context != context) || + inputState.operator && command.type == 'action' || + !(match = commandMatch(keys, command.keys))) { continue; } + if (match == 'partial') { partial.push(command); } + if (match == 'full') { full.push(command); } + } + return { + partial: partial.length && partial, + full: full.length && full + }; + } + /** @arg {string} pressed @arg {string} mapped @return {'full'|'partial'|false}*/ + function commandMatch(pressed, mapped) { + const isLastCharacter = mapped.slice(-11) == ''; + const isLastRegister = mapped.slice(-10) == ''; + if (isLastCharacter || isLastRegister) { + // Last character matches anything. + var prefixLen = mapped.length - (isLastCharacter ? 11 : 10); + var pressedPrefix = pressed.slice(0, prefixLen); + var mappedPrefix = mapped.slice(0, prefixLen); + return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' : + mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false; + } else { + return pressed == mapped ? 'full' : + mapped.indexOf(pressed) == 0 ? 'partial' : false; + } + } + /** @arg {string} keys */ + function lastChar(keys) { + var match = /^.*(<[^>]+>)$/.exec(keys); + var selectedCharacter = match ? match[1] : keys.slice(-1); + if (selectedCharacter.length > 1){ + switch(selectedCharacter){ + case '': + case '': + selectedCharacter='\n'; + break; + case '': + case '': + selectedCharacter=' '; + break; + default: + selectedCharacter=''; + break; + } + } + return selectedCharacter; + } + /** @arg {CodeMirror} cm @arg {{ (cm: CodeMirror): void }} fn @arg {number} repeat */ + function repeatFn(cm, fn, repeat) { + return function() { + for (var i = 0; i < repeat; i++) { + fn(cm); + } + }; + } + /** @arg {Pos} cur @return {Pos}*/ + function copyCursor(cur) { + return new Pos(cur.line, cur.ch); + } + /** @arg {Pos} cur1 @arg {Pos} cur2 @return {boolean} */ + function cursorEqual(cur1, cur2) { + return cur1.ch == cur2.ch && cur1.line == cur2.line; + } + /** @arg {Pos} cur1 @arg {Pos} cur2 @return {boolean}*/ + function cursorIsBefore(cur1, cur2) { + if (cur1.line < cur2.line) { + return true; + } + if (cur1.line == cur2.line && cur1.ch < cur2.ch) { + return true; + } + return false; + } + /** @arg {Pos} cur1 @arg {Pos} cur2 @return {Pos}*/ + function cursorMin(cur1, cur2) { + if (arguments.length > 2) { + // @ts-ignore + cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1)); + } + return cursorIsBefore(cur1, cur2) ? cur1 : cur2; + } + /** @arg {Pos} cur1 @arg {Pos} cur2 @return {Pos} */ + function cursorMax(cur1, cur2) { + if (arguments.length > 2) { + // @ts-ignore + cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1)); + } + return cursorIsBefore(cur1, cur2) ? cur2 : cur1; + } + /** @arg {Pos} cur1 @arg {Pos} cur2 @arg {Pos} cur3 @return {boolean}*/ + function cursorIsBetween(cur1, cur2, cur3) { + // returns true if cur2 is between cur1 and cur3. + var cur1before2 = cursorIsBefore(cur1, cur2); + var cur2before3 = cursorIsBefore(cur2, cur3); + return cur1before2 && cur2before3; + } + /** @arg {CodeMirror} cm @arg {number} lineNum */ + function lineLength(cm, lineNum) { + return cm.getLine(lineNum).length; + } + /** @arg {string} s */ + function trim(s) { + if (s.trim) { + return s.trim(); + } + return s.replace(/^\s+|\s+$/g, ''); + } + /** @arg {string} s */ + function escapeRegex(s) { + return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1'); + } + /** @arg {CodeMirror} cm @arg {number} lineNum @arg {number} column */ + function extendLineToColumn(cm, lineNum, column) { + var endCh = lineLength(cm, lineNum); + var spaces = new Array(column-endCh+1).join(' '); + cm.setCursor(new Pos(lineNum, endCh)); + cm.replaceRange(spaces, cm.getCursor()); + } + // This functions selects a rectangular block + // of text with selectionEnd as any of its corner + // Height of block: + // Difference in selectionEnd.line and first/last selection.line + // Width of the block: + // Distance between selectionEnd.ch and any(first considered here) selection.ch + /** @arg {CodeMirror} cm @arg {Pos} selectionEnd */ + function selectBlock(cm, selectionEnd) { + var selections = [], ranges = cm.listSelections(); + var head = copyCursor(cm.clipPos(selectionEnd)); + var isClipped = !cursorEqual(selectionEnd, head); + var curHead = cm.getCursor('head'); + var primIndex = getIndex(ranges, curHead); + var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor); + var max = ranges.length - 1; + var index = max - primIndex > primIndex ? max : 0; + var base = ranges[index].anchor; + + var firstLine = Math.min(base.line, head.line); + var lastLine = Math.max(base.line, head.line); + var baseCh = base.ch, headCh = head.ch; + + var dir = ranges[index].head.ch - baseCh; + var newDir = headCh - baseCh; + if (dir > 0 && newDir <= 0) { + baseCh++; + if (!isClipped) { headCh--; } + } else if (dir < 0 && newDir >= 0) { + baseCh--; + if (!wasClipped) { headCh++; } + } else if (dir < 0 && newDir == -1) { + baseCh--; + headCh++; + } + for (var line = firstLine; line <= lastLine; line++) { + var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)}; + selections.push(range); + } + cm.setSelections(selections); + selectionEnd.ch = headCh; + base.ch = baseCh; + return base; + } + /** @arg {CodeMirror} cm @arg {any} head @arg {number} height */ + function selectForInsert(cm, head, height) { + var sel = []; + for (var i = 0; i < height; i++) { + var lineHead = offsetCursor(head, i, 0); + sel.push({anchor: lineHead, head: lineHead}); + } + cm.setSelections(sel, 0); + } + // getIndex returns the index of the cursor in the selections. + /** @arg {string | any[]} ranges @arg {any} cursor @arg {string | undefined} [end] */ + function getIndex(ranges, cursor, end) { + for (var i = 0; i < ranges.length; i++) { + var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor); + var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor); + if (atAnchor || atHead) { + return i; + } + } + return -1; + } + /** @arg {CodeMirror} cm @arg {vimState} vim */ + function getSelectedAreaRange(cm, vim) { + var lastSelection = vim.lastSelection; + /** @return {[Pos,Pos]} */ + var getCurrentSelectedAreaRange = function() { + var selections = cm.listSelections(); + var start = selections[0]; + var end = selections[selections.length-1]; + var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head; + var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; + return [selectionStart, selectionEnd]; + }; + var getLastSelectedAreaRange = function() { + var selectionStart = cm.getCursor(); + var selectionEnd = cm.getCursor(); + var block = lastSelection.visualBlock; + if (block) { + var width = block.width; + var height = block.height; + selectionEnd = new Pos(selectionStart.line + height, selectionStart.ch + width); + var selections = []; + // selectBlock creates a 'proper' rectangular block. + // We do not want that in all cases, so we manually set selections. + for (var i = selectionStart.line; i < selectionEnd.line; i++) { + var anchor = new Pos(i, selectionStart.ch); + var head = new Pos(i, selectionEnd.ch); + var range = {anchor: anchor, head: head}; + selections.push(range); + } + cm.setSelections(selections); + } else { + var start = lastSelection.anchorMark.find(); + var end = lastSelection.headMark.find(); + var line = end.line - start.line; + var ch = end.ch - start.ch; + selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch}; + if (lastSelection.visualLine) { + selectionStart = new Pos(selectionStart.line, 0); + selectionEnd = new Pos(selectionEnd.line, lineLength(cm, selectionEnd.line)); + } + cm.setSelection(selectionStart, selectionEnd); + } + return [selectionStart, selectionEnd]; + }; + if (!vim.visualMode) { + // In case of replaying the action. + return getLastSelectedAreaRange(); + } else { + return getCurrentSelectedAreaRange(); + } + } + // Updates the previous selection with the current selection's values. This + // should only be called in visual mode. + /** @arg {CodeMirror} cm @arg {vimState} vim */ + function updateLastSelection(cm, vim) { + var anchor = vim.sel.anchor; + var head = vim.sel.head; + // To accommodate the effect of lastPastedText in the last selection + if (vim.lastPastedText) { + head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length); + vim.lastPastedText = null; + } + vim.lastSelection = {'anchorMark': cm.setBookmark(anchor), + 'headMark': cm.setBookmark(head), + 'anchor': copyCursor(anchor), + 'head': copyCursor(head), + 'visualMode': vim.visualMode, + 'visualLine': vim.visualLine, + 'visualBlock': vim.visualBlock}; + } + /** @arg {CodeMirrorV} cm @arg {Pos} start @arg {Pos} end @returns {[Pos, Pos]} */ + function expandSelection(cm, start, end, move) { + var sel = cm.state.vim.sel; + var head = move ? start: sel.head; + var anchor = move ? start: sel.anchor; + var tmp; + if (cursorIsBefore(end, start)) { + tmp = end; + end = start; + start = tmp; + } + if (cursorIsBefore(head, anchor)) { + head = cursorMin(start, head); + anchor = cursorMax(anchor, end); + } else { + anchor = cursorMin(start, anchor); + head = cursorMax(head, end); + head = offsetCursor(head, 0, -1); + if (head.ch == -1 && head.line != cm.firstLine()) { + head = new Pos(head.line - 1, lineLength(cm, head.line - 1)); + } + } + return [anchor, head]; + } + /** + * Updates the CodeMirror selection to match the provided vim selection. + * If no arguments are given, it uses the current vim selection state. + * @arg {CodeMirrorV} cm + * @arg {vimState["sel"]} [sel] + * @arg {"char"|"line"|"block" | undefined} [mode] + */ + function updateCmSelection(cm, sel, mode) { + var vim = cm.state.vim; + sel = sel || vim.sel; + if (!mode) { + mode = vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char'; + } + var cmSel = makeCmSelection(cm, sel, mode); + cm.setSelections(cmSel.ranges, cmSel.primary); + } + /** + * @arg {CodeMirror} cm + * @arg {import("./types").CM5RangeInterface} sel + * @arg {"char"|"line"|"block"} mode + * @arg {boolean|undefined} [exclusive] + * @return {{ranges: any, primary: number}} + */ + function makeCmSelection(cm, sel, mode, exclusive) { + var head = copyCursor(sel.head); + var anchor = copyCursor(sel.anchor); + if (mode == 'char') { + var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; + var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; + head = offsetCursor(sel.head, 0, headOffset); + anchor = offsetCursor(sel.anchor, 0, anchorOffset); + return { + ranges: [{anchor: anchor, head: head}], + primary: 0 + }; + } else if (mode == 'line') { + if (!cursorIsBefore(sel.head, sel.anchor)) { + anchor.ch = 0; + + var lastLine = cm.lastLine(); + if (head.line > lastLine) { + head.line = lastLine; + } + head.ch = lineLength(cm, head.line); + } else { + head.ch = 0; + anchor.ch = lineLength(cm, anchor.line); + } + return { + ranges: [{anchor: anchor, head: head}], + primary: 0 + }; + } else if (mode == 'block') { + var top = Math.min(anchor.line, head.line), + fromCh = anchor.ch, + bottom = Math.max(anchor.line, head.line), + toCh = head.ch; + if (fromCh < toCh) { toCh += 1; } + else { fromCh += 1; } var height = bottom - top + 1; + var primary = head.line == top ? 0 : height - 1; + var ranges = []; + for (var i = 0; i < height; i++) { + ranges.push({ + anchor: new Pos(top + i, fromCh), + head: new Pos(top + i, toCh) + }); + } + return { + ranges: ranges, + primary: primary + }; + } + throw "never happens"; + } + /** @arg {CodeMirror} cm */ + function getHead(cm) { + var cur = cm.getCursor('head'); + if (cm.getSelection().length == 1) { + // Small corner case when only 1 character is selected. The "real" + // head is the left of head and anchor. + cur = cursorMin(cur, cm.getCursor('anchor')); + } + return cur; + } + + /** + * If moveHead is set to false, the CodeMirror selection will not be + * touched. The caller assumes the responsibility of putting the cursor + * in the right place. + * @arg {CodeMirrorV} cm + * @arg {boolean} [moveHead] + */ + function exitVisualMode(cm, moveHead) { + var vim = cm.state.vim; + if (moveHead !== false) { + cm.setCursor(clipCursorToContent(cm, vim.sel.head)); + } + updateLastSelection(cm, vim); + vim.visualMode = false; + vim.visualLine = false; + vim.visualBlock = false; + if (!vim.insertMode) CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + } + + /** + * Remove any trailing newlines from the selection. For + * example, with the caret at the start of the last word on the line, + * 'dw' should word, but not the newline, while 'w' should advance the + * caret to the first character of the next line. + * @arg {CodeMirror} cm + * @arg {Pos} curStart + * @arg {Pos} curEnd + */ + function clipToLine(cm, curStart, curEnd) { + var selection = cm.getRange(curStart, curEnd); + // Only clip if the selection ends with trailing newline + whitespace + if (/\n\s*$/.test(selection)) { + var lines = selection.split('\n'); + // We know this is all whitespace. + lines.pop(); + + // Cases: + // 1. Last word is an empty line - do not clip the trailing '\n' + // 2. Last word is not an empty line - clip the trailing '\n' + // Find the line containing the last word, and clip all whitespace up + // to it. + for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) { + curEnd.line--; + curEnd.ch = 0; + } + // If the last word is not an empty line, clip an additional newline + if (line) { + curEnd.line--; + curEnd.ch = lineLength(cm, curEnd.line); + } else { + curEnd.ch = 0; + } + } + } + + // Expand the selection to line ends. + /** @arg {CodeMirror} _cm @arg {Pos} curStart @arg {Pos} curEnd */ + function expandSelectionToLine(_cm, curStart, curEnd) { + curStart.ch = 0; + curEnd.ch = 0; + curEnd.line++; + } + + /** @arg {string} [text] */ + function findFirstNonWhiteSpaceCharacter(text) { + if (!text) { + return 0; + } + var firstNonWS = text.search(/\S/); + return firstNonWS == -1 ? text.length : firstNonWS; + } + + /** + * @arg {CodeMirror} cm + * @arg {{inclusive?: boolean, innerWord?: boolean, bigWord?: boolean, noSymbol?: boolean, multiline?: boolean}} options + * @arg {Pos} [cursor] + **/ + function expandWordUnderCursor(cm, {inclusive, innerWord, bigWord, noSymbol, multiline}, cursor) { + var cur = cursor || getHead(cm); + var line = cm.getLine(cur.line); + var endLine = line; + var startLineNumber = cur.line; + var endLineNumber = startLineNumber; + var idx = cur.ch; + + var wordOnNextLine; + // Seek to first word or non-whitespace character, depending on if + // noSymbol is true. + var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0]; + if (innerWord && /\s/.test(line.charAt(idx))) { + test = function(/** @type {string} */ ch) { return /\s/.test(ch); }; + } else { + while (!test(line.charAt(idx))) { + idx++; + if (idx >= line.length) { + if (!multiline) return null; + idx--; + wordOnNextLine = findWord(cm, cur, true, bigWord, true); + break + } + } + + if (bigWord) { + test = bigWordCharTest[0]; + } else { + test = wordCharTest[0]; + if (!test(line.charAt(idx))) { + test = wordCharTest[1]; + } + } + } + + var end = idx, start = idx; + while (test(line.charAt(start)) && start >= 0) { start--; } + start++; + if (wordOnNextLine) { + end = wordOnNextLine.to; + endLineNumber = wordOnNextLine.line; + endLine = cm.getLine(endLineNumber); + if (!endLine && end == 0) end++; + } else { + while (test(line.charAt(end)) && end < line.length) { end++; } + } + + if (inclusive) { + // If present, include all whitespace after word. + // Otherwise, include all whitespace before word, except indentation. + var wordEnd = end; + var startsWithSpace = cur.ch <= start && /\s/.test(line.charAt(cur.ch)); + if (!startsWithSpace) { + while (/\s/.test(endLine.charAt(end)) && end < endLine.length) { end++; } + } + if (wordEnd == end || startsWithSpace) { + var wordStart = start; + while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; } + if (!start && !startsWithSpace) { start = wordStart; } + } + } + + return { start: new Pos(startLineNumber, start), end: new Pos(endLineNumber, end) }; + } + + /** + * Depends on the following: + * + * - editor mode should be htmlmixedmode / xml + * - mode/xml/xml.js should be loaded + * - addon/fold/xml-fold.js should be loaded + * + * If any of the above requirements are not true, this function noops. + * + * This is _NOT_ a 100% accurate implementation of vim tag text objects. + * The following caveats apply (based off cursory testing, I'm sure there + * are other discrepancies): + * + * - Does not work inside comments: + * ``` + * + * ``` + * - Does not work when tags have different cases: + * ``` + *
broken
+ * ``` + * - Does not work when cursor is inside a broken tag: + * ``` + *
+ * ``` + * @arg {CodeMirror} cm + * @arg {Pos} head + * @arg {boolean} [inclusive] + */ + function expandTagUnderCursor(cm, head, inclusive) { + var cur = head; + if (!CodeMirror.findMatchingTag || !CodeMirror.findEnclosingTag) { + return { start: cur, end: cur }; + } + + var tags = CodeMirror.findMatchingTag(cm, head) || CodeMirror.findEnclosingTag(cm, head); + if (!tags || !tags.open || !tags.close) { + return { start: cur, end: cur }; + } + + if (inclusive) { + return { start: tags.open.from, end: tags.close.to }; + } + return { start: tags.open.to, end: tags.close.from }; + } + + /** @arg {CodeMirror} cm @arg {Pos} oldCur @arg {Pos} newCur */ + function recordJumpPosition(cm, oldCur, newCur) { + if (!cursorEqual(oldCur, newCur)) { + vimGlobalState.jumpList.add(cm, oldCur, newCur); + } + } + + /** @arg {number} increment @arg {{ forward?: any; selectedCharacter?: any; }} args */ + function recordLastCharacterSearch(increment, args) { + vimGlobalState.lastCharacterSearch.increment = increment; + vimGlobalState.lastCharacterSearch.forward = args.forward; + vimGlobalState.lastCharacterSearch.selectedCharacter = args.selectedCharacter; + } + + var symbolToMode = { + '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket', + '[': 'section', ']': 'section', + '*': 'comment', '/': 'comment', + 'm': 'method', 'M': 'method', + '#': 'preprocess' + }; + var findSymbolModes = { + bracket: { + isComplete: function(state) { + if (state.nextCh === state.symb) { + state.depth++; + if (state.depth >= 1)return true; + } else if (state.nextCh === state.reverseSymb) { + state.depth--; + } + return false; + } + }, + section: { + init: function(state) { + state.curMoveThrough = true; + state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}'; + }, + isComplete: function(state) { + return state.index === 0 && state.nextCh === state.symb; + } + }, + comment: { + isComplete: function(state) { + var found = state.lastCh === '*' && state.nextCh === '/'; + state.lastCh = state.nextCh; + return found; + } + }, + // TODO: The original Vim implementation only operates on level 1 and 2. + // The current implementation doesn't check for code block level and + // therefore it operates on any levels. + method: { + init: function(state) { + state.symb = (state.symb === 'm' ? '{' : '}'); + state.reverseSymb = state.symb === '{' ? '}' : '{'; + }, + isComplete: function(state) { + if (state.nextCh === state.symb)return true; + return false; + } + }, + preprocess: { + init: function(state) { + state.index = 0; + }, + isComplete: function(state) { + if (state.nextCh === '#') { + var token = state.lineText.match(/^#(\w+)/)[1]; + if (token === 'endif') { + if (state.forward && state.depth === 0) { + return true; + } + state.depth++; + } else if (token === 'if') { + if (!state.forward && state.depth === 0) { + return true; + } + state.depth--; + } + if (token === 'else' && state.depth === 0)return true; + } + return false; + } + } + }; + /** @arg {CodeMirrorV} cm @arg {number} repeat @arg {boolean|undefined} forward @arg {string} symb */ + function findSymbol(cm, repeat, forward, symb) { + var cur = copyCursor(cm.getCursor()); + var increment = forward ? 1 : -1; + var endLine = forward ? cm.lineCount() : -1; + var curCh = cur.ch; + var line = cur.line; + var lineText = cm.getLine(line); + var state = { + lineText: lineText, + nextCh: lineText.charAt(curCh), + lastCh: null, + index: curCh, + symb: symb, + reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb], + forward: forward, + depth: 0, + curMoveThrough: false + }; + var mode = symbolToMode[symb]; + if (!mode)return cur; + var init = findSymbolModes[mode].init; + var isComplete = findSymbolModes[mode].isComplete; + if (init) { init(state); } + while (line !== endLine && repeat) { + state.index += increment; + state.nextCh = state.lineText.charAt(state.index); + if (!state.nextCh) { + line += increment; + state.lineText = cm.getLine(line) || ''; + if (increment > 0) { + state.index = 0; + } else { + var lineLen = state.lineText.length; + state.index = (lineLen > 0) ? (lineLen-1) : 0; + } + state.nextCh = state.lineText.charAt(state.index); + } + if (isComplete(state)) { + cur.line = line; + cur.ch = state.index; + repeat--; + } + } + if (state.nextCh || state.curMoveThrough) { + return new Pos(line, state.index); + } + return cur; + } + + /* + * Returns the boundaries of the next word. If the cursor in the middle of + * the word, then returns the boundaries of the current word, starting at + * the cursor. If the cursor is at the start/end of a word, and we are going + * forward/backward, respectively, find the boundaries of the next word. + * + * @arg {CodeMirror} cm CodeMirror object. + * @arg {Cursor} cur The cursor position. + * @arg {boolean} forward True to search forward. False to search + * backward. + * @arg {boolean} bigWord True if punctuation count as part of the word. + * False if only [a-zA-Z0-9] characters count as part of the word. + * @arg {boolean} emptyLineIsWord True if empty lines should be treated + * as words. + * @return {Object{from:number, to:number, line: number}} The boundaries of + * the word, or null if there are no more words. + */ + function findWord(cm, cur, forward, bigWord, emptyLineIsWord) { + var lineNum = cur.line; + var pos = cur.ch; + var line = cm.getLine(lineNum); + var dir = forward ? 1 : -1; + var charTests = bigWord ? bigWordCharTest: wordCharTest; + + if (emptyLineIsWord && line == '') { + lineNum += dir; + line = cm.getLine(lineNum); + if (!isLine(cm, lineNum)) { + return null; + } + pos = (forward) ? 0 : line.length; + } + + while (true) { + if (emptyLineIsWord && line == '') { + return { from: 0, to: 0, line: lineNum }; + } + var stop = (dir > 0) ? line.length : -1; + var wordStart = stop, wordEnd = stop; + // Find bounds of next word. + while (pos != stop) { + var foundWord = false; + for (var i = 0; i < charTests.length && !foundWord; ++i) { + if (charTests[i](line.charAt(pos))) { + wordStart = pos; + // Advance to end of word. + while (pos != stop && charTests[i](line.charAt(pos))) { + pos += dir; + } + wordEnd = pos; + foundWord = wordStart != wordEnd; + if (wordStart == cur.ch && lineNum == cur.line && + wordEnd == wordStart + dir) { + // We started at the end of a word. Find the next one. + continue; + } else { + return { + from: Math.min(wordStart, wordEnd + 1), + to: Math.max(wordStart, wordEnd), + line: lineNum }; + } + } + } + if (!foundWord) { + pos += dir; + } + } + // Advance to next/prev line. + lineNum += dir; + if (!isLine(cm, lineNum)) { + return null; + } + line = cm.getLine(lineNum); + pos = (dir > 0) ? 0 : line.length; + } + } + + /** + * @arg {CodeMirror} cm CodeMirror object. + * @arg {Pos} cur The position to start from. + * @arg {number} repeat Number of words to move past. + * @arg {boolean} forward True to search forward. False to search + * backward. + * @arg {boolean} wordEnd True to move to end of word. False to move to + * beginning of word. + * @arg {boolean} bigWord True if punctuation count as part of the word. + * False if only alphabet characters count as part of the word. + * @return {Pos|undefined} The position the cursor should move to. + */ + function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) { + var curStart = copyCursor(cur); + var words = []; + if (forward && !wordEnd || !forward && wordEnd) { + repeat++; + } + // For 'e', empty lines are not considered words, go figure. + var emptyLineIsWord = !(forward && wordEnd); + for (var i = 0; i < repeat; i++) { + var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord); + if (!word) { + var eodCh = lineLength(cm, cm.lastLine()); + words.push(forward + ? {line: cm.lastLine(), from: eodCh, to: eodCh} + : {line: 0, from: 0, to: 0}); + break; + } + words.push(word); + cur = new Pos(word.line, forward ? (word.to - 1) : word.from); + } + var shortCircuit = words.length != repeat; + var firstWord = words[0]; + var lastWord = words.pop(); + if (forward && !wordEnd) { + // w + if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) { + // We did not start in the middle of a word. Discard the extra word at the end. + lastWord = words.pop(); + } + return lastWord && new Pos(lastWord.line, lastWord.from); + } else if (forward && wordEnd) { + return lastWord && new Pos(lastWord.line, lastWord.to - 1); + } else if (!forward && wordEnd) { + // ge + if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) { + // We did not start in the middle of a word. Discard the extra word at the end. + lastWord = words.pop(); + } + return lastWord && new Pos(lastWord.line, lastWord.to); + } else { + // b + return lastWord && new Pos(lastWord.line, lastWord.from); + } + } + + /** + * @arg {CodeMirror} cm + * @arg {Pos} head + * @arg {MotionArgs} motionArgs + * @arg {vimState} vim + * @arg {boolean} keepHPos */ + function moveToEol(cm, head, motionArgs, vim, keepHPos) { + var cur = head; + var retval= new Pos(cur.line + motionArgs.repeat - 1, Infinity); + var end=cm.clipPos(retval); + end.ch--; + if (!keepHPos) { + vim.lastHPos = Infinity; + vim.lastHSPos = cm.charCoords(end,'div').left; + } + return retval; + } + + /** + * @arg {CodeMirror} cm + * @arg {number} repeat + * @arg {boolean} [forward] + * @arg {string} [character] + * @arg {Pos} [head] + */ + function moveToCharacter(cm, repeat, forward, character, head) { + if (!character) return; + var cur = head || cm.getCursor(); + var start = cur.ch; + var idx; + for (var i = 0; i < repeat; i ++) { + var line = cm.getLine(cur.line); + idx = charIdxInLine(start, line, character, forward, true); + if (idx == -1) { + return undefined; + } + start = idx; + } + if (idx != undefined) + return new Pos(cm.getCursor().line, idx); + } + + /** @arg {CodeMirrorV} cm @arg {number} repeat */ + function moveToColumn(cm, repeat) { + // repeat is always >= 1, so repeat - 1 always corresponds + // to the column we want to go to. + var line = cm.getCursor().line; + return clipCursorToContent(cm, new Pos(line, repeat - 1)); + } + + /** + * @arg {CodeMirror} cm + * @arg {vimState} vim + * @arg {string} markName + * @arg {Pos} pos */ + function updateMark(cm, vim, markName, pos) { + if (!inArray(markName, validMarks) && !latinCharRegex.test(markName)) { + return; + } + if (vim.marks[markName]) { + vim.marks[markName].clear(); + } + vim.marks[markName] = cm.setBookmark(pos); + } + + /** + * @arg {number} start + * @arg {string | any[]} line + * @arg {any} character + * @arg {boolean} [forward] + * @arg {boolean} [includeChar] */ + function charIdxInLine(start, line, character, forward, includeChar) { + // Search for char in line. + // motion_options: {forward, includeChar} + // If includeChar = true, include it too. + // If forward = true, search forward, else search backwards. + // If char is not found on this line, do nothing + var idx; + if (forward) { + idx = line.indexOf(character, start + 1); + if (idx != -1 && !includeChar) { + idx -= 1; + } + } else { + idx = line.lastIndexOf(character, start - 1); + if (idx != -1 && !includeChar) { + idx += 1; + } + } + return idx; + } + + /** @arg {CodeMirrorV} cm + * @arg {Pos} head + * @arg {number} repeat + * @arg {number} dir + * @arg {boolean} [inclusive] */ + function findParagraph(cm, head, repeat, dir, inclusive) { + var line = head.line; + var min = cm.firstLine(); + var max = cm.lastLine(); + var start, end, i = line; + /** @arg {number} i */ + function isEmpty(i) { return !cm.getLine(i); } + /** @arg {number} i @arg {number} dir @arg {boolean} [any] */ + function isBoundary(i, dir, any) { + if (any) { return isEmpty(i) != isEmpty(i + dir); } + return !isEmpty(i) && isEmpty(i + dir); + } + if (dir) { + while (min <= i && i <= max && repeat > 0) { + if (isBoundary(i, dir)) { repeat--; } + i += dir; + } + return {start: new Pos(i, 0), end: head}; + } + + var vim = cm.state.vim; + if (vim.visualLine && isBoundary(line, 1, true)) { + var anchor = vim.sel.anchor; + if (isBoundary(anchor.line, -1, true)) { + if (!inclusive || anchor.line != line) { + line += 1; + } + } + } + var startState = isEmpty(line); + for (i = line; i <= max && repeat; i++) { + if (isBoundary(i, 1, true)) { + if (!inclusive || isEmpty(i) != startState) { + repeat--; + } + } + } + end = new Pos(i, 0); + // select boundary before paragraph for the last one + if (i > max && !startState) { startState = true; } + else { inclusive = false; } + for (i = line; i > min; i--) { + if (!inclusive || isEmpty(i) == startState || i == line) { + if (isBoundary(i, -1, true)) { break; } + } + } + start = new Pos(i, 0); + return { start: start, end: end }; + } + + /** + * Based on {@link findSentence}. The internal functions have the same names, + * but their behaviour is different. findSentence() crosses line breaks and + * is used for jumping to sentence beginnings before or after the current cursor position, + * whereas getSentence() is for getting the beginning or end of the sentence + * at the current cursor position, either including (a) or excluding (i) whitespace. + * @arg {CodeMirror} cm + * @arg {Pos} cur + * @arg {number} repeat + * @arg {number} dir + * @arg {boolean} inclusive + */ + function getSentence(cm, cur, repeat, dir, inclusive /*includes whitespace*/) { + + /* + Takes an index object + { + line: the line string, + ln: line number, + pos: index in line, + dir: direction of traversal (-1 or 1) + } + and modifies the pos member to represent the + next valid position or sets the line to null if there are + no more valid positions. + */ + function nextChar(curr) { + if (curr.pos + curr.dir < 0 || curr.pos + curr.dir >= curr.line.length) { + curr.line = null; + } + else { + curr.pos += curr.dir; + } + } + /* + Performs one iteration of traversal in forward direction + Returns an index object of the sentence end + */ + function forward(cm, ln, pos, dir) { + var line = cm.getLine(ln); + + var curr = { + line: line, + ln: ln, + pos: pos, + dir: dir, + }; + + if (curr.line === "") { + return { ln: curr.ln, pos: curr.pos }; + } + + var lastSentencePos = curr.pos; + + // Move one step to skip character we start on + nextChar(curr); + + while (curr.line !== null) { + lastSentencePos = curr.pos; + if (isEndOfSentenceSymbol(curr.line[curr.pos])) { + if (!inclusive) { + return { ln: curr.ln, pos: curr.pos + 1 }; + } + else { + nextChar(curr); + while (curr.line !== null ) { + if (isWhiteSpaceString(curr.line[curr.pos])) { + lastSentencePos = curr.pos; + nextChar(curr); + } + else { + break; + } + } + return { ln: curr.ln, pos: lastSentencePos + 1 }; + } + } + nextChar(curr); + } + return { ln: curr.ln, pos: lastSentencePos + 1 }; + } + + /** + * Performs one iteration of traversal in reverse direction + * Returns an index object of the sentence start + * @arg {CodeMirror} cm + * @arg {number} ln + * @arg {number} pos + * @arg {number} dir + */ + function reverse(cm, ln, pos, dir) { + var line = cm.getLine(ln); + + var curr = { + line: line, + ln: ln, + pos: pos, + dir: dir, + }; + + if (curr.line === "") { + return { ln: curr.ln, pos: curr.pos }; + } + + var lastSentencePos = curr.pos; + + // Move one step to skip character we start on + nextChar(curr); + + while (curr.line !== null) { + if (!isWhiteSpaceString(curr.line[curr.pos]) && !isEndOfSentenceSymbol(curr.line[curr.pos])) { + lastSentencePos = curr.pos; + } + + else if (isEndOfSentenceSymbol(curr.line[curr.pos]) ) { + if (!inclusive) { + return { ln: curr.ln, pos: lastSentencePos }; + } + else { + if (isWhiteSpaceString(curr.line[curr.pos + 1])) { + return { ln: curr.ln, pos: curr.pos + 1 }; + } + else { + return { ln: curr.ln, pos: lastSentencePos }; + } + } + } + + nextChar(curr); + } + curr.line = line; + if (inclusive && isWhiteSpaceString(curr.line[curr.pos])) { + return { ln: curr.ln, pos: curr.pos }; + } + else { + return { ln: curr.ln, pos: lastSentencePos }; + } + + } + + var curr_index = { + ln: cur.line, + pos: cur.ch, + }; + + while (repeat > 0) { + if (dir < 0) { + curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir); + } + else { + curr_index = forward(cm, curr_index.ln, curr_index.pos, dir); + } + repeat--; + } + + return new Pos(curr_index.ln, curr_index.pos); + } + + function findSentence(cm, cur, repeat, dir) { + + /* + Takes an index object + { + line: the line string, + ln: line number, + pos: index in line, + dir: direction of traversal (-1 or 1) + } + and modifies the line, ln, and pos members to represent the + next valid position or sets them to null if there are + no more valid positions. + */ + function nextChar(cm, idx) { + if (idx.pos + idx.dir < 0 || idx.pos + idx.dir >= idx.line.length) { + idx.ln += idx.dir; + if (!isLine(cm, idx.ln)) { + idx.line = null; + idx.ln = null; + idx.pos = null; + return; + } + idx.line = cm.getLine(idx.ln); + idx.pos = (idx.dir > 0) ? 0 : idx.line.length - 1; + } + else { + idx.pos += idx.dir; + } + } + + /* + Performs one iteration of traversal in forward direction + Returns an index object of the new location + */ + /** @arg {CodeMirror} cm @arg {number} ln @arg {number} pos @arg {number} dir */ + function forward(cm, ln, pos, dir) { + var line = cm.getLine(ln); + var stop = (line === ""); + + var curr = { + line: line, + ln: ln, + pos: pos, + dir: dir, + }; + + var last_valid = { + ln: curr.ln, + pos: curr.pos, + }; + + var skip_empty_lines = (curr.line === ""); + + // Move one step to skip character we start on + nextChar(cm, curr); + + while (curr.line !== null) { + last_valid.ln = curr.ln; + last_valid.pos = curr.pos; + + if (curr.line === "" && !skip_empty_lines) { + return { ln: curr.ln, pos: curr.pos, }; + } + else if (stop && curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) { + return { ln: curr.ln, pos: curr.pos, }; + } + else if (isEndOfSentenceSymbol(curr.line[curr.pos]) + && !stop + && (curr.pos === curr.line.length - 1 + || isWhiteSpaceString(curr.line[curr.pos + 1]))) { + stop = true; + } + + nextChar(cm, curr); + } + + /* + Set the position to the last non whitespace character on the last + valid line in the case that we reach the end of the document. + */ + var line = cm.getLine(last_valid.ln); + last_valid.pos = 0; + for(var i = line.length - 1; i >= 0; --i) { + if (!isWhiteSpaceString(line[i])) { + last_valid.pos = i; + break; + } + } + + return last_valid; + + } + + /* + Performs one iteration of traversal in reverse direction + Returns an index object of the new location + */ + /** @arg {CodeMirror} cm @arg {number} ln @arg {number} pos @arg {number} dir */ + function reverse(cm, ln, pos, dir) { + var line = cm.getLine(ln); + + var curr = { + line: line, + ln: ln, + pos: pos, + dir: dir, + }; + + /** @type {{ln: number, pos: number|null}} */ + var last_valid = { + ln: curr.ln, + pos: null, + }; + + var skip_empty_lines = (curr.line === ""); + + // Move one step to skip character we start on + nextChar(cm, curr); + + while (curr.line !== null) { + + if (curr.line === "" && !skip_empty_lines) { + if (last_valid.pos !== null) { + return last_valid; + } + else { + return { ln: curr.ln, pos: curr.pos }; + } + } + else if (isEndOfSentenceSymbol(curr.line[curr.pos]) + && last_valid.pos !== null + && !(curr.ln === last_valid.ln && curr.pos + 1 === last_valid.pos)) { + return last_valid; + } + else if (curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) { + skip_empty_lines = false; + last_valid = { ln: curr.ln, pos: curr.pos }; + } + + nextChar(cm, curr); + } + + /* + Set the position to the first non whitespace character on the last + valid line in the case that we reach the beginning of the document. + */ + var line = cm.getLine(last_valid.ln); + last_valid.pos = 0; + for(var i = 0; i < line.length; ++i) { + if (!isWhiteSpaceString(line[i])) { + last_valid.pos = i; + break; + } + } + return last_valid; + } + + var curr_index = { + ln: cur.line, + pos: cur.ch, + }; + + while (repeat > 0) { + if (dir < 0) { + curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir); + } + else { + curr_index = forward(cm, curr_index.ln, curr_index.pos, dir); + } + repeat--; + } + + return new Pos(curr_index.ln, curr_index.pos); + } + + // TODO: perhaps this finagling of start and end positions belongs + // in codemirror/replaceRange? + /** @arg {CodeMirror} cm @arg {Pos} head @arg {string | number} symb @arg {boolean} inclusive */ + function selectCompanionObject(cm, head, symb, inclusive) { + var cur = head; + + var bracketRegexp = ({ + '(': /[()]/, ')': /[()]/, + '[': /[[\]]/, ']': /[[\]]/, + '{': /[{}]/, '}': /[{}]/, + '<': /[<>]/, '>': /[<>]/})[symb]; + var openSym = ({ + '(': '(', ')': '(', + '[': '[', ']': '[', + '{': '{', '}': '{', + '<': '<', '>': '<'})[symb]; + var curChar = cm.getLine(cur.line).charAt(cur.ch); + // Due to the behavior of scanForBracket, we need to add an offset if the + // cursor is on a matching open bracket. + var offset = curChar === openSym ? 1 : 0; + + var startBracket = cm.scanForBracket(new Pos(cur.line, cur.ch + offset), -1, undefined, {'bracketRegex': bracketRegexp}); + var endBracket = cm.scanForBracket(new Pos(cur.line, cur.ch + offset), 1, undefined, {'bracketRegex': bracketRegexp}); + + if (!startBracket || !endBracket) return null; + + var start = startBracket.pos; + var end = endBracket.pos; + + if ((start.line == end.line && start.ch > end.ch) + || (start.line > end.line)) { + var tmp = start; + start = end; + end = tmp; + } + + if (inclusive) { + end.ch += 1; + } else { + start.ch += 1; + } + + return { start: start, end: end }; + } + + // Takes in a symbol and a cursor and tries to simulate text objects that + // have identical opening and closing symbols + // TODO support across multiple lines + /** @arg {CodeMirror} cm @arg {Pos} head @arg {string} symb @arg {boolean} inclusive */ + function findBeginningAndEnd(cm, head, symb, inclusive) { + var cur = copyCursor(head); + var line = cm.getLine(cur.line); + var chars = line.split(''); + var start, end, i, len; + var firstIndex = chars.indexOf(symb); + + // the decision tree is to always look backwards for the beginning first, + // but if the cursor is in front of the first instance of the symb, + // then move the cursor forward + if (cur.ch < firstIndex) { + cur.ch = firstIndex; + } + // otherwise if the cursor is currently on the closing symbol + else if (firstIndex < cur.ch && chars[cur.ch] == symb) { + var stringAfter = /string/.test(cm.getTokenTypeAt(offsetCursor(head, 0, 1))); + var stringBefore = /string/.test(cm.getTokenTypeAt(head)); + var isStringStart = stringAfter && !stringBefore; + if (!isStringStart) { + end = cur.ch; // assign end to the current cursor + --cur.ch; // make sure to look backwards + } + } + + // if we're currently on the symbol, we've got a start + if (chars[cur.ch] == symb && !end) { + start = cur.ch + 1; // assign start to ahead of the cursor + } else { + // go backwards to find the start + for (i = cur.ch; i > -1 && !start; i--) { + if (chars[i] == symb) { + start = i + 1; + } + } + } + + // look forwards for the end symbol + if (start && !end) { + for (i = start, len = chars.length; i < len && !end; i++) { + if (chars[i] == symb) { + end = i; + } + } + } + + // nothing found + if (!start || !end) { + return { start: cur, end: cur }; + } + + // include the symbols + if (inclusive) { + --start; ++end; + } + + return { + start: new Pos(cur.line, start), + end: new Pos(cur.line, end) + }; + } + + // Search functions + defineOption('pcre', true, 'boolean'); + + class SearchState { + getQuery() { + return vimGlobalState.query; + }; + setQuery(query) { + vimGlobalState.query = query; + }; + getOverlay() { + return this.searchOverlay; + }; + setOverlay(overlay) { + this.searchOverlay = overlay; + }; + isReversed() { + return vimGlobalState.isReversed; + }; + setReversed(reversed) { + vimGlobalState.isReversed = reversed; + }; + getScrollbarAnnotate() { + return this.annotate; + }; + setScrollbarAnnotate(annotate) { + this.annotate = annotate; + }; + } /** @arg {CodeMirrorV} cm */ + function getSearchState(cm) { + var vim = cm.state.vim; + return vim.searchState_ || (vim.searchState_ = new SearchState()); + } + /** @arg {string} argString */ + function splitBySlash(argString) { + return splitBySeparator(argString, '/'); + } + + /** @arg {string} argString */ + function findUnescapedSlashes(argString) { + return findUnescapedSeparators(argString, '/'); + } + + /** @arg {string} argString @arg {string} separator */ + function splitBySeparator(argString, separator) { + var slashes = findUnescapedSeparators(argString, separator) || []; + if (!slashes.length) return []; + var tokens = []; + // in case of strings like foo/bar + if (slashes[0] !== 0) return; + for (var i = 0; i < slashes.length; i++) { + if (typeof slashes[i] == 'number') + tokens.push(argString.substring(slashes[i] + 1, slashes[i+1])); + } + return tokens; + } + + /** @arg {string} str @arg {string} separator */ + function findUnescapedSeparators(str, separator) { + if (!separator) + separator = '/'; + + var escapeNextChar = false; + var slashes = []; + for (var i = 0; i < str.length; i++) { + var c = str.charAt(i); + if (!escapeNextChar && c == separator) { + slashes.push(i); + } + escapeNextChar = !escapeNextChar && (c == '\\'); + } + return slashes; + } + + // Translates a search string from ex (vim) syntax into javascript form. + /** @arg {string} str */ + function translateRegex(str) { + // When these match, add a '\' if unescaped or remove one if escaped. + var specials = '|(){'; + // Remove, but never add, a '\' for these. + var unescape = '}'; + var escapeNextChar = false; + var out = []; + for (var i = -1; i < str.length; i++) { + var c = str.charAt(i) || ''; + var n = str.charAt(i+1) || ''; + var specialComesNext = (n && specials.indexOf(n) != -1); + if (escapeNextChar) { + if (c !== '\\' || !specialComesNext) { + out.push(c); + } + escapeNextChar = false; + } else { + if (c === '\\') { + escapeNextChar = true; + // Treat the unescape list as special for removing, but not adding '\'. + if (n && unescape.indexOf(n) != -1) { + specialComesNext = true; + } + // Not passing this test means removing a '\'. + if (!specialComesNext || n === '\\') { + out.push(c); + } + } else { + out.push(c); + if (specialComesNext && n !== '\\') { + out.push('\\'); + } + } + } + } + return out.join(''); + } + + // Translates the replace part of a search and replace from ex (vim) syntax into + // javascript form. Similar to translateRegex, but additionally fixes back references + // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'. + /** @type{Object} */ + var charUnescapes = {'\\n': '\n', '\\r': '\r', '\\t': '\t'}; + /** @arg {string} str */ + function translateRegexReplace(str) { + var escapeNextChar = false; + var out = []; + for (var i = -1; i < str.length; i++) { + var c = str.charAt(i) || ''; + var n = str.charAt(i+1) || ''; + if (charUnescapes[c + n]) { + out.push(charUnescapes[c+n]); + i++; + } else if (escapeNextChar) { + // At any point in the loop, escapeNextChar is true if the previous + // character was a '\' and was not escaped. + out.push(c); + escapeNextChar = false; + } else { + if (c === '\\') { + escapeNextChar = true; + if ((isNumber(n) || n === '$')) { + out.push('$'); + } else if (n !== '/' && n !== '\\') { + out.push('\\'); + } + } else { + if (c === '$') { + out.push('$'); + } + out.push(c); + if (n === '/') { + out.push('\\'); + } + } + } + } + return out.join(''); + } + + // Unescape \ and / in the replace part, for PCRE mode. + /** @type{Record} */ + var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t', '\\&':'&'}; + /** @arg {string} str */ + function unescapeRegexReplace(str) { + var stream = new CodeMirror.StringStream(str); + var output = []; + while (!stream.eol()) { + // Search for \. + while (stream.peek() && stream.peek() != '\\') { + output.push(stream.next()); + } + var matched = false; + for (var matcher in unescapes) { + if (stream.match(matcher, true)) { + matched = true; + output.push(unescapes[matcher]); + break; + } + } + if (!matched) { + // Don't change anything + output.push(stream.next()); + } + } + return output.join(''); + } + + /** + * Extract the regular expression from the query and return a Regexp object. + * Returns null if the query is blank. + * If ignoreCase is passed in, the Regexp object will have the 'i' flag set. + * If smartCase is passed in, and the query contains upper case letters, + * then ignoreCase is overridden, and the 'i' flag will not be set. + * If the query contains the /i in the flag part of the regular expression, + * then both ignoreCase and smartCase are ignored, and 'i' will be passed + * through to the Regex object. + * @arg {string|RegExp} query + * @arg {boolean} ignoreCase + * @arg {boolean} smartCase + */ + function parseQuery(query, ignoreCase, smartCase) { + // First update the last search register + var lastSearchRegister = vimGlobalState.registerController.getRegister('/'); + lastSearchRegister.setText(query); + // Check if the query is already a regex. + if (query instanceof RegExp) { return query; } + // First try to extract regex + flags from the input. If no flags found, + // extract just the regex. IE does not accept flags directly defined in + // the regex string in the form /regex/flags + var slashes = findUnescapedSlashes(query); + var regexPart; + var forceIgnoreCase; + if (!slashes.length) { + // Query looks like 'regexp' + regexPart = query; + } else { + // Query looks like 'regexp/...' + regexPart = query.substring(0, slashes[0]); + var flagsPart = query.substring(slashes[0]); + forceIgnoreCase = (flagsPart.indexOf('i') != -1); + } + if (!regexPart) { + return null; + } + if (!getOption('pcre')) { + regexPart = translateRegex(regexPart); + } + if (smartCase) { + ignoreCase = (/^[^A-Z]*$/).test(regexPart); + } + var regexp = new RegExp(regexPart, + (ignoreCase || forceIgnoreCase) ? 'im' : 'm'); + return regexp; + } + + /** + * dom - Document Object Manipulator + * Usage: + * dom(''|[, ...{|<$styles>}||'']) + * Examples: + * dom('div', {id:'xyz'}, dom('p', 'CM rocks!', {$color:'red'})) + * dom(document.head, dom('script', 'alert("hello!")')) + * Not supported: + * dom('p', ['arrays are objects'], Error('objects specify attributes')) + * @arg {string | HTMLElement } n + */ + function dom(n) { + if (typeof n === 'string') n = document.createElement(n); + for (var a, i = 1; i < arguments.length; i++) { + if (!(a = arguments[i])) continue; + if (typeof a !== 'object') a = document.createTextNode(a); + if (a.nodeType) n.appendChild(a); + else for (var key in a) { + if (!Object.prototype.hasOwnProperty.call(a, key)) continue; + if (key[0] === '$') n.style[key.slice(1)] = a[key]; + else n.setAttribute(key, a[key]); + } + } + return n; + } + + /** @arg {CodeMirror} cm @arg {any} template @arg {boolean} [long]*/ + function showConfirm(cm, template, long) { + var pre = dom('div', {$color: 'red', $whiteSpace: 'pre', class: 'cm-vim-message'}, template); + if (cm.openNotification) { + if (long) { + pre = dom('div', {}, pre, dom('div', {}, 'Press ENTER or type command to continue')); + if (cm.state.closeVimNotification) { + cm.state.closeVimNotification(); + } + cm.state.closeVimNotification = cm.openNotification(pre, {bottom: true, duration: 0}); + } else { + cm.openNotification(pre, {bottom: true, duration: 5000}); + } + } else { + alert(pre.innerText); + } + } + /** @arg {string} prefix @arg {string} desc */ + function makePrompt(prefix, desc) { + return dom('div', {$display: 'flex'}, + dom('span', {$fontFamily: 'monospace', $whiteSpace: 'pre', $flex: 1, $display: 'flex'}, + prefix, + dom('input', {type: 'text', autocorrect: 'off', + autocapitalize: 'off', spellcheck: 'false', $flex: 1})), + desc && dom('span', {$color: '#888'}, desc)); + } + /** + * @arg {CodeMirror} cm + * @arg {{ onClose?: any; prefix: any; desc?: any; onKeyUp?: any; onKeyDown: any; value?: any; selectValueOnOpen?: boolean; }} options + */ + function showPrompt(cm, options) { + if (keyToKeyStack.length) { + if (!options.value) options.value = ''; + virtualPrompt = options; + return; + } + var template = makePrompt(options.prefix, options.desc); + if (cm.openDialog) { + cm.openDialog(template, options.onClose, { + onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp, + bottom: true, selectValueOnOpen: false, value: options.value + }); + } + else { + var shortText = ''; + if (typeof options.prefix != "string" && options.prefix) shortText += options.prefix.textContent; + if (options.desc) shortText += " " + options.desc; + options.onClose(prompt(shortText, '')); + } + } + + /** @arg {RegExp|unknown} r1 @arg {RegExp|unknown} r2 */ + function regexEqual(r1, r2) { + if (r1 instanceof RegExp && r2 instanceof RegExp) { + var props = ['global', 'multiline', 'ignoreCase', 'source']; + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + if (r1[prop] !== r2[prop]) { + return false; + } + } + return true; + } + return false; + } + // Returns true if the query is valid. + /** + * @arg {CodeMirrorV} cm + * @arg {string | RegExp} rawQuery + * @arg {boolean | undefined} [ignoreCase] + * @arg {boolean | undefined} [smartCase] + */ + function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) { + if (!rawQuery) { + return; + } + var state = getSearchState(cm); + var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase); + if (!query) { + return; + } + highlightSearchMatches(cm, query); + if (regexEqual(query, state.getQuery())) { + return query; + } + state.setQuery(query); + return query; + } + /** @arg {RegExp} query */ + function searchOverlay(query) { + if (query.source.charAt(0) == '^') { + var matchSol = true; + } + return { + token: function(stream) { + if (matchSol && !stream.sol()) { + stream.skipToEnd(); + return; + } + var match = stream.match(query, false); + if (match) { + if (match[0].length == 0) { + // Matched empty string, skip to next. + stream.next(); + return 'searching'; + } + if (!stream.sol()) { + // Backtrack 1 to match \b + stream.backUp(1); + if (!query.exec(stream.next() + match[0])) { + stream.next(); + return null; + } + } + stream.match(query); + return 'searching'; + } + while (!stream.eol()) { + stream.next(); + if (stream.match(query, false)) break; + } + }, + query: query + }; + } + var highlightTimeout = 0; + /** @arg {CodeMirrorV} cm @arg {RegExp} query */ + function highlightSearchMatches(cm, query) { + clearTimeout(highlightTimeout); + var searchState = getSearchState(cm); + searchState.highlightTimeout = highlightTimeout; + highlightTimeout = setTimeout(function() { + if (!cm.state.vim) return; + var searchState = getSearchState(cm); + searchState.highlightTimeout = null; + var overlay = searchState.getOverlay(); + if (!overlay || query != overlay.query) { + if (overlay) { + cm.removeOverlay(overlay); + } + overlay = searchOverlay(query); + cm.addOverlay(overlay); + if (cm.showMatchesOnScrollbar) { + if (searchState.getScrollbarAnnotate()) { + searchState.getScrollbarAnnotate().clear(); + } + searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query)); + } + searchState.setOverlay(overlay); + } + }, 50); + } + /** @arg {CodeMirror} cm @arg {boolean} prev @arg {RegExp} query @arg {number | undefined} [repeat] */ + function findNext(cm, prev, query, repeat) { + return cm.operation(function() { + if (repeat === undefined) { repeat = 1; } + var pos = cm.getCursor(); + var cursor = cm.getSearchCursor(query, pos); + for (var i = 0; i < repeat; i++) { + var found = cursor.find(prev); + // @ts-ignore + if (i == 0 && found && cursorEqual(cursor.from(), pos)) { + var lastEndPos = prev ? cursor.from() : cursor.to(); + found = cursor.find(prev); + // @ts-ignore + if (found && !found[0] && cursorEqual(cursor.from(), lastEndPos)) { + // @ts-ignore + if (cm.getLine(lastEndPos.line).length == lastEndPos.ch) + found = cursor.find(prev); + } + } + if (!found) { + // SearchCursor may have returned null because it hit EOF, wrap + // around and try again. + cursor = cm.getSearchCursor(query, + // @ts-ignore + (prev) ? new Pos(cm.lastLine()) : new Pos(cm.firstLine(), 0) ); + if (!cursor.find(prev)) { + return; + } + } + } + return cursor.from(); + }); + } + /** + * Pretty much the same as `findNext`, except for the following differences: + * + * 1. Before starting the search, move to the previous search. This way if our cursor is + * already inside a match, we should return the current match. + * 2. Rather than only returning the cursor's from, we return the cursor's from and to as a tuple. + * @arg {CodeMirror} cm + * @arg {boolean} prev + * @arg {any} query + * @arg {number | undefined} repeat + * @arg {vimState} vim + */ + function findNextFromAndToInclusive(cm, prev, query, repeat, vim) { + return cm.operation(function() { + if (repeat === undefined) { repeat = 1; } + var pos = cm.getCursor(); + var cursor = cm.getSearchCursor(query, pos); + + // Go back one result to ensure that if the cursor is currently a match, we keep it. + var found = cursor.find(!prev); + + // If we haven't moved, go back one more (similar to if i==0 logic in findNext). + // @ts-ignore + if (!vim.visualMode && found && cursorEqual(cursor.from(), pos)) { + cursor.find(!prev); + } + + for (var i = 0; i < repeat; i++) { + found = cursor.find(prev); + if (!found) { + // SearchCursor may have returned null because it hit EOF, wrap + // around and try again. + cursor = cm.getSearchCursor(query, + // @ts-ignore + (prev) ? new Pos(cm.lastLine()) : new Pos(cm.firstLine(), 0) ); + if (!cursor.find(prev)) { + return; + } + } + } + return [cursor.from(), cursor.to()]; + }); + } + /** @arg {CodeMirrorV} cm */ + function clearSearchHighlight(cm) { + var state = getSearchState(cm); + if (state.highlightTimeout) { + clearTimeout(state.highlightTimeout); + state.highlightTimeout = null; + } + cm.removeOverlay(getSearchState(cm).getOverlay()); + state.setOverlay(null); + if (state.getScrollbarAnnotate()) { + state.getScrollbarAnnotate().clear(); + state.setScrollbarAnnotate(null); + } + } + /** + * Check if pos is in the specified range, INCLUSIVE. + * Range can be specified with 1 or 2 arguments. + * If the first range argument is an array, treat it as an array of line + * numbers. Match pos against any of the lines. + * If the first range argument is a number, + * if there is only 1 range argument, check if pos has the same line + * number + * if there are 2 range arguments, then check if pos is in between the two + * range arguments. + * @arg {number|Pos} pos + * @arg {number|number[]} start + * @arg {number} end + */ + function isInRange(pos, start, end) { + if (typeof pos != 'number') { + // Assume it is a cursor position. Get the line number. + pos = pos.line; + } + if (start instanceof Array) { + return inArray(pos, start); + } else { + if (typeof end == 'number') { + return (pos >= start && pos <= end); + } else { + return pos == start; + } + } + } + /** @arg {CodeMirror} cm */ + function getUserVisibleLines(cm) { + var scrollInfo = cm.getScrollInfo(); + var occludeToleranceTop = 6; + var occludeToleranceBottom = 10; + var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local'); + var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top; + var to = cm.coordsChar({left:0, top: bottomY}, 'local'); + return {top: from.line, bottom: to.line}; + } + + /** @arg {CodeMirror} cm @arg {vimState} vim @arg {string} markName */ + function getMarkPos(cm, vim, markName) { + if (markName == '\'' || markName == '`') { + return vimGlobalState.jumpList.find(cm, -1) || new Pos(0, 0); + } else if (markName == '.') { + return getLastEditPos(cm); + } + + var mark = vim.marks[markName]; + return mark && mark.find(); + } + + /** @arg {CodeMirror} cm */ + function getLastEditPos(cm) { + if (cm.getLastEditEnd) { + return cm.getLastEditEnd(); + } + // for old cm + var done = /**@type{any}*/(cm).doc.history.done; + for (var i = done.length; i--;) { + if (done[i].changes) { + return copyCursor(done[i].changes[0].to); + } + } + } + + class ExCommandDispatcher { + constructor() { + /**@type {Record} */ + this.commandMap_; + this.buildCommandMap_(); + } + processCommand(cm, input, opt_params) { + var that = this; + cm.operation(function () { + cm.curOp.isVimOp = true; + that._processCommand(cm, input, opt_params); + }); + } + _processCommand(cm, input, opt_params) { + var vim = cm.state.vim; + var commandHistoryRegister = vimGlobalState.registerController.getRegister(':'); + var previousCommand = commandHistoryRegister.toString(); + var inputStream = new CodeMirror.StringStream(input); + // update ": with the latest command whether valid or invalid + commandHistoryRegister.setText(input); + var params = opt_params || {}; + params.input = input; + try { + this.parseInput_(cm, inputStream, params); + } catch(e) { + showConfirm(cm, e + ""); + throw e; + } + + if (vim.visualMode) { + exitVisualMode(cm); + } + + var command; + var commandName; + if (!params.commandName) { + // If only a line range is defined, move to the line. + if (params.line !== undefined) { + commandName = 'move'; + } + } else { + command = this.matchCommand_(params.commandName); + if (command) { + commandName = command.name; + if (command.excludeFromCommandHistory) { + commandHistoryRegister.setText(previousCommand); + } + this.parseCommandArgs_(inputStream, params, command); + if (command.type == 'exToKey') { + // Handle Ex to Key mapping. + doKeyToKey(cm, command.toKeys, command); + return; + } else if (command.type == 'exToEx') { + // Handle Ex to Ex mapping. + this.processCommand(cm, command.toInput); + return; + } + } + } + if (!commandName) { + showConfirm(cm, 'Not an editor command ":' + input + '"'); + return; + } + try { + exCommands[commandName](cm, params); + // Possibly asynchronous commands (e.g. substitute, which might have a + // user confirmation), are responsible for calling the callback when + // done. All others have it taken care of for them here. + if ((!command || !command.possiblyAsync) && params.callback) { + params.callback(); + } + } catch(e) { + showConfirm(cm, e + ""); + throw e; + } + } + parseInput_(cm, inputStream, result) { + inputStream.eatWhile(':'); + // Parse range. + if (inputStream.eat('%')) { + result.line = cm.firstLine(); + result.lineEnd = cm.lastLine(); + } else { + result.line = this.parseLineSpec_(cm, inputStream); + if (result.line !== undefined && inputStream.eat(',')) { + result.lineEnd = this.parseLineSpec_(cm, inputStream); + } + } + + if (result.line == undefined) { + if (cm.state.vim.visualMode) { + result.selectionLine = getMarkPos(cm, cm.state.vim, '<')?.line; + result.selectionLineEnd = getMarkPos(cm, cm.state.vim, '>')?.line; + } else { + result.selectionLine = cm.getCursor().line; + } + } else { + result.selectionLine = result.line; + result.selectionLineEnd = result.lineEnd; + } + + // Parse command name. + var commandMatch = inputStream.match(/^(\w+|!!|@@|[!#&*<=>@~])/); + if (commandMatch) { + result.commandName = commandMatch[1]; + } else { + result.commandName = inputStream.match(/.*/)[0]; + } + + return result; + } + parseLineSpec_(cm, inputStream) { + var numberMatch = inputStream.match(/^(\d+)/); + if (numberMatch) { + // Absolute line number plus offset (N+M or N-M) is probably a typo, + // not something the user actually wanted. (NB: vim does allow this.) + return parseInt(numberMatch[1], 10) - 1; + } + switch (inputStream.next()) { + case '.': + return this.parseLineSpecOffset_(inputStream, cm.getCursor().line); + case '$': + return this.parseLineSpecOffset_(inputStream, cm.lastLine()); + case '\'': + var markName = inputStream.next(); + var markPos = getMarkPos(cm, cm.state.vim, markName); + if (!markPos) throw new Error('Mark not set'); + return this.parseLineSpecOffset_(inputStream, markPos.line); + case '-': + case '+': + inputStream.backUp(1); + // Offset is relative to current line if not otherwise specified. + return this.parseLineSpecOffset_(inputStream, cm.getCursor().line); + default: + inputStream.backUp(1); + return undefined; + } + } + parseLineSpecOffset_(inputStream, line) { + var offsetMatch = inputStream.match(/^([+-])?(\d+)/); + if (offsetMatch) { + var offset = parseInt(offsetMatch[2], 10); + if (offsetMatch[1] == "-") { + line -= offset; + } else { + line += offset; + } + } + return line; + } + parseCommandArgs_(inputStream, params, command) { + if (inputStream.eol()) { + return; + } + params.argString = inputStream.match(/.*/)[0]; + // Parse command-line arguments + var delim = command.argDelimiter || /\s+/; + var args = trim(params.argString).split(delim); + if (args.length && args[0]) { + params.args = args; + } + } + matchCommand_(commandName) { + // Return the command in the command map that matches the shortest + // prefix of the passed in command name. The match is guaranteed to be + // unambiguous if the defaultExCommandMap's shortNames are set up + // correctly. (see @code{defaultExCommandMap}). + for (var i = commandName.length; i > 0; i--) { + var prefix = commandName.substring(0, i); + if (this.commandMap_[prefix]) { + var command = this.commandMap_[prefix]; + if (command.name.indexOf(commandName) === 0) { + return command; + } + } + } + return null; + } + buildCommandMap_() { + this.commandMap_ = {}; + for (var i = 0; i < defaultExCommandMap.length; i++) { + var command = defaultExCommandMap[i]; + var key = command.shortName || command.name; + this.commandMap_[key] = command; + } + } + /**@type {(lhs: string, rhs: string, ctx: string, noremap?: boolean) => void} */ + map(lhs, rhs, ctx, noremap) { + if (lhs != ':' && lhs.charAt(0) == ':') { + if (ctx) { throw Error('Mode not supported for ex mappings'); } + var commandName = lhs.substring(1); + if (rhs != ':' && rhs.charAt(0) == ':') { + // Ex to Ex mapping + this.commandMap_[commandName] = { + name: commandName, + type: 'exToEx', + toInput: rhs.substring(1), + user: true + }; + } else { + // Ex to key mapping + this.commandMap_[commandName] = { + name: commandName, + type: 'exToKey', + toKeys: rhs, + user: true + }; + } + } else { + // Key to key or ex mapping + var mapping = { + keys: lhs, + type: 'keyToKey', + toKeys: rhs, + noremap: !!noremap + }; + if (ctx) { mapping.context = ctx; } + // @ts-ignore + defaultKeymap.unshift(mapping); + } + } + /**@type {(lhs: string, ctx: string) => boolean|void} */ + unmap(lhs, ctx) { + if (lhs != ':' && lhs.charAt(0) == ':') { + // Ex to Ex or Ex to key mapping + if (ctx) { throw Error('Mode not supported for ex mappings'); } + var commandName = lhs.substring(1); + if (this.commandMap_[commandName] && this.commandMap_[commandName].user) { + delete this.commandMap_[commandName]; + return true; + } + } else { + // Key to Ex or key to key mapping + var keys = lhs; + for (var i = 0; i < defaultKeymap.length; i++) { + if (keys == defaultKeymap[i].keys + && defaultKeymap[i].context === ctx) { + defaultKeymap.splice(i, 1); + return true; + } + } + } + } + } + + /** @typedef { import("./types").ExParams} ExParams */ + var exCommands = { + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + colorscheme: function(cm, params) { + if (!params.args || params.args.length < 1) { + showConfirm(cm, cm.getOption('theme')); + return; + } + cm.setOption('theme', params.args[0]); + }, + map: function(cm, params, ctx, defaultOnly) { + var mapArgs = params.args; + if (!mapArgs || mapArgs.length < 2) { + if (cm) { + showConfirm(cm, 'Invalid mapping: ' + params.input); + } + return; + } + exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx, defaultOnly); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + imap: function(cm, params) { this.map(cm, params, 'insert'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + nmap: function(cm, params) { this.map(cm, params, 'normal'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + vmap: function(cm, params) { this.map(cm, params, 'visual'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + omap: function(cm, params) { this.map(cm, params, 'operatorPending'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + noremap: function(cm, params) { this.map(cm, params, undefined, true); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + inoremap: function(cm, params) { this.map(cm, params, 'insert', true); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + nnoremap: function(cm, params) { this.map(cm, params, 'normal', true); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + vnoremap: function(cm, params) { this.map(cm, params, 'visual', true); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + onoremap: function(cm, params) { this.map(cm, params, 'operatorPending', true); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params @arg {string} ctx*/ + unmap: function(cm, params, ctx) { + var mapArgs = params.args; + if (!mapArgs || mapArgs.length < 1 || !exCommandDispatcher.unmap(mapArgs[0], ctx)) { + if (cm) { + showConfirm(cm, 'No such mapping: ' + params.input); + } + } + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + mapclear: function(cm, params) { vimApi.mapclear(); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + imapclear: function(cm, params) { vimApi.mapclear('insert'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + nmapclear: function(cm, params) { vimApi.mapclear('normal'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + vmapclear: function(cm, params) { vimApi.mapclear('visual'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + omapclear: function(cm, params) { vimApi.mapclear('operatorPending'); }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + move: function(cm, params) { + commandDispatcher.processCommand(cm, cm.state.vim, { + keys: "", + type: 'motion', + motion: 'moveToLineOrEdgeOfDocument', + motionArgs: { forward: false, explicitRepeat: true, linewise: true }, + repeatOverride: params.line+1 + }); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + set: function(cm, params) { + var setArgs = params.args; + // Options passed through to the setOption/getOption calls. May be passed in by the + // local/global versions of the set command + var setCfg = params.setCfg || {}; + if (!setArgs || setArgs.length < 1) { + if (cm) { + showConfirm(cm, 'Invalid mapping: ' + params.input); + } + return; + } + var expr = setArgs[0].split('='); + var optionName = expr.shift() || ""; + /**@type {string|boolean|undefined} */ + var value = expr.length > 0 ? expr.join('=') : undefined; + var forceGet = false; + var forceToggle = false; + + if (optionName.charAt(optionName.length - 1) == '?') { + // If post-fixed with ?, then the set is actually a get. + if (value) { throw Error('Trailing characters: ' + params.argString); } + optionName = optionName.substring(0, optionName.length - 1); + forceGet = true; + } else if (optionName.charAt(optionName.length - 1) == '!') { + optionName = optionName.substring(0, optionName.length - 1); + forceToggle = true; + } + if (value === undefined && optionName.substring(0, 2) == 'no') { + // To set boolean options to false, the option name is prefixed with + // 'no'. + optionName = optionName.substring(2); + value = false; + } + + var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean'; + if (optionIsBoolean) { + if (forceToggle) { + value = !getOption(optionName, cm, setCfg); + } else if (value == undefined) { + // Calling set with a boolean option sets it to true. + value = true; + } + } + // If no value is provided, then we assume this is a get. + if (!optionIsBoolean && value === undefined || forceGet) { + var oldValue = getOption(optionName, cm, setCfg); + if (oldValue instanceof Error) { + showConfirm(cm, oldValue.message); + } else if (oldValue === true || oldValue === false) { + showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName); + } else { + showConfirm(cm, ' ' + optionName + '=' + oldValue); + } + } else { + var setOptionReturn = setOption(optionName, value, cm, setCfg); + if (setOptionReturn instanceof Error) { + showConfirm(cm, setOptionReturn.message); + } + } + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + setlocal: function (cm, params) { + // setCfg is passed through to setOption + params.setCfg = {scope: 'local'}; + this.set(cm, params); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + setglobal: function (cm, params) { + // setCfg is passed through to setOption + params.setCfg = {scope: 'global'}; + this.set(cm, params); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + registers: function(cm, params) { + var regArgs = params.args; + var registers = vimGlobalState.registerController.registers; + var regInfo = '----------Registers----------\n\n'; + if (!regArgs) { + for (var registerName in registers) { + var text = registers[registerName].toString(); + if (text.length) { + regInfo += '"' + registerName + ' ' + text + '\n'; + } + } + } else { + var registerNames = regArgs.join(''); + for (var i = 0; i < registerNames.length; i++) { + var registerName = registerNames.charAt(i); + if (!vimGlobalState.registerController.isValidRegister(registerName)) { + continue; + } + var register = registers[registerName] || new Register(); + regInfo += '"' + registerName + ' ' + register.toString() + '\n'; + } + } + showConfirm(cm, regInfo, true); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + marks: function(cm, params) { + var filterArgs = params.args; + var marks = cm.state.vim.marks; + var regInfo = '-----------Marks-----------\nmark\tline\tcol\n\n'; + if (!filterArgs) { + for (var name in marks) { + var marker = marks[name] && marks[name].find(); + if (marker) { + regInfo += name + '\t' + marker.line + '\t' + marker.ch + '\n'; + } + } + } else { + var registerNames = filterArgs.join(''); + for (var i = 0; i < registerNames.length; i++) { + var name = registerNames.charAt(i); + var marker = marks[name] && marks[name].find(); + if (marker) { + regInfo += name + '\t' + marker.line + '\t' + marker.ch + '\n'; + } + } + } + showConfirm(cm, regInfo, true); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + sort: function(cm, params) { + var reverse, ignoreCase, unique, number, pattern; + function parseArgs() { + if (params.argString) { + var args = new CodeMirror.StringStream(params.argString); + if (args.eat('!')) { reverse = true; } + if (args.eol()) { return; } + if (!args.eatSpace()) { return 'Invalid arguments'; } + var opts = args.match(/([dinuox]+)?\s*(\/.+\/)?\s*/); + if (!opts || !args.eol()) { return 'Invalid arguments'; } + if (opts[1]) { + ignoreCase = opts[1].indexOf('i') != -1; + unique = opts[1].indexOf('u') != -1; + var decimal = opts[1].indexOf('d') != -1 || opts[1].indexOf('n') != -1; + var hex = opts[1].indexOf('x') != -1; + var octal = opts[1].indexOf('o') != -1; + if (Number(decimal) + Number(hex) + Number(octal) > 1) { return 'Invalid arguments'; } + number = decimal && 'decimal' || hex && 'hex' || octal && 'octal'; + } + if (opts[2]) { + pattern = new RegExp(opts[2].substr(1, opts[2].length - 2), ignoreCase ? 'i' : ''); + } + } + } + var err = parseArgs(); + if (err) { + showConfirm(cm, err + ': ' + params.argString); + return; + } + var lineStart = params.line || cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + if (lineStart == lineEnd) { return; } + var curStart = new Pos(lineStart, 0); + var curEnd = new Pos(lineEnd, lineLength(cm, lineEnd)); + var text = cm.getRange(curStart, curEnd).split('\n'); + var numberRegex = + (number == 'decimal') ? /(-?)([\d]+)/ : + (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i : + (number == 'octal') ? /([0-7]+)/ : null; + var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : undefined; + var numPart = [], textPart = []; + if (number || pattern) { + for (var i = 0; i < text.length; i++) { + var matchPart = pattern ? text[i].match(pattern) : null; + if (matchPart && matchPart[0] != '') { + numPart.push(matchPart); + } else if (numberRegex && numberRegex.exec(text[i])) { + numPart.push(text[i]); + } else { + textPart.push(text[i]); + } + } + } else { + textPart = text; + } + /** @arg {string} a @arg {string} b */ + function compareFn(a, b) { + if (reverse) { var tmp; tmp = a; a = b; b = tmp; } + if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); } + var amatch = numberRegex && numberRegex.exec(a); + var bmatch = numberRegex && numberRegex.exec(b); + if (!amatch || !bmatch) { return a < b ? -1 : 1; } + var anum = parseInt((amatch[1] + amatch[2]).toLowerCase(), radix); + var bnum = parseInt((bmatch[1] + bmatch[2]).toLowerCase(), radix); + return anum - bnum; + } + /** @arg {string[]} a @arg {string[]} b */ + function comparePatternFn(a, b) { + if (reverse) { var tmp; tmp = a; a = b; b = tmp; } + if (ignoreCase) { a[0] = a[0].toLowerCase(); b[0] = b[0].toLowerCase(); } + return (a[0] < b[0]) ? -1 : 1; + } + // @ts-ignore + numPart.sort(pattern ? comparePatternFn : compareFn); + if (pattern) { + for (var i = 0; i < numPart.length; i++) { + // @ts-ignore + numPart[i] = numPart[i].input; + } + } else if (!number) { textPart.sort(compareFn); } + text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart); + if (unique) { // Remove duplicate lines + var textOld = text; + var lastLine; + text = []; + for (var i = 0; i < textOld.length; i++) { + if (textOld[i] != lastLine) { + text.push(textOld[i]); + } + lastLine = textOld[i]; + } + } + cm.replaceRange(text.join('\n'), curStart, curEnd); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + vglobal: function(cm, params) { + // global inspects params.commandName + this.global(cm, params); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + normal: function(cm, params) { + var argString = params.argString; + if (argString && argString[0] == '!') { + argString = argString.slice(1); + noremap = true; + } + argString = argString.trimStart(); + if (!argString) { + showConfirm(cm, 'Argument is required.'); + return; + } + var line = params.line; + if (typeof line == 'number') { + var lineEnd = isNaN(params.lineEnd) ? line : params.lineEnd; + for (var i = line; i <= lineEnd; i++) { + cm.setCursor(i, 0); + doKeyToKey(cm, params.argString.trimStart()); + if (cm.state.vim.insertMode) { + exitInsertMode(cm, true); + } + } + } else { + doKeyToKey(cm, params.argString.trimStart()); + if (cm.state.vim.insertMode) { + exitInsertMode(cm, true); + } + } + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + global: function(cm, params) { + // a global command is of the form + // :[range]g/pattern/[cmd] + // argString holds the string /pattern/[cmd] + var argString = params.argString; + if (!argString) { + showConfirm(cm, 'Regular Expression missing from global'); + return; + } + var inverted = params.commandName[0] === 'v'; + if (argString[0] === '!' && params.commandName[0] === 'g') { + inverted = true; + argString = argString.slice(1); + } + // range is specified here + var lineStart = (params.line !== undefined) ? params.line : cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + // get the tokens from argString + var tokens = splitBySlash(argString); + var regexPart = argString, cmd = ""; + if (tokens && tokens.length) { + regexPart = tokens[0]; + cmd = tokens.slice(1, tokens.length).join('/'); + } + if (regexPart) { + // If regex part is empty, then use the previous query. Otherwise + // use the regex part as the new query. + try { + updateSearchQuery(cm, regexPart, true /** ignoreCase */, + true /** smartCase */); + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + regexPart); + return; + } + } + // now that we have the regexPart, search for regex matches in the + // specified range of lines + var query = getSearchState(cm).getQuery(); + /**@type {(string|import("./types").LineHandle)[]}*/ + var matchedLines = []; + for (var i = lineStart; i <= lineEnd; i++) { + var line = cm.getLine(i); + var matched = query.test(line); + if (matched !== inverted) { + matchedLines.push(cmd ? cm.getLineHandle(i) : line); + } + } + // if there is no [cmd], just display the list of matched lines + if (!cmd) { + showConfirm(cm, matchedLines.join('\n')); + return; + } + var index = 0; + var nextCommand = function() { + if (index < matchedLines.length) { + var lineHandle = matchedLines[index++]; + var lineNum = cm.getLineNumber(lineHandle); + if (lineNum == null) { + nextCommand(); + return; + } + var command = (lineNum + 1) + cmd; + exCommandDispatcher.processCommand(cm, command, { + callback: nextCommand + }); + } else if (cm.releaseLineHandles) { + cm.releaseLineHandles(); + } + }; + nextCommand(); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + substitute: function(cm, params) { + if (!cm.getSearchCursor) { + throw new Error('Search feature not available. Requires searchcursor.js or ' + + 'any other getSearchCursor implementation.'); + } + var argString = params.argString; + var tokens = argString ? splitBySeparator(argString, argString[0]) : []; + var regexPart = '', replacePart = '', trailing, flagsPart, count; + var confirm = false; // Whether to confirm each replace. + var global = false; // True to replace all instances on a line, false to replace only 1. + if (tokens && tokens.length) { + regexPart = tokens[0]; + if (getOption('pcre') && regexPart !== '') { + regexPart = new RegExp(regexPart).source; //normalize not escaped characters + } + replacePart = tokens[1]; + if (replacePart !== undefined) { + if (getOption('pcre')) { + replacePart = unescapeRegexReplace(replacePart.replace(/([^\\])&/g,"$1$$&")); + } else { + replacePart = translateRegexReplace(replacePart); + } + vimGlobalState.lastSubstituteReplacePart = replacePart; + } + trailing = tokens[2] ? tokens[2].split(' ') : []; + } else { + // either the argString is empty or its of the form ' hello/world' + // actually splitBySlash returns a list of tokens + // only if the string starts with a '/' + if (argString && argString.length) { + showConfirm(cm, 'Substitutions should be of the form ' + + ':s/pattern/replace/'); + return; + } + } + // After the 3rd slash, we can have flags followed by a space followed + // by count. + if (trailing) { + flagsPart = trailing[0]; + count = parseInt(trailing[1]); + if (flagsPart) { + if (flagsPart.indexOf('c') != -1) { + confirm = true; + } + if (flagsPart.indexOf('g') != -1) { + global = true; + } + if (getOption('pcre')) { + regexPart = regexPart + '/' + flagsPart; + } else { + regexPart = regexPart.replace(/\//g, "\\/") + '/' + flagsPart; + } + } + } + if (regexPart) { + // If regex part is empty, then use the previous query. Otherwise use + // the regex part as the new query. + try { + updateSearchQuery(cm, regexPart, true /** ignoreCase */, + true /** smartCase */); + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + regexPart); + return; + } + } + replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart; + if (replacePart === undefined) { + showConfirm(cm, 'No previous substitute regular expression'); + return; + } + var state = getSearchState(cm); + var query = state.getQuery(); + var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line; + var lineEnd = params.lineEnd || lineStart; + if (lineStart == cm.firstLine() && lineEnd == cm.lastLine()) { + lineEnd = Infinity; + } + if (count) { + lineStart = lineEnd; + lineEnd = lineStart + count - 1; + } + var startPos = clipCursorToContent(cm, new Pos(lineStart, 0)); + var cursor = cm.getSearchCursor(query, startPos); + doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + startinsert: function(cm, params) { + doKeyToKey(cm, params.argString == '!' ? 'A' : 'i', {}); + }, + redo: CodeMirror.commands.redo, + undo: CodeMirror.commands.undo, + /** @arg {CodeMirrorV} cm */ + write: function(cm) { + if (CodeMirror.commands.save) { + CodeMirror.commands.save(cm); + } else if (cm.save) { + // Saves to text area if no save command is defined and cm.save() is available. + cm.save(); + } + }, + /** @arg {CodeMirrorV} cm */ + nohlsearch: function(cm) { + clearSearchHighlight(cm); + }, + /** @arg {CodeMirrorV} cm */ + yank: function (cm) { + var cur = copyCursor(cm.getCursor()); + var line = cur.line; + var lineText = cm.getLine(line); + vimGlobalState.registerController.pushText( + '0', 'yank', lineText, true, true); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + delete: function(cm, params) { + var line = params.selectionLine; + var lineEnd = isNaN(params.selectionLineEnd) ? line : params.selectionLineEnd; + operators.delete(cm, {linewise: true}, [ + { anchor: new Pos(line, 0), + head: new Pos(lineEnd + 1, 0) } + ]); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + join: function(cm, params) { + var line = params.selectionLine; + var lineEnd = isNaN(params.selectionLineEnd) ? line : params.selectionLineEnd; + cm.setCursor(new Pos(line, 0)); + actions.joinLines(cm, {repeat: lineEnd - line}, cm.state.vim); + }, + /** @arg {CodeMirrorV} cm @arg {ExParams} params*/ + delmarks: function(cm, params) { + if (!params.argString || !trim(params.argString)) { + showConfirm(cm, 'Argument required'); + return; + } + + var state = cm.state.vim; + var stream = new CodeMirror.StringStream(trim(params.argString)); + while (!stream.eol()) { + stream.eatSpace(); + + // Record the streams position at the beginning of the loop for use + // in error messages. + var count = stream.pos; + + if (!stream.match(/[a-zA-Z]/, false)) { + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); + return; + } + + var sym = stream.next(); + // Check if this symbol is part of a range + if (stream.match('-', true)) { + // This symbol is part of a range. + + // The range must terminate at an alphabetic character. + if (!stream.match(/[a-zA-Z]/, false)) { + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); + return; + } + + var startMark = sym; + var finishMark = stream.next(); + // The range must terminate at an alphabetic character which + // shares the same case as the start of the range. + if (startMark && finishMark && isLowerCase(startMark) == isLowerCase(finishMark)) { + var start = startMark.charCodeAt(0); + var finish = finishMark.charCodeAt(0); + if (start >= finish) { + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); + return; + } + + // Because marks are always ASCII values, and we have + // determined that they are the same case, we can use + // their char codes to iterate through the defined range. + for (var j = 0; j <= finish - start; j++) { + var mark = String.fromCharCode(start + j); + delete state.marks[mark]; + } + } else { + showConfirm(cm, 'Invalid argument: ' + startMark + '-'); + return; + } + } else if (sym) { + // This symbol is a valid mark, and is not part of a range. + delete state.marks[sym]; + } + } + } + }; + + var exCommandDispatcher = new ExCommandDispatcher(); + +/** + * @arg {CodeMirrorV} cm CodeMirror instance we are in. + * @arg {boolean} confirm Whether to confirm each replace. + * @arg {boolean} global + * @arg {number} lineStart Line to start replacing from. + * @arg {number} lineEnd Line to stop replacing at. + * @arg {RegExp} query Query for performing matches with. + * @arg {string} replaceWith Text to replace matches with. May contain $1, + * $2, etc for replacing captured groups using JavaScript replace. + * @arg {function} [callback] A callback for when the replace is done. + */ + function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query, + replaceWith, callback) { + // Set up all the functions. + cm.state.vim.exMode = true; + var done = false; + + /** @type {Pos}*/ var lastPos; + /** @type {number}*/ var modifiedLineNumber; + /** @type {boolean}*/var joined; + function replaceAll() { + cm.operation(function() { + while (!done) { + replace(); + next(); + } + stop(); + }); + } + function replace() { + var text = cm.getRange(searchCursor.from(), searchCursor.to()); + var newText = text.replace(query, replaceWith); + var unmodifiedLineNumber = searchCursor.to().line; + searchCursor.replace(newText); + modifiedLineNumber = searchCursor.to().line; + lineEnd += modifiedLineNumber - unmodifiedLineNumber; + joined = modifiedLineNumber < unmodifiedLineNumber; + } + function findNextValidMatch() { + var lastMatchTo = lastPos && copyCursor(searchCursor.to()); + var match = searchCursor.findNext(); + if (match && !match[0] && lastMatchTo && cursorEqual(searchCursor.from(), lastMatchTo)) { + match = searchCursor.findNext(); + } + return match; + } + function next() { + // The below only loops to skip over multiple occurrences on the same + // line when 'global' is not true. + while(findNextValidMatch() && + isInRange(searchCursor.from(), lineStart, lineEnd)) { + if (!global && searchCursor.from().line == modifiedLineNumber && !joined) { + continue; + } + cm.scrollIntoView(searchCursor.from(), 30); + cm.setSelection(searchCursor.from(), searchCursor.to()); + lastPos = searchCursor.from(); + done = false; + return; + } + done = true; + } + /** @arg {(() => void) | undefined} [close] */ + function stop(close) { + if (close) { close(); } + cm.focus(); + if (lastPos) { + cm.setCursor(lastPos); + var vim = cm.state.vim; + vim.exMode = false; + vim.lastHPos = vim.lastHSPos = lastPos.ch; + } + if (callback) { callback(); } + } + /** @arg {KeyboardEvent} e @arg {any} _value @arg {any} close */ + function onPromptKeyDown(e, _value, close) { + // Swallow all keys. + CodeMirror.e_stop(e); + var keyName = vimKeyFromEvent(e); + switch (keyName) { + case 'y': + replace(); next(); break; + case 'n': + next(); break; + case 'a': + // replaceAll contains a call to close of its own. We don't want it + // to fire too early or multiple times. + var savedCallback = callback; + callback = undefined; + cm.operation(replaceAll); + callback = savedCallback; + break; + case 'l': + replace(); + // fall through and exit. + case 'q': + case '': + case '': + case '': + stop(close); + break; + } + if (done) { stop(close); } + return true; + } + + // Actually do replace. + next(); + if (done) { + showConfirm(cm, 'No matches for ' + query.source); + return; + } + if (!confirm) { + replaceAll(); + if (callback) { callback(); } + return; + } + showPrompt(cm, { + prefix: dom('span', 'replace with ', dom('strong', replaceWith), ' (y/n/a/q/l)'), + onKeyDown: onPromptKeyDown + }); + } + + /** @arg {CodeMirrorV} cm @arg {boolean} [keepCursor] */ + function exitInsertMode(cm, keepCursor) { + var vim = cm.state.vim; + var macroModeState = vimGlobalState.macroModeState; + var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.'); + var isPlaying = macroModeState.isPlaying; + var lastChange = macroModeState.lastInsertModeChanges; + if (!isPlaying) { + cm.off('change', onChange); + if (vim.insertEnd) vim.insertEnd.clear(); + vim.insertEnd = undefined; + CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); + } + if (!isPlaying && vim.insertModeRepeat > 1) { + // Perform insert mode repeat for commands like 3,a and 3,o. + repeatLastEdit(cm, vim, vim.insertModeRepeat - 1, + true /** repeatForInsert */); + // @ts-ignore + vim.lastEditInputState.repeatOverride = vim.insertModeRepeat; + } + delete vim.insertModeRepeat; + vim.insertMode = false; + if (!keepCursor) { + cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1); + } + cm.setOption('keyMap', 'vim'); + cm.setOption('disableInput', true); + cm.toggleOverwrite(false); // exit replace mode if we were in it. + // update the ". register before exiting insert mode + insertModeChangeRegister.setText(lastChange.changes.join('')); + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + if (macroModeState.isRecording) { + logInsertModeChange(macroModeState); + } + } + + /** @arg {vimKey} command*/ + function _mapCommand(command) { + defaultKeymap.unshift(command); + } + + /** + * @arg {string} keys + * @arg {string} type + * @arg {string} name + * @arg {any} args + * @arg {{ [x: string]: any; }} extra + **/ + function mapCommand(keys, type, name, args, extra) { + /**@type{any} */ + var command = {keys: keys, type: type}; + command[type] = name; + command[type + "Args"] = args; + for (var key in extra) + command[key] = extra[key]; + _mapCommand(command); + } + + // The timeout in milliseconds for the two-character ESC keymap should be + // adjusted according to your typing speed to prevent false positives. + defineOption('insertModeEscKeysTimeout', 200, 'number'); + + + /** + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {MacroModeState} macroModeState + * @arg {string} registerName + */ + function executeMacroRegister(cm, vim, macroModeState, registerName) { + var register = vimGlobalState.registerController.getRegister(registerName); + if (registerName == ':') { + // Read-only register containing last Ex command. + if (register.keyBuffer[0]) { + exCommandDispatcher.processCommand(cm, register.keyBuffer[0]); + } + macroModeState.isPlaying = false; + return; + } + var keyBuffer = register.keyBuffer; + var imc = 0; + macroModeState.isPlaying = true; + macroModeState.replaySearchQueries = register.searchQueries.slice(0); + for (var i = 0; i < keyBuffer.length; i++) { + var text = keyBuffer[i]; + var match, key; + var keyRe = /<(?:[CSMA]-)*\w+>|./gi; + while ((match = keyRe.exec(text))) { + // Pull off one command key, which is either a single character + // or a special sequence wrapped in '<' and '>', e.g. ''. + key = match[0]; + vimApi.handleKey(cm, key, 'macro'); + if (vim.insertMode) { + var changes = register.insertModeChanges[imc++].changes; + vimGlobalState.macroModeState.lastInsertModeChanges.changes = changes; + repeatInsertModeChanges(cm, changes, 1); + exitInsertMode(cm); + } + } + } + macroModeState.isPlaying = false; + } + + /** @arg {MacroModeState} macroModeState @arg {any} key */ + function logKey(macroModeState, key) { + if (macroModeState.isPlaying) { return; } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register) { + register.pushText(key); + } + } + + /** @arg {MacroModeState} macroModeState */ + function logInsertModeChange(macroModeState) { + if (macroModeState.isPlaying) { return; } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register && register.pushInsertModeChanges) { + register.pushInsertModeChanges(macroModeState.lastInsertModeChanges); + } + } + + /** @arg {MacroModeState} macroModeState @arg {string} query */ + function logSearchQuery(macroModeState, query) { + if (macroModeState.isPlaying) { return; } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register && register.pushSearchQuery) { + register.pushSearchQuery(query); + } + } + + /** + * Listens for changes made in insert mode. + * Should only be active in insert mode. + * @arg {CodeMirror} cm + * @arg {{ origin: string | undefined; text: any[]; next: any; }} changeObj + */ + function onChange(cm, changeObj) { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + if (!macroModeState.isPlaying) { + var vim = cm.state.vim; + while(changeObj) { + lastChange.expectCursorActivityForChange = true; + // @ts-ignore + if (lastChange.ignoreCount > 1) { + // @ts-ignore + lastChange.ignoreCount--; + } else if (changeObj.origin == '+input' || changeObj.origin == 'paste' + || changeObj.origin === undefined /* only in testing */) { + var selectionCount = cm.listSelections().length; + if (selectionCount > 1) + lastChange.ignoreCount = selectionCount; + var text = changeObj.text.join('\n'); + if (lastChange.maybeReset) { + lastChange.changes = []; + lastChange.maybeReset = false; + } + if (text) { + if (cm.state.overwrite && !/\n/.test(text)) { + lastChange.changes.push([text]); + } else { + if (text.length > 1) { + var insertEnd = vim && vim.insertEnd && vim.insertEnd.find(); + var cursor = cm.getCursor(); + if (insertEnd && insertEnd.line == cursor.line) { + var offset = insertEnd.ch - cursor.ch; + if (offset > 0 && offset < text.length) { + lastChange.changes.push([text, offset]); + text = ''; + } + } + } + if (text) lastChange.changes.push(text); + } + } + } + // Change objects may be chained with next. + changeObj = changeObj.next; + } + } + } + + /** + * Listens for any kind of cursor activity on CodeMirror. + * @arg {CodeMirrorV} cm + */ + function onCursorActivity(cm) { + var vim = cm.state.vim; + if (vim.insertMode) { + // Tracking cursor activity in insert mode (for macro support). + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { return; } + var lastChange = macroModeState.lastInsertModeChanges; + if (lastChange.expectCursorActivityForChange) { + lastChange.expectCursorActivityForChange = false; + } else { + // Cursor moved outside the context of an edit. Reset the change. + lastChange.maybeReset = true; + if (vim.insertEnd) vim.insertEnd.clear(); + vim.insertEnd = cm.setBookmark(cm.getCursor(), {insertLeft: true}); + } + } else if (!cm.curOp?.isVimOp) { + handleExternalSelection(cm, vim); + } + } + /** @arg {CodeMirrorV} cm @arg {vimState} vim */ + function handleExternalSelection(cm, vim) { + var anchor = cm.getCursor('anchor'); + var head = cm.getCursor('head'); + // Enter or exit visual mode to match mouse selection. + if (vim.visualMode && !cm.somethingSelected()) { + exitVisualMode(cm, false); + } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) { + vim.visualMode = true; + vim.visualLine = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); + } + if (vim.visualMode) { + // Bind CodeMirror selection model to vim selection model. + // Mouse selections are considered visual characterwise. + var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0; + var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0; + head = offsetCursor(head, 0, headOffset); + anchor = offsetCursor(anchor, 0, anchorOffset); + vim.sel = { + anchor: anchor, + head: head + }; + updateMark(cm, vim, '<', cursorMin(head, anchor)); + updateMark(cm, vim, '>', cursorMax(head, anchor)); + } else if (!vim.insertMode) { + // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse. + vim.lastHPos = cm.getCursor().ch; + } + } + + /** + * Wrapper for special keys pressed in insert mode + * @arg {string} keyName + */ + function InsertModeKey(keyName, e) { + this.keyName = keyName; + this.key = e.key; + this.ctrlKey = e.ctrlKey; + this.altKey = e.altKey; + this.metaKey = e.metaKey; + this.shiftKey = e.shiftKey; + } + + /** + * Handles raw key down events from the text area. + * - Should only be active in insert mode. + * - For recording deletes in insert mode. + * @arg {KeyboardEvent} e + */ + function onKeyEventTargetKeyDown(e) { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + var keyName = CodeMirror.keyName ? CodeMirror.keyName(e) : e.key; + if (!keyName) { return; } + + if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) { + if (lastChange.maybeReset) { + lastChange.changes = []; + lastChange.maybeReset = false; + } + lastChange.changes.push(new InsertModeKey(keyName, e)); + } + } + + /** + * Repeats the last edit, which includes exactly 1 command and at most 1 + * insert. Operator and motion commands are read from lastEditInputState, + * while action commands are read from lastEditActionCommand. + * + * If repeatForInsert is true, then the function was called by + * exitInsertMode to repeat the insert mode changes the user just made. The + * corresponding enterInsertMode call was made with a count. + * @arg {CodeMirrorV} cm + * @arg {vimState} vim + * @arg {number} repeat + * @arg {boolean} repeatForInsert + */ + function repeatLastEdit(cm, vim, repeat, repeatForInsert) { + var macroModeState = vimGlobalState.macroModeState; + macroModeState.isPlaying = true; + var lastAction = vim.lastEditActionCommand; + var cachedInputState = vim.inputState; + function repeatCommand() { + if (lastAction) { + commandDispatcher.processAction(cm, vim, lastAction); + } else { + commandDispatcher.evalInput(cm, vim); + } + } + /** @arg {number} repeat */ + function repeatInsert(repeat) { + if (macroModeState.lastInsertModeChanges.changes.length > 0) { + // For some reason, repeat cw in desktop VIM does not repeat + // insert mode changes. Will conform to that behavior. + repeat = !vim.lastEditActionCommand ? 1 : repeat; + var changeObject = macroModeState.lastInsertModeChanges; + repeatInsertModeChanges(cm, changeObject.changes, repeat); + } + } + // @ts-ignore + vim.inputState = vim.lastEditInputState; + if (lastAction && lastAction.interlaceInsertRepeat) { + // o and O repeat have to be interlaced with insert repeats so that the + // insertions appear on separate lines instead of the last line. + for (var i = 0; i < repeat; i++) { + repeatCommand(); + repeatInsert(1); + } + } else { + if (!repeatForInsert) { + // Hack to get the cursor to end up at the right place. If I is + // repeated in insert mode repeat, cursor will be 1 insert + // change set left of where it should be. + repeatCommand(); + } + repeatInsert(repeat); + } + vim.inputState = cachedInputState; + if (vim.insertMode && !repeatForInsert) { + // Don't exit insert mode twice. If repeatForInsert is set, then we + // were called by an exitInsertMode call lower on the stack. + exitInsertMode(cm); + } + macroModeState.isPlaying = false; + } + /**@arg {CodeMirrorV} cm, @arg {string} key */ + function sendCmKey(cm, key) { + CodeMirror.lookupKey(key, 'vim-insert', function keyHandler(binding) { + if (typeof binding == 'string') { + CodeMirror.commands[binding](cm); + } else { + binding(cm); + } + return true; + }); + } + function repeatInsertModeChanges(cm, changes, repeat) { + var head = cm.getCursor('head'); + var visualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.visualBlock; + if (visualBlock) { + // Set up block selection again for repeating the changes. + selectForInsert(cm, head, visualBlock + 1); + repeat = cm.listSelections().length; + cm.setCursor(head); + } + for (var i = 0; i < repeat; i++) { + if (visualBlock) { + cm.setCursor(offsetCursor(head, i, 0)); + } + for (var j = 0; j < changes.length; j++) { + var change = changes[j]; + if (change instanceof InsertModeKey) { + sendCmKey(cm, change.keyName); + } else if (typeof change == "string") { + cm.replaceSelection(change); + } else { + var start = cm.getCursor(); + var end = offsetCursor(start, 0, change[0].length - (change[1] || 0)); + cm.replaceRange(change[0], start, change[1] ? start: end); + cm.setCursor(end); + } + } + } + if (visualBlock) { + cm.setCursor(offsetCursor(head, 0, 1)); + } + } + + // multiselect support + /** @arg {vimState} state */ + function cloneVimState(state) { + var n = new state.constructor(); + Object.keys(state).forEach(function(key) { + if (key == "insertEnd") return; + var o = state[key]; + if (Array.isArray(o)) + o = o.slice(); + else if (o && typeof o == "object" && o.constructor != Object) + o = cloneVimState(o); + n[key] = o; + }); + if (state.sel) { + n.sel = { + head: state.sel.head && copyCursor(state.sel.head), + anchor: state.sel.anchor && copyCursor(state.sel.anchor) + }; + } + return n; + } + /** @arg {CodeMirror} cm_ @arg {string} key @arg {string} origin */ + function multiSelectHandleKey(cm_, key, origin) { + var vim = maybeInitVimState(cm_); + var cm = /**@type {CodeMirrorV}*/(cm_); + /** @type {boolean | undefined} */ + var isHandled = false; + var vim = vimApi.maybeInitVimState_(cm); + var visualBlock = vim.visualBlock || vim.wasInVisualBlock; + + if (cm.state.closeVimNotification) { + var close = cm.state.closeVimNotification; + cm.state.closeVimNotification = null; + close(); + if (key == '') { + clearInputState(cm); + return true; + } + } + + var wasMultiselect = cm.isInMultiSelectMode(); + if (vim.wasInVisualBlock && !wasMultiselect) { + vim.wasInVisualBlock = false; + } else if (wasMultiselect && vim.visualBlock) { + vim.wasInVisualBlock = true; + } + + if (key == '' && !vim.insertMode && !vim.visualMode && wasMultiselect && vim.status == "") { + // allow editor to exit multiselect + clearInputState(cm); + // @ts-ignore + } else if (visualBlock || !wasMultiselect || cm.inVirtualSelectionMode) { + isHandled = vimApi.handleKey(cm, key, origin); + } else { + var old = cloneVimState(vim); + var changeQueueList = vim.inputState.changeQueueList || []; + + cm.operation(function() { + if (cm.curOp) + cm.curOp.isVimOp = true; + var index = 0; + cm.forEachSelection(function() { + cm.state.vim.inputState.changeQueue = changeQueueList[index]; + var head = cm.getCursor("head"); + var anchor = cm.getCursor("anchor"); + var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0; + var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0; + head = offsetCursor(head, 0, headOffset); + anchor = offsetCursor(anchor, 0, anchorOffset); + cm.state.vim.sel.head = head; + cm.state.vim.sel.anchor = anchor; + + isHandled = vimApi.handleKey(cm, key, origin); + if (cm.virtualSelection) { + changeQueueList[index] = cm.state.vim.inputState.changeQueue; + cm.state.vim = cloneVimState(old); + } + index++; + }); + if (cm.curOp?.cursorActivity && !isHandled) + cm.curOp.cursorActivity = false; + cm.state.vim = vim; + vim.inputState.changeQueueList = changeQueueList; + vim.inputState.changeQueue = null; + }, true); + } + // some commands may bring visualMode and selection out of sync + if (isHandled && !vim.visualMode && !vim.insert && vim.visualMode != cm.somethingSelected()) { + handleExternalSelection(cm, vim); + } + return isHandled; + } + resetVimGlobalState(); + + return vimApi; +} + +function indexFromPos(doc, pos) { + var ch = pos.ch; + var lineNumber = pos.line + 1; + if (lineNumber < 1) { + lineNumber = 1; + ch = 0; + } + if (lineNumber > doc.lines) { + lineNumber = doc.lines; + ch = Number.MAX_VALUE; + } + var line = doc.line(lineNumber); + return Math.min(line.from + Math.max(0, ch), line.to); +} +function posFromIndex(doc, offset) { + let line = doc.lineAt(offset); + return { line: line.number - 1, ch: offset - line.from }; +} +class Pos { + constructor(line, ch) { + this.line = line; + this.ch = ch; + } +} +function on(emitter, type, f) { + if (emitter.addEventListener) { + emitter.addEventListener(type, f, false); + } + else { + var map = emitter._handlers || (emitter._handlers = {}); + map[type] = (map[type] || []).concat(f); + } +} +function off(emitter, type, f) { + if (emitter.removeEventListener) { + emitter.removeEventListener(type, f, false); + } + else { + var map = emitter._handlers, arr = map && map[type]; + if (arr) { + var index = arr.indexOf(f); + if (index > -1) { + map[type] = arr.slice(0, index).concat(arr.slice(index + 1)); + } + } + } +} +function signal(emitter, type, ...args) { + var _a; + var handlers = (_a = emitter._handlers) === null || _a === void 0 ? void 0 : _a[type]; + if (!handlers) + return; + for (var i = 0; i < handlers.length; ++i) { + handlers[i](...args); + } +} +function signalTo(handlers, ...args) { + if (!handlers) + return; + for (var i = 0; i < handlers.length; ++i) { + handlers[i](...args); + } +} +let wordChar; +try { + wordChar = /*@__PURE__*/new RegExp("[\\w\\p{Alphabetic}\\p{Number}_]", "u"); +} +catch (_) { + wordChar = /[\w]/; +} +// workaround for missing api for merging transactions +function dispatchChange(cm, transaction) { + var view = cm.cm6; + if (view.state.readOnly) + return; + var type = "input.type.compose"; + if (cm.curOp) { + if (!cm.curOp.lastChange) + type = "input.type.compose.start"; + } + if (transaction.annotations) { + try { + transaction.annotations.some(function (note) { + if (note.value == "input") + note.value = type; + }); + } + catch (e) { + console.error(e); + } + } + else { + transaction.userEvent = type; + } + return view.dispatch(transaction); +} +function runHistoryCommand(cm, revert) { + var _a; + if (cm.curOp) { + cm.curOp.$changeStart = undefined; + } + (revert ? undo : redo)(cm.cm6); + let changeStartIndex = (_a = cm.curOp) === null || _a === void 0 ? void 0 : _a.$changeStart; + // vim mode expects the changed text to be either selected or cursor placed at the start + if (changeStartIndex != null) { + cm.cm6.dispatch({ selection: { anchor: changeStartIndex } }); + } +} +var keys = {}; +/*@__PURE__*/"Left|Right|Up|Down|Backspace|Delete".split("|").forEach(key => { + keys[key] = (cm) => runScopeHandlers(cm.cm6, { key: key }, "editor"); +}); +class CodeMirror { + // -------------------------- + openDialog(template, callback, options) { + return openDialog(this, template, callback, options); + } + ; + openNotification(template, options) { + return openNotification(this, template, options); + } + ; + constructor(cm6) { + this.state = {}; + this.marks = Object.create(null); + this.$mid = 0; // marker id counter + this.options = {}; + this._handlers = {}; + this.$lastChangeEndOffset = 0; + this.virtualSelection = null; + this.cm6 = cm6; + this.onChange = this.onChange.bind(this); + this.onSelectionChange = this.onSelectionChange.bind(this); + } + ; + on(type, f) { on(this, type, f); } + off(type, f) { off(this, type, f); } + signal(type, e, handlers) { signal(this, type, e, handlers); } + indexFromPos(pos) { + return indexFromPos(this.cm6.state.doc, pos); + } + ; + posFromIndex(offset) { + return posFromIndex(this.cm6.state.doc, offset); + } + ; + foldCode(pos) { + let view = this.cm6; + let ranges = view.state.selection.ranges; + let doc = this.cm6.state.doc; + let index = indexFromPos(doc, pos); + let tmpRanges = EditorSelection.create([EditorSelection.range(index, index)], 0).ranges; + view.state.selection.ranges = tmpRanges; + foldCode(view); + view.state.selection.ranges = ranges; + } + firstLine() { return 0; } + ; + lastLine() { return this.cm6.state.doc.lines - 1; } + ; + lineCount() { return this.cm6.state.doc.lines; } + ; + setCursor(line, ch) { + if (typeof line === 'object') { + ch = line.ch; + line = line.line; + } + var offset = indexFromPos(this.cm6.state.doc, { line, ch: ch || 0 }); + this.cm6.dispatch({ selection: { anchor: offset } }, { scrollIntoView: !this.curOp }); + if (this.curOp && !this.curOp.isVimOp) + this.onBeforeEndOperation(); + } + ; + getCursor(p) { + var sel = this.cm6.state.selection.main; + var offset = p == "head" || !p + ? sel.head + : p == "anchor" + ? sel.anchor + : p == "start" + ? sel.from + : p == "end" + ? sel.to + : null; + if (offset == null) + throw new Error("Invalid cursor type"); + return this.posFromIndex(offset); + } + ; + listSelections() { + var doc = this.cm6.state.doc; + return this.cm6.state.selection.ranges.map(r => { + return { + anchor: posFromIndex(doc, r.anchor), + head: posFromIndex(doc, r.head), + }; + }); + } + ; + setSelections(p, primIndex) { + var doc = this.cm6.state.doc; + var ranges = p.map(x => { + var head = indexFromPos(doc, x.head); + var anchor = indexFromPos(doc, x.anchor); + // workaround for codemirror bug, see https://github.com/replit/codemirror-vim/issues/169 + if (head == anchor) + return EditorSelection.cursor(head, 1); + return EditorSelection.range(anchor, head); + }); + this.cm6.dispatch({ + selection: EditorSelection.create(ranges, primIndex) + }); + } + ; + setSelection(anchor, head, options) { + this.setSelections([{ anchor, head }], 0); + if (options && options.origin == '*mouse') { + this.onBeforeEndOperation(); + } + } + ; + getLine(row) { + var doc = this.cm6.state.doc; + if (row < 0 || row >= doc.lines) + return ""; + return this.cm6.state.doc.line(row + 1).text; + } + ; + getLineHandle(row) { + if (!this.$lineHandleChanges) + this.$lineHandleChanges = []; + return { row: row, index: this.indexFromPos(new Pos(row, 0)) }; + } + getLineNumber(handle) { + var updates = this.$lineHandleChanges; + if (!updates) + return null; + var offset = handle.index; + for (var i = 0; i < updates.length; i++) { + offset = updates[i].changes.mapPos(offset, 1, MapMode.TrackAfter); + if (offset == null) + return null; + } + var pos = this.posFromIndex(offset); + return pos.ch == 0 ? pos.line : null; + } + releaseLineHandles() { + this.$lineHandleChanges = undefined; + } + getRange(s, e) { + var doc = this.cm6.state.doc; + return this.cm6.state.sliceDoc(indexFromPos(doc, s), indexFromPos(doc, e)); + } + ; + replaceRange(text, s, e, source) { + if (!e) + e = s; + var doc = this.cm6.state.doc; + var from = indexFromPos(doc, s); + var to = indexFromPos(doc, e); + dispatchChange(this, { changes: { from, to, insert: text } }); + } + ; + replaceSelection(text) { + dispatchChange(this, this.cm6.state.replaceSelection(text)); + } + ; + replaceSelections(replacements) { + var ranges = this.cm6.state.selection.ranges; + var changes = ranges.map((r, i) => { + return { from: r.from, to: r.to, insert: replacements[i] || "" }; + }); + dispatchChange(this, { changes }); + } + ; + getSelection() { + return this.getSelections().join("\n"); + } + ; + getSelections() { + var cm = this.cm6; + return cm.state.selection.ranges.map(r => cm.state.sliceDoc(r.from, r.to)); + } + ; + somethingSelected() { + return this.cm6.state.selection.ranges.some(r => !r.empty); + } + ; + getInputField() { + return this.cm6.contentDOM; + } + ; + clipPos(p) { + var doc = this.cm6.state.doc; + var ch = p.ch; + var lineNumber = p.line + 1; + if (lineNumber < 1) { + lineNumber = 1; + ch = 0; + } + if (lineNumber > doc.lines) { + lineNumber = doc.lines; + ch = Number.MAX_VALUE; + } + var line = doc.line(lineNumber); + ch = Math.min(Math.max(0, ch), line.to - line.from); + return new Pos(lineNumber - 1, ch); + } + ; + getValue() { + return this.cm6.state.doc.toString(); + } + ; + setValue(text) { + var cm = this.cm6; + return cm.dispatch({ + changes: { from: 0, to: cm.state.doc.length, insert: text }, + selection: EditorSelection.range(0, 0) + }); + } + ; + focus() { + return this.cm6.focus(); + } + ; + blur() { + return this.cm6.contentDOM.blur(); + } + ; + defaultTextHeight() { + return this.cm6.defaultLineHeight; + } + ; + findMatchingBracket(pos, _options) { + var state = this.cm6.state; + var offset = indexFromPos(state.doc, pos); + var m = matchBrackets(state, offset + 1, -1); + if (m && m.end) { + return { to: posFromIndex(state.doc, m.end.from) }; + } + m = matchBrackets(state, offset, 1); + if (m && m.end) { + return { to: posFromIndex(state.doc, m.end.from) }; + } + return { to: undefined }; + } + ; + scanForBracket(pos, dir, style, config) { + return scanForBracket(this, pos, dir, style, config); + } + ; + indentLine(line, more) { + // todo how to indent only one line instead of selection + if (more) + this.indentMore(); + else + this.indentLess(); + } + ; + indentMore() { + indentMore(this.cm6); + } + ; + indentLess() { + indentLess(this.cm6); + } + ; + execCommand(name) { + if (name == "indentAuto") + CodeMirror.commands.indentAuto(this); + else if (name == "goLineLeft") + cursorLineBoundaryBackward(this.cm6); + else if (name == "goLineRight") { + cursorLineBoundaryForward(this.cm6); + let state = this.cm6.state; + let cur = state.selection.main.head; + if (cur < state.doc.length && state.sliceDoc(cur, cur + 1) !== "\n") { + cursorCharBackward(this.cm6); + } + } + else + console.log(name + " is not implemented"); + } + ; + setBookmark(cursor, options) { + var assoc = (options === null || options === void 0 ? void 0 : options.insertLeft) ? 1 : -1; + var offset = this.indexFromPos(cursor); + var bm = new Marker(this, offset, assoc); + return bm; + } + ; + addOverlay({ query }) { + let cm6Query = new SearchQuery({ + regexp: true, + search: query.source, + caseSensitive: !/i/.test(query.flags), + }); + if (cm6Query.valid) { + cm6Query.forVim = true; + this.cm6Query = cm6Query; + let effect = setSearchQuery.of(cm6Query); + this.cm6.dispatch({ effects: effect }); + return cm6Query; + } + } + ; + removeOverlay(overlay) { + if (!this.cm6Query) + return; + this.cm6Query.forVim = false; + let effect = setSearchQuery.of(this.cm6Query); + this.cm6.dispatch({ effects: effect }); + } + ; + getSearchCursor(query, pos) { + var cm = this; + var last = null; + var lastCM5Result = null; + if (pos.ch == undefined) + pos.ch = Number.MAX_VALUE; + var firstOffset = indexFromPos(cm.cm6.state.doc, pos); + var source = query.source.replace(/(\\.|{(?:\d+(?:,\d*)?|,\d+)})|[{}]/g, function (a, b) { + if (!b) + return "\\" + a; + return b; + }); + function rCursor(doc, from = 0, to = doc.length) { + return new RegExpCursor(doc, source, { ignoreCase: query.ignoreCase }, from, to); + } + function nextMatch(from) { + var doc = cm.cm6.state.doc; + if (from > doc.length) + return null; + let res = rCursor(doc, from).next(); + return res.done ? null : res.value; + } + var ChunkSize = 10000; + function prevMatchInRange(from, to) { + var doc = cm.cm6.state.doc; + for (let size = 1;; size++) { + let start = Math.max(from, to - size * ChunkSize); + let cursor = rCursor(doc, start, to), range = null; + while (!cursor.next().done) + range = cursor.value; + if (range && (start == from || range.from > start + 10)) + return range; + if (start == from) + return null; + } + } + return { + findNext: function () { return this.find(false); }, + findPrevious: function () { return this.find(true); }, + find: function (back) { + var doc = cm.cm6.state.doc; + if (back) { + let endAt = last ? (last.from == last.to ? last.to - 1 : last.from) : firstOffset; + last = prevMatchInRange(0, endAt); + } + else { + let startFrom = last ? (last.from == last.to ? last.to + 1 : last.to) : firstOffset; + last = nextMatch(startFrom); + } + lastCM5Result = last && { + from: posFromIndex(doc, last.from), + to: posFromIndex(doc, last.to), + match: last.match, + }; + return last && last.match; + }, + from: function () { return lastCM5Result === null || lastCM5Result === void 0 ? void 0 : lastCM5Result.from; }, + to: function () { return lastCM5Result === null || lastCM5Result === void 0 ? void 0 : lastCM5Result.to; }, + replace: function (text) { + if (last) { + dispatchChange(cm, { + changes: { from: last.from, to: last.to, insert: text } + }); + last.to = last.from + text.length; + if (lastCM5Result) { + lastCM5Result.to = posFromIndex(cm.cm6.state.doc, last.to); + } + } + } + }; + } + ; + findPosV(start, amount, unit, goalColumn) { + let { cm6 } = this; + const doc = cm6.state.doc; + let pixels = unit == 'page' ? cm6.dom.clientHeight : 0; + const startOffset = indexFromPos(doc, start); + let range = EditorSelection.cursor(startOffset, 1, undefined, goalColumn); + let count = Math.round(Math.abs(amount)); + for (let i = 0; i < count; i++) { + if (unit == 'page') { + range = cm6.moveVertically(range, amount > 0, pixels); + } + else if (unit == 'line') { + range = cm6.moveVertically(range, amount > 0); + } + } + let pos = posFromIndex(doc, range.head); + // set hitside to true if there was no place to move and cursor was clipped to the edge + // of document. Needed for gj/gk + if ((amount < 0 && + range.head == 0 && goalColumn != 0 && + start.line == 0 && start.ch != 0) || (amount > 0 && + range.head == doc.length && pos.ch != goalColumn + && start.line == pos.line)) { + pos.hitSide = true; + } + return pos; + } + ; + charCoords(pos, mode) { + var rect = this.cm6.contentDOM.getBoundingClientRect(); + var offset = indexFromPos(this.cm6.state.doc, pos); + var coords = this.cm6.coordsAtPos(offset); + var d = -rect.top; + return { left: ((coords === null || coords === void 0 ? void 0 : coords.left) || 0) - rect.left, top: ((coords === null || coords === void 0 ? void 0 : coords.top) || 0) + d, bottom: ((coords === null || coords === void 0 ? void 0 : coords.bottom) || 0) + d }; + } + ; + coordsChar(coords, mode) { + var rect = this.cm6.contentDOM.getBoundingClientRect(); + var offset = this.cm6.posAtCoords({ x: coords.left + rect.left, y: coords.top + rect.top }) || 0; + return posFromIndex(this.cm6.state.doc, offset); + } + ; + getScrollInfo() { + var scroller = this.cm6.scrollDOM; + return { + left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight, + width: scroller.scrollWidth, + clientHeight: scroller.clientHeight, clientWidth: scroller.clientWidth + }; + } + ; + scrollTo(x, y) { + if (x != null) + this.cm6.scrollDOM.scrollLeft = x; + if (y != null) + this.cm6.scrollDOM.scrollTop = y; + } + ; + scrollIntoView(pos, margin) { + if (pos) { + var offset = this.indexFromPos(pos); + this.cm6.dispatch({ + effects: EditorView.scrollIntoView(offset) + }); + } + else { + this.cm6.dispatch({ scrollIntoView: true, userEvent: "scroll" }); + } + } + ; + getWrapperElement() { + return this.cm6.dom; + } + ; + // for tests + getMode() { + return { name: this.getOption("mode") }; + } + ; + setSize(w, h) { + this.cm6.dom.style.width = w + 4 + "px"; + this.cm6.dom.style.height = h + "px"; + this.refresh(); + } + refresh() { + this.cm6.measure(); + } + // event listeners + destroy() { + this.removeOverlay(); + } + ; + getLastEditEnd() { + return this.posFromIndex(this.$lastChangeEndOffset); + } + ; + onChange(update) { + if (this.$lineHandleChanges) { + this.$lineHandleChanges.push(update); + } + for (let i in this.marks) { + let m = this.marks[i]; + m.update(update.changes); + } + if (this.virtualSelection) { + this.virtualSelection.ranges = this.virtualSelection.ranges.map(range => range.map(update.changes)); + } + var curOp = this.curOp = this.curOp || {}; + update.changes.iterChanges((fromA, toA, fromB, toB, text) => { + if (curOp.$changeStart == null || curOp.$changeStart > fromB) + curOp.$changeStart = fromB; + this.$lastChangeEndOffset = toB; + var change = { text: text.toJSON() }; + if (!curOp.lastChange) { + curOp.lastChange = curOp.change = change; + } + else { + curOp.lastChange.next = curOp.lastChange = change; + } + }, true); + if (!curOp.changeHandlers) + curOp.changeHandlers = this._handlers["change"] && this._handlers["change"].slice(); + } + ; + onSelectionChange() { + var curOp = this.curOp = this.curOp || {}; + if (!curOp.cursorActivityHandlers) + curOp.cursorActivityHandlers = this._handlers["cursorActivity"] && this._handlers["cursorActivity"].slice(); + this.curOp.cursorActivity = true; + } + ; + operation(fn, force) { + if (!this.curOp) + this.curOp = { $d: 0 }; + this.curOp.$d++; + try { + var result = fn(); + } + finally { + if (this.curOp) { + this.curOp.$d--; + if (!this.curOp.$d) + this.onBeforeEndOperation(); + } + } + return result; + } + ; + onBeforeEndOperation() { + var op = this.curOp; + var scrollIntoView = false; + if (op) { + if (op.change) { + signalTo(op.changeHandlers, this, op.change); + } + if (op && op.cursorActivity) { + signalTo(op.cursorActivityHandlers, this, null); + if (op.isVimOp) + scrollIntoView = true; + } + this.curOp = null; + } + if (scrollIntoView) + this.scrollIntoView(); + } + ; + moveH(increment, unit) { + if (unit == 'char') { + // todo + var cur = this.getCursor(); + this.setCursor(cur.line, cur.ch + increment); + } + } + ; + setOption(name, val) { + switch (name) { + case "keyMap": + this.state.keyMap = val; + break; + case "textwidth": + this.state.textwidth = val; + break; + } + } + ; + getOption(name) { + switch (name) { + case "firstLineNumber": return 1; + case "tabSize": return this.cm6.state.tabSize || 4; + case "readOnly": return this.cm6.state.readOnly; + case "indentWithTabs": return this.cm6.state.facet(indentUnit) == "\t"; // TODO + case "indentUnit": return this.cm6.state.facet(indentUnit).length || 2; + case "textwidth": return this.state.textwidth; + // for tests + case "keyMap": return this.state.keyMap || "vim"; + } + } + ; + toggleOverwrite(on) { + this.state.overwrite = on; + } + ; + getTokenTypeAt(pos) { + var _a; + // only comment|string are needed + var offset = this.indexFromPos(pos); + var tree = ensureSyntaxTree(this.cm6.state, offset); + var node = tree === null || tree === void 0 ? void 0 : tree.resolve(offset); + var type = ((_a = node === null || node === void 0 ? void 0 : node.type) === null || _a === void 0 ? void 0 : _a.name) || ""; + if (/comment/i.test(type)) + return "comment"; + if (/string/i.test(type)) + return "string"; + return ""; + } + ; + overWriteSelection(text) { + var doc = this.cm6.state.doc; + var sel = this.cm6.state.selection; + var ranges = sel.ranges.map(x => { + if (x.empty) { + var ch = x.to < doc.length ? doc.sliceString(x.from, x.to + 1) : ""; + if (ch && !/\n/.test(ch)) + return EditorSelection.range(x.from, x.to + 1); + } + return x; + }); + this.cm6.dispatch({ + selection: EditorSelection.create(ranges, sel.mainIndex) + }); + this.replaceSelection(text); + } + /*** multiselect ****/ + isInMultiSelectMode() { + return this.cm6.state.selection.ranges.length > 1; + } + virtualSelectionMode() { + return !!this.virtualSelection; + } + forEachSelection(command) { + var selection = this.cm6.state.selection; + this.virtualSelection = EditorSelection.create(selection.ranges, selection.mainIndex); + for (var i = 0; i < this.virtualSelection.ranges.length; i++) { + var range = this.virtualSelection.ranges[i]; + if (!range) + continue; + this.cm6.dispatch({ selection: EditorSelection.create([range]) }); + command(); + this.virtualSelection.ranges[i] = this.cm6.state.selection.ranges[0]; + } + this.cm6.dispatch({ selection: this.virtualSelection }); + this.virtualSelection = null; + } + hardWrap(options) { + return hardWrap(this, options); + } +} +CodeMirror.isMac = typeof navigator != "undefined" && /*@__PURE__*//Mac/.test(navigator.platform); +// -------------------------- +CodeMirror.Pos = Pos; +CodeMirror.StringStream = StringStream; +CodeMirror.commands = { + cursorCharLeft: function (cm) { cursorCharLeft(cm.cm6); }, + redo: function (cm) { runHistoryCommand(cm, false); }, + undo: function (cm) { runHistoryCommand(cm, true); }, + newlineAndIndent: function (cm) { + insertNewlineAndIndent({ + state: cm.cm6.state, + dispatch: (tr) => { + return dispatchChange(cm, tr); + } + }); + }, + indentAuto: function (cm) { + indentSelection(cm.cm6); + }, + newlineAndIndentContinueComment: undefined, + save: undefined, +}; +CodeMirror.isWordChar = function (ch) { + return wordChar.test(ch); +}; +CodeMirror.keys = keys; +CodeMirror.addClass = function (el, str) { }; +CodeMirror.rmClass = function (el, str) { }; +CodeMirror.e_preventDefault = function (e) { + e.preventDefault(); +}; +CodeMirror.e_stop = function (e) { + var _a, _b; + (_a = e === null || e === void 0 ? void 0 : e.stopPropagation) === null || _a === void 0 ? void 0 : _a.call(e); + (_b = e === null || e === void 0 ? void 0 : e.preventDefault) === null || _b === void 0 ? void 0 : _b.call(e); +}; +CodeMirror.lookupKey = function lookupKey(key, map, handle) { + var result = CodeMirror.keys[key]; + if (result) + handle(result); +}; +CodeMirror.on = on; +CodeMirror.off = off; +CodeMirror.signal = signal; +CodeMirror.findMatchingTag = findMatchingTag; +CodeMirror.findEnclosingTag = findEnclosingTag; +CodeMirror.keyName = undefined; +/************* dialog *************/ +function dialogDiv(cm, template, bottom) { + var dialog = document.createElement("div"); + dialog.appendChild(template); + return dialog; +} +function closeNotification(cm, newVal) { + if (cm.state.currentNotificationClose) + cm.state.currentNotificationClose(); + cm.state.currentNotificationClose = newVal; +} +function openNotification(cm, template, options) { + closeNotification(cm, close); + var dialog = dialogDiv(cm, template, options && options.bottom); + var closed = false; + var doneTimer; + var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000; + function close() { + if (closed) + return; + closed = true; + clearTimeout(doneTimer); + dialog.remove(); + hideDialog(cm, dialog); + } + dialog.onclick = function (e) { + e.preventDefault(); + close(); + }; + showDialog(cm, dialog); + if (duration) + doneTimer = setTimeout(close, duration); + return close; +} +function showDialog(cm, dialog) { + var oldDialog = cm.state.dialog; + cm.state.dialog = dialog; + if (dialog && oldDialog !== dialog) { + if (oldDialog && oldDialog.contains(document.activeElement)) + cm.focus(); + if (oldDialog && oldDialog.parentElement) { + oldDialog.parentElement.replaceChild(dialog, oldDialog); + } + else if (oldDialog) { + oldDialog.remove(); + } + CodeMirror.signal(cm, "dialog"); + } +} +function hideDialog(cm, dialog) { + if (cm.state.dialog == dialog) { + cm.state.dialog = null; + CodeMirror.signal(cm, "dialog"); + } +} +function openDialog(me, template, callback, options) { + if (!options) + options = {}; + closeNotification(me, undefined); + var dialog = dialogDiv(me, template, options.bottom); + var closed = false; + showDialog(me, dialog); + function close(newVal) { + if (typeof newVal == 'string') { + inp.value = newVal; + } + else { + if (closed) + return; + closed = true; + hideDialog(me, dialog); + if (!me.state.dialog) + me.focus(); + if (options.onClose) + options.onClose(dialog); + } + } + var inp = dialog.getElementsByTagName("input")[0]; + if (inp) { + if (options.value) { + inp.value = options.value; + if (options.selectValueOnOpen !== false) + inp.select(); + } + if (options.onInput) + CodeMirror.on(inp, "input", function (e) { options.onInput(e, inp.value, close); }); + if (options.onKeyUp) + CodeMirror.on(inp, "keyup", function (e) { options.onKeyUp(e, inp.value, close); }); + CodeMirror.on(inp, "keydown", function (e) { + if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { + return; + } + if (e.keyCode == 13) + callback(inp.value); + if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) { + inp.blur(); + CodeMirror.e_stop(e); + close(); + } + }); + if (options.closeOnBlur !== false) + CodeMirror.on(inp, "blur", function () { + setTimeout(function () { + if (document.activeElement === inp) + return; + close(); + }); + }); + inp.focus(); + } + return close; +} +var matching = { "(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<", "<": ">>", ">": "<<" }; +function bracketRegex(config) { + return config && config.bracketRegex || /[(){}[\]]/; +} +function scanForBracket(cm, where, dir, style, config) { + var maxScanLen = (config && config.maxScanLineLength) || 10000; + var maxScanLines = (config && config.maxScanLines) || 1000; + var stack = []; + var re = bracketRegex(config); + var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) + : Math.max(cm.firstLine() - 1, where.line - maxScanLines); + for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { + var line = cm.getLine(lineNo); + if (!line) + continue; + var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; + if (line.length > maxScanLen) + continue; + if (lineNo == where.line) + pos = where.ch - (dir < 0 ? 1 : 0); + for (; pos != end; pos += dir) { + var ch = line.charAt(pos); + if (re.test(ch) /*&& (style === undefined || + (cm.getTokenTypeAt(new Pos(lineNo, pos + 1)) || "") == (style || ""))*/) { + var match = matching[ch]; + if (match && (match.charAt(1) == ">") == (dir > 0)) + stack.push(ch); + else if (!stack.length) + return { pos: new Pos(lineNo, pos), ch: ch }; + else + stack.pop(); + } + } + } + return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; +} +function findMatchingTag(cm, pos) { +} +function findEnclosingTag(cm, pos) { + var _a, _b; + var state = cm.cm6.state; + var offset = cm.indexFromPos(pos); + if (offset < state.doc.length) { + var text = state.sliceDoc(offset, offset + 1); + if (text == "<") + offset++; + } + var tree = ensureSyntaxTree(state, offset); + var node = (tree === null || tree === void 0 ? void 0 : tree.resolve(offset)) || null; + while (node) { + if (((_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.type.name) == 'OpenTag' + && ((_b = node.lastChild) === null || _b === void 0 ? void 0 : _b.type.name) == 'CloseTag') { + return { + open: convertRange(state.doc, node.firstChild), + close: convertRange(state.doc, node.lastChild), + }; + } + node = node.parent; + } +} +function convertRange(doc, cm6Range) { + return { + from: posFromIndex(doc, cm6Range.from), + to: posFromIndex(doc, cm6Range.to) + }; +} +class Marker { + constructor(cm, offset, assoc) { + this.cm = cm; + this.id = cm.$mid++; + this.offset = offset; + this.assoc = assoc; + cm.marks[this.id] = this; + } + ; + clear() { delete this.cm.marks[this.id]; } + ; + find() { + if (this.offset == null) + return null; + return this.cm.posFromIndex(this.offset); + } + ; + update(change) { + if (this.offset != null) + this.offset = change.mapPos(this.offset, this.assoc, MapMode.TrackDel); + } +} +function hardWrap(cm, options) { + var _a; + var max = options.column || cm.getOption('textwidth') || 80; + var allowMerge = options.allowMerge != false; + var row = Math.min(options.from, options.to); + var endRow = Math.max(options.from, options.to); + while (row <= endRow) { + var line = cm.getLine(row); + if (line.length > max) { + var space = findSpace(line, max, 5); + if (space) { + var indentation = (_a = /^\s*/.exec(line)) === null || _a === void 0 ? void 0 : _a[0]; + cm.replaceRange("\n" + indentation, new Pos(row, space.start), new Pos(row, space.end)); + } + endRow++; + } + else if (allowMerge && /\S/.test(line) && row != endRow) { + var nextLine = cm.getLine(row + 1); + if (nextLine && /\S/.test(nextLine)) { + var trimmedLine = line.replace(/\s+$/, ""); + var trimmedNextLine = nextLine.replace(/^\s+/, ""); + var mergedLine = trimmedLine + " " + trimmedNextLine; + var space = findSpace(mergedLine, max, 5); + if (space && space.start > trimmedLine.length || mergedLine.length < max) { + cm.replaceRange(" ", new Pos(row, trimmedLine.length), new Pos(row + 1, nextLine.length - trimmedNextLine.length)); + row--; + endRow--; + } + else if (trimmedLine.length < line.length) { + cm.replaceRange("", new Pos(row, trimmedLine.length), new Pos(row, line.length)); + } + } + } + row++; + } + return row; + function findSpace(line, max, min) { + if (line.length < max) + return; + var before = line.slice(0, max); + var after = line.slice(max); + var spaceAfter = /^(?:(\s+)|(\S+)(\s+))/.exec(after); + var spaceBefore = /(?:(\s+)|(\s+)(\S+))$/.exec(before); + var start = 0; + var end = 0; + if (spaceBefore && !spaceBefore[2]) { + start = max - spaceBefore[1].length; + end = max; + } + if (spaceAfter && !spaceAfter[2]) { + if (!start) + start = max; + end = max + spaceAfter[1].length; + } + if (start) { + return { + start: start, + end: end + }; + } + if (spaceBefore && spaceBefore[2] && spaceBefore.index > min) { + return { + start: spaceBefore.index, + end: spaceBefore.index + spaceBefore[2].length + }; + } + if (spaceAfter && spaceAfter[2]) { + start = max + spaceAfter[2].length; + return { + start: start, + end: start + spaceAfter[3].length + }; + } + } +} + +// backwards compatibility for old versions not supporting getDrawSelectionConfig +let getDrawSelectionConfig = View.getDrawSelectionConfig || /*@__PURE__*/function () { + let defaultConfig = { cursorBlinkRate: 1200 }; + return function () { + return defaultConfig; + }; +}(); +class Piece { + constructor(left, top, height, fontFamily, fontSize, fontWeight, color, className, letter, partial) { + this.left = left; + this.top = top; + this.height = height; + this.fontFamily = fontFamily; + this.fontSize = fontSize; + this.fontWeight = fontWeight; + this.color = color; + this.className = className; + this.letter = letter; + this.partial = partial; + } + draw() { + let elt = document.createElement("div"); + elt.className = this.className; + this.adjust(elt); + return elt; + } + adjust(elt) { + elt.style.left = this.left + "px"; + elt.style.top = this.top + "px"; + elt.style.height = this.height + "px"; + elt.style.lineHeight = this.height + "px"; + elt.style.fontFamily = this.fontFamily; + elt.style.fontSize = this.fontSize; + elt.style.fontWeight = this.fontWeight; + elt.style.color = this.partial ? "transparent" : this.color; + elt.className = this.className; + elt.textContent = this.letter; + } + eq(p) { + return this.left == p.left && this.top == p.top && this.height == p.height && + this.fontFamily == p.fontFamily && this.fontSize == p.fontSize && + this.fontWeight == p.fontWeight && this.color == p.color && + this.className == p.className && + this.letter == p.letter; + } +} +class BlockCursorPlugin { + constructor(view, cm) { + this.view = view; + this.rangePieces = []; + this.cursors = []; + this.cm = cm; + this.measureReq = { read: this.readPos.bind(this), write: this.drawSel.bind(this) }; + this.cursorLayer = view.scrollDOM.appendChild(document.createElement("div")); + this.cursorLayer.className = "cm-cursorLayer cm-vimCursorLayer"; + this.cursorLayer.setAttribute("aria-hidden", "true"); + view.requestMeasure(this.measureReq); + this.setBlinkRate(); + } + setBlinkRate() { + let config = getDrawSelectionConfig(this.cm.cm6.state); + let blinkRate = config.cursorBlinkRate; + this.cursorLayer.style.animationDuration = blinkRate + "ms"; + } + update(update) { + if (update.selectionSet || update.geometryChanged || update.viewportChanged) { + this.view.requestMeasure(this.measureReq); + this.cursorLayer.style.animationName = this.cursorLayer.style.animationName == "cm-blink" ? "cm-blink2" : "cm-blink"; + } + if (configChanged(update)) + this.setBlinkRate(); + } + scheduleRedraw() { + this.view.requestMeasure(this.measureReq); + } + readPos() { + let { state } = this.view; + let cursors = []; + for (let r of state.selection.ranges) { + let prim = r == state.selection.main; + let piece = measureCursor(this.cm, this.view, r, prim); + if (piece) + cursors.push(piece); + } + return { cursors }; + } + drawSel({ cursors }) { + if (cursors.length != this.cursors.length || cursors.some((c, i) => !c.eq(this.cursors[i]))) { + let oldCursors = this.cursorLayer.children; + if (oldCursors.length !== cursors.length) { + this.cursorLayer.textContent = ""; + for (const c of cursors) + this.cursorLayer.appendChild(c.draw()); + } + else { + cursors.forEach((c, idx) => c.adjust(oldCursors[idx])); + } + this.cursors = cursors; + } + } + destroy() { + this.cursorLayer.remove(); + } +} +function configChanged(update) { + return getDrawSelectionConfig(update.startState) != getDrawSelectionConfig(update.state); +} +const themeSpec = { + ".cm-vimMode .cm-line": { + "& ::selection": { backgroundColor: "transparent !important" }, + "&::selection": { backgroundColor: "transparent !important" }, + caretColor: "transparent !important", + }, + ".cm-fat-cursor": { + position: "absolute", + background: "#ff9696", + border: "none", + whiteSpace: "pre", + }, + "&:not(.cm-focused) .cm-fat-cursor": { + background: "none", + outline: "solid 1px #ff9696", + color: "transparent !important", + }, +}; +const hideNativeSelection = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.theme(themeSpec)); +function getBase(view) { + let rect = view.scrollDOM.getBoundingClientRect(); + let left = view.textDirection == Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth; + return { left: left - view.scrollDOM.scrollLeft, top: rect.top - view.scrollDOM.scrollTop }; +} +function measureCursor(cm, view, cursor, primary) { + var _a, _b; + let head = cursor.head; + let fatCursor = false; + let hCoeff = 1; + let vim = cm.state.vim; + if (vim && (!vim.insertMode || cm.state.overwrite)) { + fatCursor = true; + if (vim.visualBlock && !primary) + return null; + if (cursor.anchor < cursor.head) { + let letter = head < view.state.doc.length && view.state.sliceDoc(head, head + 1); + if (letter != "\n") + head--; + } + if (cm.state.overwrite) + hCoeff = 0.2; + else if (vim.status) + hCoeff = 0.5; + } + if (fatCursor) { + let letter = head < view.state.doc.length && view.state.sliceDoc(head, head + 1); + if (letter && (/[\uDC00-\uDFFF]/.test(letter) && head > 1)) { + // step back if cursor is on the second half of a surrogate pair + head--; + letter = view.state.sliceDoc(head, head + 1); + } + let pos = view.coordsAtPos(head, 1); + if (!pos) + return null; + let base = getBase(view); + let domAtPos = view.domAtPos(head); + let node = domAtPos ? domAtPos.node : view.contentDOM; + while (domAtPos && domAtPos.node instanceof HTMLElement) { + node = domAtPos.node; + domAtPos = { node: domAtPos.node.childNodes[domAtPos.offset], offset: 0 }; + } + if (!(node instanceof HTMLElement)) { + if (!node.parentNode) + return null; + node = node.parentNode; + } + let style = getComputedStyle(node); + let left = pos.left; + // TODO remove coordsAtPos when all supported versions of codemirror have coordsForChar api + let charCoords = (_b = (_a = view).coordsForChar) === null || _b === void 0 ? void 0 : _b.call(_a, head); + if (charCoords) { + left = charCoords.left; + } + if (!letter || letter == "\n" || letter == "\r") { + letter = "\xa0"; + } + else if (letter == "\t") { + letter = "\xa0"; + var nextPos = view.coordsAtPos(head + 1, -1); + if (nextPos) { + left = nextPos.left - (nextPos.left - pos.left) / parseInt(style.tabSize); + } + } + else if ((/[\uD800-\uDBFF]/.test(letter) && head < view.state.doc.length - 1)) { + // include the second half of a surrogate pair in cursor + letter += view.state.sliceDoc(head + 1, head + 2); + } + let h = (pos.bottom - pos.top); + return new Piece(left - base.left, pos.top - base.top + h * (1 - hCoeff), h * hCoeff, style.fontFamily, style.fontSize, style.fontWeight, style.color, primary ? "cm-fat-cursor cm-cursor-primary" : "cm-fat-cursor cm-cursor-secondary", letter, hCoeff != 1); + } + else { + return null; + } +} + +var FIREFOX_LINUX = typeof navigator != "undefined" + && /*@__PURE__*//linux/i.test(navigator.platform) + && /*@__PURE__*// Gecko\/\d+/.exec(navigator.userAgent); +const Vim = /*@__PURE__*/initVim(CodeMirror); +const HighlightMargin = 250; +const vimStyle = /*@__PURE__*/EditorView.baseTheme({ + ".cm-vimMode .cm-cursorLayer:not(.cm-vimCursorLayer)": { + display: "none", + }, + ".cm-vim-panel": { + padding: "0px 10px", + fontFamily: "monospace", + minHeight: "1.3em", + }, + ".cm-vim-panel input": { + border: "none", + outline: "none", + backgroundColor: "inherit", + }, + "&light .cm-searchMatch": { backgroundColor: "#ffff0054" }, + "&dark .cm-searchMatch": { backgroundColor: "#00ffff8a" }, +}); +const vimPlugin = /*@__PURE__*/ViewPlugin.fromClass(class { + constructor(view) { + this.status = ""; + this.query = null; + this.decorations = Decoration.none; + this.waitForCopy = false; + this.lastKeydown = ''; + this.useNextTextInput = false; + this.compositionText = ''; + this.view = view; + const cm = (this.cm = new CodeMirror(view)); + Vim.enterVimMode(this.cm); + this.view.cm = this.cm; + this.cm.state.vimPlugin = this; + this.blockCursor = new BlockCursorPlugin(view, cm); + this.updateClass(); + this.cm.on("vim-command-done", () => { + if (cm.state.vim) + cm.state.vim.status = ""; + this.blockCursor.scheduleRedraw(); + this.updateStatus(); + }); + this.cm.on("vim-mode-change", (e) => { + if (!cm.state.vim) + return; + cm.state.vim.mode = e.mode; + if (e.subMode) { + cm.state.vim.mode += " block"; + } + cm.state.vim.status = ""; + this.blockCursor.scheduleRedraw(); + this.updateClass(); + this.updateStatus(); + }); + this.cm.on("dialog", () => { + if (this.cm.state.statusbar) { + this.updateStatus(); + } + else { + view.dispatch({ + effects: showVimPanel.of(!!this.cm.state.dialog), + }); + } + }); + this.dom = document.createElement("span"); + this.dom.style.cssText = "position: absolute; right: 10px; top: 1px"; + this.statusButton = document.createElement("span"); + this.statusButton.onclick = (e) => { + Vim.handleKey(this.cm, "", "user"); + this.cm.focus(); + }; + this.statusButton.style.cssText = "cursor: pointer"; + } + update(update) { + var _a; + if ((update.viewportChanged || update.docChanged) && this.query) { + this.highlight(this.query); + } + if (update.docChanged) { + this.cm.onChange(update); + } + if (update.selectionSet) { + this.cm.onSelectionChange(); + } + if (update.viewportChanged) ; + if (this.cm.curOp && !this.cm.curOp.isVimOp) { + this.cm.onBeforeEndOperation(); + } + if (update.transactions) { + for (let tr of update.transactions) + for (let effect of tr.effects) { + if (effect.is(setSearchQuery)) { + let forVim = (_a = effect.value) === null || _a === void 0 ? void 0 : _a.forVim; + if (!forVim) { + this.highlight(null); + } + else { + let query = effect.value.create(); + this.highlight(query); + } + } + } + } + this.blockCursor.update(update); + } + updateClass() { + const state = this.cm.state; + if (!state.vim || (state.vim.insertMode && !state.overwrite)) + this.view.scrollDOM.classList.remove("cm-vimMode"); + else + this.view.scrollDOM.classList.add("cm-vimMode"); + } + updateStatus() { + let dom = this.cm.state.statusbar; + let vim = this.cm.state.vim; + if (!dom || !vim) + return; + let dialog = this.cm.state.dialog; + if (dialog) { + if (dialog.parentElement != dom) { + dom.textContent = ""; + dom.appendChild(dialog); + } + } + else { + dom.textContent = ""; + var status = (vim.mode || "normal").toUpperCase(); + if (vim.insertModeReturn) + status += "(C-O)"; + this.statusButton.textContent = `--${status}--`; + dom.appendChild(this.statusButton); + } + this.dom.textContent = vim.status; + dom.appendChild(this.dom); + } + destroy() { + Vim.leaveVimMode(this.cm); + this.updateClass(); + this.blockCursor.destroy(); + delete this.view.cm; + } + highlight(query) { + this.query = query; + if (!query) + return (this.decorations = Decoration.none); + let { view } = this; + let builder = new RangeSetBuilder(); + for (let i = 0, ranges = view.visibleRanges, l = ranges.length; i < l; i++) { + let { from, to } = ranges[i]; + while (i < l - 1 && to > ranges[i + 1].from - 2 * HighlightMargin) + to = ranges[++i].to; + query.highlight(view.state, from, to, (from, to) => { + builder.add(from, to, matchMark); + }); + } + return (this.decorations = builder.finish()); + } + handleKey(e, view) { + const cm = this.cm; + let vim = cm.state.vim; + if (!vim) + return; + const key = Vim.vimKeyFromEvent(e, vim); + CodeMirror.signal(this.cm, 'inputEvent', { type: "handleKey", key }); + if (!key) + return; + // clear search highlight + if (key == "" && + !vim.insertMode && + !vim.visualMode && + this.query /* && !cm.inMultiSelectMode*/) { + const searchState = vim.searchState_; + if (searchState) { + cm.removeOverlay(searchState.getOverlay()); + searchState.setOverlay(null); + } + } + let isCopy = key === "" && !CodeMirror.isMac; + if (isCopy && cm.somethingSelected()) { + this.waitForCopy = true; + return true; + } + vim.status = (vim.status || "") + key; + let result = Vim.multiSelectHandleKey(cm, key, "user"); + vim = Vim.maybeInitVimState_(cm); // the object can change if there is an exception in handleKey + // insert mode + if (!result && vim.insertMode && cm.state.overwrite) { + if (e.key && e.key.length == 1 && !/\n/.test(e.key)) { + result = true; + cm.overWriteSelection(e.key); + } + else if (e.key == "Backspace") { + result = true; + CodeMirror.commands.cursorCharLeft(cm); + } + } + if (result) { + CodeMirror.signal(this.cm, 'vim-keypress', key); + e.preventDefault(); + e.stopPropagation(); + this.blockCursor.scheduleRedraw(); + } + this.updateStatus(); + return !!result; + } +}, { + eventHandlers: { + copy: function (e, view) { + if (!this.waitForCopy) + return; + this.waitForCopy = false; + Promise.resolve().then(() => { + var cm = this.cm; + var vim = cm.state.vim; + if (!vim) + return; + if (vim.insertMode) { + cm.setSelection(cm.getCursor(), cm.getCursor()); + } + else { + cm.operation(() => { + if (cm.curOp) + cm.curOp.isVimOp = true; + Vim.handleKey(cm, '', 'user'); + }); + } + }); + }, + compositionstart: function (e, view) { + this.useNextTextInput = true; + CodeMirror.signal(this.cm, 'inputEvent', e); + }, + compositionupdate: function (e, view) { + CodeMirror.signal(this.cm, 'inputEvent', e); + }, + compositionend: function (e, view) { + CodeMirror.signal(this.cm, 'inputEvent', e); + }, + keypress: function (e, view) { + CodeMirror.signal(this.cm, 'inputEvent', e); + if (this.lastKeydown == "Dead") + this.handleKey(e, view); + }, + keydown: function (e, view) { + CodeMirror.signal(this.cm, 'inputEvent', e); + this.lastKeydown = e.key; + if (this.lastKeydown == "Unidentified" + || this.lastKeydown == "Process" + || this.lastKeydown == "Dead") { + this.useNextTextInput = true; + } + else { + this.useNextTextInput = false; + this.handleKey(e, view); + } + }, + }, + provide: () => { + return [ + EditorView.inputHandler.of((view, from, to, text) => { + var _a, _b; + var cm = getCM(view); + if (!cm) + return false; + var vim = (_a = cm.state) === null || _a === void 0 ? void 0 : _a.vim; + var vimPlugin = cm.state.vimPlugin; + if (vim && !vim.insertMode && !((_b = cm.curOp) === null || _b === void 0 ? void 0 : _b.isVimOp)) { + if (text === "\0\0") { + return true; + } + CodeMirror.signal(cm, 'inputEvent', { + type: "text", + text, + from, + to, + }); + if (text.length == 1 && vimPlugin.useNextTextInput) { + if (vim.expectLiteralNext && view.composing) { + vimPlugin.compositionText = text; + return false; + } + if (vimPlugin.compositionText) { + var toRemove = vimPlugin.compositionText; + vimPlugin.compositionText = ''; + var head = view.state.selection.main.head; + var textInDoc = view.state.sliceDoc(head - toRemove.length, head); + if (toRemove === textInDoc) { + var pos = cm.getCursor(); + cm.replaceRange('', cm.posFromIndex(head - toRemove.length), pos); + } + } + vimPlugin.handleKey({ + key: text, + preventDefault: () => { }, + stopPropagation: () => { } + }); + forceEndComposition(view); + return true; + } + } + return false; + }) + ]; + }, + decorations: (v) => v.decorations, +}); +/** + * removes contenteditable element and adds it back to end + * IME composition in normal mode + * this method works on all browsers except for Firefox on Linux + * where we need to reset textContent of editor + * (which doesn't work on other browsers) + */ +function forceEndComposition(view) { + var parent = view.scrollDOM.parentElement; + if (!parent) + return; + if (FIREFOX_LINUX) { + view.contentDOM.textContent = "\0\0"; + view.contentDOM.dispatchEvent(new CustomEvent("compositionend")); + return; + } + var sibling = view.scrollDOM.nextSibling; + var selection = window.getSelection(); + var savedSelection = selection && { + anchorNode: selection.anchorNode, + anchorOffset: selection.anchorOffset, + focusNode: selection.focusNode, + focusOffset: selection.focusOffset + }; + view.scrollDOM.remove(); + parent.insertBefore(view.scrollDOM, sibling); + try { + if (savedSelection && selection) { + selection.setPosition(savedSelection.anchorNode, savedSelection.anchorOffset); + if (savedSelection.focusNode) { + selection.extend(savedSelection.focusNode, savedSelection.focusOffset); + } + } + } + catch (e) { + console.error(e); + } + view.focus(); + view.contentDOM.dispatchEvent(new CustomEvent("compositionend")); +} +const matchMark = /*@__PURE__*/Decoration.mark({ class: "cm-searchMatch" }); +const showVimPanel = /*@__PURE__*/StateEffect.define(); +const vimPanelState = /*@__PURE__*/StateField.define({ + create: () => false, + update(value, tr) { + for (let e of tr.effects) + if (e.is(showVimPanel)) + value = e.value; + return value; + }, + provide: (f) => { + return showPanel.from(f, (on) => (on ? createVimPanel : null)); + }, +}); +function createVimPanel(view) { + let dom = document.createElement("div"); + dom.className = "cm-vim-panel"; + let cm = view.cm; + if (cm.state.dialog) { + dom.appendChild(cm.state.dialog); + } + return { top: false, dom }; +} +function statusPanel(view) { + let dom = document.createElement("div"); + dom.className = "cm-vim-panel"; + let cm = view.cm; + cm.state.statusbar = dom; + cm.state.vimPlugin.updateStatus(); + return { dom }; +} +function vim(options = {}) { + return [ + vimStyle, + vimPlugin, + hideNativeSelection, + options.status ? showPanel.of(statusPanel) : vimPanelState, + ]; +} +function getCM(view) { + return view.cm || null; +} + +export { CodeMirror, Vim, getCM, vim }; diff --git a/package.json b/package.json index b5f1d82..ca72261 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "testAll": "yarn run static && yarn run test && cd dev/cm5 && yarn run buildAndTest", "build": "cm-buildhelper src/index.ts", "publish": "yarn run build && npm publish", - "prepare": "npm run build" + "prepare": "yarn run build" }, "keywords": [ "editor", @@ -34,9 +34,13 @@ }, "devDependencies": { "@codemirror/buildhelper": "^1.0.1", + "@codemirror/commands": "6.1.1", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/language": "^6.1.0", + "@codemirror/search": "6.2.1", + "@codemirror/state": "6.4.1", + "@codemirror/view": "6.24.1", "codemirror": "6.0.1", "typescript": "^5.0.2", "vite": "^2.9.6" @@ -44,5 +48,6 @@ "repository": { "type": "git", "url": "https://github.com/replit/codemirror-vim.git" - } + }, + "packageManager": "yarn@4.14.1" } diff --git a/yarn.lock b/yarn.lock index 5bc06bd..df44180 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,1418 +1,2247 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@^7.22.13": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" - integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== - dependencies: - "@babel/highlight" "^7.23.4" - chalk "^2.4.2" - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/highlight@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" - integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@codemirror/autocomplete@^6.0.0": - version "6.12.0" - resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.12.0.tgz#3fa620a8a3f42ded7751749916e8375f6bbbb333" - integrity sha512-r4IjdYFthwbCQyvqnSlx0WBHRHi8nBvU+WjJxFUij81qsBfhNudf/XKKmmC2j3m0LaOYUQTf3qiEK1J8lO1sdg== - dependencies: - "@codemirror/language" "^6.0.0" - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.17.0" - "@lezer/common" "^1.0.0" - -"@codemirror/buildhelper@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@codemirror/buildhelper/-/buildhelper-1.0.1.tgz#b680a66a7afa38a9614c5a0dcf46211e7aedc7dd" - integrity sha512-RbXu1opzxeeilMzKunOFkM83rRBqvsAPax36sa3JW+jVxL88E7aK2bFi3ugbQUNogCFyxuNk2m9zeyY63kFcPQ== - dependencies: - "@lezer/generator" "^1.0.0" - "@marijn/buildtool" "^1.0.0" - "@marijn/testtool" "^0.1.1" - -"@codemirror/commands@^6.0.0": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.1.1.tgz#f92a343f53f4ecff10fc1f4114d0c9e49e7715b7" - integrity sha512-ibDohwkk7vyu3VsnZNlQhwk0OETBtlkYV+6AHfn5Zgq0sxa+yGVX+apwtC3M4wh6AH7yU5si/NysoECs5EGS3Q== - dependencies: - "@codemirror/language" "^6.0.0" - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - "@lezer/common" "^1.0.0" - -"@codemirror/lang-javascript@^6.0.0": - version "6.2.2" - resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz#7141090b22994bef85bcc5608a3bc1257f2db2ad" - integrity sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg== - dependencies: - "@codemirror/autocomplete" "^6.0.0" - "@codemirror/language" "^6.6.0" - "@codemirror/lint" "^6.0.0" - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.17.0" - "@lezer/common" "^1.0.0" - "@lezer/javascript" "^1.0.0" - -"@codemirror/lang-xml@^6.0.0": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@codemirror/lang-xml/-/lang-xml-6.0.2.tgz#66f75390bf8013fd8645db9cdd0b1d177e0777a4" - integrity sha512-JQYZjHL2LAfpiZI2/qZ/qzDuSqmGKMwyApYmEUUCTxLM4MWS7sATUEfIguZQr9Zjx/7gcdnewb039smF6nC2zw== - dependencies: - "@codemirror/autocomplete" "^6.0.0" - "@codemirror/language" "^6.4.0" - "@codemirror/state" "^6.0.0" - "@lezer/common" "^1.0.0" - "@lezer/xml" "^1.0.0" - -"@codemirror/language@^6.0.0", "@codemirror/language@^6.1.0", "@codemirror/language@^6.4.0", "@codemirror/language@^6.6.0": - version "6.10.1" - resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.10.1.tgz#428c932a158cb75942387acfe513c1ece1090b05" - integrity sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ== - dependencies: - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.23.0" - "@lezer/common" "^1.1.0" - "@lezer/highlight" "^1.0.0" - "@lezer/lr" "^1.0.0" - style-mod "^4.0.0" - -"@codemirror/lint@^6.0.0": - version "6.5.0" - resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.5.0.tgz#ea43b6e653dcc5bcd93456b55e9fe62e63f326d9" - integrity sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g== - dependencies: - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - crelt "^1.0.5" - -"@codemirror/search@^6.0.0": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.2.1.tgz#953ca99e04877f1caa1b521a2b3180dfd1adc499" - integrity sha512-Q1JgUSBjQZRPIddlXzad/AVDigdhriLxQNFyP0gfrDTq6LDHNhr95U/tW3bpVssGenkaLzujtu/7XoK4kyvL3g== - dependencies: - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - crelt "^1.0.5" - -"@codemirror/state@^6.0.0", "@codemirror/state@^6.4.0": - version "6.4.1" - resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.4.1.tgz#da57143695c056d9a3c38705ed34136e2b68171b" - integrity sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A== - -"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0": - version "6.24.1" - resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.24.1.tgz#c151d589dc27f9197c68d395811b93c21c801767" - integrity sha512-sBfP4rniPBRQzNakwuQEqjEuiJDWJyF2kqLLqij4WXRoVwPPJfjx966Eq3F7+OPQxDtMt/Q9MWLoZLWjeveBlg== - dependencies: - "@codemirror/state" "^6.4.0" - style-mod "^4.1.0" - w3c-keyname "^2.2.4" - -"@esbuild/linux-loong64@0.14.54": - version "0.14.54" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" - integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== - -"@jridgewell/sourcemap-codec@^1.4.15": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@lezer/common@^1.0.0", "@lezer/common@^1.1.0", "@lezer/common@^1.2.0": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.2.1.tgz#198b278b7869668e1bebbe687586e12a42731049" - integrity sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ== - -"@lezer/generator@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@lezer/generator/-/generator-1.1.1.tgz#c4708b2b12de27f10bf7de4596f96deabac51808" - integrity sha512-GrB5EwOn09yf5YfDk6TcspHcF7PQZP83665moY5VmOURwCmNOx+Qit3mwp7W/EsGpqd4PPRioxNkYwYZii69cw== - dependencies: - "@lezer/common" "^1.0.0" - "@lezer/lr" "^1.0.0" - -"@lezer/highlight@^1.0.0", "@lezer/highlight@^1.1.3": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.0.tgz#e5898c3644208b4b589084089dceeea2966f7780" - integrity sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA== - dependencies: - "@lezer/common" "^1.0.0" - -"@lezer/javascript@^1.0.0": - version "1.4.13" - resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-1.4.13.tgz#e6459a000e1d7369db3e97b1764da63eeb5afe1b" - integrity sha512-5IBr8LIO3xJdJH1e9aj/ZNLE4LSbdsx25wFmGRAZsj2zSmwAYjx26JyU/BYOCpRQlu1jcv1z3vy4NB9+UkfRow== - dependencies: - "@lezer/common" "^1.2.0" - "@lezer/highlight" "^1.1.3" - "@lezer/lr" "^1.3.0" - -"@lezer/lr@^1.0.0", "@lezer/lr@^1.3.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.0.tgz#ed52a75dbbfbb0d1eb63710ea84c35ee647cb67e" - integrity sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg== - dependencies: - "@lezer/common" "^1.0.0" - -"@lezer/xml@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@lezer/xml/-/xml-1.0.4.tgz#d565dd84af9ec0f620b0bb5f043b1233e63ffb0a" - integrity sha512-WmXKb5eX8+rRfZYSNRR5TPee/ZoDgBdVS/rj1VCJGDKa5gNldIctQYibCoFVyNhvZsyL/8nHbZJZPM4gnXN2Vw== - dependencies: - "@lezer/common" "^1.2.0" - "@lezer/highlight" "^1.0.0" - "@lezer/lr" "^1.0.0" - -"@marijn/buildtool@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@marijn/buildtool/-/buildtool-1.0.0.tgz#4e5f6a55391087b031bc9f9ec96acf873a1b3ff7" - integrity sha512-yweS09UWiKGAIvteZcE0H/ddDPOjetI4lzO491z0Lph+hgV6RDF0sEZuZv3K51jwz+UU3rQVskjebHCgNxDE6A== - dependencies: - "@types/mocha" "^9.1.1" - acorn "^8.10.0" - acorn-walk "^8.2.0" - rollup "^4.8.0" - rollup-plugin-dts "^6.1.0" - typescript "^5.3.3" - -"@marijn/testtool@^0.1.1": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@marijn/testtool/-/testtool-0.1.2.tgz#e0a9cd2b0cdf84ccc09fc1a7d7b8b40467a6c34c" - integrity sha512-NXbnM5EsDibhHhQFToCLTWuPp8h4iAuQb4Ch0d2OI0MMvCFAQDVi0wLgfVq11SCqcgA8K/w/iJTnMpbnoE/uUA== - dependencies: - esmoduleserve "^0.2.0" - ist "^1.1.7" - mocha "^10.0.0" - selenium-webdriver "^4.10.0" - serve-static "^1.14.1" - -"@rollup/rollup-android-arm-eabi@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz#38c3abd1955a3c21d492af6b1a1dca4bb1d894d6" - integrity sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w== - -"@rollup/rollup-android-arm64@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz#3822e929f415627609e53b11cec9a4be806de0e2" - integrity sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ== - -"@rollup/rollup-darwin-arm64@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz#6c082de71f481f57df6cfa3701ab2a7afde96f69" - integrity sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ== - -"@rollup/rollup-darwin-x64@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz#c34ca0d31f3c46a22c9afa0e944403eea0edcfd8" - integrity sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg== - -"@rollup/rollup-linux-arm-gnueabihf@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz#48e899c1e438629c072889b824a98787a7c2362d" - integrity sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA== - -"@rollup/rollup-linux-arm64-gnu@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz#788c2698a119dc229062d40da6ada8a090a73a68" - integrity sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA== - -"@rollup/rollup-linux-arm64-musl@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz#3882a4e3a564af9e55804beeb67076857b035ab7" - integrity sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ== - -"@rollup/rollup-linux-riscv64-gnu@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz#0c6ad792e1195c12bfae634425a3d2aa0fe93ab7" - integrity sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw== - -"@rollup/rollup-linux-x64-gnu@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz#9d62485ea0f18d8674033b57aa14fb758f6ec6e3" - integrity sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA== - -"@rollup/rollup-linux-x64-musl@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz#50e8167e28b33c977c1f813def2b2074d1435e05" - integrity sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw== - -"@rollup/rollup-win32-arm64-msvc@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz#68d233272a2004429124494121a42c4aebdc5b8e" - integrity sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw== - -"@rollup/rollup-win32-ia32-msvc@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz#366ca62221d1689e3b55a03f4ae12ae9ba595d40" - integrity sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA== - -"@rollup/rollup-win32-x64-msvc@4.12.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz#9ffdf9ed133a7464f4ae187eb9e1294413fab235" - integrity sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg== - -"@types/estree@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" - integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== - -"@types/mocha@^9.1.1": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" - integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== - -"@ungap/promise-all-settled@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" - integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== - -acorn-walk@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn-walk@^8.2.0: - version "8.3.2" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" - integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== - -acorn@^8.0.4: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== - -acorn@^8.10.0: - version "8.11.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== - -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -camelcase@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -codemirror@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29" - integrity sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg== - dependencies: - "@codemirror/autocomplete" "^6.0.0" - "@codemirror/commands" "^6.0.0" - "@codemirror/language" "^6.0.0" - "@codemirror/lint" "^6.0.0" - "@codemirror/search" "^6.0.0" - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -crelt@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" - integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -esbuild-android-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be" - integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== - -esbuild-android-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771" - integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== - -esbuild-darwin-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25" - integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug== - -esbuild-darwin-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73" - integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== - -esbuild-freebsd-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d" - integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== - -esbuild-freebsd-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48" - integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== - -esbuild-linux-32@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5" - integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== - -esbuild-linux-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652" - integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== - -esbuild-linux-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b" - integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== - -esbuild-linux-arm@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59" - integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== - -esbuild-linux-mips64le@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34" - integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== - -esbuild-linux-ppc64le@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e" - integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== - -esbuild-linux-riscv64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8" - integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== - -esbuild-linux-s390x@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6" - integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== - -esbuild-netbsd-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81" - integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== - -esbuild-openbsd-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b" - integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== - -esbuild-sunos-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da" - integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== - -esbuild-windows-32@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31" - integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== - -esbuild-windows-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4" - integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== - -esbuild-windows-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982" - integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== - -esbuild@^0.14.27: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2" - integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA== - optionalDependencies: - "@esbuild/linux-loong64" "0.14.54" - esbuild-android-64 "0.14.54" - esbuild-android-arm64 "0.14.54" - esbuild-darwin-64 "0.14.54" - esbuild-darwin-arm64 "0.14.54" - esbuild-freebsd-64 "0.14.54" - esbuild-freebsd-arm64 "0.14.54" - esbuild-linux-32 "0.14.54" - esbuild-linux-64 "0.14.54" - esbuild-linux-arm "0.14.54" - esbuild-linux-arm64 "0.14.54" - esbuild-linux-mips64le "0.14.54" - esbuild-linux-ppc64le "0.14.54" - esbuild-linux-riscv64 "0.14.54" - esbuild-linux-s390x "0.14.54" - esbuild-netbsd-64 "0.14.54" - esbuild-openbsd-64 "0.14.54" - esbuild-sunos-64 "0.14.54" - esbuild-windows-32 "0.14.54" - esbuild-windows-64 "0.14.54" - esbuild-windows-arm64 "0.14.54" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -esmoduleserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/esmoduleserve/-/esmoduleserve-0.2.0.tgz#01ed56cbee408f624641dab0b2916a54407c891e" - integrity sha512-vg1j7fzKZUFR5TCsYQ3PABfBRMRi6V9K7mxcRh2MftO3gwAHBwYaPHtLHFDsKVSxdHmpu/GgT37lsRT+vezaKQ== - dependencies: - acorn "^8.0.4" - acorn-walk "^8.0.0" - resolve "^1.15.1" - serve-static "^1.14.1" - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -hasown@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.1.tgz#26f48f039de2c0f8d3356c223fb8d50253519faa" - integrity sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA== - dependencies: - function-bind "^1.1.2" - -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.13.0, is-core-module@^2.9.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== - dependencies: - hasown "^2.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -ist@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/ist/-/ist-1.1.7.tgz#64161305bca42937d8e05394a2883c3431c5f7ff" - integrity sha512-ex9JyqY+tCjBlxN1pXlqxEgtGGUGp1TG83ll1xpu8SfPgOhfAhEGCuepNHlB+d7Le+hLoBcfCu/G0ZQaFbi9hA== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jszip@^3.10.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" - integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== - dependencies: - lie "~3.3.0" - pako "~1.0.2" - readable-stream "~2.3.6" - setimmediate "^1.0.5" - -lie@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" - integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== - dependencies: - immediate "~3.0.5" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -log-symbols@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -magic-string@^0.30.4: - version "0.30.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.7.tgz#0cecd0527d473298679da95a2d7aeb8c64048505" - integrity sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.4.15" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -mocha@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" - integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== - dependencies: - "@ungap/promise-all-settled" "1.1.2" - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.2.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - nanoid "3.3.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -nanoid@3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" - integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== - -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -pako@~1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -postcss@^8.4.13: - version "8.4.35" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7" - integrity sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA== - dependencies: - nanoid "^3.3.7" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve@^1.15.1: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^1.22.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -rimraf@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rollup-plugin-dts@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-6.1.0.tgz#56e9c5548dac717213c6a4aa9df523faf04f75ae" - integrity sha512-ijSCPICkRMDKDLBK9torss07+8dl9UpY9z1N/zTeA1cIqdzMlpkV3MOOC7zukyvQfDyxa1s3Dl2+DeiP/G6DOw== - dependencies: - magic-string "^0.30.4" - optionalDependencies: - "@babel/code-frame" "^7.22.13" - -"rollup@>=2.59.0 <2.78.0": - version "2.77.3" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12" - integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g== - optionalDependencies: - fsevents "~2.3.2" - -rollup@^4.8.0: - version "4.12.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.12.0.tgz#0b6d1e5f3d46bbcf244deec41a7421dc54cc45b5" - integrity sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q== - dependencies: - "@types/estree" "1.0.5" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.12.0" - "@rollup/rollup-android-arm64" "4.12.0" - "@rollup/rollup-darwin-arm64" "4.12.0" - "@rollup/rollup-darwin-x64" "4.12.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.12.0" - "@rollup/rollup-linux-arm64-gnu" "4.12.0" - "@rollup/rollup-linux-arm64-musl" "4.12.0" - "@rollup/rollup-linux-riscv64-gnu" "4.12.0" - "@rollup/rollup-linux-x64-gnu" "4.12.0" - "@rollup/rollup-linux-x64-musl" "4.12.0" - "@rollup/rollup-win32-arm64-msvc" "4.12.0" - "@rollup/rollup-win32-ia32-msvc" "4.12.0" - "@rollup/rollup-win32-x64-msvc" "4.12.0" - fsevents "~2.3.2" - -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -selenium-webdriver@^4.10.0: - version "4.18.1" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.18.1.tgz#bcd19048b4aba5411edb7b5266d9390578fc7fed" - integrity sha512-uP4OJ5wR4+VjdTi5oi/k8oieV2fIhVdVuaOPrklKghgS59w7Zz3nGa5gcG73VcU9EBRv5IZEBRhPr7qFJAj5mQ== - dependencies: - jszip "^3.10.1" - tmp "^0.2.1" - ws ">=8.14.2" - -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== - dependencies: - randombytes "^2.1.0" - -serve-static@^1.14.1: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-json-comments@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -style-mod@^4.0.0, style-mod@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.1.0.tgz#a313a14f4ae8bb4d52878c0053c4327fb787ec09" - integrity sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA== - -supports-color@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -typescript@^5.0.2, typescript@^5.3.3: - version "5.3.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" - integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -vite@^2.9.6: - version "2.9.17" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.17.tgz#6b770525e12fa2a2e3a0fa0d028d304f4f7dc7d4" - integrity sha512-XxcRzra6d7xrKXH66jZUgb+srThoPu+TLJc06GifUyKq9JmjHkc1Numc8ra0h56rju2jfVWw3B3fs5l3OFMvUw== - dependencies: - esbuild "^0.14.27" - postcss "^8.4.13" - resolve "^1.22.0" - rollup ">=2.59.0 <2.78.0" - optionalDependencies: - fsevents "~2.3.2" - -w3c-keyname@^2.2.4: - version "2.2.8" - resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" - integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== - -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -ws@>=8.14.2: - version "8.16.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" - integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-unparser@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" - -yargs@16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 9 + cacheKey: 10c0 + +"@babel/code-frame@npm:^7.22.13": + version: 7.23.5 + resolution: "@babel/code-frame@npm:7.23.5" + dependencies: + "@babel/highlight": "npm:^7.23.4" + chalk: "npm:^2.4.2" + checksum: 10c0/a10e843595ddd9f97faa99917414813c06214f4d9205294013e20c70fbdf4f943760da37dec1d998bf3e6fc20fa2918a47c0e987a7e458663feb7698063ad7c6 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-validator-identifier@npm:7.22.20" + checksum: 10c0/dcad63db345fb110e032de46c3688384b0008a42a4845180ce7cd62b1a9c0507a1bed727c4d1060ed1a03ae57b4d918570259f81724aaac1a5b776056f37504e + languageName: node + linkType: hard + +"@babel/highlight@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/highlight@npm:7.23.4" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.22.20" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + checksum: 10c0/fbff9fcb2f5539289c3c097d130e852afd10d89a3a08ac0b5ebebbc055cc84a4bcc3dcfed463d488cde12dd0902ef1858279e31d7349b2e8cee43913744bda33 + languageName: node + linkType: hard + +"@codemirror/autocomplete@npm:^6.0.0": + version: 6.12.0 + resolution: "@codemirror/autocomplete@npm:6.12.0" + dependencies: + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.17.0" + "@lezer/common": "npm:^1.0.0" + peerDependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + "@lezer/common": ^1.0.0 + checksum: 10c0/9f627f27e25b83816f3b60fe45285d0c976b6bdc0a4b01e7fc1ab90a536113dc5b6a1adfbe403963b97bdb84343a464e58b1614e8d522a4e937949ec81e227f3 + languageName: node + linkType: hard + +"@codemirror/buildhelper@npm:^1.0.1": + version: 1.0.1 + resolution: "@codemirror/buildhelper@npm:1.0.1" + dependencies: + "@lezer/generator": "npm:^1.0.0" + "@marijn/buildtool": "npm:^1.0.0" + "@marijn/testtool": "npm:^0.1.1" + bin: + cm-buildhelper: bin/cm-buildhelper.js + cm-runtests: bin/cm-runtests.js + checksum: 10c0/13db16275d9102053778d275fad0e5834e5cd4ff75e219b047feab8b3f23fd5f03f2e52e9522a7e99d8c701c349f5da9e180cf191c556e1d6fc0699f85ee99ef + languageName: node + linkType: hard + +"@codemirror/commands@npm:6.1.1, @codemirror/commands@npm:^6.0.0": + version: 6.1.1 + resolution: "@codemirror/commands@npm:6.1.1" + dependencies: + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.0.0" + "@lezer/common": "npm:^1.0.0" + checksum: 10c0/79d8a439d5cd3622f5be3f0ae729db83e6b4300d2b06bc715c5653e4a2af6b0e28ca59cf581a9e89218d148b59ab331a809ecad85f33c2436de43435b07a1915 + languageName: node + linkType: hard + +"@codemirror/lang-javascript@npm:^6.0.0": + version: 6.2.2 + resolution: "@codemirror/lang-javascript@npm:6.2.2" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/language": "npm:^6.6.0" + "@codemirror/lint": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.17.0" + "@lezer/common": "npm:^1.0.0" + "@lezer/javascript": "npm:^1.0.0" + checksum: 10c0/348969167fa5f18e8ec7e3614d408a95a54514f5625afcc680377d58a53d0f4ea412dcaeee65f0965e7d96f358d513b5c2744641b21ee5d0d976905b33715e48 + languageName: node + linkType: hard + +"@codemirror/lang-xml@npm:^6.0.0": + version: 6.0.2 + resolution: "@codemirror/lang-xml@npm:6.0.2" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/language": "npm:^6.4.0" + "@codemirror/state": "npm:^6.0.0" + "@lezer/common": "npm:^1.0.0" + "@lezer/xml": "npm:^1.0.0" + checksum: 10c0/3d9b27dca01fa4816c0f528225d86f84826156f10de82a7af4e356430ff3cc19a36d4130d6ad0bb376569704130d90a578850199516aff8ae849b92d94a194a7 + languageName: node + linkType: hard + +"@codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.1.0, @codemirror/language@npm:^6.4.0, @codemirror/language@npm:^6.6.0": + version: 6.10.1 + resolution: "@codemirror/language@npm:6.10.1" + dependencies: + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.23.0" + "@lezer/common": "npm:^1.1.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + style-mod: "npm:^4.0.0" + checksum: 10c0/b910b7be491d3c73389e31d54a6910ad82b35f8ba7a7a48048ea949d881a16e261789b133bee7d458bc5cb1a14eaa2e18f50ac643da17a29d9323d346d3f2a74 + languageName: node + linkType: hard + +"@codemirror/lint@npm:^6.0.0": + version: 6.5.0 + resolution: "@codemirror/lint@npm:6.5.0" + dependencies: + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.0.0" + crelt: "npm:^1.0.5" + checksum: 10c0/5180cc4c56bf8da78d9e506c3f4d281108194103c050a9babd1304bc50a91085d99616cd19cc9fae3c0d55a3288ab5112c1cb4b4b52c4fd032d6396954b0abd8 + languageName: node + linkType: hard + +"@codemirror/search@npm:6.2.1, @codemirror/search@npm:^6.0.0": + version: 6.2.1 + resolution: "@codemirror/search@npm:6.2.1" + dependencies: + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.0.0" + crelt: "npm:^1.0.5" + checksum: 10c0/8b513f6271e88d544d86af3b968ac043dcc4976e0f7c56d44e8062e5cc48b8184d1c1197b40cfb2855809bd7a896844140102a238da2425723d0b02d75c2785e + languageName: node + linkType: hard + +"@codemirror/state@npm:6.4.1, @codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0": + version: 6.4.1 + resolution: "@codemirror/state@npm:6.4.1" + checksum: 10c0/cdab74d0ca4e262531a257ac419c9c44124f3ace8b0ca1262598a9218fbb6fd8f0afeb4b5ed2f64552a9573a0fc5d55481d4b9b05e9505ef729f9bd0f9469423 + languageName: node + linkType: hard + +"@codemirror/view@npm:6.24.1, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0": + version: 6.24.1 + resolution: "@codemirror/view@npm:6.24.1" + dependencies: + "@codemirror/state": "npm:^6.4.0" + style-mod: "npm:^4.1.0" + w3c-keyname: "npm:^2.2.4" + checksum: 10c0/56ec45fa5b064310dd9561cec324633b992c1b918b09c304e8856a4bc22eab0d686c5ab37ed8ccb6e4b19bd0dade5c8a3dd256f056305e7d91b303e847949d23 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.14.54": + version: 0.14.54 + resolution: "@esbuild/linux-loong64@npm:0.14.54" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.15": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: 10c0/0c6b5ae663087558039052a626d2d7ed5208da36cfd707dcc5cea4a07cfc918248403dcb5989a8f7afaf245ce0573b7cc6fd94c4a30453bd10e44d9363940ba5 + languageName: node + linkType: hard + +"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0": + version: 1.2.1 + resolution: "@lezer/common@npm:1.2.1" + checksum: 10c0/af61436dc026f8deebaded13d8e1beea2ae307cbbfb270116cdedadb8208f0674da9c3b5963128a2b1cd4072b4e90bc8128133f4feaf31b6e801e4568f1a15a6 + languageName: node + linkType: hard + +"@lezer/generator@npm:^1.0.0": + version: 1.1.1 + resolution: "@lezer/generator@npm:1.1.1" + dependencies: + "@lezer/common": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + bin: + lezer-generator: dist/lezer-generator.cjs + checksum: 10c0/0ff80c71143afde46a3187e5dbd06e6439875fa85a9e1bc3c0c42514cda4e42d4647a0e2309a3647d7b75dc4e3aa5c067d0a261e0203ff55de1901b40c8f17ad + languageName: node + linkType: hard + +"@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3": + version: 1.2.0 + resolution: "@lezer/highlight@npm:1.2.0" + dependencies: + "@lezer/common": "npm:^1.0.0" + checksum: 10c0/d4312f95b78e4b6f10833b1cf99601c6381c22b755bbf60fd61d6fe9b4cf7780650e2e2dadf75beb8d94824dcb5ec81da5cfc9ca54122688a482e488103105aa + languageName: node + linkType: hard + +"@lezer/javascript@npm:^1.0.0": + version: 1.4.13 + resolution: "@lezer/javascript@npm:1.4.13" + dependencies: + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.1.3" + "@lezer/lr": "npm:^1.3.0" + checksum: 10c0/0d2f18eec3f0eeddcf01b61ca94dbe29e792ce1797aaefeef6886607de151b6b66c6d7180c6e4ea0895ccbb28d4b596b70e70685c4e1ad05b5ccbe34cc9552a2 + languageName: node + linkType: hard + +"@lezer/lr@npm:^1.0.0, @lezer/lr@npm:^1.3.0": + version: 1.4.0 + resolution: "@lezer/lr@npm:1.4.0" + dependencies: + "@lezer/common": "npm:^1.0.0" + checksum: 10c0/1e3af297cc142bb6676cb3c4e1bd310da2e93b53273cf745dc85d839a08e1d3cbbd67e0fc0322a480cf25ee215fefe967c53bc2af3ddf5d9b1bf267081dfa164 + languageName: node + linkType: hard + +"@lezer/xml@npm:^1.0.0": + version: 1.0.4 + resolution: "@lezer/xml@npm:1.0.4" + dependencies: + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10c0/04f8538dd5ff89d7cce984e5a852cbfb626fb9077ba52568755ead1d20ba9ac21a5fd449b3dba856901b9ddfbbd7a14348b3c559f1ac501bde986fda9c35fc76 + languageName: node + linkType: hard + +"@marijn/buildtool@npm:^1.0.0": + version: 1.0.0 + resolution: "@marijn/buildtool@npm:1.0.0" + dependencies: + "@types/mocha": "npm:^9.1.1" + acorn: "npm:^8.10.0" + acorn-walk: "npm:^8.2.0" + rollup: "npm:^4.8.0" + rollup-plugin-dts: "npm:^6.1.0" + typescript: "npm:^5.3.3" + checksum: 10c0/871462c561940b4040d922a3a567d07fa9f254f242f58c40fa872d43c70cac8e93f3dc55a7a9151b2ac07f0281a704b010904ddde3ab44bd54ba57b8115e973a + languageName: node + linkType: hard + +"@marijn/testtool@npm:^0.1.1": + version: 0.1.2 + resolution: "@marijn/testtool@npm:0.1.2" + dependencies: + esmoduleserve: "npm:^0.2.0" + ist: "npm:^1.1.7" + mocha: "npm:^10.0.0" + selenium-webdriver: "npm:^4.10.0" + serve-static: "npm:^1.14.1" + checksum: 10c0/b2b7c5bd0ef3f6c122830d50447474fe3659b568f007f78367c6f097422c44ca0f81989ab4612f91b7ca699847626a13d1ce85f6a50b4becdf53e9d588503661 + languageName: node + linkType: hard + +"@replit/codemirror-vim@workspace:.": + version: 0.0.0-use.local + resolution: "@replit/codemirror-vim@workspace:." + dependencies: + "@codemirror/buildhelper": "npm:^1.0.1" + "@codemirror/commands": "npm:6.1.1" + "@codemirror/lang-javascript": "npm:^6.0.0" + "@codemirror/lang-xml": "npm:^6.0.0" + "@codemirror/language": "npm:^6.1.0" + "@codemirror/search": "npm:6.2.1" + "@codemirror/state": "npm:6.4.1" + "@codemirror/view": "npm:6.24.1" + codemirror: "npm:6.0.1" + typescript: "npm:^5.0.2" + vite: "npm:^2.9.6" + peerDependencies: + "@codemirror/commands": ^6.0.0 + "@codemirror/language": ^6.1.0 + "@codemirror/search": ^6.2.0 + "@codemirror/state": ^6.0.1 + "@codemirror/view": ^6.0.3 + languageName: unknown + linkType: soft + +"@rollup/rollup-android-arm-eabi@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.12.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-android-arm64@npm:4.12.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.12.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.12.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.12.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.12.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.12.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.12.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.12.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.12.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.12.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.12.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.12.0": + version: 4.12.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.12.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@types/estree@npm:1.0.5": + version: 1.0.5 + resolution: "@types/estree@npm:1.0.5" + checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d + languageName: node + linkType: hard + +"@types/mocha@npm:^9.1.1": + version: 9.1.1 + resolution: "@types/mocha@npm:9.1.1" + checksum: 10c0/d033742ce0c92b917815b6e515165ee396866d0db0c0bbe0c301e49402abe3a61bd51e5bb1df7577f1fac60c99ee505fa744f515b476cc934ecb57f709f327e9 + languageName: node + linkType: hard + +"@ungap/promise-all-settled@npm:1.1.2": + version: 1.1.2 + resolution: "@ungap/promise-all-settled@npm:1.1.2" + checksum: 10c0/7f9862bae3b6ce30675783428933be1738dca278901a6bcb55c29b8f54c08863ec8e6a7c884119877d90336501c33b7cfda36355ec7af4d703f65f54cb768913 + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 + languageName: node + linkType: hard + +"acorn-walk@npm:^8.0.0": + version: 8.2.0 + resolution: "acorn-walk@npm:8.2.0" + checksum: 10c0/dbe92f5b2452c93e960c5594e666dd1fae141b965ff2cb4a1e1d0381e3e4db4274c5ce4ffa3d681a86ca2a8d4e29d5efc0670a08e23fd2800051ea387df56ca2 + languageName: node + linkType: hard + +"acorn-walk@npm:^8.2.0": + version: 8.3.2 + resolution: "acorn-walk@npm:8.3.2" + checksum: 10c0/7e2a8dad5480df7f872569b9dccff2f3da7e65f5353686b1d6032ab9f4ddf6e3a2cb83a9b52cf50b1497fd522154dda92f0abf7153290cc79cd14721ff121e52 + languageName: node + linkType: hard + +"acorn@npm:^8.0.4": + version: 8.8.0 + resolution: "acorn@npm:8.8.0" + bin: + acorn: bin/acorn + checksum: 10c0/5efce4f59554e0ab766f32932cba34b86cc2ecdf24fcd27463beff41d8a1b1b9575c21f92c1b9f7f82b93374a9d5aed33c91f93e2d0cb1bdf3f1e06ec131e816 + languageName: node + linkType: hard + +"acorn@npm:^8.10.0": + version: 8.11.3 + resolution: "acorn@npm:8.11.3" + bin: + acorn: bin/acorn + checksum: 10c0/3ff155f8812e4a746fee8ecff1f227d527c4c45655bb1fad6347c3cb58e46190598217551b1500f18542d2bbe5c87120cb6927f5a074a59166fbdd9468f0a299 + languageName: node + linkType: hard + +"ansi-colors@npm:4.1.1": + version: 4.1.1 + resolution: "ansi-colors@npm:4.1.1" + checksum: 10c0/6086ade4336b4250b6b25e144b83e5623bcaf654d3df0c3546ce09c9c5ff999cb6a6f00c87e802d05cf98aef79d92dc76ade2670a2493b8dcb80220bec457838 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"anymatch@npm:~3.1.2": + version: 3.1.2 + resolution: "anymatch@npm:3.1.2" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/900645535aee46ed7958f4f5b5e38abcbf474b5230406e913de15fc9a1310f0d5322775deb609688efe31010fa57831e55d36040b19826c22ce61d537e9b9759 + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"binary-extensions@npm:^2.0.0": + version: 2.2.0 + resolution: "binary-extensions@npm:2.2.0" + checksum: 10c0/d73d8b897238a2d3ffa5f59c0241870043aa7471335e89ea5e1ff48edb7c2d0bb471517a3e4c5c3f4c043615caa2717b5f80a5e61e07503d51dc85cb848e665d + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f + languageName: node + linkType: hard + +"braces@npm:~3.0.2": + version: 3.0.2 + resolution: "braces@npm:3.0.2" + dependencies: + fill-range: "npm:^7.0.1" + checksum: 10c0/321b4d675791479293264019156ca322163f02dc06e3c4cab33bb15cd43d80b51efef69b0930cfde3acd63d126ebca24cd0544fa6f261e093a0fb41ab9dda381 + languageName: node + linkType: hard + +"browser-stdout@npm:1.3.1": + version: 1.3.1 + resolution: "browser-stdout@npm:1.3.1" + checksum: 10c0/c40e482fd82be872b6ea7b9f7591beafbf6f5ba522fe3dade98ba1573a1c29a11101564993e4eb44e5488be8f44510af072df9a9637c739217eb155ceb639205 + languageName: node + linkType: hard + +"camelcase@npm:^6.0.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"chalk@npm:^4.1.0": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"chokidar@npm:3.5.3": + version: 3.5.3 + resolution: "chokidar@npm:3.5.3" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/1076953093e0707c882a92c66c0f56ba6187831aa51bb4de878c1fec59ae611a3bf02898f190efec8e77a086b8df61c2b2a3ea324642a0558bdf8ee6c5dc9ca1 + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"cliui@npm:^7.0.2": + version: 7.0.4 + resolution: "cliui@npm:7.0.4" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/6035f5daf7383470cef82b3d3db00bec70afb3423538c50394386ffbbab135e26c3689c41791f911fa71b62d13d3863c712fdd70f0fbdffd938a1e6fd09aac00 + languageName: node + linkType: hard + +"codemirror@npm:6.0.1": + version: 6.0.1 + resolution: "codemirror@npm:6.0.1" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/commands": "npm:^6.0.0" + "@codemirror/language": "npm:^6.0.0" + "@codemirror/lint": "npm:^6.0.0" + "@codemirror/search": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.0.0" + checksum: 10c0/219b0f6ee91d373380fba2e0564a2665990a3cdada0b01861768005b09061187c58eeb3db96aef486777b02b77b50a50ee843635e3743c47d3725034913c4b60 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 + languageName: node + linkType: hard + +"crelt@npm:^1.0.5": + version: 1.0.6 + resolution: "crelt@npm:1.0.6" + checksum: 10c0/e0fb76dff50c5eb47f2ea9b786c17f9425c66276025adee80876bdbf4a84ab72e899e56d3928431ab0cb057a105ef704df80fe5726ef0f7b1658f815521bdf09 + languageName: node + linkType: hard + +"debug@npm:2.6.9": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 + languageName: node + linkType: hard + +"debug@npm:4.3.4": + version: 4.3.4 + resolution: "debug@npm:4.3.4" + dependencies: + ms: "npm:2.1.2" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736 + languageName: node + linkType: hard + +"decamelize@npm:^4.0.0": + version: 4.0.0 + resolution: "decamelize@npm:4.0.0" + checksum: 10c0/e06da03fc05333e8cd2778c1487da67ffbea5b84e03ca80449519b8fa61f888714bbc6f459ea963d5641b4aa98832130eb5cd193d90ae9f0a27eee14be8e278d + languageName: node + linkType: hard + +"depd@npm:2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c + languageName: node + linkType: hard + +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 + languageName: node + linkType: hard + +"diff@npm:5.0.0": + version: 5.0.0 + resolution: "diff@npm:5.0.0" + checksum: 10c0/08c5904779bbababcd31f1707657b1ad57f8a9b65e6f88d3fb501d09a965d5f8d73066898a7d3f35981f9e4101892c61d99175d421f3b759533213c253d91134 + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10c0/b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: 10c0/f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"esbuild-android-64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-android-64@npm:0.14.54" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"esbuild-android-arm64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-android-arm64@npm:0.14.54" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"esbuild-darwin-64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-darwin-64@npm:0.14.54" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"esbuild-darwin-arm64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-darwin-arm64@npm:0.14.54" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"esbuild-freebsd-64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-freebsd-64@npm:0.14.54" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"esbuild-freebsd-arm64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-freebsd-arm64@npm:0.14.54" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"esbuild-linux-32@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-linux-32@npm:0.14.54" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"esbuild-linux-64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-linux-64@npm:0.14.54" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"esbuild-linux-arm64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-linux-arm64@npm:0.14.54" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"esbuild-linux-arm@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-linux-arm@npm:0.14.54" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"esbuild-linux-mips64le@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-linux-mips64le@npm:0.14.54" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"esbuild-linux-ppc64le@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-linux-ppc64le@npm:0.14.54" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"esbuild-linux-riscv64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-linux-riscv64@npm:0.14.54" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"esbuild-linux-s390x@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-linux-s390x@npm:0.14.54" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"esbuild-netbsd-64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-netbsd-64@npm:0.14.54" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"esbuild-openbsd-64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-openbsd-64@npm:0.14.54" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"esbuild-sunos-64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-sunos-64@npm:0.14.54" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"esbuild-windows-32@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-windows-32@npm:0.14.54" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"esbuild-windows-64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-windows-64@npm:0.14.54" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"esbuild-windows-arm64@npm:0.14.54": + version: 0.14.54 + resolution: "esbuild-windows-arm64@npm:0.14.54" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"esbuild@npm:^0.14.27": + version: 0.14.54 + resolution: "esbuild@npm:0.14.54" + dependencies: + "@esbuild/linux-loong64": "npm:0.14.54" + esbuild-android-64: "npm:0.14.54" + esbuild-android-arm64: "npm:0.14.54" + esbuild-darwin-64: "npm:0.14.54" + esbuild-darwin-arm64: "npm:0.14.54" + esbuild-freebsd-64: "npm:0.14.54" + esbuild-freebsd-arm64: "npm:0.14.54" + esbuild-linux-32: "npm:0.14.54" + esbuild-linux-64: "npm:0.14.54" + esbuild-linux-arm: "npm:0.14.54" + esbuild-linux-arm64: "npm:0.14.54" + esbuild-linux-mips64le: "npm:0.14.54" + esbuild-linux-ppc64le: "npm:0.14.54" + esbuild-linux-riscv64: "npm:0.14.54" + esbuild-linux-s390x: "npm:0.14.54" + esbuild-netbsd-64: "npm:0.14.54" + esbuild-openbsd-64: "npm:0.14.54" + esbuild-sunos-64: "npm:0.14.54" + esbuild-windows-32: "npm:0.14.54" + esbuild-windows-64: "npm:0.14.54" + esbuild-windows-arm64: "npm:0.14.54" + dependenciesMeta: + "@esbuild/linux-loong64": + optional: true + esbuild-android-64: + optional: true + esbuild-android-arm64: + optional: true + esbuild-darwin-64: + optional: true + esbuild-darwin-arm64: + optional: true + esbuild-freebsd-64: + optional: true + esbuild-freebsd-arm64: + optional: true + esbuild-linux-32: + optional: true + esbuild-linux-64: + optional: true + esbuild-linux-arm: + optional: true + esbuild-linux-arm64: + optional: true + esbuild-linux-mips64le: + optional: true + esbuild-linux-ppc64le: + optional: true + esbuild-linux-riscv64: + optional: true + esbuild-linux-s390x: + optional: true + esbuild-netbsd-64: + optional: true + esbuild-openbsd-64: + optional: true + esbuild-sunos-64: + optional: true + esbuild-windows-32: + optional: true + esbuild-windows-64: + optional: true + esbuild-windows-arm64: + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/1df3cf7c5175ebee284fd027f287385a07ce8a0f0460a4412881aeff707577d91e55302f220ee8397b3b5aa17f4ceeb80eac16f36fc676532ff1b744e5965f2d + languageName: node + linkType: hard + +"escalade@npm:^3.1.1": + version: 3.1.1 + resolution: "escalade@npm:3.1.1" + checksum: 10c0/afd02e6ca91ffa813e1108b5e7756566173d6bc0d1eb951cb44d6b21702ec17c1cf116cfe75d4a2b02e05acb0b808a7a9387d0d1ca5cf9c04ad03a8445c3e46d + languageName: node + linkType: hard + +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 + languageName: node + linkType: hard + +"escape-string-regexp@npm:4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"esmoduleserve@npm:^0.2.0": + version: 0.2.0 + resolution: "esmoduleserve@npm:0.2.0" + dependencies: + acorn: "npm:^8.0.4" + acorn-walk: "npm:^8.0.0" + resolve: "npm:^1.15.1" + serve-static: "npm:^1.14.1" + bin: + esmoduleserve: index.js + checksum: 10c0/309b4e60baae6e9adb11f1a8f9dd6261f11e25176e278b826aac7bfb0ad47b688aff83c66444d52cc7335096d683585668cc474129ea676b3227e36e44e59111 + languageName: node + linkType: hard + +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"fill-range@npm:^7.0.1": + version: 7.0.1 + resolution: "fill-range@npm:7.0.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/7cdad7d426ffbaadf45aeb5d15ec675bbd77f7597ad5399e3d2766987ed20bda24d5fac64b3ee79d93276f5865608bb22344a26b9b1ae6c4d00bd94bf611623f + languageName: node + linkType: hard + +"find-up@npm:5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat@npm:^5.0.2": + version: 5.0.2 + resolution: "flat@npm:5.0.2" + bin: + flat: cli.js + checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe + languageName: node + linkType: hard + +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"glob-parent@npm:~5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob@npm:7.2.0": + version: 7.2.0 + resolution: "glob@npm:7.2.0" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.0.4" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/478b40e38be5a3d514e64950e1e07e0ac120585add6a37c98d0ed24d72d9127d734d2a125786073c8deb687096e84ae82b641c441a869ada3a9cc91b68978632 + languageName: node + linkType: hard + +"glob@npm:^7.1.3": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"hasown@npm:^2.0.0": + version: 2.0.1 + resolution: "hasown@npm:2.0.1" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/9e27e70e8e4204f4124c8f99950d1ba2b1f5174864fd39ff26da190f9ea6488c1b3927dcc64981c26d1f637a971783c9489d62c829d393ea509e6f1ba20370bb + languageName: node + linkType: hard + +"he@npm:1.2.0": + version: 1.2.0 + resolution: "he@npm:1.2.0" + bin: + he: bin/he + checksum: 10c0/a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 + languageName: node + linkType: hard + +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 + languageName: node + linkType: hard + +"immediate@npm:~3.0.5": + version: 3.0.6 + resolution: "immediate@npm:3.0.6" + checksum: 10c0/f8ba7ede69bee9260241ad078d2d535848745ff5f6995c7c7cb41cfdc9ccc213f66e10fa5afb881f90298b24a3f7344b637b592beb4f54e582770cdce3f1f039 + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:~2.0.3": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: "npm:^2.0.0" + checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 + languageName: node + linkType: hard + +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.9.0": + version: 2.13.1 + resolution: "is-core-module@npm:2.13.1" + dependencies: + hasown: "npm:^2.0.0" + checksum: 10c0/2cba9903aaa52718f11c4896dabc189bab980870aae86a62dc0d5cedb546896770ee946fb14c84b7adf0735f5eaea4277243f1b95f5cefa90054f92fbcac2518 + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-glob@npm:^4.0.1, is-glob@npm:~4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-plain-obj@npm:^2.1.0": + version: 2.1.0 + resolution: "is-plain-obj@npm:2.1.0" + checksum: 10c0/e5c9814cdaa627a9ad0a0964ded0e0491bfd9ace405c49a5d63c88b30a162f1512c069d5b80997893c4d0181eadc3fed02b4ab4b81059aba5620bfcdfdeb9c53 + languageName: node + linkType: hard + +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: 10c0/00cbe3455c3756be68d2542c416cab888aebd5012781d6819749fefb15162ff23e38501fe681b3d751c73e8ff561ac09a5293eba6f58fdf0178462ce6dcb3453 + languageName: node + linkType: hard + +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce + languageName: node + linkType: hard + +"ist@npm:^1.1.7": + version: 1.1.7 + resolution: "ist@npm:1.1.7" + checksum: 10c0/3af45f3200f5eca126abd55292a6ec6db698661d2867eff1737e7a1d4b96cba89baf82fe6d32a302d7b0e9b61f8f6ff3fc0d379df0ddf5cdcec2b4a1f89efc2a + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + languageName: node + linkType: hard + +"jszip@npm:^3.10.1": + version: 3.10.1 + resolution: "jszip@npm:3.10.1" + dependencies: + lie: "npm:~3.3.0" + pako: "npm:~1.0.2" + readable-stream: "npm:~2.3.6" + setimmediate: "npm:^1.0.5" + checksum: 10c0/58e01ec9c4960383fb8b38dd5f67b83ccc1ec215bf74c8a5b32f42b6e5fb79fada5176842a11409c4051b5b94275044851814a31076bf49e1be218d3ef57c863 + languageName: node + linkType: hard + +"lie@npm:~3.3.0": + version: 3.3.0 + resolution: "lie@npm:3.3.0" + dependencies: + immediate: "npm:~3.0.5" + checksum: 10c0/56dd113091978f82f9dc5081769c6f3b947852ecf9feccaf83e14a123bc630c2301439ce6182521e5fbafbde88e88ac38314327a4e0493a1bea7e0699a7af808 + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"log-symbols@npm:4.1.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" + dependencies: + chalk: "npm:^4.1.0" + is-unicode-supported: "npm:^0.1.0" + checksum: 10c0/67f445a9ffa76db1989d0fa98586e5bc2fd5247260dafb8ad93d9f0ccd5896d53fb830b0e54dade5ad838b9de2006c826831a3c528913093af20dff8bd24aca6 + languageName: node + linkType: hard + +"magic-string@npm:^0.30.4": + version: 0.30.7 + resolution: "magic-string@npm:0.30.7" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.4.15" + checksum: 10c0/d1d949f7a53c37c6e685f4ea7b2b151c2fe0cc5af8f1f979ecba916f7d60d58f35309aaf4c8b09ce1aef7c160b957be39a38b52b478a91650750931e4ddd5daf + languageName: node + linkType: hard + +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: 10c0/b92cd0adc44888c7135a185bfd0dddc42c32606401c72896a842ae15da71eb88858f17669af41e498b463cd7eb998f7b48939a25b08374c7924a9c8a6f8a81b0 + languageName: node + linkType: hard + +"minimatch@npm:5.0.1": + version: 5.0.1 + resolution: "minimatch@npm:5.0.1" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/baa60fc5839205f13d6c266d8ad4d160ae37c33f66b130b5640acac66deff84b934ac6307f5dc5e4b30362c51284817c12df7c9746ffb600b9009c581e0b1634 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + +"minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"mocha@npm:^10.0.0": + version: 10.0.0 + resolution: "mocha@npm:10.0.0" + dependencies: + "@ungap/promise-all-settled": "npm:1.1.2" + ansi-colors: "npm:4.1.1" + browser-stdout: "npm:1.3.1" + chokidar: "npm:3.5.3" + debug: "npm:4.3.4" + diff: "npm:5.0.0" + escape-string-regexp: "npm:4.0.0" + find-up: "npm:5.0.0" + glob: "npm:7.2.0" + he: "npm:1.2.0" + js-yaml: "npm:4.1.0" + log-symbols: "npm:4.1.0" + minimatch: "npm:5.0.1" + ms: "npm:2.1.3" + nanoid: "npm:3.3.3" + serialize-javascript: "npm:6.0.0" + strip-json-comments: "npm:3.1.1" + supports-color: "npm:8.1.1" + workerpool: "npm:6.2.1" + yargs: "npm:16.2.0" + yargs-parser: "npm:20.2.4" + yargs-unparser: "npm:2.0.0" + bin: + _mocha: bin/_mocha + mocha: bin/mocha.js + checksum: 10c0/45728af7cd5a640bd964e4c1d1c9e5318499e9ba3494493a4bd05b3f03ca55bc51397323e160baab29407a56e7a7223cbb23f03e95a69317b44660099521038f + languageName: node + linkType: hard + +"ms@npm:2.0.0": + version: 2.0.0 + resolution: "ms@npm:2.0.0" + checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc + languageName: node + linkType: hard + +"ms@npm:2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"nanoid@npm:3.3.3": + version: 3.3.3 + resolution: "nanoid@npm:3.3.3" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/d7ab68893cdb92dd2152d505e56571d571c65b71a9815f9dfb3c9a8cbf943fe43c9777d9a95a3b81ef01e442fec8409a84375c08f90a5753610a9f22672d953a + languageName: node + linkType: hard + +"nanoid@npm:^3.3.7": + version: 3.3.7 + resolution: "nanoid@npm:3.3.7" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.3.0 + resolution: "node-gyp@npm:12.3.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + undici: "npm:^6.25.0" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/9d9032b405cbe42f72a105259d9eb679376470c102df4a2dbaa51e07d59bf741dcffb85897087ea9d8318b9cabb824a8978af51508ae142f0239ae1e6a3c2329 + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"on-finished@npm:2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10c0/46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 + languageName: node + linkType: hard + +"once@npm:^1.3.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"pako@npm:~1.0.2": + version: 1.0.11 + resolution: "pako@npm:1.0.11" + checksum: 10c0/86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe + languageName: node + linkType: hard + +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0": + version: 1.0.0 + resolution: "picocolors@npm:1.0.0" + checksum: 10c0/20a5b249e331c14479d94ec6817a182fd7a5680debae82705747b2db7ec50009a5f6648d0621c561b0572703f84dbef0858abcbd5856d3c5511426afcb1961f7 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"picomatch@npm:^4.0.4": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 + languageName: node + linkType: hard + +"postcss@npm:^8.4.13": + version: 8.4.35 + resolution: "postcss@npm:8.4.35" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.0.0" + source-map-js: "npm:^1.0.2" + checksum: 10c0/e8dd04e48001eb5857abc9475365bf08f4e508ddf9bc0b8525449a95d190f10d025acebc5b56ac2e94b3c7146790e4ae78989bb9633cb7ee20d1cc9b7dc909b2 + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 + languageName: node + linkType: hard + +"randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: "npm:^5.1.0" + checksum: 10c0/50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3 + languageName: node + linkType: hard + +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 + languageName: node + linkType: hard + +"readable-stream@npm:~2.3.6": + version: 2.3.7 + resolution: "readable-stream@npm:2.3.7" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10c0/1708755e6cf9daff6ff60fa5b4575636472289c5b95d38058a91f94732b8d024a940a0d4d955639195ce42c22cab16973ee8fea8deedd24b5fec3dd596465f86 + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: "npm:^2.2.1" + checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"resolve@npm:^1.15.1": + version: 1.22.1 + resolution: "resolve@npm:1.22.1" + dependencies: + is-core-module: "npm:^2.9.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/6d58b1cb40f3fc80b9e45dd799d84cdc3829a993e4b9fa3b59d331e1dfacd0870e1851f4d0eb549d68c796e0b7087b43d1aec162653ccccff9e18191221a6e7d + languageName: node + linkType: hard + +"resolve@npm:^1.22.0": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.15.1#optional!builtin": + version: 1.22.1 + resolution: "resolve@patch:resolve@npm%3A1.22.1#optional!builtin::version=1.22.1&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.9.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/0d8ccceba5537769c42aa75e4aa75ae854aac866a11d7e9ffdb1663f0158ee646a0d48fc2818ed5e7fb364d64220a1fb9092a160e11e00cbdd5fbab39a13092c + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.22.0#optional!builtin": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 + languageName: node + linkType: hard + +"rimraf@npm:^3.0.0": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: bin.js + checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 + languageName: node + linkType: hard + +"rollup-plugin-dts@npm:^6.1.0": + version: 6.1.0 + resolution: "rollup-plugin-dts@npm:6.1.0" + dependencies: + "@babel/code-frame": "npm:^7.22.13" + magic-string: "npm:^0.30.4" + peerDependencies: + rollup: ^3.29.4 || ^4 + typescript: ^4.5 || ^5.0 + dependenciesMeta: + "@babel/code-frame": + optional: true + checksum: 10c0/8fed9f292fec09a21fdac86964aee92f6d76cfa688faa7f47065a1b25e12be209d437329868d706f8a189c246b5e918efc6046eb09cd974cc7989e68fe746927 + languageName: node + linkType: hard + +"rollup@npm:>=2.59.0 <2.78.0": + version: 2.77.3 + resolution: "rollup@npm:2.77.3" + dependencies: + fsevents: "npm:~2.3.2" + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/7e04ba4e8fdbc3a4a368013e4b788044c16fe94e7301aacbf38e37210983e159b97887ddd3333be9f78fedb30264f094c111ff56a0207c21d4e1745248a7aa42 + languageName: node + linkType: hard + +"rollup@npm:^4.8.0": + version: 4.12.0 + resolution: "rollup@npm:4.12.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.12.0" + "@rollup/rollup-android-arm64": "npm:4.12.0" + "@rollup/rollup-darwin-arm64": "npm:4.12.0" + "@rollup/rollup-darwin-x64": "npm:4.12.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.12.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.12.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.12.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.12.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.12.0" + "@rollup/rollup-linux-x64-musl": "npm:4.12.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.12.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.12.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.12.0" + "@types/estree": "npm:1.0.5" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/1650168231ae8e2bd6fb4d82cc232e53b5c0fe67895188fa683370c9bd3f1febaa28e45c6b100cea333e54f8f5fae6f4d0eea7d86256ec2cc3e38212c55796d6 + languageName: node + linkType: hard + +"safe-buffer@npm:^5.1.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 + languageName: node + linkType: hard + +"selenium-webdriver@npm:^4.10.0": + version: 4.18.1 + resolution: "selenium-webdriver@npm:4.18.1" + dependencies: + jszip: "npm:^3.10.1" + tmp: "npm:^0.2.1" + ws: "npm:>=8.14.2" + checksum: 10c0/49124e16e42f3979e555e4b7f30fd4d0a9bc721de3f6c484137ed41ba2660b1e143df59d477ef4d6ba9a9262414ac95ff8a34c1a6e5f4916b30e26dc5a7ec723 + languageName: node + linkType: hard + +"semver@npm:^7.3.5": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + languageName: node + linkType: hard + +"send@npm:0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10c0/0eb134d6a51fc13bbcb976a1f4214ea1e33f242fae046efc311e80aff66c7a43603e26a79d9d06670283a13000e51be6e0a2cb80ff0942eaf9f1cd30b7ae736a + languageName: node + linkType: hard + +"serialize-javascript@npm:6.0.0": + version: 6.0.0 + resolution: "serialize-javascript@npm:6.0.0" + dependencies: + randombytes: "npm:^2.1.0" + checksum: 10c0/73104922ef0a919064346eea21caab99de1a019a1f5fb54a7daa7fcabc39e83b387a2a363e52a889598c3b1bcf507c4b2a7b26df76e991a310657af20eea2e7c + languageName: node + linkType: hard + +"serve-static@npm:^1.14.1": + version: 1.15.0 + resolution: "serve-static@npm:1.15.0" + dependencies: + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.18.0" + checksum: 10c0/fa9f0e21a540a28f301258dfe1e57bb4f81cd460d28f0e973860477dd4acef946a1f41748b5bd41c73b621bea2029569c935faa38578fd34cd42a9b4947088ba + languageName: node + linkType: hard + +"setimmediate@npm:^1.0.5": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49 + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc + languageName: node + linkType: hard + +"source-map-js@npm:^1.0.2": + version: 1.0.2 + resolution: "source-map-js@npm:1.0.2" + checksum: 10c0/32f2dfd1e9b7168f9a9715eb1b4e21905850f3b50cf02cf476e47e4eebe8e6b762b63a64357896aa29b37e24922b4282df0f492e0d2ace572b43d15525976ff8 + languageName: node + linkType: hard + +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + +"string-width@npm:^4.1.0, string-width@npm:^4.2.0": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e + languageName: node + linkType: hard + +"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-json-comments@npm:3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"style-mod@npm:^4.0.0, style-mod@npm:^4.1.0": + version: 4.1.0 + resolution: "style-mod@npm:4.1.0" + checksum: 10c0/1dce7b0f3d1a91c9e6c37200627b5a9ffa943916bb5879dfaa800c3929fcc11b9bfcc2a6097f49ec3462b254b7afcbcf63caeae6895f53c80122523dc9e5ddb7 + languageName: node + linkType: hard + +"supports-color@npm:8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.14 + resolution: "tar@npm:7.5.14" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/619573265fa45295ff0b378f1097ab43187ab7b66e9483d3ad8f467c287674fb182ec878ef50a08761b8ab487863cb429902cf65fe361d47e330a95bfc4ca9e8 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12": + version: 0.2.16 + resolution: "tinyglobby@npm:0.2.16" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/f2e09fd93dd95c41e522113b686ff6f7c13020962f8698a864a257f3d7737599afc47722b7ab726e12f8a813f779906187911ff8ee6701ede65072671a7e934b + languageName: node + linkType: hard + +"tmp@npm:^0.2.1": + version: 0.2.1 + resolution: "tmp@npm:0.2.1" + dependencies: + rimraf: "npm:^3.0.0" + checksum: 10c0/67607aa012059c9ce697bee820ee51bc0f39b29a8766def4f92d3f764d67c7cf9205d537d24e0cb1ce9685c40d4c628ead010910118ea18348666b5c46ed9123 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 + languageName: node + linkType: hard + +"typescript@npm:^5.0.2, typescript@npm:^5.3.3": + version: 5.3.3 + resolution: "typescript@npm:5.3.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/e33cef99d82573624fc0f854a2980322714986bc35b9cb4d1ce736ed182aeab78e2cb32b385efa493b2a976ef52c53e20d6c6918312353a91850e2b76f1ea44f + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.0.2#optional!builtin, typescript@patch:typescript@npm%3A^5.3.3#optional!builtin": + version: 5.3.3 + resolution: "typescript@patch:typescript@npm%3A5.3.3#optional!builtin::version=5.3.3&hash=e012d7" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/1d0a5f4ce496c42caa9a30e659c467c5686eae15d54b027ee7866744952547f1be1262f2d40de911618c242b510029d51d43ff605dba8fb740ec85ca2d3f9500 + languageName: node + linkType: hard + +"undici@npm:^6.25.0": + version: 6.25.0 + resolution: "undici@npm:6.25.0" + checksum: 10c0/2597cc6689bdb02c210c557b1f85febbfda65becae6e6fc1061508e2f33734d25207f81cd8af56ada9956329eb3a7bd7431e87dcfeceba20ee87059b57dcf985 + languageName: node + linkType: hard + +"util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"vite@npm:^2.9.6": + version: 2.9.17 + resolution: "vite@npm:2.9.17" + dependencies: + esbuild: "npm:^0.14.27" + fsevents: "npm:~2.3.2" + postcss: "npm:^8.4.13" + resolve: "npm:^1.22.0" + rollup: "npm:>=2.59.0 <2.78.0" + peerDependencies: + less: "*" + sass: "*" + stylus: "*" + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + less: + optional: true + sass: + optional: true + stylus: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/3d555e1e9a099894a8a6843ca223a0edb4fcf1eaf794d6419243480f95a65dfe87b52d4ce2ee240f0ba8487a37d93ac708860684a8a24648ed79f5950162fbeb + languageName: node + linkType: hard + +"w3c-keyname@npm:^2.2.4": + version: 2.2.8 + resolution: "w3c-keyname@npm:2.2.8" + checksum: 10c0/37cf335c90efff31672ebb345577d681e2177f7ff9006a9ad47c68c5a9d265ba4a7b39d6c2599ceea639ca9315584ce4bd9c9fbf7a7217bfb7a599e71943c4c4 + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.1 + resolution: "which@npm:6.0.1" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10c0/7e710e54ea36d2d6183bee2f9caa27a3b47b9baf8dee55a199b736fcf85eab3b9df7556fca3d02b50af7f3dfba5ea3a45644189836df06267df457e354da66d5 + languageName: node + linkType: hard + +"workerpool@npm:6.2.1": + version: 6.2.1 + resolution: "workerpool@npm:6.2.1" + checksum: 10c0/f0efd2d74eafd58eaeb36d7d85837d080f75c52b64893cff317b66257dd308e5c9f85ef0b12904f6c7f24ed2365bc3cfeba1f1d16aa736d84d6ef8156ae37c80 + languageName: node + linkType: hard + +"wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"ws@npm:>=8.14.2": + version: 8.16.0 + resolution: "ws@npm:8.16.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/a7783bb421c648b1e622b423409cb2a58ac5839521d2f689e84bc9dc41d59379c692dd405b15a997ea1d4c0c2e5314ad707332d0c558f15232d2bc07c0b4618a + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yargs-parser@npm:20.2.4": + version: 20.2.4 + resolution: "yargs-parser@npm:20.2.4" + checksum: 10c0/08dc341f0b9f940c2fffc1d1decf3be00e28cabd2b578a694901eccc7dcd10577f10c6aa1b040fdd9a68b2042515a60f18476543bccacf9f3ce2c8534cd87435 + languageName: node + linkType: hard + +"yargs-parser@npm:^20.2.2": + version: 20.2.9 + resolution: "yargs-parser@npm:20.2.9" + checksum: 10c0/0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72 + languageName: node + linkType: hard + +"yargs-unparser@npm:2.0.0": + version: 2.0.0 + resolution: "yargs-unparser@npm:2.0.0" + dependencies: + camelcase: "npm:^6.0.0" + decamelize: "npm:^4.0.0" + flat: "npm:^5.0.2" + is-plain-obj: "npm:^2.1.0" + checksum: 10c0/a5a7d6dc157efa95122e16780c019f40ed91d4af6d2bac066db8194ed0ec5c330abb115daa5a79ff07a9b80b8ea80c925baacf354c4c12edd878c0529927ff03 + languageName: node + linkType: hard + +"yargs@npm:16.2.0": + version: 16.2.0 + resolution: "yargs@npm:16.2.0" + dependencies: + cliui: "npm:^7.0.2" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.0" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^20.2.2" + checksum: 10c0/b1dbfefa679848442454b60053a6c95d62f2d2e21dd28def92b647587f415969173c6e99a0f3bab4f1b67ee8283bf735ebe3544013f09491186ba9e8a9a2b651 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard