LaravelTest
5734 строки · 219.1 Кб
1// CodeMirror, copyright (c) by Marijn Haverbeke and others
2// Distributed under an MIT license: https://codemirror.net/LICENSE
3
4/**
5* Supported keybindings:
6* Too many to list. Refer to defaultKeymap below.
7*
8* Supported Ex commands:
9* Refer to defaultExCommandMap below.
10*
11* Registers: unnamed, -, ., :, /, _, a-z, A-Z, 0-9
12* (Does not respect the special case for number registers when delete
13* operator is made with these commands: %, (, ), , /, ?, n, N, {, } )
14* TODO: Implement the remaining registers.
15*
16* Marks: a-z, A-Z, and 0-9
17* TODO: Implement the remaining special marks. They have more complex
18* behavior.
19*
20* Events:
21* 'vim-mode-change' - raised on the editor anytime the current mode changes,
22* Event object: {mode: "visual", subMode: "linewise"}
23*
24* Code structure:
25* 1. Default keymap
26* 2. Variable declarations and short basic helpers
27* 3. Instance (External API) implementation
28* 4. Internal state tracking objects (input state, counter) implementation
29* and instantiation
30* 5. Key handler (the main command dispatcher) implementation
31* 6. Motion, operator, and action implementations
32* 7. Helper functions for the key handler, motions, operators, and actions
33* 8. Set up Vim to work as a keymap for CodeMirror.
34* 9. Ex command implementations.
35*/
36
37(function(mod) {38if (typeof exports == "object" && typeof module == "object") // CommonJS39mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog"), require("../addon/edit/matchbrackets.js"));40else if (typeof define == "function" && define.amd) // AMD41define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog", "../addon/edit/matchbrackets"], mod);42else // Plain browser env43mod(CodeMirror);44})(function(CodeMirror) {45'use strict';46
47var Pos = CodeMirror.Pos;48
49function transformCursor(cm, range) {50var vim = cm.state.vim;51if (!vim || vim.insertMode) return range.head;52var head = vim.sel.head;53if (!head) return range.head;54
55if (vim.visualBlock) {56if (range.head.line != head.line) {57return;58}59}60if (range.from() == range.anchor && !range.empty()) {61if (range.head.line == head.line && range.head.ch != head.ch)62return new Pos(range.head.line, range.head.ch - 1);63}64
65return range.head;66}67
68var defaultKeymap = [69// Key to key mapping. This goes first to make it possible to override70// existing mappings.71{ keys: '<Left>', type: 'keyToKey', toKeys: 'h' },72{ keys: '<Right>', type: 'keyToKey', toKeys: 'l' },73{ keys: '<Up>', type: 'keyToKey', toKeys: 'k' },74{ keys: '<Down>', type: 'keyToKey', toKeys: 'j' },75{ keys: 'g<Up>', type: 'keyToKey', toKeys: 'gk' },76{ keys: 'g<Down>', type: 'keyToKey', toKeys: 'gj' },77{ keys: '<Space>', type: 'keyToKey', toKeys: 'l' },78{ keys: '<BS>', type: 'keyToKey', toKeys: 'h', context: 'normal'},79{ keys: '<Del>', type: 'keyToKey', toKeys: 'x', context: 'normal'},80{ keys: '<C-Space>', type: 'keyToKey', toKeys: 'W' },81{ keys: '<C-BS>', type: 'keyToKey', toKeys: 'B', context: 'normal' },82{ keys: '<S-Space>', type: 'keyToKey', toKeys: 'w' },83{ keys: '<S-BS>', type: 'keyToKey', toKeys: 'b', context: 'normal' },84{ keys: '<C-n>', type: 'keyToKey', toKeys: 'j' },85{ keys: '<C-p>', type: 'keyToKey', toKeys: 'k' },86{ keys: '<C-[>', type: 'keyToKey', toKeys: '<Esc>' },87{ keys: '<C-c>', type: 'keyToKey', toKeys: '<Esc>' },88{ keys: '<C-[>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },89{ keys: '<C-c>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },90{ keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' },91{ keys: 's', type: 'keyToKey', toKeys: 'c', context: 'visual'},92{ keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' },93{ keys: 'S', type: 'keyToKey', toKeys: 'VdO', context: 'visual' },94{ keys: '<Home>', type: 'keyToKey', toKeys: '0' },95{ keys: '<End>', type: 'keyToKey', toKeys: '$' },96{ keys: '<PageUp>', type: 'keyToKey', toKeys: '<C-b>' },97{ keys: '<PageDown>', type: 'keyToKey', toKeys: '<C-f>' },98{ keys: '<CR>', type: 'keyToKey', toKeys: 'j^', context: 'normal' },99{ keys: '<Ins>', type: 'keyToKey', toKeys: 'i', context: 'normal'},100{ keys: '<Ins>', type: 'action', action: 'toggleOverwrite', context: 'insert' },101// Motions102{ keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }},103{ keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }},104{ keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }},105{ keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }},106{ keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }},107{ keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }},108{ keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }},109{ keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }},110{ keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }},111{ keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }},112{ keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }},113{ keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }},114{ keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }},115{ keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }},116{ keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }},117{ keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }},118{ keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }},119{ keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }},120{ keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }},121{ keys: '(', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: false }},122{ keys: ')', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: true }},123{ keys: '<C-f>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }},124{ keys: '<C-b>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }},125{ keys: '<C-d>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }},126{ keys: '<C-u>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }},127{ keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},128{ keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},129{keys: "g$", type: "motion", motion: "moveToEndOfDisplayLine"},130{keys: "g^", type: "motion", motion: "moveToStartOfDisplayLine"},131{keys: "g0", type: "motion", motion: "moveToStartOfDisplayLine"},132{ keys: '0', type: 'motion', motion: 'moveToStartOfLine' },133{ keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' },134{ keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }},135{ keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }},136{ keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},137{ keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }},138{ keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }},139{ keys: 'f<character>', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }},140{ keys: 'F<character>', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }},141{ keys: 't<character>', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }},142{ keys: 'T<character>', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }},143{ keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }},144{ keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }},145{ keys: '\'<character>', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}},146{ keys: '`<character>', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}},147{ keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },148{ keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },149{ keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },150{ keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },151// the next two aren't motions but must come before more general motion declarations152{ keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}},153{ keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}},154{ keys: ']<character>', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}},155{ keys: '[<character>', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}},156{ keys: '|', type: 'motion', motion: 'moveToColumn'},157{ keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'},158{ keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'},159// Operators160{ keys: 'd', type: 'operator', operator: 'delete' },161{ keys: 'y', type: 'operator', operator: 'yank' },162{ keys: 'c', type: 'operator', operator: 'change' },163{ keys: '=', type: 'operator', operator: 'indentAuto' },164{ keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }},165{ keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }},166{ keys: 'g~', type: 'operator', operator: 'changeCase' },167{ keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true },168{ keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true },169{ keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }},170{ keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }},171{ keys: 'gn', type: 'motion', motion: 'findAndSelectNextInclusive', motionArgs: { forward: true }},172{ keys: 'gN', type: 'motion', motion: 'findAndSelectNextInclusive', motionArgs: { forward: false }},173// Operator-Motion dual commands174{ keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }},175{ keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }},176{ keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},177{ keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'},178{ keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'expandToLine', motionArgs: { linewise: true }, context: 'normal'},179{ keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'},180{ keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},181{ keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'},182{ keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'},183{ keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'},184{ keys: '<C-u>', type: 'operatorMotion', operator: 'delete', motion: 'moveToStartOfLine', context: 'insert' },185{ keys: '<C-w>', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' },186//ignore C-w in normal mode187{ keys: '<C-w>', type: 'idle', context: 'normal' },188// Actions189{ keys: '<C-i>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }},190{ keys: '<C-o>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }},191{ keys: '<C-e>', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }},192{ keys: '<C-y>', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }},193{ keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' },194{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },195{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },196{ keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },197{ keys: 'gi', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'lastEdit' }, context: 'normal' },198{ keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' },199{ keys: 'gI', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'bol'}, context: 'normal' },200{ keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' },201{ keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },202{ keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },203{ keys: 'v', type: 'action', action: 'toggleVisualMode' },204{ keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }},205{ keys: '<C-v>', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},206{ keys: '<C-q>', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},207{ keys: 'gv', type: 'action', action: 'reselectLastSelection' },208{ keys: 'J', type: 'action', action: 'joinLines', isEdit: true },209{ keys: 'gJ', type: 'action', action: 'joinLines', actionArgs: { keepSpaces: true }, isEdit: true },210{ keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }},211{ keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }},212{ keys: 'r<character>', type: 'action', action: 'replace', isEdit: true },213{ keys: '@<character>', type: 'action', action: 'replayMacro' },214{ keys: 'q<character>', type: 'action', action: 'enterMacroRecordMode' },215// Handle Replace-mode as a special case of insert mode.216{ keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }, context: 'normal'},217{ keys: 'R', type: 'operator', operator: 'change', operatorArgs: { linewise: true, fullLine: true }, context: 'visual', exitVisualBlock: true},218{ keys: 'u', type: 'action', action: 'undo', context: 'normal' },219{ keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true },220{ keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true },221{ keys: '<C-r>', type: 'action', action: 'redo' },222{ keys: 'm<character>', type: 'action', action: 'setMark' },223{ keys: '"<character>', type: 'action', action: 'setRegister' },224{ keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }},225{ keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },226{ keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }},227{ keys: 'z<CR>', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },228{ keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }},229{ keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },230{ keys: '.', type: 'action', action: 'repeatLastEdit' },231{ keys: '<C-a>', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}},232{ keys: '<C-x>', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}},233{ keys: '<C-t>', type: 'action', action: 'indent', actionArgs: { indentRight: true }, context: 'insert' },234{ keys: '<C-d>', type: 'action', action: 'indent', actionArgs: { indentRight: false }, context: 'insert' },235// Text object motions236{ keys: 'a<character>', type: 'motion', motion: 'textObjectManipulation' },237{ keys: 'i<character>', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }},238// Search239{ keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},240{ keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},241{ keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},242{ keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},243{ keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},244{ keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},245// Ex command246{ keys: ':', type: 'ex' }247];248var defaultKeymapLength = defaultKeymap.length;249
250/**251* Ex commands
252* Care must be taken when adding to the default Ex command map. For any
253* pair of commands that have a shared prefix, at least one of their
254* shortNames must not match the prefix of the other command.
255*/
256var defaultExCommandMap = [257{ name: 'colorscheme', shortName: 'colo' },258{ name: 'map' },259{ name: 'imap', shortName: 'im' },260{ name: 'nmap', shortName: 'nm' },261{ name: 'vmap', shortName: 'vm' },262{ name: 'unmap' },263{ name: 'write', shortName: 'w' },264{ name: 'undo', shortName: 'u' },265{ name: 'redo', shortName: 'red' },266{ name: 'set', shortName: 'se' },267{ name: 'setlocal', shortName: 'setl' },268{ name: 'setglobal', shortName: 'setg' },269{ name: 'sort', shortName: 'sor' },270{ name: 'substitute', shortName: 's', possiblyAsync: true },271{ name: 'nohlsearch', shortName: 'noh' },272{ name: 'yank', shortName: 'y' },273{ name: 'delmarks', shortName: 'delm' },274{ name: 'registers', shortName: 'reg', excludeFromCommandHistory: true },275{ name: 'vglobal', shortName: 'v' },276{ name: 'global', shortName: 'g' }277];278
279var Vim = function() {280function enterVimMode(cm) {281cm.setOption('disableInput', true);282cm.setOption('showCursorWhenSelecting', false);283CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});284cm.on('cursorActivity', onCursorActivity);285maybeInitVimState(cm);286CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));287}288
289function leaveVimMode(cm) {290cm.setOption('disableInput', false);291cm.off('cursorActivity', onCursorActivity);292CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));293cm.state.vim = null;294if (highlightTimeout) clearTimeout(highlightTimeout);295}296
297function detachVimMap(cm, next) {298if (this == CodeMirror.keyMap.vim) {299cm.options.$customCursor = null;300CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor");301}302
303if (!next || next.attach != attachVimMap)304leaveVimMode(cm);305}306function attachVimMap(cm, prev) {307if (this == CodeMirror.keyMap.vim) {308if (cm.curOp) cm.curOp.selectionChanged = true;309cm.options.$customCursor = transformCursor;310CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor");311}312
313if (!prev || prev.attach != attachVimMap)314enterVimMode(cm);315}316
317// Deprecated, simply setting the keymap works again.318CodeMirror.defineOption('vimMode', false, function(cm, val, prev) {319if (val && cm.getOption("keyMap") != "vim")320cm.setOption("keyMap", "vim");321else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap")))322cm.setOption("keyMap", "default");323});324
325function cmKey(key, cm) {326if (!cm) { return undefined; }327if (this[key]) { return this[key]; }328var vimKey = cmKeyToVimKey(key);329if (!vimKey) {330return false;331}332var cmd = vimApi.findKey(cm, vimKey);333if (typeof cmd == 'function') {334CodeMirror.signal(cm, 'vim-keypress', vimKey);335}336return cmd;337}338
339var modifiers = {Shift:'S',Ctrl:'C',Alt:'A',Cmd:'D',Mod:'A',CapsLock:''};340var specialKeys = {Enter:'CR',Backspace:'BS',Delete:'Del',Insert:'Ins'};341function cmKeyToVimKey(key) {342if (key.charAt(0) == '\'') {343// Keypress character binding of format "'a'"344return key.charAt(1);345}346var pieces = key.split(/-(?!$)/);347var lastPiece = pieces[pieces.length - 1];348if (pieces.length == 1 && pieces[0].length == 1) {349// No-modifier bindings use literal character bindings above. Skip.350return false;351} else if (pieces.length == 2 && pieces[0] == 'Shift' && lastPiece.length == 1) {352// Ignore Shift+char bindings as they should be handled by literal character.353return false;354}355var hasCharacter = false;356for (var i = 0; i < pieces.length; i++) {357var piece = pieces[i];358if (piece in modifiers) { pieces[i] = modifiers[piece]; }359else { hasCharacter = true; }360if (piece in specialKeys) { pieces[i] = specialKeys[piece]; }361}362if (!hasCharacter) {363// Vim does not support modifier only keys.364return false;365}366// TODO: Current bindings expect the character to be lower case, but367// it looks like vim key notation uses upper case.368if (isUpperCase(lastPiece)) {369pieces[pieces.length - 1] = lastPiece.toLowerCase();370}371return '<' + pieces.join('-') + '>';372}373
374function getOnPasteFn(cm) {375var vim = cm.state.vim;376if (!vim.onPasteFn) {377vim.onPasteFn = function() {378if (!vim.insertMode) {379cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));380actions.enterInsertMode(cm, {}, vim);381}382};383}384return vim.onPasteFn;385}386
387var numberRegex = /[\d]/;388var wordCharTest = [CodeMirror.isWordChar, function(ch) {389return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch);390}], bigWordCharTest = [function(ch) {391return /\S/.test(ch);392}];393function makeKeyRange(start, size) {394var keys = [];395for (var i = start; i < start + size; i++) {396keys.push(String.fromCharCode(i));397}398return keys;399}400var upperCaseAlphabet = makeKeyRange(65, 26);401var lowerCaseAlphabet = makeKeyRange(97, 26);402var numbers = makeKeyRange(48, 10);403var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);404var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"', '.', ':', '_', '/']);405var upperCaseChars;406try { upperCaseChars = new RegExp("^[\\p{Lu}]$", "u"); }407catch (_) { upperCaseChars = /^[A-Z]$/; }408
409function isLine(cm, line) {410return line >= cm.firstLine() && line <= cm.lastLine();411}412function isLowerCase(k) {413return (/^[a-z]$/).test(k);414}415function isMatchableSymbol(k) {416return '()[]{}'.indexOf(k) != -1;417}418function isNumber(k) {419return numberRegex.test(k);420}421function isUpperCase(k) {422return upperCaseChars.test(k);423}424function isWhiteSpaceString(k) {425return (/^\s*$/).test(k);426}427function isEndOfSentenceSymbol(k) {428return '.?!'.indexOf(k) != -1;429}430function inArray(val, arr) {431for (var i = 0; i < arr.length; i++) {432if (arr[i] == val) {433return true;434}435}436return false;437}438
439var options = {};440function defineOption(name, defaultValue, type, aliases, callback) {441if (defaultValue === undefined && !callback) {442throw Error('defaultValue is required unless callback is provided');443}444if (!type) { type = 'string'; }445options[name] = {446type: type,447defaultValue: defaultValue,448callback: callback449};450if (aliases) {451for (var i = 0; i < aliases.length; i++) {452options[aliases[i]] = options[name];453}454}455if (defaultValue) {456setOption(name, defaultValue);457}458}459
460function setOption(name, value, cm, cfg) {461var option = options[name];462cfg = cfg || {};463var scope = cfg.scope;464if (!option) {465return new Error('Unknown option: ' + name);466}467if (option.type == 'boolean') {468if (value && value !== true) {469return new Error('Invalid argument: ' + name + '=' + value);470} else if (value !== false) {471// Boolean options are set to true if value is not defined.472value = true;473}474}475if (option.callback) {476if (scope !== 'local') {477option.callback(value, undefined);478}479if (scope !== 'global' && cm) {480option.callback(value, cm);481}482} else {483if (scope !== 'local') {484option.value = option.type == 'boolean' ? !!value : value;485}486if (scope !== 'global' && cm) {487cm.state.vim.options[name] = {value: value};488}489}490}491
492function getOption(name, cm, cfg) {493var option = options[name];494cfg = cfg || {};495var scope = cfg.scope;496if (!option) {497return new Error('Unknown option: ' + name);498}499if (option.callback) {500var local = cm && option.callback(undefined, cm);501if (scope !== 'global' && local !== undefined) {502return local;503}504if (scope !== 'local') {505return option.callback();506}507return;508} else {509var local = (scope !== 'global') && (cm && cm.state.vim.options[name]);510return (local || (scope !== 'local') && option || {}).value;511}512}513
514defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) {515// Option is local. Do nothing for global.516if (cm === undefined) {517return;518}519// The 'filetype' option proxies to the CodeMirror 'mode' option.520if (name === undefined) {521var mode = cm.getOption('mode');522return mode == 'null' ? '' : mode;523} else {524var mode = name == '' ? 'null' : name;525cm.setOption('mode', mode);526}527});528
529var createCircularJumpList = function() {530var size = 100;531var pointer = -1;532var head = 0;533var tail = 0;534var buffer = new Array(size);535function add(cm, oldCur, newCur) {536var current = pointer % size;537var curMark = buffer[current];538function useNextSlot(cursor) {539var next = ++pointer % size;540var trashMark = buffer[next];541if (trashMark) {542trashMark.clear();543}544buffer[next] = cm.setBookmark(cursor);545}546if (curMark) {547var markPos = curMark.find();548// avoid recording redundant cursor position549if (markPos && !cursorEqual(markPos, oldCur)) {550useNextSlot(oldCur);551}552} else {553useNextSlot(oldCur);554}555useNextSlot(newCur);556head = pointer;557tail = pointer - size + 1;558if (tail < 0) {559tail = 0;560}561}562function move(cm, offset) {563pointer += offset;564if (pointer > head) {565pointer = head;566} else if (pointer < tail) {567pointer = tail;568}569var mark = buffer[(size + pointer) % size];570// skip marks that are temporarily removed from text buffer571if (mark && !mark.find()) {572var inc = offset > 0 ? 1 : -1;573var newCur;574var oldCur = cm.getCursor();575do {576pointer += inc;577mark = buffer[(size + pointer) % size];578// skip marks that are the same as current position579if (mark &&580(newCur = mark.find()) &&581!cursorEqual(oldCur, newCur)) {582break;583}584} while (pointer < head && pointer > tail);585}586return mark;587}588function find(cm, offset) {589var oldPointer = pointer;590var mark = move(cm, offset);591pointer = oldPointer;592return mark && mark.find();593}594return {595cachedCursor: undefined, //used for # and * jumps596add: add,597find: find,598move: move599};600};601
602// Returns an object to track the changes associated insert mode. It603// clones the object that is passed in, or creates an empty object one if604// none is provided.605var createInsertModeChanges = function(c) {606if (c) {607// Copy construction608return {609changes: c.changes,610expectCursorActivityForChange: c.expectCursorActivityForChange611};612}613return {614// Change list615changes: [],616// Set to true on change, false on cursorActivity.617expectCursorActivityForChange: false618};619};620
621function MacroModeState() {622this.latestRegister = undefined;623this.isPlaying = false;624this.isRecording = false;625this.replaySearchQueries = [];626this.onRecordingDone = undefined;627this.lastInsertModeChanges = createInsertModeChanges();628}629MacroModeState.prototype = {630exitMacroRecordMode: function() {631var macroModeState = vimGlobalState.macroModeState;632if (macroModeState.onRecordingDone) {633macroModeState.onRecordingDone(); // close dialog634}635macroModeState.onRecordingDone = undefined;636macroModeState.isRecording = false;637},638enterMacroRecordMode: function(cm, registerName) {639var register =640vimGlobalState.registerController.getRegister(registerName);641if (register) {642register.clear();643this.latestRegister = registerName;644if (cm.openDialog) {645this.onRecordingDone = cm.openDialog(646document.createTextNode('(recording)['+registerName+']'), null, {bottom:true});647}648this.isRecording = true;649}650}651};652
653function maybeInitVimState(cm) {654if (!cm.state.vim) {655// Store instance state in the CodeMirror object.656cm.state.vim = {657inputState: new InputState(),658// Vim's input state that triggered the last edit, used to repeat659// motions and operators with '.'.660lastEditInputState: undefined,661// Vim's action command before the last edit, used to repeat actions662// with '.' and insert mode repeat.663lastEditActionCommand: undefined,664// When using jk for navigation, if you move from a longer line to a665// shorter line, the cursor may clip to the end of the shorter line.666// If j is pressed again and cursor goes to the next line, the667// cursor should go back to its horizontal position on the longer668// line if it can. This is to keep track of the horizontal position.669lastHPos: -1,670// Doing the same with screen-position for gj/gk671lastHSPos: -1,672// The last motion command run. Cleared if a non-motion command gets673// executed in between.674lastMotion: null,675marks: {},676insertMode: false,677// Repeat count for changes made in insert mode, triggered by key678// sequences like 3,i. Only exists when insertMode is true.679insertModeRepeat: undefined,680visualMode: false,681// If we are in visual line mode. No effect if visualMode is false.682visualLine: false,683visualBlock: false,684lastSelection: null,685lastPastedText: null,686sel: {},687// Buffer-local/window-local values of vim options.688options: {}689};690}691return cm.state.vim;692}693var vimGlobalState;694function resetVimGlobalState() {695vimGlobalState = {696// The current search query.697searchQuery: null,698// Whether we are searching backwards.699searchIsReversed: false,700// Replace part of the last substituted pattern701lastSubstituteReplacePart: undefined,702jumpList: createCircularJumpList(),703macroModeState: new MacroModeState,704// Recording latest f, t, F or T motion command.705lastCharacterSearch: {increment:0, forward:true, selectedCharacter:''},706registerController: new RegisterController({}),707// search history buffer708searchHistoryController: new HistoryController(),709// ex Command history buffer710exCommandHistoryController : new HistoryController()711};712for (var optionName in options) {713var option = options[optionName];714option.value = option.defaultValue;715}716}717
718var lastInsertModeKeyTimer;719var vimApi= {720buildKeyMap: function() {721// TODO: Convert keymap into dictionary format for fast lookup.722},723// Testing hook, though it might be useful to expose the register724// controller anyway.725getRegisterController: function() {726return vimGlobalState.registerController;727},728// Testing hook.729resetVimGlobalState_: resetVimGlobalState,730
731// Testing hook.732getVimGlobalState_: function() {733return vimGlobalState;734},735
736// Testing hook.737maybeInitVimState_: maybeInitVimState,738
739suppressErrorLogging: false,740
741InsertModeKey: InsertModeKey,742map: function(lhs, rhs, ctx) {743// Add user defined key bindings.744exCommandDispatcher.map(lhs, rhs, ctx);745},746unmap: function(lhs, ctx) {747return exCommandDispatcher.unmap(lhs, ctx);748},749// Non-recursive map function.750// NOTE: This will not create mappings to key maps that aren't present751// in the default key map. See TODO at bottom of function.752noremap: function(lhs, rhs, ctx) {753function toCtxArray(ctx) {754return ctx ? [ctx] : ['normal', 'insert', 'visual'];755}756var ctxsToMap = toCtxArray(ctx);757// Look through all actual defaults to find a map candidate.758var actualLength = defaultKeymap.length, origLength = defaultKeymapLength;759for (var i = actualLength - origLength;760i < actualLength && ctxsToMap.length;761i++) {762var mapping = defaultKeymap[i];763// Omit mappings that operate in the wrong context(s) and those of invalid type.764if (mapping.keys == rhs &&765(!ctx || !mapping.context || mapping.context === ctx) &&766mapping.type.substr(0, 2) !== 'ex' &&767mapping.type.substr(0, 3) !== 'key') {768// Make a shallow copy of the original keymap entry.769var newMapping = {};770for (var key in mapping) {771newMapping[key] = mapping[key];772}773// Modify it point to the new mapping with the proper context.774newMapping.keys = lhs;775if (ctx && !newMapping.context) {776newMapping.context = ctx;777}778// Add it to the keymap with a higher priority than the original.779this._mapCommand(newMapping);780// Record the mapped contexts as complete.781var mappedCtxs = toCtxArray(mapping.context);782ctxsToMap = ctxsToMap.filter(function(el) { return mappedCtxs.indexOf(el) === -1; });783}784}785// TODO: Create non-recursive keyToKey mappings for the unmapped contexts once those exist.786},787// Remove all user-defined mappings for the provided context.788mapclear: function(ctx) {789// Partition the existing keymap into user-defined and true defaults.790var actualLength = defaultKeymap.length,791origLength = defaultKeymapLength;792var userKeymap = defaultKeymap.slice(0, actualLength - origLength);793defaultKeymap = defaultKeymap.slice(actualLength - origLength);794if (ctx) {795// If a specific context is being cleared, we need to keep mappings796// from all other contexts.797for (var i = userKeymap.length - 1; i >= 0; i--) {798var mapping = userKeymap[i];799if (ctx !== mapping.context) {800if (mapping.context) {801this._mapCommand(mapping);802} else {803// `mapping` applies to all contexts so create keymap copies804// for each context except the one being cleared.805var contexts = ['normal', 'insert', 'visual'];806for (var j in contexts) {807if (contexts[j] !== ctx) {808var newMapping = {};809for (var key in mapping) {810newMapping[key] = mapping[key];811}812newMapping.context = contexts[j];813this._mapCommand(newMapping);814}815}816}817}818}819}820},821// TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace822// them, or somehow make them work with the existing CodeMirror setOption/getOption API.823setOption: setOption,824getOption: getOption,825defineOption: defineOption,826defineEx: function(name, prefix, func){827if (!prefix) {828prefix = name;829} else if (name.indexOf(prefix) !== 0) {830throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');831}832exCommands[name]=func;833exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};834},835handleKey: function (cm, key, origin) {836var command = this.findKey(cm, key, origin);837if (typeof command === 'function') {838return command();839}840},841/**842* This is the outermost function called by CodeMirror, after keys have
843* been mapped to their Vim equivalents.
844*
845* Finds a command based on the key (and cached keys if there is a
846* multi-key sequence). Returns `undefined` if no key is matched, a noop
847* function if a partial match is found (multi-key), and a function to
848* execute the bound command if a a key is matched. The function always
849* returns true.
850*/
851findKey: function(cm, key, origin) {852var vim = maybeInitVimState(cm);853function handleMacroRecording() {854var macroModeState = vimGlobalState.macroModeState;855if (macroModeState.isRecording) {856if (key == 'q') {857macroModeState.exitMacroRecordMode();858clearInputState(cm);859return true;860}861if (origin != 'mapping') {862logKey(macroModeState, key);863}864}865}866function handleEsc() {867if (key == '<Esc>') {868// Clear input state and get back to normal mode.869clearInputState(cm);870if (vim.visualMode) {871exitVisualMode(cm);872} else if (vim.insertMode) {873exitInsertMode(cm);874}875return true;876}877}878function doKeyToKey(keys) {879// TODO: prevent infinite recursion.880var match;881while (keys) {882// Pull off one command key, which is either a single character883// or a special sequence wrapped in '<' and '>', e.g. '<Space>'.884match = (/<\w+-.+?>|<\w+>|./).exec(keys);885key = match[0];886keys = keys.substring(match.index + key.length);887vimApi.handleKey(cm, key, 'mapping');888}889}890
891function handleKeyInsertMode() {892if (handleEsc()) { return true; }893var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;894var keysAreChars = key.length == 1;895var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');896// Need to check all key substrings in insert mode.897while (keys.length > 1 && match.type != 'full') {898var keys = vim.inputState.keyBuffer = keys.slice(1);899var thisMatch = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');900if (thisMatch.type != 'none') { match = thisMatch; }901}902if (match.type == 'none') { clearInputState(cm); return false; }903else if (match.type == 'partial') {904if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }905lastInsertModeKeyTimer = window.setTimeout(906function() { if (vim.insertMode && vim.inputState.keyBuffer) { clearInputState(cm); } },907getOption('insertModeEscKeysTimeout'));908return !keysAreChars;909}910
911if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }912if (keysAreChars) {913var selections = cm.listSelections();914for (var i = 0; i < selections.length; i++) {915var here = selections[i].head;916cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input');917}918vimGlobalState.macroModeState.lastInsertModeChanges.changes.pop();919}920clearInputState(cm);921return match.command;922}923
924function handleKeyNonInsertMode() {925if (handleMacroRecording() || handleEsc()) { return true; }926
927var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;928if (/^[1-9]\d*$/.test(keys)) { return true; }929
930var keysMatcher = /^(\d*)(.*)$/.exec(keys);931if (!keysMatcher) { clearInputState(cm); return false; }932var context = vim.visualMode ? 'visual' :933'normal';934var mainKey = keysMatcher[2] || keysMatcher[1];935if (vim.inputState.operatorShortcut && vim.inputState.operatorShortcut.slice(-1) == mainKey) {936// multikey operators act linewise by repeating only the last character937mainKey = vim.inputState.operatorShortcut;938}939var match = commandDispatcher.matchCommand(mainKey, defaultKeymap, vim.inputState, context);940if (match.type == 'none') { clearInputState(cm); return false; }941else if (match.type == 'partial') { return true; }942
943vim.inputState.keyBuffer = '';944var keysMatcher = /^(\d*)(.*)$/.exec(keys);945if (keysMatcher[1] && keysMatcher[1] != '0') {946vim.inputState.pushRepeatDigit(keysMatcher[1]);947}948return match.command;949}950
951var command;952if (vim.insertMode) { command = handleKeyInsertMode(); }953else { command = handleKeyNonInsertMode(); }954if (command === false) {955return !vim.insertMode && key.length === 1 ? function() { return true; } : undefined;956} else if (command === true) {957// TODO: Look into using CodeMirror's multi-key handling.958// Return no-op since we are caching the key. Counts as handled, but959// don't want act on it just yet.960return function() { return true; };961} else {962return function() {963return cm.operation(function() {964cm.curOp.isVimOp = true;965try {966if (command.type == 'keyToKey') {967doKeyToKey(command.toKeys);968} else {969commandDispatcher.processCommand(cm, vim, command);970}971} catch (e) {972// clear VIM state in case it's in a bad state.973cm.state.vim = undefined;974maybeInitVimState(cm);975if (!vimApi.suppressErrorLogging) {976console['log'](e);977}978throw e;979}980return true;981});982};983}984},985handleEx: function(cm, input) {986exCommandDispatcher.processCommand(cm, input);987},988
989defineMotion: defineMotion,990defineAction: defineAction,991defineOperator: defineOperator,992mapCommand: mapCommand,993_mapCommand: _mapCommand,994
995defineRegister: defineRegister,996
997exitVisualMode: exitVisualMode,998exitInsertMode: exitInsertMode999};1000
1001// Represents the current input state.1002function InputState() {1003this.prefixRepeat = [];1004this.motionRepeat = [];1005
1006this.operator = null;1007this.operatorArgs = null;1008this.motion = null;1009this.motionArgs = null;1010this.keyBuffer = []; // For matching multi-key commands.1011this.registerName = null; // Defaults to the unnamed register.1012}1013InputState.prototype.pushRepeatDigit = function(n) {1014if (!this.operator) {1015this.prefixRepeat = this.prefixRepeat.concat(n);1016} else {1017this.motionRepeat = this.motionRepeat.concat(n);1018}1019};1020InputState.prototype.getRepeat = function() {1021var repeat = 0;1022if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) {1023repeat = 1;1024if (this.prefixRepeat.length > 0) {1025repeat *= parseInt(this.prefixRepeat.join(''), 10);1026}1027if (this.motionRepeat.length > 0) {1028repeat *= parseInt(this.motionRepeat.join(''), 10);1029}1030}1031return repeat;1032};1033
1034function clearInputState(cm, reason) {1035cm.state.vim.inputState = new InputState();1036CodeMirror.signal(cm, 'vim-command-done', reason);1037}1038
1039/*1040* Register stores information about copy and paste registers. Besides
1041* text, a register must store whether it is linewise (i.e., when it is
1042* pasted, should it insert itself into a new line, or should the text be
1043* inserted at the cursor position.)
1044*/
1045function Register(text, linewise, blockwise) {1046this.clear();1047this.keyBuffer = [text || ''];1048this.insertModeChanges = [];1049this.searchQueries = [];1050this.linewise = !!linewise;1051this.blockwise = !!blockwise;1052}1053Register.prototype = {1054setText: function(text, linewise, blockwise) {1055this.keyBuffer = [text || ''];1056this.linewise = !!linewise;1057this.blockwise = !!blockwise;1058},1059pushText: function(text, linewise) {1060// if this register has ever been set to linewise, use linewise.1061if (linewise) {1062if (!this.linewise) {1063this.keyBuffer.push('\n');1064}1065this.linewise = true;1066}1067this.keyBuffer.push(text);1068},1069pushInsertModeChanges: function(changes) {1070this.insertModeChanges.push(createInsertModeChanges(changes));1071},1072pushSearchQuery: function(query) {1073this.searchQueries.push(query);1074},1075clear: function() {1076this.keyBuffer = [];1077this.insertModeChanges = [];1078this.searchQueries = [];1079this.linewise = false;1080},1081toString: function() {1082return this.keyBuffer.join('');1083}1084};1085
1086/**1087* Defines an external register.
1088*
1089* The name should be a single character that will be used to reference the register.
1090* The register should support setText, pushText, clear, and toString(). See Register
1091* for a reference implementation.
1092*/
1093function defineRegister(name, register) {1094var registers = vimGlobalState.registerController.registers;1095if (!name || name.length != 1) {1096throw Error('Register name must be 1 character');1097}1098if (registers[name]) {1099throw Error('Register already defined ' + name);1100}1101registers[name] = register;1102validRegisters.push(name);1103}1104
1105/*1106* vim registers allow you to keep many independent copy and paste buffers.
1107* See http://usevim.com/2012/04/13/registers/ for an introduction.
1108*
1109* RegisterController keeps the state of all the registers. An initial
1110* state may be passed in. The unnamed register '"' will always be
1111* overridden.
1112*/
1113function RegisterController(registers) {1114this.registers = registers;1115this.unnamedRegister = registers['"'] = new Register();1116registers['.'] = new Register();1117registers[':'] = new Register();1118registers['/'] = new Register();1119}1120RegisterController.prototype = {1121pushText: function(registerName, operator, text, linewise, blockwise) {1122// The black hole register, "_, means delete/yank to nowhere.1123if (registerName === '_') return;1124if (linewise && text.charAt(text.length - 1) !== '\n'){1125text += '\n';1126}1127// Lowercase and uppercase registers refer to the same register.1128// Uppercase just means append.1129var register = this.isValidRegister(registerName) ?1130this.getRegister(registerName) : null;1131// if no register/an invalid register was specified, things go to the1132// default registers1133if (!register) {1134switch (operator) {1135case 'yank':1136// The 0 register contains the text from the most recent yank.1137this.registers['0'] = new Register(text, linewise, blockwise);1138break;1139case 'delete':1140case 'change':1141if (text.indexOf('\n') == -1) {1142// Delete less than 1 line. Update the small delete register.1143this.registers['-'] = new Register(text, linewise);1144} else {1145// Shift down the contents of the numbered registers and put the1146// deleted text into register 1.1147this.shiftNumericRegisters_();1148this.registers['1'] = new Register(text, linewise);1149}1150break;1151}1152// Make sure the unnamed register is set to what just happened1153this.unnamedRegister.setText(text, linewise, blockwise);1154return;1155}1156
1157// If we've gotten to this point, we've actually specified a register1158var append = isUpperCase(registerName);1159if (append) {1160register.pushText(text, linewise);1161} else {1162register.setText(text, linewise, blockwise);1163}1164// The unnamed register always has the same value as the last used1165// register.1166this.unnamedRegister.setText(register.toString(), linewise);1167},1168// Gets the register named @name. If one of @name doesn't already exist,1169// create it. If @name is invalid, return the unnamedRegister.1170getRegister: function(name) {1171if (!this.isValidRegister(name)) {1172return this.unnamedRegister;1173}1174name = name.toLowerCase();1175if (!this.registers[name]) {1176this.registers[name] = new Register();1177}1178return this.registers[name];1179},1180isValidRegister: function(name) {1181return name && inArray(name, validRegisters);1182},1183shiftNumericRegisters_: function() {1184for (var i = 9; i >= 2; i--) {1185this.registers[i] = this.getRegister('' + (i - 1));1186}1187}1188};1189function HistoryController() {1190this.historyBuffer = [];1191this.iterator = 0;1192this.initialPrefix = null;1193}1194HistoryController.prototype = {1195// the input argument here acts a user entered prefix for a small time1196// until we start autocompletion in which case it is the autocompleted.1197nextMatch: function (input, up) {1198var historyBuffer = this.historyBuffer;1199var dir = up ? -1 : 1;1200if (this.initialPrefix === null) this.initialPrefix = input;1201for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i+= dir) {1202var element = historyBuffer[i];1203for (var j = 0; j <= element.length; j++) {1204if (this.initialPrefix == element.substring(0, j)) {1205this.iterator = i;1206return element;1207}1208}1209}1210// should return the user input in case we reach the end of buffer.1211if (i >= historyBuffer.length) {1212this.iterator = historyBuffer.length;1213return this.initialPrefix;1214}1215// return the last autocompleted query or exCommand as it is.1216if (i < 0 ) return input;1217},1218pushInput: function(input) {1219var index = this.historyBuffer.indexOf(input);1220if (index > -1) this.historyBuffer.splice(index, 1);1221if (input.length) this.historyBuffer.push(input);1222},1223reset: function() {1224this.initialPrefix = null;1225this.iterator = this.historyBuffer.length;1226}1227};1228var commandDispatcher = {1229matchCommand: function(keys, keyMap, inputState, context) {1230var matches = commandMatches(keys, keyMap, context, inputState);1231if (!matches.full && !matches.partial) {1232return {type: 'none'};1233} else if (!matches.full && matches.partial) {1234return {type: 'partial'};1235}1236
1237var bestMatch;1238for (var i = 0; i < matches.full.length; i++) {1239var match = matches.full[i];1240if (!bestMatch) {1241bestMatch = match;1242}1243}1244if (bestMatch.keys.slice(-11) == '<character>') {1245var character = lastChar(keys);1246if (!character) return {type: 'none'};1247inputState.selectedCharacter = character;1248}1249return {type: 'full', command: bestMatch};1250},1251processCommand: function(cm, vim, command) {1252vim.inputState.repeatOverride = command.repeatOverride;1253switch (command.type) {1254case 'motion':1255this.processMotion(cm, vim, command);1256break;1257case 'operator':1258this.processOperator(cm, vim, command);1259break;1260case 'operatorMotion':1261this.processOperatorMotion(cm, vim, command);1262break;1263case 'action':1264this.processAction(cm, vim, command);1265break;1266case 'search':1267this.processSearch(cm, vim, command);1268break;1269case 'ex':1270case 'keyToEx':1271this.processEx(cm, vim, command);1272break;1273default:1274break;1275}1276},1277processMotion: function(cm, vim, command) {1278vim.inputState.motion = command.motion;1279vim.inputState.motionArgs = copyArgs(command.motionArgs);1280this.evalInput(cm, vim);1281},1282processOperator: function(cm, vim, command) {1283var inputState = vim.inputState;1284if (inputState.operator) {1285if (inputState.operator == command.operator) {1286// Typing an operator twice like 'dd' makes the operator operate1287// linewise1288inputState.motion = 'expandToLine';1289inputState.motionArgs = { linewise: true };1290this.evalInput(cm, vim);1291return;1292} else {1293// 2 different operators in a row doesn't make sense.1294clearInputState(cm);1295}1296}1297inputState.operator = command.operator;1298inputState.operatorArgs = copyArgs(command.operatorArgs);1299if (command.keys.length > 1) {1300inputState.operatorShortcut = command.keys;1301}1302if (command.exitVisualBlock) {1303vim.visualBlock = false;1304updateCmSelection(cm);1305}1306if (vim.visualMode) {1307// Operating on a selection in visual mode. We don't need a motion.1308this.evalInput(cm, vim);1309}1310},1311processOperatorMotion: function(cm, vim, command) {1312var visualMode = vim.visualMode;1313var operatorMotionArgs = copyArgs(command.operatorMotionArgs);1314if (operatorMotionArgs) {1315// Operator motions may have special behavior in visual mode.1316if (visualMode && operatorMotionArgs.visualLine) {1317vim.visualLine = true;1318}1319}1320this.processOperator(cm, vim, command);1321if (!visualMode) {1322this.processMotion(cm, vim, command);1323}1324},1325processAction: function(cm, vim, command) {1326var inputState = vim.inputState;1327var repeat = inputState.getRepeat();1328var repeatIsExplicit = !!repeat;1329var actionArgs = copyArgs(command.actionArgs) || {};1330if (inputState.selectedCharacter) {1331actionArgs.selectedCharacter = inputState.selectedCharacter;1332}1333// Actions may or may not have motions and operators. Do these first.1334if (command.operator) {1335this.processOperator(cm, vim, command);1336}1337if (command.motion) {1338this.processMotion(cm, vim, command);1339}1340if (command.motion || command.operator) {1341this.evalInput(cm, vim);1342}1343actionArgs.repeat = repeat || 1;1344actionArgs.repeatIsExplicit = repeatIsExplicit;1345actionArgs.registerName = inputState.registerName;1346clearInputState(cm);1347vim.lastMotion = null;1348if (command.isEdit) {1349this.recordLastEdit(vim, inputState, command);1350}1351actions[command.action](cm, actionArgs, vim);1352},1353processSearch: function(cm, vim, command) {1354if (!cm.getSearchCursor) {1355// Search depends on SearchCursor.1356return;1357}1358var forward = command.searchArgs.forward;1359var wholeWordOnly = command.searchArgs.wholeWordOnly;1360getSearchState(cm).setReversed(!forward);1361var promptPrefix = (forward) ? '/' : '?';1362var originalQuery = getSearchState(cm).getQuery();1363var originalScrollPos = cm.getScrollInfo();1364function handleQuery(query, ignoreCase, smartCase) {1365vimGlobalState.searchHistoryController.pushInput(query);1366vimGlobalState.searchHistoryController.reset();1367try {1368updateSearchQuery(cm, query, ignoreCase, smartCase);1369} catch (e) {1370showConfirm(cm, 'Invalid regex: ' + query);1371clearInputState(cm);1372return;1373}1374commandDispatcher.processMotion(cm, vim, {1375type: 'motion',1376motion: 'findNext',1377motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist }1378});1379}1380function onPromptClose(query) {1381cm.scrollTo(originalScrollPos.left, originalScrollPos.top);1382handleQuery(query, true /** ignoreCase */, true /** smartCase */);1383var macroModeState = vimGlobalState.macroModeState;1384if (macroModeState.isRecording) {1385logSearchQuery(macroModeState, query);1386}1387}1388function onPromptKeyUp(e, query, close) {1389var keyName = CodeMirror.keyName(e), up, offset;1390if (keyName == 'Up' || keyName == 'Down') {1391up = keyName == 'Up' ? true : false;1392offset = e.target ? e.target.selectionEnd : 0;1393query = vimGlobalState.searchHistoryController.nextMatch(query, up) || '';1394close(query);1395if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length);1396} else {1397if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift')1398vimGlobalState.searchHistoryController.reset();1399}1400var parsedQuery;1401try {1402parsedQuery = updateSearchQuery(cm, query,1403true /** ignoreCase */, true /** smartCase */);1404} catch (e) {1405// Swallow bad regexes for incremental search.1406}1407if (parsedQuery) {1408cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30);1409} else {1410clearSearchHighlight(cm);1411cm.scrollTo(originalScrollPos.left, originalScrollPos.top);1412}1413}1414function onPromptKeyDown(e, query, close) {1415var keyName = CodeMirror.keyName(e);1416if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' ||1417(keyName == 'Backspace' && query == '')) {1418vimGlobalState.searchHistoryController.pushInput(query);1419vimGlobalState.searchHistoryController.reset();1420updateSearchQuery(cm, originalQuery);1421clearSearchHighlight(cm);1422cm.scrollTo(originalScrollPos.left, originalScrollPos.top);1423CodeMirror.e_stop(e);1424clearInputState(cm);1425close();1426cm.focus();1427} else if (keyName == 'Up' || keyName == 'Down') {1428CodeMirror.e_stop(e);1429} else if (keyName == 'Ctrl-U') {1430// Ctrl-U clears input.1431CodeMirror.e_stop(e);1432close('');1433}1434}1435switch (command.searchArgs.querySrc) {1436case 'prompt':1437var macroModeState = vimGlobalState.macroModeState;1438if (macroModeState.isPlaying) {1439var query = macroModeState.replaySearchQueries.shift();1440handleQuery(query, true /** ignoreCase */, false /** smartCase */);1441} else {1442showPrompt(cm, {1443onClose: onPromptClose,1444prefix: promptPrefix,1445desc: '(JavaScript regexp)',1446onKeyUp: onPromptKeyUp,1447onKeyDown: onPromptKeyDown1448});1449}1450break;1451case 'wordUnderCursor':1452var word = expandWordUnderCursor(cm, false /** inclusive */,1453true /** forward */, false /** bigWord */,1454true /** noSymbol */);1455var isKeyword = true;1456if (!word) {1457word = expandWordUnderCursor(cm, false /** inclusive */,1458true /** forward */, false /** bigWord */,1459false /** noSymbol */);1460isKeyword = false;1461}1462if (!word) {1463return;1464}1465var query = cm.getLine(word.start.line).substring(word.start.ch,1466word.end.ch);1467if (isKeyword && wholeWordOnly) {1468query = '\\b' + query + '\\b';1469} else {1470query = escapeRegex(query);1471}1472
1473// cachedCursor is used to save the old position of the cursor1474// when * or # causes vim to seek for the nearest word and shift1475// the cursor before entering the motion.1476vimGlobalState.jumpList.cachedCursor = cm.getCursor();1477cm.setCursor(word.start);1478
1479handleQuery(query, true /** ignoreCase */, false /** smartCase */);1480break;1481}1482},1483processEx: function(cm, vim, command) {1484function onPromptClose(input) {1485// Give the prompt some time to close so that if processCommand shows1486// an error, the elements don't overlap.1487vimGlobalState.exCommandHistoryController.pushInput(input);1488vimGlobalState.exCommandHistoryController.reset();1489exCommandDispatcher.processCommand(cm, input);1490}1491function onPromptKeyDown(e, input, close) {1492var keyName = CodeMirror.keyName(e), up, offset;1493if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' ||1494(keyName == 'Backspace' && input == '')) {1495vimGlobalState.exCommandHistoryController.pushInput(input);1496vimGlobalState.exCommandHistoryController.reset();1497CodeMirror.e_stop(e);1498clearInputState(cm);1499close();1500cm.focus();1501}1502if (keyName == 'Up' || keyName == 'Down') {1503CodeMirror.e_stop(e);1504up = keyName == 'Up' ? true : false;1505offset = e.target ? e.target.selectionEnd : 0;1506input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || '';1507close(input);1508if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length);1509} else if (keyName == 'Ctrl-U') {1510// Ctrl-U clears input.1511CodeMirror.e_stop(e);1512close('');1513} else {1514if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift')1515vimGlobalState.exCommandHistoryController.reset();1516}1517}1518if (command.type == 'keyToEx') {1519// Handle user defined Ex to Ex mappings1520exCommandDispatcher.processCommand(cm, command.exArgs.input);1521} else {1522if (vim.visualMode) {1523showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',1524onKeyDown: onPromptKeyDown, selectValueOnOpen: false});1525} else {1526showPrompt(cm, { onClose: onPromptClose, prefix: ':',1527onKeyDown: onPromptKeyDown});1528}1529}1530},1531evalInput: function(cm, vim) {1532// If the motion command is set, execute both the operator and motion.1533// Otherwise return.1534var inputState = vim.inputState;1535var motion = inputState.motion;1536var motionArgs = inputState.motionArgs || {};1537var operator = inputState.operator;1538var operatorArgs = inputState.operatorArgs || {};1539var registerName = inputState.registerName;1540var sel = vim.sel;1541// TODO: Make sure cm and vim selections are identical outside visual mode.1542var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head'));1543var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor'));1544var oldHead = copyCursor(origHead);1545var oldAnchor = copyCursor(origAnchor);1546var newHead, newAnchor;1547var repeat;1548if (operator) {1549this.recordLastEdit(vim, inputState);1550}1551if (inputState.repeatOverride !== undefined) {1552// If repeatOverride is specified, that takes precedence over the1553// input state's repeat. Used by Ex mode and can be user defined.1554repeat = inputState.repeatOverride;1555} else {1556repeat = inputState.getRepeat();1557}1558if (repeat > 0 && motionArgs.explicitRepeat) {1559motionArgs.repeatIsExplicit = true;1560} else if (motionArgs.noRepeat ||1561(!motionArgs.explicitRepeat && repeat === 0)) {1562repeat = 1;1563motionArgs.repeatIsExplicit = false;1564}1565if (inputState.selectedCharacter) {1566// If there is a character input, stick it in all of the arg arrays.1567motionArgs.selectedCharacter = operatorArgs.selectedCharacter =1568inputState.selectedCharacter;1569}1570motionArgs.repeat = repeat;1571clearInputState(cm);1572if (motion) {1573var motionResult = motions[motion](cm, origHead, motionArgs, vim, inputState);1574vim.lastMotion = motions[motion];1575if (!motionResult) {1576return;1577}1578if (motionArgs.toJumplist) {1579var jumpList = vimGlobalState.jumpList;1580// if the current motion is # or *, use cachedCursor1581var cachedCursor = jumpList.cachedCursor;1582if (cachedCursor) {1583recordJumpPosition(cm, cachedCursor, motionResult);1584delete jumpList.cachedCursor;1585} else {1586recordJumpPosition(cm, origHead, motionResult);1587}1588}1589if (motionResult instanceof Array) {1590newAnchor = motionResult[0];1591newHead = motionResult[1];1592} else {1593newHead = motionResult;1594}1595// TODO: Handle null returns from motion commands better.1596if (!newHead) {1597newHead = copyCursor(origHead);1598}1599if (vim.visualMode) {1600if (!(vim.visualBlock && newHead.ch === Infinity)) {1601newHead = clipCursorToContent(cm, newHead);1602}1603if (newAnchor) {1604newAnchor = clipCursorToContent(cm, newAnchor);1605}1606newAnchor = newAnchor || oldAnchor;1607sel.anchor = newAnchor;1608sel.head = newHead;1609updateCmSelection(cm);1610updateMark(cm, vim, '<',1611cursorIsBefore(newAnchor, newHead) ? newAnchor1612: newHead);1613updateMark(cm, vim, '>',1614cursorIsBefore(newAnchor, newHead) ? newHead1615: newAnchor);1616} else if (!operator) {1617newHead = clipCursorToContent(cm, newHead);1618cm.setCursor(newHead.line, newHead.ch);1619}1620}1621if (operator) {1622if (operatorArgs.lastSel) {1623// Replaying a visual mode operation1624newAnchor = oldAnchor;1625var lastSel = operatorArgs.lastSel;1626var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line);1627var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch);1628if (lastSel.visualLine) {1629// Linewise Visual mode: The same number of lines.1630newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch);1631} else if (lastSel.visualBlock) {1632// Blockwise Visual mode: The same number of lines and columns.1633newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset);1634} else if (lastSel.head.line == lastSel.anchor.line) {1635// Normal Visual mode within one line: The same number of characters.1636newHead = new Pos(oldAnchor.line, oldAnchor.ch + chOffset);1637} else {1638// Normal Visual mode with several lines: The same number of lines, in the1639// last line the same number of characters as in the last line the last time.1640newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch);1641}1642vim.visualMode = true;1643vim.visualLine = lastSel.visualLine;1644vim.visualBlock = lastSel.visualBlock;1645sel = vim.sel = {1646anchor: newAnchor,1647head: newHead1648};1649updateCmSelection(cm);1650} else if (vim.visualMode) {1651operatorArgs.lastSel = {1652anchor: copyCursor(sel.anchor),1653head: copyCursor(sel.head),1654visualBlock: vim.visualBlock,1655visualLine: vim.visualLine1656};1657}1658var curStart, curEnd, linewise, mode;1659var cmSel;1660if (vim.visualMode) {1661// Init visual op1662curStart = cursorMin(sel.head, sel.anchor);1663curEnd = cursorMax(sel.head, sel.anchor);1664linewise = vim.visualLine || operatorArgs.linewise;1665mode = vim.visualBlock ? 'block' :1666linewise ? 'line' :1667'char';1668cmSel = makeCmSelection(cm, {1669anchor: curStart,1670head: curEnd1671}, mode);1672if (linewise) {1673var ranges = cmSel.ranges;1674if (mode == 'block') {1675// Linewise operators in visual block mode extend to end of line1676for (var i = 0; i < ranges.length; i++) {1677ranges[i].head.ch = lineLength(cm, ranges[i].head.line);1678}1679} else if (mode == 'line') {1680ranges[0].head = new Pos(ranges[0].head.line + 1, 0);1681}1682}1683} else {1684// Init motion op1685curStart = copyCursor(newAnchor || oldAnchor);1686curEnd = copyCursor(newHead || oldHead);1687if (cursorIsBefore(curEnd, curStart)) {1688var tmp = curStart;1689curStart = curEnd;1690curEnd = tmp;1691}1692linewise = motionArgs.linewise || operatorArgs.linewise;1693if (linewise) {1694// Expand selection to entire line.1695expandSelectionToLine(cm, curStart, curEnd);1696} else if (motionArgs.forward) {1697// Clip to trailing newlines only if the motion goes forward.1698clipToLine(cm, curStart, curEnd);1699}1700mode = 'char';1701var exclusive = !motionArgs.inclusive || linewise;1702cmSel = makeCmSelection(cm, {1703anchor: curStart,1704head: curEnd1705}, mode, exclusive);1706}1707cm.setSelections(cmSel.ranges, cmSel.primary);1708vim.lastMotion = null;1709operatorArgs.repeat = repeat; // For indent in visual mode.1710operatorArgs.registerName = registerName;1711// Keep track of linewise as it affects how paste and change behave.1712operatorArgs.linewise = linewise;1713var operatorMoveTo = operators[operator](1714cm, operatorArgs, cmSel.ranges, oldAnchor, newHead);1715if (vim.visualMode) {1716exitVisualMode(cm, operatorMoveTo != null);1717}1718if (operatorMoveTo) {1719cm.setCursor(operatorMoveTo);1720}1721}1722},1723recordLastEdit: function(vim, inputState, actionCommand) {1724var macroModeState = vimGlobalState.macroModeState;1725if (macroModeState.isPlaying) { return; }1726vim.lastEditInputState = inputState;1727vim.lastEditActionCommand = actionCommand;1728macroModeState.lastInsertModeChanges.changes = [];1729macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;1730macroModeState.lastInsertModeChanges.visualBlock = vim.visualBlock ? vim.sel.head.line - vim.sel.anchor.line : 0;1731}1732};1733
1734/**1735* typedef {Object{line:number,ch:number}} Cursor An object containing the
1736* position of the cursor.
1737*/
1738// All of the functions below return Cursor objects.1739var motions = {1740moveToTopLine: function(cm, _head, motionArgs) {1741var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;1742return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));1743},1744moveToMiddleLine: function(cm) {1745var range = getUserVisibleLines(cm);1746var line = Math.floor((range.top + range.bottom) * 0.5);1747return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));1748},1749moveToBottomLine: function(cm, _head, motionArgs) {1750var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;1751return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));1752},1753expandToLine: function(_cm, head, motionArgs) {1754// Expands forward to end of line, and then to next line if repeat is1755// >1. Does not handle backward motion!1756var cur = head;1757return new Pos(cur.line + motionArgs.repeat - 1, Infinity);1758},1759findNext: function(cm, _head, motionArgs) {1760var state = getSearchState(cm);1761var query = state.getQuery();1762if (!query) {1763return;1764}1765var prev = !motionArgs.forward;1766// If search is initiated with ? instead of /, negate direction.1767prev = (state.isReversed()) ? !prev : prev;1768highlightSearchMatches(cm, query);1769return findNext(cm, prev/** prev */, query, motionArgs.repeat);1770},1771/**1772* Find and select the next occurrence of the search query. If the cursor is currently
1773* within a match, then find and select the current match. Otherwise, find the next occurrence in the
1774* appropriate direction.
1775*
1776* This differs from `findNext` in the following ways:
1777*
1778* 1. Instead of only returning the "from", this returns a "from", "to" range.
1779* 2. If the cursor is currently inside a search match, this selects the current match
1780* instead of the next match.
1781* 3. If there is no associated operator, this will turn on visual mode.
1782*/
1783findAndSelectNextInclusive: function(cm, _head, motionArgs, vim, prevInputState) {1784var state = getSearchState(cm);1785var query = state.getQuery();1786
1787if (!query) {1788return;1789}1790
1791var prev = !motionArgs.forward;1792prev = (state.isReversed()) ? !prev : prev;1793
1794// next: [from, to] | null1795var next = findNextFromAndToInclusive(cm, prev, query, motionArgs.repeat, vim);1796
1797// No matches.1798if (!next) {1799return;1800}1801
1802// If there's an operator that will be executed, return the selection.1803if (prevInputState.operator) {1804return next;1805}1806
1807// At this point, we know that there is no accompanying operator -- let's1808// deal with visual mode in order to select an appropriate match.1809
1810var from = next[0];1811// For whatever reason, when we use the "to" as returned by searchcursor.js directly,1812// the resulting selection is extended by 1 char. Let's shrink it so that only the1813// match is selected.1814var to = new Pos(next[1].line, next[1].ch - 1);1815
1816if (vim.visualMode) {1817// If we were in visualLine or visualBlock mode, get out of it.1818if (vim.visualLine || vim.visualBlock) {1819vim.visualLine = false;1820vim.visualBlock = false;1821CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: ""});1822}1823
1824// If we're currently in visual mode, we should extend the selection to include1825// the search result.1826var anchor = vim.sel.anchor;1827if (anchor) {1828if (state.isReversed()) {1829if (motionArgs.forward) {1830return [anchor, from];1831}1832
1833return [anchor, to];1834} else {1835if (motionArgs.forward) {1836return [anchor, to];1837}1838
1839return [anchor, from];1840}1841}1842} else {1843// Let's turn visual mode on.1844vim.visualMode = true;1845vim.visualLine = false;1846vim.visualBlock = false;1847CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: ""});1848}1849
1850return prev ? [to, from] : [from, to];1851},1852goToMark: function(cm, _head, motionArgs, vim) {1853var pos = getMarkPos(cm, vim, motionArgs.selectedCharacter);1854if (pos) {1855return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos;1856}1857return null;1858},1859moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) {1860if (vim.visualBlock && motionArgs.sameLine) {1861var sel = vim.sel;1862return [1863clipCursorToContent(cm, new Pos(sel.anchor.line, sel.head.ch)),1864clipCursorToContent(cm, new Pos(sel.head.line, sel.anchor.ch))1865];1866} else {1867return ([vim.sel.head, vim.sel.anchor]);1868}1869},1870jumpToMark: function(cm, head, motionArgs, vim) {1871var best = head;1872for (var i = 0; i < motionArgs.repeat; i++) {1873var cursor = best;1874for (var key in vim.marks) {1875if (!isLowerCase(key)) {1876continue;1877}1878var mark = vim.marks[key].find();1879var isWrongDirection = (motionArgs.forward) ?1880cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark);1881
1882if (isWrongDirection) {1883continue;1884}1885if (motionArgs.linewise && (mark.line == cursor.line)) {1886continue;1887}1888
1889var equal = cursorEqual(cursor, best);1890var between = (motionArgs.forward) ?1891cursorIsBetween(cursor, mark, best) :1892cursorIsBetween(best, mark, cursor);1893
1894if (equal || between) {1895best = mark;1896}1897}1898}1899
1900if (motionArgs.linewise) {1901// Vim places the cursor on the first non-whitespace character of1902// the line if there is one, else it places the cursor at the end1903// of the line, regardless of whether a mark was found.1904best = new Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line)));1905}1906return best;1907},1908moveByCharacters: function(_cm, head, motionArgs) {1909var cur = head;1910var repeat = motionArgs.repeat;1911var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;1912return new Pos(cur.line, ch);1913},1914moveByLines: function(cm, head, motionArgs, vim) {1915var cur = head;1916var endCh = cur.ch;1917// Depending what our last motion was, we may want to do different1918// things. If our last motion was moving vertically, we want to1919// preserve the HPos from our last horizontal move. If our last motion1920// was going to the end of a line, moving vertically we should go to1921// the end of the line, etc.1922switch (vim.lastMotion) {1923case this.moveByLines:1924case this.moveByDisplayLines:1925case this.moveByScroll:1926case this.moveToColumn:1927case this.moveToEol:1928endCh = vim.lastHPos;1929break;1930default:1931vim.lastHPos = endCh;1932}1933var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);1934var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;1935var first = cm.firstLine();1936var last = cm.lastLine();1937var posV = cm.findPosV(cur, (motionArgs.forward ? repeat : -repeat), 'line', vim.lastHSPos);1938var hasMarkedText = motionArgs.forward ? posV.line > line : posV.line < line;1939if (hasMarkedText) {1940line = posV.line;1941endCh = posV.ch;1942}1943// Vim go to line begin or line end when cursor at first/last line and1944// move to previous/next line is triggered.1945if (line < first && cur.line == first){1946return this.moveToStartOfLine(cm, head, motionArgs, vim);1947} else if (line > last && cur.line == last){1948return moveToEol(cm, head, motionArgs, vim, true);1949}1950if (motionArgs.toFirstChar){1951endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));1952vim.lastHPos = endCh;1953}1954vim.lastHSPos = cm.charCoords(new Pos(line, endCh),'div').left;1955return new Pos(line, endCh);1956},1957moveByDisplayLines: function(cm, head, motionArgs, vim) {1958var cur = head;1959switch (vim.lastMotion) {1960case this.moveByDisplayLines:1961case this.moveByScroll:1962case this.moveByLines:1963case this.moveToColumn:1964case this.moveToEol:1965break;1966default:1967vim.lastHSPos = cm.charCoords(cur,'div').left;1968}1969var repeat = motionArgs.repeat;1970var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos);1971if (res.hitSide) {1972if (motionArgs.forward) {1973var lastCharCoords = cm.charCoords(res, 'div');1974var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos };1975var res = cm.coordsChar(goalCoords, 'div');1976} else {1977var resCoords = cm.charCoords(new Pos(cm.firstLine(), 0), 'div');1978resCoords.left = vim.lastHSPos;1979res = cm.coordsChar(resCoords, 'div');1980}1981}1982vim.lastHPos = res.ch;1983return res;1984},1985moveByPage: function(cm, head, motionArgs) {1986// CodeMirror only exposes functions that move the cursor page down, so1987// doing this bad hack to move the cursor and move it back. evalInput1988// will move the cursor to where it should be in the end.1989var curStart = head;1990var repeat = motionArgs.repeat;1991return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page');1992},1993moveByParagraph: function(cm, head, motionArgs) {1994var dir = motionArgs.forward ? 1 : -1;1995return findParagraph(cm, head, motionArgs.repeat, dir);1996},1997moveBySentence: function(cm, head, motionArgs) {1998var dir = motionArgs.forward ? 1 : -1;1999return findSentence(cm, head, motionArgs.repeat, dir);2000},2001moveByScroll: function(cm, head, motionArgs, vim) {2002var scrollbox = cm.getScrollInfo();2003var curEnd = null;2004var repeat = motionArgs.repeat;2005if (!repeat) {2006repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());2007}2008var orig = cm.charCoords(head, 'local');2009motionArgs.repeat = repeat;2010var curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim);2011if (!curEnd) {2012return null;2013}2014var dest = cm.charCoords(curEnd, 'local');2015cm.scrollTo(null, scrollbox.top + dest.top - orig.top);2016return curEnd;2017},2018moveByWords: function(cm, head, motionArgs) {2019return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward,2020!!motionArgs.wordEnd, !!motionArgs.bigWord);2021},2022moveTillCharacter: function(cm, _head, motionArgs) {2023var repeat = motionArgs.repeat;2024var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,2025motionArgs.selectedCharacter);2026var increment = motionArgs.forward ? -1 : 1;2027recordLastCharacterSearch(increment, motionArgs);2028if (!curEnd) return null;2029curEnd.ch += increment;2030return curEnd;2031},2032moveToCharacter: function(cm, head, motionArgs) {2033var repeat = motionArgs.repeat;2034recordLastCharacterSearch(0, motionArgs);2035return moveToCharacter(cm, repeat, motionArgs.forward,2036motionArgs.selectedCharacter) || head;2037},2038moveToSymbol: function(cm, head, motionArgs) {2039var repeat = motionArgs.repeat;2040return findSymbol(cm, repeat, motionArgs.forward,2041motionArgs.selectedCharacter) || head;2042},2043moveToColumn: function(cm, head, motionArgs, vim) {2044var repeat = motionArgs.repeat;2045// repeat is equivalent to which column we want to move to!2046vim.lastHPos = repeat - 1;2047vim.lastHSPos = cm.charCoords(head,'div').left;2048return moveToColumn(cm, repeat);2049},2050moveToEol: function(cm, head, motionArgs, vim) {2051return moveToEol(cm, head, motionArgs, vim, false);2052},2053moveToFirstNonWhiteSpaceCharacter: function(cm, head) {2054// Go to the start of the line where the text begins, or the end for2055// whitespace-only lines2056var cursor = head;2057return new Pos(cursor.line,2058findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)));2059},2060moveToMatchedSymbol: function(cm, head) {2061var cursor = head;2062var line = cursor.line;2063var ch = cursor.ch;2064var lineText = cm.getLine(line);2065var symbol;2066for (; ch < lineText.length; ch++) {2067symbol = lineText.charAt(ch);2068if (symbol && isMatchableSymbol(symbol)) {2069var style = cm.getTokenTypeAt(new Pos(line, ch + 1));2070if (style !== "string" && style !== "comment") {2071break;2072}2073}2074}2075if (ch < lineText.length) {2076// Only include angle brackets in analysis if they are being matched.2077var re = (ch === '<' || ch === '>') ? /[(){}[\]<>]/ : /[(){}[\]]/;2078var matched = cm.findMatchingBracket(new Pos(line, ch), {bracketRegex: re});2079return matched.to;2080} else {2081return cursor;2082}2083},2084moveToStartOfLine: function(_cm, head) {2085return new Pos(head.line, 0);2086},2087moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) {2088var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();2089if (motionArgs.repeatIsExplicit) {2090lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');2091}2092return new Pos(lineNum,2093findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)));2094},2095moveToStartOfDisplayLine: function(cm) {2096cm.execCommand("goLineLeft");2097return cm.getCursor();2098},2099moveToEndOfDisplayLine: function(cm) {2100cm.execCommand("goLineRight");2101var head = cm.getCursor();2102if (head.sticky == "before") head.ch--;2103return head;2104},2105textObjectManipulation: function(cm, head, motionArgs, vim) {2106// TODO: lots of possible exceptions that can be thrown here. Try da(2107// outside of a () block.2108var mirroredPairs = {'(': ')', ')': '(',2109'{': '}', '}': '{',2110'[': ']', ']': '[',2111'<': '>', '>': '<'};2112var selfPaired = {'\'': true, '"': true, '`': true};2113
2114var character = motionArgs.selectedCharacter;2115// 'b' refers to '()' block.2116// 'B' refers to '{}' block.2117if (character == 'b') {2118character = '(';2119} else if (character == 'B') {2120character = '{';2121}2122
2123// Inclusive is the difference between a and i2124// TODO: Instead of using the additional text object map to perform text2125// object operations, merge the map into the defaultKeyMap and use2126// motionArgs to define behavior. Define separate entries for 'aw',2127// 'iw', 'a[', 'i[', etc.2128var inclusive = !motionArgs.textObjectInner;2129
2130var tmp;2131if (mirroredPairs[character]) {2132tmp = selectCompanionObject(cm, head, character, inclusive);2133} else if (selfPaired[character]) {2134tmp = findBeginningAndEnd(cm, head, character, inclusive);2135} else if (character === 'W') {2136tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,2137true /** bigWord */);2138} else if (character === 'w') {2139tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,2140false /** bigWord */);2141} else if (character === 'p') {2142tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive);2143motionArgs.linewise = true;2144if (vim.visualMode) {2145if (!vim.visualLine) { vim.visualLine = true; }2146} else {2147var operatorArgs = vim.inputState.operatorArgs;2148if (operatorArgs) { operatorArgs.linewise = true; }2149tmp.end.line--;2150}2151} else if (character === 't') {2152tmp = expandTagUnderCursor(cm, head, inclusive);2153} else {2154// No text object defined for this, don't move.2155return null;2156}2157
2158if (!cm.state.vim.visualMode) {2159return [tmp.start, tmp.end];2160} else {2161return expandSelection(cm, tmp.start, tmp.end);2162}2163},2164
2165repeatLastCharacterSearch: function(cm, head, motionArgs) {2166var lastSearch = vimGlobalState.lastCharacterSearch;2167var repeat = motionArgs.repeat;2168var forward = motionArgs.forward === lastSearch.forward;2169var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);2170cm.moveH(-increment, 'char');2171motionArgs.inclusive = forward ? true : false;2172var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);2173if (!curEnd) {2174cm.moveH(increment, 'char');2175return head;2176}2177curEnd.ch += increment;2178return curEnd;2179}2180};2181
2182function defineMotion(name, fn) {2183motions[name] = fn;2184}2185
2186function fillArray(val, times) {2187var arr = [];2188for (var i = 0; i < times; i++) {2189arr.push(val);2190}2191return arr;2192}2193/**2194* An operator acts on a text selection. It receives the list of selections
2195* as input. The corresponding CodeMirror selection is guaranteed to
2196* match the input selection.
2197*/
2198var operators = {2199change: function(cm, args, ranges) {2200var finalHead, text;2201var vim = cm.state.vim;2202var anchor = ranges[0].anchor,2203head = ranges[0].head;2204if (!vim.visualMode) {2205text = cm.getRange(anchor, head);2206var lastState = vim.lastEditInputState || {};2207if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) {2208// Exclude trailing whitespace if the range is not all whitespace.2209var match = (/\s+$/).exec(text);2210if (match && lastState.motionArgs && lastState.motionArgs.forward) {2211head = offsetCursor(head, 0, - match[0].length);2212text = text.slice(0, - match[0].length);2213}2214}2215var prevLineEnd = new Pos(anchor.line - 1, Number.MAX_VALUE);2216var wasLastLine = cm.firstLine() == cm.lastLine();2217if (head.line > cm.lastLine() && args.linewise && !wasLastLine) {2218cm.replaceRange('', prevLineEnd, head);2219} else {2220cm.replaceRange('', anchor, head);2221}2222if (args.linewise) {2223// Push the next line back down, if there is a next line.2224if (!wasLastLine) {2225cm.setCursor(prevLineEnd);2226CodeMirror.commands.newlineAndIndent(cm);2227}2228// make sure cursor ends up at the end of the line.2229anchor.ch = Number.MAX_VALUE;2230}2231finalHead = anchor;2232} else if (args.fullLine) {2233head.ch = Number.MAX_VALUE;2234head.line--;2235cm.setSelection(anchor, head)2236text = cm.getSelection();2237cm.replaceSelection("");2238finalHead = anchor;2239} else {2240text = cm.getSelection();2241var replacement = fillArray('', ranges.length);2242cm.replaceSelections(replacement);2243finalHead = cursorMin(ranges[0].head, ranges[0].anchor);2244}2245vimGlobalState.registerController.pushText(2246args.registerName, 'change', text,2247args.linewise, ranges.length > 1);2248actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim);2249},2250// delete is a javascript keyword.2251'delete': function(cm, args, ranges) {2252var finalHead, text;2253var vim = cm.state.vim;2254if (!vim.visualBlock) {2255var anchor = ranges[0].anchor,2256head = ranges[0].head;2257if (args.linewise &&2258head.line != cm.firstLine() &&2259anchor.line == cm.lastLine() &&2260anchor.line == head.line - 1) {2261// Special case for dd on last line (and first line).2262if (anchor.line == cm.firstLine()) {2263anchor.ch = 0;2264} else {2265anchor = new Pos(anchor.line - 1, lineLength(cm, anchor.line - 1));2266}2267}2268text = cm.getRange(anchor, head);2269cm.replaceRange('', anchor, head);2270finalHead = anchor;2271if (args.linewise) {2272finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor);2273}2274} else {2275text = cm.getSelection();2276var replacement = fillArray('', ranges.length);2277cm.replaceSelections(replacement);2278finalHead = cursorMin(ranges[0].head, ranges[0].anchor);2279}2280vimGlobalState.registerController.pushText(2281args.registerName, 'delete', text,2282args.linewise, vim.visualBlock);2283return clipCursorToContent(cm, finalHead);2284},2285indent: function(cm, args, ranges) {2286var vim = cm.state.vim;2287var startLine = ranges[0].anchor.line;2288var endLine = vim.visualBlock ?2289ranges[ranges.length - 1].anchor.line :2290ranges[0].head.line;2291// In visual mode, n> shifts the selection right n times, instead of2292// shifting n lines right once.2293var repeat = (vim.visualMode) ? args.repeat : 1;2294if (args.linewise) {2295// The only way to delete a newline is to delete until the start of2296// the next line, so in linewise mode evalInput will include the next2297// line. We don't want this in indent, so we go back a line.2298endLine--;2299}2300for (var i = startLine; i <= endLine; i++) {2301for (var j = 0; j < repeat; j++) {2302cm.indentLine(i, args.indentRight);2303}2304}2305return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);2306},2307indentAuto: function(cm, _args, ranges) {2308cm.execCommand("indentAuto");2309return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);2310},2311changeCase: function(cm, args, ranges, oldAnchor, newHead) {2312var selections = cm.getSelections();2313var swapped = [];2314var toLower = args.toLower;2315for (var j = 0; j < selections.length; j++) {2316var toSwap = selections[j];2317var text = '';2318if (toLower === true) {2319text = toSwap.toLowerCase();2320} else if (toLower === false) {2321text = toSwap.toUpperCase();2322} else {2323for (var i = 0; i < toSwap.length; i++) {2324var character = toSwap.charAt(i);2325text += isUpperCase(character) ? character.toLowerCase() :2326character.toUpperCase();2327}2328}2329swapped.push(text);2330}2331cm.replaceSelections(swapped);2332if (args.shouldMoveCursor){2333return newHead;2334} else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) {2335return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor);2336} else if (args.linewise){2337return oldAnchor;2338} else {2339return cursorMin(ranges[0].anchor, ranges[0].head);2340}2341},2342yank: function(cm, args, ranges, oldAnchor) {2343var vim = cm.state.vim;2344var text = cm.getSelection();2345var endPos = vim.visualMode2346? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor)2347: oldAnchor;2348vimGlobalState.registerController.pushText(2349args.registerName, 'yank',2350text, args.linewise, vim.visualBlock);2351return endPos;2352}2353};2354
2355function defineOperator(name, fn) {2356operators[name] = fn;2357}2358
2359var actions = {2360jumpListWalk: function(cm, actionArgs, vim) {2361if (vim.visualMode) {2362return;2363}2364var repeat = actionArgs.repeat;2365var forward = actionArgs.forward;2366var jumpList = vimGlobalState.jumpList;2367
2368var mark = jumpList.move(cm, forward ? repeat : -repeat);2369var markPos = mark ? mark.find() : undefined;2370markPos = markPos ? markPos : cm.getCursor();2371cm.setCursor(markPos);2372},2373scroll: function(cm, actionArgs, vim) {2374if (vim.visualMode) {2375return;2376}2377var repeat = actionArgs.repeat || 1;2378var lineHeight = cm.defaultTextHeight();2379var top = cm.getScrollInfo().top;2380var delta = lineHeight * repeat;2381var newPos = actionArgs.forward ? top + delta : top - delta;2382var cursor = copyCursor(cm.getCursor());2383var cursorCoords = cm.charCoords(cursor, 'local');2384if (actionArgs.forward) {2385if (newPos > cursorCoords.top) {2386cursor.line += (newPos - cursorCoords.top) / lineHeight;2387cursor.line = Math.ceil(cursor.line);2388cm.setCursor(cursor);2389cursorCoords = cm.charCoords(cursor, 'local');2390cm.scrollTo(null, cursorCoords.top);2391} else {2392// Cursor stays within bounds. Just reposition the scroll window.2393cm.scrollTo(null, newPos);2394}2395} else {2396var newBottom = newPos + cm.getScrollInfo().clientHeight;2397if (newBottom < cursorCoords.bottom) {2398cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight;2399cursor.line = Math.floor(cursor.line);2400cm.setCursor(cursor);2401cursorCoords = cm.charCoords(cursor, 'local');2402cm.scrollTo(2403null, cursorCoords.bottom - cm.getScrollInfo().clientHeight);2404} else {2405// Cursor stays within bounds. Just reposition the scroll window.2406cm.scrollTo(null, newPos);2407}2408}2409},2410scrollToCursor: function(cm, actionArgs) {2411var lineNum = cm.getCursor().line;2412var charCoords = cm.charCoords(new Pos(lineNum, 0), 'local');2413var height = cm.getScrollInfo().clientHeight;2414var y = charCoords.top;2415var lineHeight = charCoords.bottom - y;2416switch (actionArgs.position) {2417case 'center': y = y - (height / 2) + lineHeight;2418break;2419case 'bottom': y = y - height + lineHeight;2420break;2421}2422cm.scrollTo(null, y);2423},2424replayMacro: function(cm, actionArgs, vim) {2425var registerName = actionArgs.selectedCharacter;2426var repeat = actionArgs.repeat;2427var macroModeState = vimGlobalState.macroModeState;2428if (registerName == '@') {2429registerName = macroModeState.latestRegister;2430} else {2431macroModeState.latestRegister = registerName;2432}2433while(repeat--){2434executeMacroRegister(cm, vim, macroModeState, registerName);2435}2436},2437enterMacroRecordMode: function(cm, actionArgs) {2438var macroModeState = vimGlobalState.macroModeState;2439var registerName = actionArgs.selectedCharacter;2440if (vimGlobalState.registerController.isValidRegister(registerName)) {2441macroModeState.enterMacroRecordMode(cm, registerName);2442}2443},2444toggleOverwrite: function(cm) {2445if (!cm.state.overwrite) {2446cm.toggleOverwrite(true);2447cm.setOption('keyMap', 'vim-replace');2448CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});2449} else {2450cm.toggleOverwrite(false);2451cm.setOption('keyMap', 'vim-insert');2452CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});2453}2454},2455enterInsertMode: function(cm, actionArgs, vim) {2456if (cm.getOption('readOnly')) { return; }2457vim.insertMode = true;2458vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;2459var insertAt = (actionArgs) ? actionArgs.insertAt : null;2460var sel = vim.sel;2461var head = actionArgs.head || cm.getCursor('head');2462var height = cm.listSelections().length;2463if (insertAt == 'eol') {2464head = new Pos(head.line, lineLength(cm, head.line));2465} else if (insertAt == 'bol') {2466head = new Pos(head.line, 0);2467} else if (insertAt == 'charAfter') {2468head = offsetCursor(head, 0, 1);2469} else if (insertAt == 'firstNonBlank') {2470head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head);2471} else if (insertAt == 'startOfSelectedArea') {2472if (!vim.visualMode)2473return;2474if (!vim.visualBlock) {2475if (sel.head.line < sel.anchor.line) {2476head = sel.head;2477} else {2478head = new Pos(sel.anchor.line, 0);2479}2480} else {2481head = new Pos(2482Math.min(sel.head.line, sel.anchor.line),2483Math.min(sel.head.ch, sel.anchor.ch));2484height = Math.abs(sel.head.line - sel.anchor.line) + 1;2485}2486} else if (insertAt == 'endOfSelectedArea') {2487if (!vim.visualMode)2488return;2489if (!vim.visualBlock) {2490if (sel.head.line >= sel.anchor.line) {2491head = offsetCursor(sel.head, 0, 1);2492} else {2493head = new Pos(sel.anchor.line, 0);2494}2495} else {2496head = new Pos(2497Math.min(sel.head.line, sel.anchor.line),2498Math.max(sel.head.ch, sel.anchor.ch) + 1);2499height = Math.abs(sel.head.line - sel.anchor.line) + 1;2500}2501} else if (insertAt == 'inplace') {2502if (vim.visualMode){2503return;2504}2505} else if (insertAt == 'lastEdit') {2506head = getLastEditPos(cm) || head;2507}2508cm.setOption('disableInput', false);2509if (actionArgs && actionArgs.replace) {2510// Handle Replace-mode as a special case of insert mode.2511cm.toggleOverwrite(true);2512cm.setOption('keyMap', 'vim-replace');2513CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});2514} else {2515cm.toggleOverwrite(false);2516cm.setOption('keyMap', 'vim-insert');2517CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});2518}2519if (!vimGlobalState.macroModeState.isPlaying) {2520// Only record if not replaying.2521cm.on('change', onChange);2522CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);2523}2524if (vim.visualMode) {2525exitVisualMode(cm);2526}2527selectForInsert(cm, head, height);2528},2529toggleVisualMode: function(cm, actionArgs, vim) {2530var repeat = actionArgs.repeat;2531var anchor = cm.getCursor();2532var head;2533// TODO: The repeat should actually select number of characters/lines2534// equal to the repeat times the size of the previous visual2535// operation.2536if (!vim.visualMode) {2537// Entering visual mode2538vim.visualMode = true;2539vim.visualLine = !!actionArgs.linewise;2540vim.visualBlock = !!actionArgs.blockwise;2541head = clipCursorToContent(2542cm, new Pos(anchor.line, anchor.ch + repeat - 1));2543vim.sel = {2544anchor: anchor,2545head: head2546};2547CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});2548updateCmSelection(cm);2549updateMark(cm, vim, '<', cursorMin(anchor, head));2550updateMark(cm, vim, '>', cursorMax(anchor, head));2551} else if (vim.visualLine ^ actionArgs.linewise ||2552vim.visualBlock ^ actionArgs.blockwise) {2553// Toggling between modes2554vim.visualLine = !!actionArgs.linewise;2555vim.visualBlock = !!actionArgs.blockwise;2556CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});2557updateCmSelection(cm);2558} else {2559exitVisualMode(cm);2560}2561},2562reselectLastSelection: function(cm, _actionArgs, vim) {2563var lastSelection = vim.lastSelection;2564if (vim.visualMode) {2565updateLastSelection(cm, vim);2566}2567if (lastSelection) {2568var anchor = lastSelection.anchorMark.find();2569var head = lastSelection.headMark.find();2570if (!anchor || !head) {2571// If the marks have been destroyed due to edits, do nothing.2572return;2573}2574vim.sel = {2575anchor: anchor,2576head: head2577};2578vim.visualMode = true;2579vim.visualLine = lastSelection.visualLine;2580vim.visualBlock = lastSelection.visualBlock;2581updateCmSelection(cm);2582updateMark(cm, vim, '<', cursorMin(anchor, head));2583updateMark(cm, vim, '>', cursorMax(anchor, head));2584CodeMirror.signal(cm, 'vim-mode-change', {2585mode: 'visual',2586subMode: vim.visualLine ? 'linewise' :2587vim.visualBlock ? 'blockwise' : ''});2588}2589},2590joinLines: function(cm, actionArgs, vim) {2591var curStart, curEnd;2592if (vim.visualMode) {2593curStart = cm.getCursor('anchor');2594curEnd = cm.getCursor('head');2595if (cursorIsBefore(curEnd, curStart)) {2596var tmp = curEnd;2597curEnd = curStart;2598curStart = tmp;2599}2600curEnd.ch = lineLength(cm, curEnd.line) - 1;2601} else {2602// Repeat is the number of lines to join. Minimum 2 lines.2603var repeat = Math.max(actionArgs.repeat, 2);2604curStart = cm.getCursor();2605curEnd = clipCursorToContent(cm, new Pos(curStart.line + repeat - 1,2606Infinity));2607}2608var finalCh = 0;2609for (var i = curStart.line; i < curEnd.line; i++) {2610finalCh = lineLength(cm, curStart.line);2611var tmp = new Pos(curStart.line + 1,2612lineLength(cm, curStart.line + 1));2613var text = cm.getRange(curStart, tmp);2614text = actionArgs.keepSpaces2615? text.replace(/\n\r?/g, '')2616: text.replace(/\n\s*/g, ' ');2617cm.replaceRange(text, curStart, tmp);2618}2619var curFinalPos = new Pos(curStart.line, finalCh);2620if (vim.visualMode) {2621exitVisualMode(cm, false);2622}2623cm.setCursor(curFinalPos);2624},2625newLineAndEnterInsertMode: function(cm, actionArgs, vim) {2626vim.insertMode = true;2627var insertAt = copyCursor(cm.getCursor());2628if (insertAt.line === cm.firstLine() && !actionArgs.after) {2629// Special case for inserting newline before start of document.2630cm.replaceRange('\n', new Pos(cm.firstLine(), 0));2631cm.setCursor(cm.firstLine(), 0);2632} else {2633insertAt.line = (actionArgs.after) ? insertAt.line :2634insertAt.line - 1;2635insertAt.ch = lineLength(cm, insertAt.line);2636cm.setCursor(insertAt);2637var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment ||2638CodeMirror.commands.newlineAndIndent;2639newlineFn(cm);2640}2641this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim);2642},2643paste: function(cm, actionArgs, vim) {2644var cur = copyCursor(cm.getCursor());2645var register = vimGlobalState.registerController.getRegister(2646actionArgs.registerName);2647var text = register.toString();2648if (!text) {2649return;2650}2651if (actionArgs.matchIndent) {2652var tabSize = cm.getOption("tabSize");2653// length that considers tabs and tabSize2654var whitespaceLength = function(str) {2655var tabs = (str.split("\t").length - 1);2656var spaces = (str.split(" ").length - 1);2657return tabs * tabSize + spaces * 1;2658};2659var currentLine = cm.getLine(cm.getCursor().line);2660var indent = whitespaceLength(currentLine.match(/^\s*/)[0]);2661// chomp last newline b/c don't want it to match /^\s*/gm2662var chompedText = text.replace(/\n$/, '');2663var wasChomped = text !== chompedText;2664var firstIndent = whitespaceLength(text.match(/^\s*/)[0]);2665var text = chompedText.replace(/^\s*/gm, function(wspace) {2666var newIndent = indent + (whitespaceLength(wspace) - firstIndent);2667if (newIndent < 0) {2668return "";2669}2670else if (cm.getOption("indentWithTabs")) {2671var quotient = Math.floor(newIndent / tabSize);2672return Array(quotient + 1).join('\t');2673}2674else {2675return Array(newIndent + 1).join(' ');2676}2677});2678text += wasChomped ? "\n" : "";2679}2680if (actionArgs.repeat > 1) {2681var text = Array(actionArgs.repeat + 1).join(text);2682}2683var linewise = register.linewise;2684var blockwise = register.blockwise;2685if (blockwise) {2686text = text.split('\n');2687if (linewise) {2688text.pop();2689}2690for (var i = 0; i < text.length; i++) {2691text[i] = (text[i] == '') ? ' ' : text[i];2692}2693cur.ch += actionArgs.after ? 1 : 0;2694cur.ch = Math.min(lineLength(cm, cur.line), cur.ch);2695} else if (linewise) {2696if(vim.visualMode) {2697text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n';2698} else if (actionArgs.after) {2699// Move the newline at the end to the start instead, and paste just2700// before the newline character of the line we are on right now.2701text = '\n' + text.slice(0, text.length - 1);2702cur.ch = lineLength(cm, cur.line);2703} else {2704cur.ch = 0;2705}2706} else {2707cur.ch += actionArgs.after ? 1 : 0;2708}2709var curPosFinal;2710var idx;2711if (vim.visualMode) {2712// save the pasted text for reselection if the need arises2713vim.lastPastedText = text;2714var lastSelectionCurEnd;2715var selectedArea = getSelectedAreaRange(cm, vim);2716var selectionStart = selectedArea[0];2717var selectionEnd = selectedArea[1];2718var selectedText = cm.getSelection();2719var selections = cm.listSelections();2720var emptyStrings = new Array(selections.length).join('1').split('1');2721// save the curEnd marker before it get cleared due to cm.replaceRange.2722if (vim.lastSelection) {2723lastSelectionCurEnd = vim.lastSelection.headMark.find();2724}2725// push the previously selected text to unnamed register2726vimGlobalState.registerController.unnamedRegister.setText(selectedText);2727if (blockwise) {2728// first delete the selected text2729cm.replaceSelections(emptyStrings);2730// Set new selections as per the block length of the yanked text2731selectionEnd = new Pos(selectionStart.line + text.length-1, selectionStart.ch);2732cm.setCursor(selectionStart);2733selectBlock(cm, selectionEnd);2734cm.replaceSelections(text);2735curPosFinal = selectionStart;2736} else if (vim.visualBlock) {2737cm.replaceSelections(emptyStrings);2738cm.setCursor(selectionStart);2739cm.replaceRange(text, selectionStart, selectionStart);2740curPosFinal = selectionStart;2741} else {2742cm.replaceRange(text, selectionStart, selectionEnd);2743curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1);2744}2745// restore the the curEnd marker2746if(lastSelectionCurEnd) {2747vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd);2748}2749if (linewise) {2750curPosFinal.ch=0;2751}2752} else {2753if (blockwise) {2754cm.setCursor(cur);2755for (var i = 0; i < text.length; i++) {2756var line = cur.line+i;2757if (line > cm.lastLine()) {2758cm.replaceRange('\n', new Pos(line, 0));2759}2760var lastCh = lineLength(cm, line);2761if (lastCh < cur.ch) {2762extendLineToColumn(cm, line, cur.ch);2763}2764}2765cm.setCursor(cur);2766selectBlock(cm, new Pos(cur.line + text.length-1, cur.ch));2767cm.replaceSelections(text);2768curPosFinal = cur;2769} else {2770cm.replaceRange(text, cur);2771// Now fine tune the cursor to where we want it.2772if (linewise && actionArgs.after) {2773curPosFinal = new Pos(2774cur.line + 1,2775findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)));2776} else if (linewise && !actionArgs.after) {2777curPosFinal = new Pos(2778cur.line,2779findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));2780} else if (!linewise && actionArgs.after) {2781idx = cm.indexFromPos(cur);2782curPosFinal = cm.posFromIndex(idx + text.length - 1);2783} else {2784idx = cm.indexFromPos(cur);2785curPosFinal = cm.posFromIndex(idx + text.length);2786}2787}2788}2789if (vim.visualMode) {2790exitVisualMode(cm, false);2791}2792cm.setCursor(curPosFinal);2793},2794undo: function(cm, actionArgs) {2795cm.operation(function() {2796repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();2797cm.setCursor(cm.getCursor('anchor'));2798});2799},2800redo: function(cm, actionArgs) {2801repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();2802},2803setRegister: function(_cm, actionArgs, vim) {2804vim.inputState.registerName = actionArgs.selectedCharacter;2805},2806setMark: function(cm, actionArgs, vim) {2807var markName = actionArgs.selectedCharacter;2808updateMark(cm, vim, markName, cm.getCursor());2809},2810replace: function(cm, actionArgs, vim) {2811var replaceWith = actionArgs.selectedCharacter;2812var curStart = cm.getCursor();2813var replaceTo;2814var curEnd;2815var selections = cm.listSelections();2816if (vim.visualMode) {2817curStart = cm.getCursor('start');2818curEnd = cm.getCursor('end');2819} else {2820var line = cm.getLine(curStart.line);2821replaceTo = curStart.ch + actionArgs.repeat;2822if (replaceTo > line.length) {2823replaceTo=line.length;2824}2825curEnd = new Pos(curStart.line, replaceTo);2826}2827if (replaceWith=='\n') {2828if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);2829// special case, where vim help says to replace by just one line-break2830(CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);2831} else {2832var replaceWithStr = cm.getRange(curStart, curEnd);2833//replace all characters in range by selected, but keep linebreaks2834replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith);2835if (vim.visualBlock) {2836// Tabs are split in visua block before replacing2837var spaces = new Array(cm.getOption("tabSize")+1).join(' ');2838replaceWithStr = cm.getSelection();2839replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n');2840cm.replaceSelections(replaceWithStr);2841} else {2842cm.replaceRange(replaceWithStr, curStart, curEnd);2843}2844if (vim.visualMode) {2845curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ?2846selections[0].anchor : selections[0].head;2847cm.setCursor(curStart);2848exitVisualMode(cm, false);2849} else {2850cm.setCursor(offsetCursor(curEnd, 0, -1));2851}2852}2853},2854incrementNumberToken: function(cm, actionArgs) {2855var cur = cm.getCursor();2856var lineStr = cm.getLine(cur.line);2857var re = /(-?)(?:(0x)([\da-f]+)|(0b|0|)(\d+))/gi;2858var match;2859var start;2860var end;2861var numberStr;2862while ((match = re.exec(lineStr)) !== null) {2863start = match.index;2864end = start + match[0].length;2865if (cur.ch < end)break;2866}2867if (!actionArgs.backtrack && (end <= cur.ch))return;2868if (match) {2869var baseStr = match[2] || match[4]2870var digits = match[3] || match[5]2871var increment = actionArgs.increase ? 1 : -1;2872var base = {'0b': 2, '0': 8, '': 10, '0x': 16}[baseStr.toLowerCase()];2873var number = parseInt(match[1] + digits, base) + (increment * actionArgs.repeat);2874numberStr = number.toString(base);2875var zeroPadding = baseStr ? new Array(digits.length - numberStr.length + 1 + match[1].length).join('0') : ''2876if (numberStr.charAt(0) === '-') {2877numberStr = '-' + baseStr + zeroPadding + numberStr.substr(1);2878} else {2879numberStr = baseStr + zeroPadding + numberStr;2880}2881var from = new Pos(cur.line, start);2882var to = new Pos(cur.line, end);2883cm.replaceRange(numberStr, from, to);2884} else {2885return;2886}2887cm.setCursor(new Pos(cur.line, start + numberStr.length - 1));2888},2889repeatLastEdit: function(cm, actionArgs, vim) {2890var lastEditInputState = vim.lastEditInputState;2891if (!lastEditInputState) { return; }2892var repeat = actionArgs.repeat;2893if (repeat && actionArgs.repeatIsExplicit) {2894vim.lastEditInputState.repeatOverride = repeat;2895} else {2896repeat = vim.lastEditInputState.repeatOverride || repeat;2897}2898repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);2899},2900indent: function(cm, actionArgs) {2901cm.indentLine(cm.getCursor().line, actionArgs.indentRight);2902},2903exitInsertMode: exitInsertMode2904};2905
2906function defineAction(name, fn) {2907actions[name] = fn;2908}2909
2910/*2911* Below are miscellaneous utility functions used by vim.js
2912*/
2913
2914/**2915* Clips cursor to ensure that line is within the buffer's range
2916* If includeLineBreak is true, then allow cur.ch == lineLength.
2917*/
2918function clipCursorToContent(cm, cur) {2919var vim = cm.state.vim;2920var includeLineBreak = vim.insertMode || vim.visualMode;2921var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() );2922var maxCh = lineLength(cm, line) - 1 + !!includeLineBreak;2923var ch = Math.min(Math.max(0, cur.ch), maxCh);2924return new Pos(line, ch);2925}2926function copyArgs(args) {2927var ret = {};2928for (var prop in args) {2929if (args.hasOwnProperty(prop)) {2930ret[prop] = args[prop];2931}2932}2933return ret;2934}2935function offsetCursor(cur, offsetLine, offsetCh) {2936if (typeof offsetLine === 'object') {2937offsetCh = offsetLine.ch;2938offsetLine = offsetLine.line;2939}2940return new Pos(cur.line + offsetLine, cur.ch + offsetCh);2941}2942function commandMatches(keys, keyMap, context, inputState) {2943// Partial matches are not applied. They inform the key handler2944// that the current key sequence is a subsequence of a valid key2945// sequence, so that the key buffer is not cleared.2946var match, partial = [], full = [];2947for (var i = 0; i < keyMap.length; i++) {2948var command = keyMap[i];2949if (context == 'insert' && command.context != 'insert' ||2950command.context && command.context != context ||2951inputState.operator && command.type == 'action' ||2952!(match = commandMatch(keys, command.keys))) { continue; }2953if (match == 'partial') { partial.push(command); }2954if (match == 'full') { full.push(command); }2955}2956return {2957partial: partial.length && partial,2958full: full.length && full2959};2960}2961function commandMatch(pressed, mapped) {2962if (mapped.slice(-11) == '<character>') {2963// Last character matches anything.2964var prefixLen = mapped.length - 11;2965var pressedPrefix = pressed.slice(0, prefixLen);2966var mappedPrefix = mapped.slice(0, prefixLen);2967return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' :2968mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false;2969} else {2970return pressed == mapped ? 'full' :2971mapped.indexOf(pressed) == 0 ? 'partial' : false;2972}2973}2974function lastChar(keys) {2975var match = /^.*(<[^>]+>)$/.exec(keys);2976var selectedCharacter = match ? match[1] : keys.slice(-1);2977if (selectedCharacter.length > 1){2978switch(selectedCharacter){2979case '<CR>':2980selectedCharacter='\n';2981break;2982case '<Space>':2983selectedCharacter=' ';2984break;2985default:2986selectedCharacter='';2987break;2988}2989}2990return selectedCharacter;2991}2992function repeatFn(cm, fn, repeat) {2993return function() {2994for (var i = 0; i < repeat; i++) {2995fn(cm);2996}2997};2998}2999function copyCursor(cur) {3000return new Pos(cur.line, cur.ch);3001}3002function cursorEqual(cur1, cur2) {3003return cur1.ch == cur2.ch && cur1.line == cur2.line;3004}3005function cursorIsBefore(cur1, cur2) {3006if (cur1.line < cur2.line) {3007return true;3008}3009if (cur1.line == cur2.line && cur1.ch < cur2.ch) {3010return true;3011}3012return false;3013}3014function cursorMin(cur1, cur2) {3015if (arguments.length > 2) {3016cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1));3017}3018return cursorIsBefore(cur1, cur2) ? cur1 : cur2;3019}3020function cursorMax(cur1, cur2) {3021if (arguments.length > 2) {3022cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1));3023}3024return cursorIsBefore(cur1, cur2) ? cur2 : cur1;3025}3026function cursorIsBetween(cur1, cur2, cur3) {3027// returns true if cur2 is between cur1 and cur3.3028var cur1before2 = cursorIsBefore(cur1, cur2);3029var cur2before3 = cursorIsBefore(cur2, cur3);3030return cur1before2 && cur2before3;3031}3032function lineLength(cm, lineNum) {3033return cm.getLine(lineNum).length;3034}3035function trim(s) {3036if (s.trim) {3037return s.trim();3038}3039return s.replace(/^\s+|\s+$/g, '');3040}3041function escapeRegex(s) {3042return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');3043}3044function extendLineToColumn(cm, lineNum, column) {3045var endCh = lineLength(cm, lineNum);3046var spaces = new Array(column-endCh+1).join(' ');3047cm.setCursor(new Pos(lineNum, endCh));3048cm.replaceRange(spaces, cm.getCursor());3049}3050// This functions selects a rectangular block3051// of text with selectionEnd as any of its corner3052// Height of block:3053// Difference in selectionEnd.line and first/last selection.line3054// Width of the block:3055// Distance between selectionEnd.ch and any(first considered here) selection.ch3056function selectBlock(cm, selectionEnd) {3057var selections = [], ranges = cm.listSelections();3058var head = copyCursor(cm.clipPos(selectionEnd));3059var isClipped = !cursorEqual(selectionEnd, head);3060var curHead = cm.getCursor('head');3061var primIndex = getIndex(ranges, curHead);3062var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor);3063var max = ranges.length - 1;3064var index = max - primIndex > primIndex ? max : 0;3065var base = ranges[index].anchor;3066
3067var firstLine = Math.min(base.line, head.line);3068var lastLine = Math.max(base.line, head.line);3069var baseCh = base.ch, headCh = head.ch;3070
3071var dir = ranges[index].head.ch - baseCh;3072var newDir = headCh - baseCh;3073if (dir > 0 && newDir <= 0) {3074baseCh++;3075if (!isClipped) { headCh--; }3076} else if (dir < 0 && newDir >= 0) {3077baseCh--;3078if (!wasClipped) { headCh++; }3079} else if (dir < 0 && newDir == -1) {3080baseCh--;3081headCh++;3082}3083for (var line = firstLine; line <= lastLine; line++) {3084var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)};3085selections.push(range);3086}3087cm.setSelections(selections);3088selectionEnd.ch = headCh;3089base.ch = baseCh;3090return base;3091}3092function selectForInsert(cm, head, height) {3093var sel = [];3094for (var i = 0; i < height; i++) {3095var lineHead = offsetCursor(head, i, 0);3096sel.push({anchor: lineHead, head: lineHead});3097}3098cm.setSelections(sel, 0);3099}3100// getIndex returns the index of the cursor in the selections.3101function getIndex(ranges, cursor, end) {3102for (var i = 0; i < ranges.length; i++) {3103var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor);3104var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor);3105if (atAnchor || atHead) {3106return i;3107}3108}3109return -1;3110}3111function getSelectedAreaRange(cm, vim) {3112var lastSelection = vim.lastSelection;3113var getCurrentSelectedAreaRange = function() {3114var selections = cm.listSelections();3115var start = selections[0];3116var end = selections[selections.length-1];3117var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;3118var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;3119return [selectionStart, selectionEnd];3120};3121var getLastSelectedAreaRange = function() {3122var selectionStart = cm.getCursor();3123var selectionEnd = cm.getCursor();3124var block = lastSelection.visualBlock;3125if (block) {3126var width = block.width;3127var height = block.height;3128selectionEnd = new Pos(selectionStart.line + height, selectionStart.ch + width);3129var selections = [];3130// selectBlock creates a 'proper' rectangular block.3131// We do not want that in all cases, so we manually set selections.3132for (var i = selectionStart.line; i < selectionEnd.line; i++) {3133var anchor = new Pos(i, selectionStart.ch);3134var head = new Pos(i, selectionEnd.ch);3135var range = {anchor: anchor, head: head};3136selections.push(range);3137}3138cm.setSelections(selections);3139} else {3140var start = lastSelection.anchorMark.find();3141var end = lastSelection.headMark.find();3142var line = end.line - start.line;3143var ch = end.ch - start.ch;3144selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch};3145if (lastSelection.visualLine) {3146selectionStart = new Pos(selectionStart.line, 0);3147selectionEnd = new Pos(selectionEnd.line, lineLength(cm, selectionEnd.line));3148}3149cm.setSelection(selectionStart, selectionEnd);3150}3151return [selectionStart, selectionEnd];3152};3153if (!vim.visualMode) {3154// In case of replaying the action.3155return getLastSelectedAreaRange();3156} else {3157return getCurrentSelectedAreaRange();3158}3159}3160// Updates the previous selection with the current selection's values. This3161// should only be called in visual mode.3162function updateLastSelection(cm, vim) {3163var anchor = vim.sel.anchor;3164var head = vim.sel.head;3165// To accommodate the effect of lastPastedText in the last selection3166if (vim.lastPastedText) {3167head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length);3168vim.lastPastedText = null;3169}3170vim.lastSelection = {'anchorMark': cm.setBookmark(anchor),3171'headMark': cm.setBookmark(head),3172'anchor': copyCursor(anchor),3173'head': copyCursor(head),3174'visualMode': vim.visualMode,3175'visualLine': vim.visualLine,3176'visualBlock': vim.visualBlock};3177}3178function expandSelection(cm, start, end) {3179var sel = cm.state.vim.sel;3180var head = sel.head;3181var anchor = sel.anchor;3182var tmp;3183if (cursorIsBefore(end, start)) {3184tmp = end;3185end = start;3186start = tmp;3187}3188if (cursorIsBefore(head, anchor)) {3189head = cursorMin(start, head);3190anchor = cursorMax(anchor, end);3191} else {3192anchor = cursorMin(start, anchor);3193head = cursorMax(head, end);3194head = offsetCursor(head, 0, -1);3195if (head.ch == -1 && head.line != cm.firstLine()) {3196head = new Pos(head.line - 1, lineLength(cm, head.line - 1));3197}3198}3199return [anchor, head];3200}3201/**3202* Updates the CodeMirror selection to match the provided vim selection.
3203* If no arguments are given, it uses the current vim selection state.
3204*/
3205function updateCmSelection(cm, sel, mode) {3206var vim = cm.state.vim;3207sel = sel || vim.sel;3208var mode = mode ||3209vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char';3210var cmSel = makeCmSelection(cm, sel, mode);3211cm.setSelections(cmSel.ranges, cmSel.primary);3212}3213function makeCmSelection(cm, sel, mode, exclusive) {3214var head = copyCursor(sel.head);3215var anchor = copyCursor(sel.anchor);3216if (mode == 'char') {3217var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;3218var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;3219head = offsetCursor(sel.head, 0, headOffset);3220anchor = offsetCursor(sel.anchor, 0, anchorOffset);3221return {3222ranges: [{anchor: anchor, head: head}],3223primary: 03224};3225} else if (mode == 'line') {3226if (!cursorIsBefore(sel.head, sel.anchor)) {3227anchor.ch = 0;3228
3229var lastLine = cm.lastLine();3230if (head.line > lastLine) {3231head.line = lastLine;3232}3233head.ch = lineLength(cm, head.line);3234} else {3235head.ch = 0;3236anchor.ch = lineLength(cm, anchor.line);3237}3238return {3239ranges: [{anchor: anchor, head: head}],3240primary: 03241};3242} else if (mode == 'block') {3243var top = Math.min(anchor.line, head.line),3244fromCh = anchor.ch,3245bottom = Math.max(anchor.line, head.line),3246toCh = head.ch;3247if (fromCh < toCh) { toCh += 1 }3248else { fromCh += 1 };3249var height = bottom - top + 1;3250var primary = head.line == top ? 0 : height - 1;3251var ranges = [];3252for (var i = 0; i < height; i++) {3253ranges.push({3254anchor: new Pos(top + i, fromCh),3255head: new Pos(top + i, toCh)3256});3257}3258return {3259ranges: ranges,3260primary: primary3261};3262}3263}3264function getHead(cm) {3265var cur = cm.getCursor('head');3266if (cm.getSelection().length == 1) {3267// Small corner case when only 1 character is selected. The "real"3268// head is the left of head and anchor.3269cur = cursorMin(cur, cm.getCursor('anchor'));3270}3271return cur;3272}3273
3274/**3275* If moveHead is set to false, the CodeMirror selection will not be
3276* touched. The caller assumes the responsibility of putting the cursor
3277* in the right place.
3278*/
3279function exitVisualMode(cm, moveHead) {3280var vim = cm.state.vim;3281if (moveHead !== false) {3282cm.setCursor(clipCursorToContent(cm, vim.sel.head));3283}3284updateLastSelection(cm, vim);3285vim.visualMode = false;3286vim.visualLine = false;3287vim.visualBlock = false;3288if (!vim.insertMode) CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});3289}3290
3291// Remove any trailing newlines from the selection. For3292// example, with the caret at the start of the last word on the line,3293// 'dw' should word, but not the newline, while 'w' should advance the3294// caret to the first character of the next line.3295function clipToLine(cm, curStart, curEnd) {3296var selection = cm.getRange(curStart, curEnd);3297// Only clip if the selection ends with trailing newline + whitespace3298if (/\n\s*$/.test(selection)) {3299var lines = selection.split('\n');3300// We know this is all whitespace.3301lines.pop();3302
3303// Cases:3304// 1. Last word is an empty line - do not clip the trailing '\n'3305// 2. Last word is not an empty line - clip the trailing '\n'3306var line;3307// Find the line containing the last word, and clip all whitespace up3308// to it.3309for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {3310curEnd.line--;3311curEnd.ch = 0;3312}3313// If the last word is not an empty line, clip an additional newline3314if (line) {3315curEnd.line--;3316curEnd.ch = lineLength(cm, curEnd.line);3317} else {3318curEnd.ch = 0;3319}3320}3321}3322
3323// Expand the selection to line ends.3324function expandSelectionToLine(_cm, curStart, curEnd) {3325curStart.ch = 0;3326curEnd.ch = 0;3327curEnd.line++;3328}3329
3330function findFirstNonWhiteSpaceCharacter(text) {3331if (!text) {3332return 0;3333}3334var firstNonWS = text.search(/\S/);3335return firstNonWS == -1 ? text.length : firstNonWS;3336}3337
3338function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {3339var cur = getHead(cm);3340var line = cm.getLine(cur.line);3341var idx = cur.ch;3342
3343// Seek to first word or non-whitespace character, depending on if3344// noSymbol is true.3345var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0];3346while (!test(line.charAt(idx))) {3347idx++;3348if (idx >= line.length) { return null; }3349}3350
3351if (bigWord) {3352test = bigWordCharTest[0];3353} else {3354test = wordCharTest[0];3355if (!test(line.charAt(idx))) {3356test = wordCharTest[1];3357}3358}3359
3360var end = idx, start = idx;3361while (test(line.charAt(end)) && end < line.length) { end++; }3362while (test(line.charAt(start)) && start >= 0) { start--; }3363start++;3364
3365if (inclusive) {3366// If present, include all whitespace after word.3367// Otherwise, include all whitespace before word, except indentation.3368var wordEnd = end;3369while (/\s/.test(line.charAt(end)) && end < line.length) { end++; }3370if (wordEnd == end) {3371var wordStart = start;3372while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; }3373if (!start) { start = wordStart; }3374}3375}3376return { start: new Pos(cur.line, start), end: new Pos(cur.line, end) };3377}3378
3379/**3380* Depends on the following:
3381*
3382* - editor mode should be htmlmixedmode / xml
3383* - mode/xml/xml.js should be loaded
3384* - addon/fold/xml-fold.js should be loaded
3385*
3386* If any of the above requirements are not true, this function noops.
3387*
3388* This is _NOT_ a 100% accurate implementation of vim tag text objects.
3389* The following caveats apply (based off cursory testing, I'm sure there
3390* are other discrepancies):
3391*
3392* - Does not work inside comments:
3393* ```
3394* <!-- <div>broken</div> -->
3395* ```
3396* - Does not work when tags have different cases:
3397* ```
3398* <div>broken</DIV>
3399* ```
3400* - Does not work when cursor is inside a broken tag:
3401* ```
3402* <div><brok><en></div>
3403* ```
3404*/
3405function expandTagUnderCursor(cm, head, inclusive) {3406var cur = head;3407if (!CodeMirror.findMatchingTag || !CodeMirror.findEnclosingTag) {3408return { start: cur, end: cur };3409}3410
3411var tags = CodeMirror.findMatchingTag(cm, head) || CodeMirror.findEnclosingTag(cm, head);3412if (!tags || !tags.open || !tags.close) {3413return { start: cur, end: cur };3414}3415
3416if (inclusive) {3417return { start: tags.open.from, end: tags.close.to };3418}3419return { start: tags.open.to, end: tags.close.from };3420}3421
3422function recordJumpPosition(cm, oldCur, newCur) {3423if (!cursorEqual(oldCur, newCur)) {3424vimGlobalState.jumpList.add(cm, oldCur, newCur);3425}3426}3427
3428function recordLastCharacterSearch(increment, args) {3429vimGlobalState.lastCharacterSearch.increment = increment;3430vimGlobalState.lastCharacterSearch.forward = args.forward;3431vimGlobalState.lastCharacterSearch.selectedCharacter = args.selectedCharacter;3432}3433
3434var symbolToMode = {3435'(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket',3436'[': 'section', ']': 'section',3437'*': 'comment', '/': 'comment',3438'm': 'method', 'M': 'method',3439'#': 'preprocess'3440};3441var findSymbolModes = {3442bracket: {3443isComplete: function(state) {3444if (state.nextCh === state.symb) {3445state.depth++;3446if (state.depth >= 1)return true;3447} else if (state.nextCh === state.reverseSymb) {3448state.depth--;3449}3450return false;3451}3452},3453section: {3454init: function(state) {3455state.curMoveThrough = true;3456state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}';3457},3458isComplete: function(state) {3459return state.index === 0 && state.nextCh === state.symb;3460}3461},3462comment: {3463isComplete: function(state) {3464var found = state.lastCh === '*' && state.nextCh === '/';3465state.lastCh = state.nextCh;3466return found;3467}3468},3469// TODO: The original Vim implementation only operates on level 1 and 2.3470// The current implementation doesn't check for code block level and3471// therefore it operates on any levels.3472method: {3473init: function(state) {3474state.symb = (state.symb === 'm' ? '{' : '}');3475state.reverseSymb = state.symb === '{' ? '}' : '{';3476},3477isComplete: function(state) {3478if (state.nextCh === state.symb)return true;3479return false;3480}3481},3482preprocess: {3483init: function(state) {3484state.index = 0;3485},3486isComplete: function(state) {3487if (state.nextCh === '#') {3488var token = state.lineText.match(/^#(\w+)/)[1];3489if (token === 'endif') {3490if (state.forward && state.depth === 0) {3491return true;3492}3493state.depth++;3494} else if (token === 'if') {3495if (!state.forward && state.depth === 0) {3496return true;3497}3498state.depth--;3499}3500if (token === 'else' && state.depth === 0)return true;3501}3502return false;3503}3504}3505};3506function findSymbol(cm, repeat, forward, symb) {3507var cur = copyCursor(cm.getCursor());3508var increment = forward ? 1 : -1;3509var endLine = forward ? cm.lineCount() : -1;3510var curCh = cur.ch;3511var line = cur.line;3512var lineText = cm.getLine(line);3513var state = {3514lineText: lineText,3515nextCh: lineText.charAt(curCh),3516lastCh: null,3517index: curCh,3518symb: symb,3519reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],3520forward: forward,3521depth: 0,3522curMoveThrough: false3523};3524var mode = symbolToMode[symb];3525if (!mode)return cur;3526var init = findSymbolModes[mode].init;3527var isComplete = findSymbolModes[mode].isComplete;3528if (init) { init(state); }3529while (line !== endLine && repeat) {3530state.index += increment;3531state.nextCh = state.lineText.charAt(state.index);3532if (!state.nextCh) {3533line += increment;3534state.lineText = cm.getLine(line) || '';3535if (increment > 0) {3536state.index = 0;3537} else {3538var lineLen = state.lineText.length;3539state.index = (lineLen > 0) ? (lineLen-1) : 0;3540}3541state.nextCh = state.lineText.charAt(state.index);3542}3543if (isComplete(state)) {3544cur.line = line;3545cur.ch = state.index;3546repeat--;3547}3548}3549if (state.nextCh || state.curMoveThrough) {3550return new Pos(line, state.index);3551}3552return cur;3553}3554
3555/*3556* Returns the boundaries of the next word. If the cursor in the middle of
3557* the word, then returns the boundaries of the current word, starting at
3558* the cursor. If the cursor is at the start/end of a word, and we are going
3559* forward/backward, respectively, find the boundaries of the next word.
3560*
3561* @param {CodeMirror} cm CodeMirror object.
3562* @param {Cursor} cur The cursor position.
3563* @param {boolean} forward True to search forward. False to search
3564* backward.
3565* @param {boolean} bigWord True if punctuation count as part of the word.
3566* False if only [a-zA-Z0-9] characters count as part of the word.
3567* @param {boolean} emptyLineIsWord True if empty lines should be treated
3568* as words.
3569* @return {Object{from:number, to:number, line: number}} The boundaries of
3570* the word, or null if there are no more words.
3571*/
3572function findWord(cm, cur, forward, bigWord, emptyLineIsWord) {3573var lineNum = cur.line;3574var pos = cur.ch;3575var line = cm.getLine(lineNum);3576var dir = forward ? 1 : -1;3577var charTests = bigWord ? bigWordCharTest: wordCharTest;3578
3579if (emptyLineIsWord && line == '') {3580lineNum += dir;3581line = cm.getLine(lineNum);3582if (!isLine(cm, lineNum)) {3583return null;3584}3585pos = (forward) ? 0 : line.length;3586}3587
3588while (true) {3589if (emptyLineIsWord && line == '') {3590return { from: 0, to: 0, line: lineNum };3591}3592var stop = (dir > 0) ? line.length : -1;3593var wordStart = stop, wordEnd = stop;3594// Find bounds of next word.3595while (pos != stop) {3596var foundWord = false;3597for (var i = 0; i < charTests.length && !foundWord; ++i) {3598if (charTests[i](line.charAt(pos))) {3599wordStart = pos;3600// Advance to end of word.3601while (pos != stop && charTests[i](line.charAt(pos))) {3602pos += dir;3603}3604wordEnd = pos;3605foundWord = wordStart != wordEnd;3606if (wordStart == cur.ch && lineNum == cur.line &&3607wordEnd == wordStart + dir) {3608// We started at the end of a word. Find the next one.3609continue;3610} else {3611return {3612from: Math.min(wordStart, wordEnd + 1),3613to: Math.max(wordStart, wordEnd),3614line: lineNum };3615}3616}3617}3618if (!foundWord) {3619pos += dir;3620}3621}3622// Advance to next/prev line.3623lineNum += dir;3624if (!isLine(cm, lineNum)) {3625return null;3626}3627line = cm.getLine(lineNum);3628pos = (dir > 0) ? 0 : line.length;3629}3630}3631
3632/**3633* @param {CodeMirror} cm CodeMirror object.
3634* @param {Pos} cur The position to start from.
3635* @param {int} repeat Number of words to move past.
3636* @param {boolean} forward True to search forward. False to search
3637* backward.
3638* @param {boolean} wordEnd True to move to end of word. False to move to
3639* beginning of word.
3640* @param {boolean} bigWord True if punctuation count as part of the word.
3641* False if only alphabet characters count as part of the word.
3642* @return {Cursor} The position the cursor should move to.
3643*/
3644function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) {3645var curStart = copyCursor(cur);3646var words = [];3647if (forward && !wordEnd || !forward && wordEnd) {3648repeat++;3649}3650// For 'e', empty lines are not considered words, go figure.3651var emptyLineIsWord = !(forward && wordEnd);3652for (var i = 0; i < repeat; i++) {3653var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord);3654if (!word) {3655var eodCh = lineLength(cm, cm.lastLine());3656words.push(forward3657? {line: cm.lastLine(), from: eodCh, to: eodCh}3658: {line: 0, from: 0, to: 0});3659break;3660}3661words.push(word);3662cur = new Pos(word.line, forward ? (word.to - 1) : word.from);3663}3664var shortCircuit = words.length != repeat;3665var firstWord = words[0];3666var lastWord = words.pop();3667if (forward && !wordEnd) {3668// w3669if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) {3670// We did not start in the middle of a word. Discard the extra word at the end.3671lastWord = words.pop();3672}3673return new Pos(lastWord.line, lastWord.from);3674} else if (forward && wordEnd) {3675return new Pos(lastWord.line, lastWord.to - 1);3676} else if (!forward && wordEnd) {3677// ge3678if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) {3679// We did not start in the middle of a word. Discard the extra word at the end.3680lastWord = words.pop();3681}3682return new Pos(lastWord.line, lastWord.to);3683} else {3684// b3685return new Pos(lastWord.line, lastWord.from);3686}3687}3688
3689function moveToEol(cm, head, motionArgs, vim, keepHPos) {3690var cur = head;3691var retval= new Pos(cur.line + motionArgs.repeat - 1, Infinity);3692var end=cm.clipPos(retval);3693end.ch--;3694if (!keepHPos) {3695vim.lastHPos = Infinity;3696vim.lastHSPos = cm.charCoords(end,'div').left;3697}3698return retval;3699}3700
3701function moveToCharacter(cm, repeat, forward, character) {3702var cur = cm.getCursor();3703var start = cur.ch;3704var idx;3705for (var i = 0; i < repeat; i ++) {3706var line = cm.getLine(cur.line);3707idx = charIdxInLine(start, line, character, forward, true);3708if (idx == -1) {3709return null;3710}3711start = idx;3712}3713return new Pos(cm.getCursor().line, idx);3714}3715
3716function moveToColumn(cm, repeat) {3717// repeat is always >= 1, so repeat - 1 always corresponds3718// to the column we want to go to.3719var line = cm.getCursor().line;3720return clipCursorToContent(cm, new Pos(line, repeat - 1));3721}3722
3723function updateMark(cm, vim, markName, pos) {3724if (!inArray(markName, validMarks)) {3725return;3726}3727if (vim.marks[markName]) {3728vim.marks[markName].clear();3729}3730vim.marks[markName] = cm.setBookmark(pos);3731}3732
3733function charIdxInLine(start, line, character, forward, includeChar) {3734// Search for char in line.3735// motion_options: {forward, includeChar}3736// If includeChar = true, include it too.3737// If forward = true, search forward, else search backwards.3738// If char is not found on this line, do nothing3739var idx;3740if (forward) {3741idx = line.indexOf(character, start + 1);3742if (idx != -1 && !includeChar) {3743idx -= 1;3744}3745} else {3746idx = line.lastIndexOf(character, start - 1);3747if (idx != -1 && !includeChar) {3748idx += 1;3749}3750}3751return idx;3752}3753
3754function findParagraph(cm, head, repeat, dir, inclusive) {3755var line = head.line;3756var min = cm.firstLine();3757var max = cm.lastLine();3758var start, end, i = line;3759function isEmpty(i) { return !cm.getLine(i); }3760function isBoundary(i, dir, any) {3761if (any) { return isEmpty(i) != isEmpty(i + dir); }3762return !isEmpty(i) && isEmpty(i + dir);3763}3764if (dir) {3765while (min <= i && i <= max && repeat > 0) {3766if (isBoundary(i, dir)) { repeat--; }3767i += dir;3768}3769return new Pos(i, 0);3770}3771
3772var vim = cm.state.vim;3773if (vim.visualLine && isBoundary(line, 1, true)) {3774var anchor = vim.sel.anchor;3775if (isBoundary(anchor.line, -1, true)) {3776if (!inclusive || anchor.line != line) {3777line += 1;3778}3779}3780}3781var startState = isEmpty(line);3782for (i = line; i <= max && repeat; i++) {3783if (isBoundary(i, 1, true)) {3784if (!inclusive || isEmpty(i) != startState) {3785repeat--;3786}3787}3788}3789end = new Pos(i, 0);3790// select boundary before paragraph for the last one3791if (i > max && !startState) { startState = true; }3792else { inclusive = false; }3793for (i = line; i > min; i--) {3794if (!inclusive || isEmpty(i) == startState || i == line) {3795if (isBoundary(i, -1, true)) { break; }3796}3797}3798start = new Pos(i, 0);3799return { start: start, end: end };3800}3801
3802function findSentence(cm, cur, repeat, dir) {3803
3804/*3805Takes an index object
3806{
3807line: the line string,
3808ln: line number,
3809pos: index in line,
3810dir: direction of traversal (-1 or 1)
3811}
3812and modifies the line, ln, and pos members to represent the
3813next valid position or sets them to null if there are
3814no more valid positions.
3815*/
3816function nextChar(cm, idx) {3817if (idx.pos + idx.dir < 0 || idx.pos + idx.dir >= idx.line.length) {3818idx.ln += idx.dir;3819if (!isLine(cm, idx.ln)) {3820idx.line = null;3821idx.ln = null;3822idx.pos = null;3823return;3824}3825idx.line = cm.getLine(idx.ln);3826idx.pos = (idx.dir > 0) ? 0 : idx.line.length - 1;3827}3828else {3829idx.pos += idx.dir;3830}3831}3832
3833/*3834Performs one iteration of traversal in forward direction
3835Returns an index object of the new location
3836*/
3837function forward(cm, ln, pos, dir) {3838var line = cm.getLine(ln);3839var stop = (line === "");3840
3841var curr = {3842line: line,3843ln: ln,3844pos: pos,3845dir: dir,3846}3847
3848var last_valid = {3849ln: curr.ln,3850pos: curr.pos,3851}3852
3853var skip_empty_lines = (curr.line === "");3854
3855// Move one step to skip character we start on3856nextChar(cm, curr);3857
3858while (curr.line !== null) {3859last_valid.ln = curr.ln;3860last_valid.pos = curr.pos;3861
3862if (curr.line === "" && !skip_empty_lines) {3863return { ln: curr.ln, pos: curr.pos, };3864}3865else if (stop && curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) {3866return { ln: curr.ln, pos: curr.pos, };3867}3868else if (isEndOfSentenceSymbol(curr.line[curr.pos])3869&& !stop3870&& (curr.pos === curr.line.length - 13871|| isWhiteSpaceString(curr.line[curr.pos + 1]))) {3872stop = true;3873}3874
3875nextChar(cm, curr);3876}3877
3878/*3879Set the position to the last non whitespace character on the last
3880valid line in the case that we reach the end of the document.
3881*/
3882var line = cm.getLine(last_valid.ln);3883last_valid.pos = 0;3884for(var i = line.length - 1; i >= 0; --i) {3885if (!isWhiteSpaceString(line[i])) {3886last_valid.pos = i;3887break;3888}3889}3890
3891return last_valid;3892
3893}3894
3895/*3896Performs one iteration of traversal in reverse direction
3897Returns an index object of the new location
3898*/
3899function reverse(cm, ln, pos, dir) {3900var line = cm.getLine(ln);3901
3902var curr = {3903line: line,3904ln: ln,3905pos: pos,3906dir: dir,3907}3908
3909var last_valid = {3910ln: curr.ln,3911pos: null,3912};3913
3914var skip_empty_lines = (curr.line === "");3915
3916// Move one step to skip character we start on3917nextChar(cm, curr);3918
3919while (curr.line !== null) {3920
3921if (curr.line === "" && !skip_empty_lines) {3922if (last_valid.pos !== null) {3923return last_valid;3924}3925else {3926return { ln: curr.ln, pos: curr.pos };3927}3928}3929else if (isEndOfSentenceSymbol(curr.line[curr.pos])3930&& last_valid.pos !== null3931&& !(curr.ln === last_valid.ln && curr.pos + 1 === last_valid.pos)) {3932return last_valid;3933}3934else if (curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) {3935skip_empty_lines = false;3936last_valid = { ln: curr.ln, pos: curr.pos }3937}3938
3939nextChar(cm, curr);3940}3941
3942/*3943Set the position to the first non whitespace character on the last
3944valid line in the case that we reach the beginning of the document.
3945*/
3946var line = cm.getLine(last_valid.ln);3947last_valid.pos = 0;3948for(var i = 0; i < line.length; ++i) {3949if (!isWhiteSpaceString(line[i])) {3950last_valid.pos = i;3951break;3952}3953}3954return last_valid;3955}3956
3957var curr_index = {3958ln: cur.line,3959pos: cur.ch,3960};3961
3962while (repeat > 0) {3963if (dir < 0) {3964curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir);3965}3966else {3967curr_index = forward(cm, curr_index.ln, curr_index.pos, dir);3968}3969repeat--;3970}3971
3972return new Pos(curr_index.ln, curr_index.pos);3973}3974
3975// TODO: perhaps this finagling of start and end positions belongs3976// in codemirror/replaceRange?3977function selectCompanionObject(cm, head, symb, inclusive) {3978var cur = head, start, end;3979
3980var bracketRegexp = ({3981'(': /[()]/, ')': /[()]/,3982'[': /[[\]]/, ']': /[[\]]/,3983'{': /[{}]/, '}': /[{}]/,3984'<': /[<>]/, '>': /[<>]/})[symb];3985var openSym = ({3986'(': '(', ')': '(',3987'[': '[', ']': '[',3988'{': '{', '}': '{',3989'<': '<', '>': '<'})[symb];3990var curChar = cm.getLine(cur.line).charAt(cur.ch);3991// Due to the behavior of scanForBracket, we need to add an offset if the3992// cursor is on a matching open bracket.3993var offset = curChar === openSym ? 1 : 0;3994
3995start = cm.scanForBracket(new Pos(cur.line, cur.ch + offset), -1, undefined, {'bracketRegex': bracketRegexp});3996end = cm.scanForBracket(new Pos(cur.line, cur.ch + offset), 1, undefined, {'bracketRegex': bracketRegexp});3997
3998if (!start || !end) {3999return { start: cur, end: cur };4000}4001
4002start = start.pos;4003end = end.pos;4004
4005if ((start.line == end.line && start.ch > end.ch)4006|| (start.line > end.line)) {4007var tmp = start;4008start = end;4009end = tmp;4010}4011
4012if (inclusive) {4013end.ch += 1;4014} else {4015start.ch += 1;4016}4017
4018return { start: start, end: end };4019}4020
4021// Takes in a symbol and a cursor and tries to simulate text objects that4022// have identical opening and closing symbols4023// TODO support across multiple lines4024function findBeginningAndEnd(cm, head, symb, inclusive) {4025var cur = copyCursor(head);4026var line = cm.getLine(cur.line);4027var chars = line.split('');4028var start, end, i, len;4029var firstIndex = chars.indexOf(symb);4030
4031// the decision tree is to always look backwards for the beginning first,4032// but if the cursor is in front of the first instance of the symb,4033// then move the cursor forward4034if (cur.ch < firstIndex) {4035cur.ch = firstIndex;4036// Why is this line even here???4037// cm.setCursor(cur.line, firstIndex+1);4038}4039// otherwise if the cursor is currently on the closing symbol4040else if (firstIndex < cur.ch && chars[cur.ch] == symb) {4041end = cur.ch; // assign end to the current cursor4042--cur.ch; // make sure to look backwards4043}4044
4045// if we're currently on the symbol, we've got a start4046if (chars[cur.ch] == symb && !end) {4047start = cur.ch + 1; // assign start to ahead of the cursor4048} else {4049// go backwards to find the start4050for (i = cur.ch; i > -1 && !start; i--) {4051if (chars[i] == symb) {4052start = i + 1;4053}4054}4055}4056
4057// look forwards for the end symbol4058if (start && !end) {4059for (i = start, len = chars.length; i < len && !end; i++) {4060if (chars[i] == symb) {4061end = i;4062}4063}4064}4065
4066// nothing found4067if (!start || !end) {4068return { start: cur, end: cur };4069}4070
4071// include the symbols4072if (inclusive) {4073--start; ++end;4074}4075
4076return {4077start: new Pos(cur.line, start),4078end: new Pos(cur.line, end)4079};4080}4081
4082// Search functions4083defineOption('pcre', true, 'boolean');4084function SearchState() {}4085SearchState.prototype = {4086getQuery: function() {4087return vimGlobalState.query;4088},4089setQuery: function(query) {4090vimGlobalState.query = query;4091},4092getOverlay: function() {4093return this.searchOverlay;4094},4095setOverlay: function(overlay) {4096this.searchOverlay = overlay;4097},4098isReversed: function() {4099return vimGlobalState.isReversed;4100},4101setReversed: function(reversed) {4102vimGlobalState.isReversed = reversed;4103},4104getScrollbarAnnotate: function() {4105return this.annotate;4106},4107setScrollbarAnnotate: function(annotate) {4108this.annotate = annotate;4109}4110};4111function getSearchState(cm) {4112var vim = cm.state.vim;4113return vim.searchState_ || (vim.searchState_ = new SearchState());4114}4115function splitBySlash(argString) {4116return splitBySeparator(argString, '/');4117}4118
4119function findUnescapedSlashes(argString) {4120return findUnescapedSeparators(argString, '/');4121}4122
4123function splitBySeparator(argString, separator) {4124var slashes = findUnescapedSeparators(argString, separator) || [];4125if (!slashes.length) return [];4126var tokens = [];4127// in case of strings like foo/bar4128if (slashes[0] !== 0) return;4129for (var i = 0; i < slashes.length; i++) {4130if (typeof slashes[i] == 'number')4131tokens.push(argString.substring(slashes[i] + 1, slashes[i+1]));4132}4133return tokens;4134}4135
4136function findUnescapedSeparators(str, separator) {4137if (!separator)4138separator = '/';4139
4140var escapeNextChar = false;4141var slashes = [];4142for (var i = 0; i < str.length; i++) {4143var c = str.charAt(i);4144if (!escapeNextChar && c == separator) {4145slashes.push(i);4146}4147escapeNextChar = !escapeNextChar && (c == '\\');4148}4149return slashes;4150}4151
4152// Translates a search string from ex (vim) syntax into javascript form.4153function translateRegex(str) {4154// When these match, add a '\' if unescaped or remove one if escaped.4155var specials = '|(){';4156// Remove, but never add, a '\' for these.4157var unescape = '}';4158var escapeNextChar = false;4159var out = [];4160for (var i = -1; i < str.length; i++) {4161var c = str.charAt(i) || '';4162var n = str.charAt(i+1) || '';4163var specialComesNext = (n && specials.indexOf(n) != -1);4164if (escapeNextChar) {4165if (c !== '\\' || !specialComesNext) {4166out.push(c);4167}4168escapeNextChar = false;4169} else {4170if (c === '\\') {4171escapeNextChar = true;4172// Treat the unescape list as special for removing, but not adding '\'.4173if (n && unescape.indexOf(n) != -1) {4174specialComesNext = true;4175}4176// Not passing this test means removing a '\'.4177if (!specialComesNext || n === '\\') {4178out.push(c);4179}4180} else {4181out.push(c);4182if (specialComesNext && n !== '\\') {4183out.push('\\');4184}4185}4186}4187}4188return out.join('');4189}4190
4191// Translates the replace part of a search and replace from ex (vim) syntax into4192// javascript form. Similar to translateRegex, but additionally fixes back references4193// (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'.4194var charUnescapes = {'\\n': '\n', '\\r': '\r', '\\t': '\t'};4195function translateRegexReplace(str) {4196var escapeNextChar = false;4197var out = [];4198for (var i = -1; i < str.length; i++) {4199var c = str.charAt(i) || '';4200var n = str.charAt(i+1) || '';4201if (charUnescapes[c + n]) {4202out.push(charUnescapes[c+n]);4203i++;4204} else if (escapeNextChar) {4205// At any point in the loop, escapeNextChar is true if the previous4206// character was a '\' and was not escaped.4207out.push(c);4208escapeNextChar = false;4209} else {4210if (c === '\\') {4211escapeNextChar = true;4212if ((isNumber(n) || n === '$')) {4213out.push('$');4214} else if (n !== '/' && n !== '\\') {4215out.push('\\');4216}4217} else {4218if (c === '$') {4219out.push('$');4220}4221out.push(c);4222if (n === '/') {4223out.push('\\');4224}4225}4226}4227}4228return out.join('');4229}4230
4231// Unescape \ and / in the replace part, for PCRE mode.4232var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t', '\\&':'&'};4233function unescapeRegexReplace(str) {4234var stream = new CodeMirror.StringStream(str);4235var output = [];4236while (!stream.eol()) {4237// Search for \.4238while (stream.peek() && stream.peek() != '\\') {4239output.push(stream.next());4240}4241var matched = false;4242for (var matcher in unescapes) {4243if (stream.match(matcher, true)) {4244matched = true;4245output.push(unescapes[matcher]);4246break;4247}4248}4249if (!matched) {4250// Don't change anything4251output.push(stream.next());4252}4253}4254return output.join('');4255}4256
4257/**4258* Extract the regular expression from the query and return a Regexp object.
4259* Returns null if the query is blank.
4260* If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
4261* If smartCase is passed in, and the query contains upper case letters,
4262* then ignoreCase is overridden, and the 'i' flag will not be set.
4263* If the query contains the /i in the flag part of the regular expression,
4264* then both ignoreCase and smartCase are ignored, and 'i' will be passed
4265* through to the Regex object.
4266*/
4267function parseQuery(query, ignoreCase, smartCase) {4268// First update the last search register4269var lastSearchRegister = vimGlobalState.registerController.getRegister('/');4270lastSearchRegister.setText(query);4271// Check if the query is already a regex.4272if (query instanceof RegExp) { return query; }4273// First try to extract regex + flags from the input. If no flags found,4274// extract just the regex. IE does not accept flags directly defined in4275// the regex string in the form /regex/flags4276var slashes = findUnescapedSlashes(query);4277var regexPart;4278var forceIgnoreCase;4279if (!slashes.length) {4280// Query looks like 'regexp'4281regexPart = query;4282} else {4283// Query looks like 'regexp/...'4284regexPart = query.substring(0, slashes[0]);4285var flagsPart = query.substring(slashes[0]);4286forceIgnoreCase = (flagsPart.indexOf('i') != -1);4287}4288if (!regexPart) {4289return null;4290}4291if (!getOption('pcre')) {4292regexPart = translateRegex(regexPart);4293}4294if (smartCase) {4295ignoreCase = (/^[^A-Z]*$/).test(regexPart);4296}4297var regexp = new RegExp(regexPart,4298(ignoreCase || forceIgnoreCase) ? 'im' : 'm');4299return regexp;4300}4301
4302/**4303* dom - Document Object Manipulator
4304* Usage:
4305* dom('<tag>'|<node>[, ...{<attributes>|<$styles>}|<child-node>|'<text>'])
4306* Examples:
4307* dom('div', {id:'xyz'}, dom('p', 'CM rocks!', {$color:'red'}))
4308* dom(document.head, dom('script', 'alert("hello!")'))
4309* Not supported:
4310* dom('p', ['arrays are objects'], Error('objects specify attributes'))
4311*/
4312function dom(n) {4313if (typeof n === 'string') n = document.createElement(n);4314for (var a, i = 1; i < arguments.length; i++) {4315if (!(a = arguments[i])) continue;4316if (typeof a !== 'object') a = document.createTextNode(a);4317if (a.nodeType) n.appendChild(a);4318else for (var key in a) {4319if (!Object.prototype.hasOwnProperty.call(a, key)) continue;4320if (key[0] === '$') n.style[key.slice(1)] = a[key];4321else n.setAttribute(key, a[key]);4322}4323}4324return n;4325}4326
4327function showConfirm(cm, template) {4328var pre = dom('pre', {$color: 'red', class: 'cm-vim-message'}, template);4329if (cm.openNotification) {4330cm.openNotification(pre, {bottom: true, duration: 5000});4331} else {4332alert(pre.innerText);4333}4334}4335
4336function makePrompt(prefix, desc) {4337return dom(document.createDocumentFragment(),4338dom('span', {$fontFamily: 'monospace', $whiteSpace: 'pre'},4339prefix,4340dom('input', {type: 'text', autocorrect: 'off',4341autocapitalize: 'off', spellcheck: 'false'})),4342desc && dom('span', {$color: '#888'}, desc));4343}4344
4345function showPrompt(cm, options) {4346var template = makePrompt(options.prefix, options.desc);4347if (cm.openDialog) {4348cm.openDialog(template, options.onClose, {4349onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp,4350bottom: true, selectValueOnOpen: false, value: options.value4351});4352}4353else {4354var shortText = '';4355if (typeof options.prefix != "string" && options.prefix) shortText += options.prefix.textContent;4356if (options.desc) shortText += " " + options.desc;4357options.onClose(prompt(shortText, ''));4358}4359}4360
4361function regexEqual(r1, r2) {4362if (r1 instanceof RegExp && r2 instanceof RegExp) {4363var props = ['global', 'multiline', 'ignoreCase', 'source'];4364for (var i = 0; i < props.length; i++) {4365var prop = props[i];4366if (r1[prop] !== r2[prop]) {4367return false;4368}4369}4370return true;4371}4372return false;4373}4374// Returns true if the query is valid.4375function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {4376if (!rawQuery) {4377return;4378}4379var state = getSearchState(cm);4380var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase);4381if (!query) {4382return;4383}4384highlightSearchMatches(cm, query);4385if (regexEqual(query, state.getQuery())) {4386return query;4387}4388state.setQuery(query);4389return query;4390}4391function searchOverlay(query) {4392if (query.source.charAt(0) == '^') {4393var matchSol = true;4394}4395return {4396token: function(stream) {4397if (matchSol && !stream.sol()) {4398stream.skipToEnd();4399return;4400}4401var match = stream.match(query, false);4402if (match) {4403if (match[0].length == 0) {4404// Matched empty string, skip to next.4405stream.next();4406return 'searching';4407}4408if (!stream.sol()) {4409// Backtrack 1 to match \b4410stream.backUp(1);4411if (!query.exec(stream.next() + match[0])) {4412stream.next();4413return null;4414}4415}4416stream.match(query);4417return 'searching';4418}4419while (!stream.eol()) {4420stream.next();4421if (stream.match(query, false)) break;4422}4423},4424query: query4425};4426}4427var highlightTimeout = 0;4428function highlightSearchMatches(cm, query) {4429clearTimeout(highlightTimeout);4430highlightTimeout = setTimeout(function() {4431if (!cm.state.vim) return;4432var searchState = getSearchState(cm);4433var overlay = searchState.getOverlay();4434if (!overlay || query != overlay.query) {4435if (overlay) {4436cm.removeOverlay(overlay);4437}4438overlay = searchOverlay(query);4439cm.addOverlay(overlay);4440if (cm.showMatchesOnScrollbar) {4441if (searchState.getScrollbarAnnotate()) {4442searchState.getScrollbarAnnotate().clear();4443}4444searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query));4445}4446searchState.setOverlay(overlay);4447}4448}, 50);4449}4450function findNext(cm, prev, query, repeat) {4451if (repeat === undefined) { repeat = 1; }4452return cm.operation(function() {4453var pos = cm.getCursor();4454var cursor = cm.getSearchCursor(query, pos);4455for (var i = 0; i < repeat; i++) {4456var found = cursor.find(prev);4457if (i == 0 && found && cursorEqual(cursor.from(), pos)) {4458var lastEndPos = prev ? cursor.from() : cursor.to();4459found = cursor.find(prev);4460if (found && !found[0] && cursorEqual(cursor.from(), lastEndPos)) {4461if (cm.getLine(lastEndPos.line).length == lastEndPos.ch)4462found = cursor.find(prev);4463}4464}4465if (!found) {4466// SearchCursor may have returned null because it hit EOF, wrap4467// around and try again.4468cursor = cm.getSearchCursor(query,4469(prev) ? new Pos(cm.lastLine()) : new Pos(cm.firstLine(), 0) );4470if (!cursor.find(prev)) {4471return;4472}4473}4474}4475return cursor.from();4476});4477}4478/**4479* Pretty much the same as `findNext`, except for the following differences:
4480*
4481* 1. Before starting the search, move to the previous search. This way if our cursor is
4482* already inside a match, we should return the current match.
4483* 2. Rather than only returning the cursor's from, we return the cursor's from and to as a tuple.
4484*/
4485function findNextFromAndToInclusive(cm, prev, query, repeat, vim) {4486if (repeat === undefined) { repeat = 1; }4487return cm.operation(function() {4488var pos = cm.getCursor();4489var cursor = cm.getSearchCursor(query, pos);4490
4491// Go back one result to ensure that if the cursor is currently a match, we keep it.4492var found = cursor.find(!prev);4493
4494// If we haven't moved, go back one more (similar to if i==0 logic in findNext).4495if (!vim.visualMode && found && cursorEqual(cursor.from(), pos)) {4496cursor.find(!prev);4497}4498
4499for (var i = 0; i < repeat; i++) {4500found = cursor.find(prev);4501if (!found) {4502// SearchCursor may have returned null because it hit EOF, wrap4503// around and try again.4504cursor = cm.getSearchCursor(query,4505(prev) ? new Pos(cm.lastLine()) : new Pos(cm.firstLine(), 0) );4506if (!cursor.find(prev)) {4507return;4508}4509}4510}4511return [cursor.from(), cursor.to()];4512});4513}4514function clearSearchHighlight(cm) {4515var state = getSearchState(cm);4516cm.removeOverlay(getSearchState(cm).getOverlay());4517state.setOverlay(null);4518if (state.getScrollbarAnnotate()) {4519state.getScrollbarAnnotate().clear();4520state.setScrollbarAnnotate(null);4521}4522}4523/**4524* Check if pos is in the specified range, INCLUSIVE.
4525* Range can be specified with 1 or 2 arguments.
4526* If the first range argument is an array, treat it as an array of line
4527* numbers. Match pos against any of the lines.
4528* If the first range argument is a number,
4529* if there is only 1 range argument, check if pos has the same line
4530* number
4531* if there are 2 range arguments, then check if pos is in between the two
4532* range arguments.
4533*/
4534function isInRange(pos, start, end) {4535if (typeof pos != 'number') {4536// Assume it is a cursor position. Get the line number.4537pos = pos.line;4538}4539if (start instanceof Array) {4540return inArray(pos, start);4541} else {4542if (typeof end == 'number') {4543return (pos >= start && pos <= end);4544} else {4545return pos == start;4546}4547}4548}4549function getUserVisibleLines(cm) {4550var scrollInfo = cm.getScrollInfo();4551var occludeToleranceTop = 6;4552var occludeToleranceBottom = 10;4553var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');4554var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;4555var to = cm.coordsChar({left:0, top: bottomY}, 'local');4556return {top: from.line, bottom: to.line};4557}4558
4559function getMarkPos(cm, vim, markName) {4560if (markName == '\'' || markName == '`') {4561return vimGlobalState.jumpList.find(cm, -1) || new Pos(0, 0);4562} else if (markName == '.') {4563return getLastEditPos(cm);4564}4565
4566var mark = vim.marks[markName];4567return mark && mark.find();4568}4569
4570function getLastEditPos(cm) {4571var done = cm.doc.history.done;4572for (var i = done.length; i--;) {4573if (done[i].changes) {4574return copyCursor(done[i].changes[0].to);4575}4576}4577}4578
4579var ExCommandDispatcher = function() {4580this.buildCommandMap_();4581};4582ExCommandDispatcher.prototype = {4583processCommand: function(cm, input, opt_params) {4584var that = this;4585cm.operation(function () {4586cm.curOp.isVimOp = true;4587that._processCommand(cm, input, opt_params);4588});4589},4590_processCommand: function(cm, input, opt_params) {4591var vim = cm.state.vim;4592var commandHistoryRegister = vimGlobalState.registerController.getRegister(':');4593var previousCommand = commandHistoryRegister.toString();4594if (vim.visualMode) {4595exitVisualMode(cm);4596}4597var inputStream = new CodeMirror.StringStream(input);4598// update ": with the latest command whether valid or invalid4599commandHistoryRegister.setText(input);4600var params = opt_params || {};4601params.input = input;4602try {4603this.parseInput_(cm, inputStream, params);4604} catch(e) {4605showConfirm(cm, e.toString());4606throw e;4607}4608var command;4609var commandName;4610if (!params.commandName) {4611// If only a line range is defined, move to the line.4612if (params.line !== undefined) {4613commandName = 'move';4614}4615} else {4616command = this.matchCommand_(params.commandName);4617if (command) {4618commandName = command.name;4619if (command.excludeFromCommandHistory) {4620commandHistoryRegister.setText(previousCommand);4621}4622this.parseCommandArgs_(inputStream, params, command);4623if (command.type == 'exToKey') {4624// Handle Ex to Key mapping.4625for (var i = 0; i < command.toKeys.length; i++) {4626vimApi.handleKey(cm, command.toKeys[i], 'mapping');4627}4628return;4629} else if (command.type == 'exToEx') {4630// Handle Ex to Ex mapping.4631this.processCommand(cm, command.toInput);4632return;4633}4634}4635}4636if (!commandName) {4637showConfirm(cm, 'Not an editor command ":' + input + '"');4638return;4639}4640try {4641exCommands[commandName](cm, params);4642// Possibly asynchronous commands (e.g. substitute, which might have a4643// user confirmation), are responsible for calling the callback when4644// done. All others have it taken care of for them here.4645if ((!command || !command.possiblyAsync) && params.callback) {4646params.callback();4647}4648} catch(e) {4649showConfirm(cm, e.toString());4650throw e;4651}4652},4653parseInput_: function(cm, inputStream, result) {4654inputStream.eatWhile(':');4655// Parse range.4656if (inputStream.eat('%')) {4657result.line = cm.firstLine();4658result.lineEnd = cm.lastLine();4659} else {4660result.line = this.parseLineSpec_(cm, inputStream);4661if (result.line !== undefined && inputStream.eat(',')) {4662result.lineEnd = this.parseLineSpec_(cm, inputStream);4663}4664}4665
4666// Parse command name.4667var commandMatch = inputStream.match(/^(\w+|!!|@@|[!#&*<=>@~])/);4668if (commandMatch) {4669result.commandName = commandMatch[1];4670} else {4671result.commandName = inputStream.match(/.*/)[0];4672}4673
4674return result;4675},4676parseLineSpec_: function(cm, inputStream) {4677var numberMatch = inputStream.match(/^(\d+)/);4678if (numberMatch) {4679// Absolute line number plus offset (N+M or N-M) is probably a typo,4680// not something the user actually wanted. (NB: vim does allow this.)4681return parseInt(numberMatch[1], 10) - 1;4682}4683switch (inputStream.next()) {4684case '.':4685return this.parseLineSpecOffset_(inputStream, cm.getCursor().line);4686case '$':4687return this.parseLineSpecOffset_(inputStream, cm.lastLine());4688case '\'':4689var markName = inputStream.next();4690var markPos = getMarkPos(cm, cm.state.vim, markName);4691if (!markPos) throw new Error('Mark not set');4692return this.parseLineSpecOffset_(inputStream, markPos.line);4693case '-':4694case '+':4695inputStream.backUp(1);4696// Offset is relative to current line if not otherwise specified.4697return this.parseLineSpecOffset_(inputStream, cm.getCursor().line);4698default:4699inputStream.backUp(1);4700return undefined;4701}4702},4703parseLineSpecOffset_: function(inputStream, line) {4704var offsetMatch = inputStream.match(/^([+-])?(\d+)/);4705if (offsetMatch) {4706var offset = parseInt(offsetMatch[2], 10);4707if (offsetMatch[1] == "-") {4708line -= offset;4709} else {4710line += offset;4711}4712}4713return line;4714},4715parseCommandArgs_: function(inputStream, params, command) {4716if (inputStream.eol()) {4717return;4718}4719params.argString = inputStream.match(/.*/)[0];4720// Parse command-line arguments4721var delim = command.argDelimiter || /\s+/;4722var args = trim(params.argString).split(delim);4723if (args.length && args[0]) {4724params.args = args;4725}4726},4727matchCommand_: function(commandName) {4728// Return the command in the command map that matches the shortest4729// prefix of the passed in command name. The match is guaranteed to be4730// unambiguous if the defaultExCommandMap's shortNames are set up4731// correctly. (see @code{defaultExCommandMap}).4732for (var i = commandName.length; i > 0; i--) {4733var prefix = commandName.substring(0, i);4734if (this.commandMap_[prefix]) {4735var command = this.commandMap_[prefix];4736if (command.name.indexOf(commandName) === 0) {4737return command;4738}4739}4740}4741return null;4742},4743buildCommandMap_: function() {4744this.commandMap_ = {};4745for (var i = 0; i < defaultExCommandMap.length; i++) {4746var command = defaultExCommandMap[i];4747var key = command.shortName || command.name;4748this.commandMap_[key] = command;4749}4750},4751map: function(lhs, rhs, ctx) {4752if (lhs != ':' && lhs.charAt(0) == ':') {4753if (ctx) { throw Error('Mode not supported for ex mappings'); }4754var commandName = lhs.substring(1);4755if (rhs != ':' && rhs.charAt(0) == ':') {4756// Ex to Ex mapping4757this.commandMap_[commandName] = {4758name: commandName,4759type: 'exToEx',4760toInput: rhs.substring(1),4761user: true4762};4763} else {4764// Ex to key mapping4765this.commandMap_[commandName] = {4766name: commandName,4767type: 'exToKey',4768toKeys: rhs,4769user: true4770};4771}4772} else {4773if (rhs != ':' && rhs.charAt(0) == ':') {4774// Key to Ex mapping.4775var mapping = {4776keys: lhs,4777type: 'keyToEx',4778exArgs: { input: rhs.substring(1) }4779};4780if (ctx) { mapping.context = ctx; }4781defaultKeymap.unshift(mapping);4782} else {4783// Key to key mapping4784var mapping = {4785keys: lhs,4786type: 'keyToKey',4787toKeys: rhs4788};4789if (ctx) { mapping.context = ctx; }4790defaultKeymap.unshift(mapping);4791}4792}4793},4794unmap: function(lhs, ctx) {4795if (lhs != ':' && lhs.charAt(0) == ':') {4796// Ex to Ex or Ex to key mapping4797if (ctx) { throw Error('Mode not supported for ex mappings'); }4798var commandName = lhs.substring(1);4799if (this.commandMap_[commandName] && this.commandMap_[commandName].user) {4800delete this.commandMap_[commandName];4801return true;4802}4803} else {4804// Key to Ex or key to key mapping4805var keys = lhs;4806for (var i = 0; i < defaultKeymap.length; i++) {4807if (keys == defaultKeymap[i].keys4808&& defaultKeymap[i].context === ctx) {4809defaultKeymap.splice(i, 1);4810return true;4811}4812}4813}4814}4815};4816
4817var exCommands = {4818colorscheme: function(cm, params) {4819if (!params.args || params.args.length < 1) {4820showConfirm(cm, cm.getOption('theme'));4821return;4822}4823cm.setOption('theme', params.args[0]);4824},4825map: function(cm, params, ctx) {4826var mapArgs = params.args;4827if (!mapArgs || mapArgs.length < 2) {4828if (cm) {4829showConfirm(cm, 'Invalid mapping: ' + params.input);4830}4831return;4832}4833exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx);4834},4835imap: function(cm, params) { this.map(cm, params, 'insert'); },4836nmap: function(cm, params) { this.map(cm, params, 'normal'); },4837vmap: function(cm, params) { this.map(cm, params, 'visual'); },4838unmap: function(cm, params, ctx) {4839var mapArgs = params.args;4840if (!mapArgs || mapArgs.length < 1 || !exCommandDispatcher.unmap(mapArgs[0], ctx)) {4841if (cm) {4842showConfirm(cm, 'No such mapping: ' + params.input);4843}4844}4845},4846move: function(cm, params) {4847commandDispatcher.processCommand(cm, cm.state.vim, {4848type: 'motion',4849motion: 'moveToLineOrEdgeOfDocument',4850motionArgs: { forward: false, explicitRepeat: true,4851linewise: true },4852repeatOverride: params.line+1});4853},4854set: function(cm, params) {4855var setArgs = params.args;4856// Options passed through to the setOption/getOption calls. May be passed in by the4857// local/global versions of the set command4858var setCfg = params.setCfg || {};4859if (!setArgs || setArgs.length < 1) {4860if (cm) {4861showConfirm(cm, 'Invalid mapping: ' + params.input);4862}4863return;4864}4865var expr = setArgs[0].split('=');4866var optionName = expr[0];4867var value = expr[1];4868var forceGet = false;4869
4870if (optionName.charAt(optionName.length - 1) == '?') {4871// If post-fixed with ?, then the set is actually a get.4872if (value) { throw Error('Trailing characters: ' + params.argString); }4873optionName = optionName.substring(0, optionName.length - 1);4874forceGet = true;4875}4876if (value === undefined && optionName.substring(0, 2) == 'no') {4877// To set boolean options to false, the option name is prefixed with4878// 'no'.4879optionName = optionName.substring(2);4880value = false;4881}4882
4883var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean';4884if (optionIsBoolean && value == undefined) {4885// Calling set with a boolean option sets it to true.4886value = true;4887}4888// If no value is provided, then we assume this is a get.4889if (!optionIsBoolean && value === undefined || forceGet) {4890var oldValue = getOption(optionName, cm, setCfg);4891if (oldValue instanceof Error) {4892showConfirm(cm, oldValue.message);4893} else if (oldValue === true || oldValue === false) {4894showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName);4895} else {4896showConfirm(cm, ' ' + optionName + '=' + oldValue);4897}4898} else {4899var setOptionReturn = setOption(optionName, value, cm, setCfg);4900if (setOptionReturn instanceof Error) {4901showConfirm(cm, setOptionReturn.message);4902}4903}4904},4905setlocal: function (cm, params) {4906// setCfg is passed through to setOption4907params.setCfg = {scope: 'local'};4908this.set(cm, params);4909},4910setglobal: function (cm, params) {4911// setCfg is passed through to setOption4912params.setCfg = {scope: 'global'};4913this.set(cm, params);4914},4915registers: function(cm, params) {4916var regArgs = params.args;4917var registers = vimGlobalState.registerController.registers;4918var regInfo = '----------Registers----------\n\n';4919if (!regArgs) {4920for (var registerName in registers) {4921var text = registers[registerName].toString();4922if (text.length) {4923regInfo += '"' + registerName + ' ' + text + '\n'4924}4925}4926} else {4927var registerName;4928regArgs = regArgs.join('');4929for (var i = 0; i < regArgs.length; i++) {4930registerName = regArgs.charAt(i);4931if (!vimGlobalState.registerController.isValidRegister(registerName)) {4932continue;4933}4934var register = registers[registerName] || new Register();4935regInfo += '"' + registerName + ' ' + register.toString() + '\n'4936}4937}4938showConfirm(cm, regInfo);4939},4940sort: function(cm, params) {4941var reverse, ignoreCase, unique, number, pattern;4942function parseArgs() {4943if (params.argString) {4944var args = new CodeMirror.StringStream(params.argString);4945if (args.eat('!')) { reverse = true; }4946if (args.eol()) { return; }4947if (!args.eatSpace()) { return 'Invalid arguments'; }4948var opts = args.match(/([dinuox]+)?\s*(\/.+\/)?\s*/);4949if (!opts && !args.eol()) { return 'Invalid arguments'; }4950if (opts[1]) {4951ignoreCase = opts[1].indexOf('i') != -1;4952unique = opts[1].indexOf('u') != -1;4953var decimal = opts[1].indexOf('d') != -1 || opts[1].indexOf('n') != -1 && 1;4954var hex = opts[1].indexOf('x') != -1 && 1;4955var octal = opts[1].indexOf('o') != -1 && 1;4956if (decimal + hex + octal > 1) { return 'Invalid arguments'; }4957number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';4958}4959if (opts[2]) {4960pattern = new RegExp(opts[2].substr(1, opts[2].length - 2), ignoreCase ? 'i' : '');4961}4962}4963}4964var err = parseArgs();4965if (err) {4966showConfirm(cm, err + ': ' + params.argString);4967return;4968}4969var lineStart = params.line || cm.firstLine();4970var lineEnd = params.lineEnd || params.line || cm.lastLine();4971if (lineStart == lineEnd) { return; }4972var curStart = new Pos(lineStart, 0);4973var curEnd = new Pos(lineEnd, lineLength(cm, lineEnd));4974var text = cm.getRange(curStart, curEnd).split('\n');4975var numberRegex = pattern ? pattern :4976(number == 'decimal') ? /(-?)([\d]+)/ :4977(number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i :4978(number == 'octal') ? /([0-7]+)/ : null;4979var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null;4980var numPart = [], textPart = [];4981if (number || pattern) {4982for (var i = 0; i < text.length; i++) {4983var matchPart = pattern ? text[i].match(pattern) : null;4984if (matchPart && matchPart[0] != '') {4985numPart.push(matchPart);4986} else if (!pattern && numberRegex.exec(text[i])) {4987numPart.push(text[i]);4988} else {4989textPart.push(text[i]);4990}4991}4992} else {4993textPart = text;4994}4995function compareFn(a, b) {4996if (reverse) { var tmp; tmp = a; a = b; b = tmp; }4997if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); }4998var anum = number && numberRegex.exec(a);4999var bnum = number && numberRegex.exec(b);5000if (!anum) { return a < b ? -1 : 1; }5001anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix);5002bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix);5003return anum - bnum;5004}5005function comparePatternFn(a, b) {5006if (reverse) { var tmp; tmp = a; a = b; b = tmp; }5007if (ignoreCase) { a[0] = a[0].toLowerCase(); b[0] = b[0].toLowerCase(); }5008return (a[0] < b[0]) ? -1 : 1;5009}5010numPart.sort(pattern ? comparePatternFn : compareFn);5011if (pattern) {5012for (var i = 0; i < numPart.length; i++) {5013numPart[i] = numPart[i].input;5014}5015} else if (!number) { textPart.sort(compareFn); }5016text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart);5017if (unique) { // Remove duplicate lines5018var textOld = text;5019var lastLine;5020text = [];5021for (var i = 0; i < textOld.length; i++) {5022if (textOld[i] != lastLine) {5023text.push(textOld[i]);5024}5025lastLine = textOld[i];5026}5027}5028cm.replaceRange(text.join('\n'), curStart, curEnd);5029},5030vglobal: function(cm, params) {5031// global inspects params.commandName5032this.global(cm, params);5033},5034global: function(cm, params) {5035// a global command is of the form5036// :[range]g/pattern/[cmd]5037// argString holds the string /pattern/[cmd]5038var argString = params.argString;5039if (!argString) {5040showConfirm(cm, 'Regular Expression missing from global');5041return;5042}5043var inverted = params.commandName[0] === 'v';5044// range is specified here5045var lineStart = (params.line !== undefined) ? params.line : cm.firstLine();5046var lineEnd = params.lineEnd || params.line || cm.lastLine();5047// get the tokens from argString5048var tokens = splitBySlash(argString);5049var regexPart = argString, cmd;5050if (tokens.length) {5051regexPart = tokens[0];5052cmd = tokens.slice(1, tokens.length).join('/');5053}5054if (regexPart) {5055// If regex part is empty, then use the previous query. Otherwise5056// use the regex part as the new query.5057try {5058updateSearchQuery(cm, regexPart, true /** ignoreCase */,5059true /** smartCase */);5060} catch (e) {5061showConfirm(cm, 'Invalid regex: ' + regexPart);5062return;5063}5064}5065// now that we have the regexPart, search for regex matches in the5066// specified range of lines5067var query = getSearchState(cm).getQuery();5068var matchedLines = [];5069for (var i = lineStart; i <= lineEnd; i++) {5070var line = cm.getLineHandle(i);5071var matched = query.test(line.text);5072if (matched !== inverted) {5073matchedLines.push(cmd ? line : line.text);5074}5075}5076// if there is no [cmd], just display the list of matched lines5077if (!cmd) {5078showConfirm(cm, matchedLines.join('\n'));5079return;5080}5081var index = 0;5082var nextCommand = function() {5083if (index < matchedLines.length) {5084var line = matchedLines[index++];5085var lineNum = cm.getLineNumber(line);5086if (lineNum == null) {5087nextCommand();5088return;5089}5090var command = (lineNum + 1) + cmd;5091exCommandDispatcher.processCommand(cm, command, {5092callback: nextCommand5093});5094}5095};5096nextCommand();5097},5098substitute: function(cm, params) {5099if (!cm.getSearchCursor) {5100throw new Error('Search feature not available. Requires searchcursor.js or ' +5101'any other getSearchCursor implementation.');5102}5103var argString = params.argString;5104var tokens = argString ? splitBySeparator(argString, argString[0]) : [];5105var regexPart, replacePart = '', trailing, flagsPart, count;5106var confirm = false; // Whether to confirm each replace.5107var global = false; // True to replace all instances on a line, false to replace only 1.5108if (tokens.length) {5109regexPart = tokens[0];5110if (getOption('pcre') && regexPart !== '') {5111regexPart = new RegExp(regexPart).source; //normalize not escaped characters5112}5113replacePart = tokens[1];5114if (replacePart !== undefined) {5115if (getOption('pcre')) {5116replacePart = unescapeRegexReplace(replacePart.replace(/([^\\])&/g,"$1$$&"));5117} else {5118replacePart = translateRegexReplace(replacePart);5119}5120vimGlobalState.lastSubstituteReplacePart = replacePart;5121}5122trailing = tokens[2] ? tokens[2].split(' ') : [];5123} else {5124// either the argString is empty or its of the form ' hello/world'5125// actually splitBySlash returns a list of tokens5126// only if the string starts with a '/'5127if (argString && argString.length) {5128showConfirm(cm, 'Substitutions should be of the form ' +5129':s/pattern/replace/');5130return;5131}5132}5133// After the 3rd slash, we can have flags followed by a space followed5134// by count.5135if (trailing) {5136flagsPart = trailing[0];5137count = parseInt(trailing[1]);5138if (flagsPart) {5139if (flagsPart.indexOf('c') != -1) {5140confirm = true;5141}5142if (flagsPart.indexOf('g') != -1) {5143global = true;5144}5145if (getOption('pcre')) {5146regexPart = regexPart + '/' + flagsPart;5147} else {5148regexPart = regexPart.replace(/\//g, "\\/") + '/' + flagsPart;5149}5150}5151}5152if (regexPart) {5153// If regex part is empty, then use the previous query. Otherwise use5154// the regex part as the new query.5155try {5156updateSearchQuery(cm, regexPart, true /** ignoreCase */,5157true /** smartCase */);5158} catch (e) {5159showConfirm(cm, 'Invalid regex: ' + regexPart);5160return;5161}5162}5163replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart;5164if (replacePart === undefined) {5165showConfirm(cm, 'No previous substitute regular expression');5166return;5167}5168var state = getSearchState(cm);5169var query = state.getQuery();5170var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;5171var lineEnd = params.lineEnd || lineStart;5172if (lineStart == cm.firstLine() && lineEnd == cm.lastLine()) {5173lineEnd = Infinity;5174}5175if (count) {5176lineStart = lineEnd;5177lineEnd = lineStart + count - 1;5178}5179var startPos = clipCursorToContent(cm, new Pos(lineStart, 0));5180var cursor = cm.getSearchCursor(query, startPos);5181doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback);5182},5183redo: CodeMirror.commands.redo,5184undo: CodeMirror.commands.undo,5185write: function(cm) {5186if (CodeMirror.commands.save) {5187// If a save command is defined, call it.5188CodeMirror.commands.save(cm);5189} else if (cm.save) {5190// Saves to text area if no save command is defined and cm.save() is available.5191cm.save();5192}5193},5194nohlsearch: function(cm) {5195clearSearchHighlight(cm);5196},5197yank: function (cm) {5198var cur = copyCursor(cm.getCursor());5199var line = cur.line;5200var lineText = cm.getLine(line);5201vimGlobalState.registerController.pushText(5202'0', 'yank', lineText, true, true);5203},5204delmarks: function(cm, params) {5205if (!params.argString || !trim(params.argString)) {5206showConfirm(cm, 'Argument required');5207return;5208}5209
5210var state = cm.state.vim;5211var stream = new CodeMirror.StringStream(trim(params.argString));5212while (!stream.eol()) {5213stream.eatSpace();5214
5215// Record the streams position at the beginning of the loop for use5216// in error messages.5217var count = stream.pos;5218
5219if (!stream.match(/[a-zA-Z]/, false)) {5220showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));5221return;5222}5223
5224var sym = stream.next();5225// Check if this symbol is part of a range5226if (stream.match('-', true)) {5227// This symbol is part of a range.5228
5229// The range must terminate at an alphabetic character.5230if (!stream.match(/[a-zA-Z]/, false)) {5231showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));5232return;5233}5234
5235var startMark = sym;5236var finishMark = stream.next();5237// The range must terminate at an alphabetic character which5238// shares the same case as the start of the range.5239if (isLowerCase(startMark) && isLowerCase(finishMark) ||5240isUpperCase(startMark) && isUpperCase(finishMark)) {5241var start = startMark.charCodeAt(0);5242var finish = finishMark.charCodeAt(0);5243if (start >= finish) {5244showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));5245return;5246}5247
5248// Because marks are always ASCII values, and we have5249// determined that they are the same case, we can use5250// their char codes to iterate through the defined range.5251for (var j = 0; j <= finish - start; j++) {5252var mark = String.fromCharCode(start + j);5253delete state.marks[mark];5254}5255} else {5256showConfirm(cm, 'Invalid argument: ' + startMark + '-');5257return;5258}5259} else {5260// This symbol is a valid mark, and is not part of a range.5261delete state.marks[sym];5262}5263}5264}5265};5266
5267var exCommandDispatcher = new ExCommandDispatcher();5268
5269/**5270* @param {CodeMirror} cm CodeMirror instance we are in.
5271* @param {boolean} confirm Whether to confirm each replace.
5272* @param {Cursor} lineStart Line to start replacing from.
5273* @param {Cursor} lineEnd Line to stop replacing at.
5274* @param {RegExp} query Query for performing matches with.
5275* @param {string} replaceWith Text to replace matches with. May contain $1,
5276* $2, etc for replacing captured groups using JavaScript replace.
5277* @param {function()} callback A callback for when the replace is done.
5278*/
5279function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query,5280replaceWith, callback) {5281// Set up all the functions.5282cm.state.vim.exMode = true;5283var done = false;5284var lastPos, modifiedLineNumber, joined;5285function replaceAll() {5286cm.operation(function() {5287while (!done) {5288replace();5289next();5290}5291stop();5292});5293}5294function replace() {5295var text = cm.getRange(searchCursor.from(), searchCursor.to());5296var newText = text.replace(query, replaceWith);5297var unmodifiedLineNumber = searchCursor.to().line;5298searchCursor.replace(newText);5299modifiedLineNumber = searchCursor.to().line;5300lineEnd += modifiedLineNumber - unmodifiedLineNumber;5301joined = modifiedLineNumber < unmodifiedLineNumber;5302}5303function findNextValidMatch() {5304var lastMatchTo = lastPos && copyCursor(searchCursor.to());5305var match = searchCursor.findNext();5306if (match && !match[0] && lastMatchTo && cursorEqual(searchCursor.from(), lastMatchTo)) {5307match = searchCursor.findNext();5308}5309return match;5310}5311function next() {5312// The below only loops to skip over multiple occurrences on the same5313// line when 'global' is not true.5314while(findNextValidMatch() &&5315isInRange(searchCursor.from(), lineStart, lineEnd)) {5316if (!global && searchCursor.from().line == modifiedLineNumber && !joined) {5317continue;5318}5319cm.scrollIntoView(searchCursor.from(), 30);5320cm.setSelection(searchCursor.from(), searchCursor.to());5321lastPos = searchCursor.from();5322done = false;5323return;5324}5325done = true;5326}5327function stop(close) {5328if (close) { close(); }5329cm.focus();5330if (lastPos) {5331cm.setCursor(lastPos);5332var vim = cm.state.vim;5333vim.exMode = false;5334vim.lastHPos = vim.lastHSPos = lastPos.ch;5335}5336if (callback) { callback(); }5337}5338function onPromptKeyDown(e, _value, close) {5339// Swallow all keys.5340CodeMirror.e_stop(e);5341var keyName = CodeMirror.keyName(e);5342switch (keyName) {5343case 'Y':5344replace(); next(); break;5345case 'N':5346next(); break;5347case 'A':5348// replaceAll contains a call to close of its own. We don't want it5349// to fire too early or multiple times.5350var savedCallback = callback;5351callback = undefined;5352cm.operation(replaceAll);5353callback = savedCallback;5354break;5355case 'L':5356replace();5357// fall through and exit.5358case 'Q':5359case 'Esc':5360case 'Ctrl-C':5361case 'Ctrl-[':5362stop(close);5363break;5364}5365if (done) { stop(close); }5366return true;5367}5368
5369// Actually do replace.5370next();5371if (done) {5372showConfirm(cm, 'No matches for ' + query.source);5373return;5374}5375if (!confirm) {5376replaceAll();5377if (callback) { callback(); }5378return;5379}5380showPrompt(cm, {5381prefix: dom('span', 'replace with ', dom('strong', replaceWith), ' (y/n/a/q/l)'),5382onKeyDown: onPromptKeyDown5383});5384}5385
5386CodeMirror.keyMap.vim = {5387attach: attachVimMap,5388detach: detachVimMap,5389call: cmKey5390};5391
5392function exitInsertMode(cm) {5393var vim = cm.state.vim;5394var macroModeState = vimGlobalState.macroModeState;5395var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.');5396var isPlaying = macroModeState.isPlaying;5397var lastChange = macroModeState.lastInsertModeChanges;5398if (!isPlaying) {5399cm.off('change', onChange);5400CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);5401}5402if (!isPlaying && vim.insertModeRepeat > 1) {5403// Perform insert mode repeat for commands like 3,a and 3,o.5404repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,5405true /** repeatForInsert */);5406vim.lastEditInputState.repeatOverride = vim.insertModeRepeat;5407}5408delete vim.insertModeRepeat;5409vim.insertMode = false;5410cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1);5411cm.setOption('keyMap', 'vim');5412cm.setOption('disableInput', true);5413cm.toggleOverwrite(false); // exit replace mode if we were in it.5414// update the ". register before exiting insert mode5415insertModeChangeRegister.setText(lastChange.changes.join(''));5416CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});5417if (macroModeState.isRecording) {5418logInsertModeChange(macroModeState);5419}5420}5421
5422function _mapCommand(command) {5423defaultKeymap.unshift(command);5424}5425
5426function mapCommand(keys, type, name, args, extra) {5427var command = {keys: keys, type: type};5428command[type] = name;5429command[type + "Args"] = args;5430for (var key in extra)5431command[key] = extra[key];5432_mapCommand(command);5433}5434
5435// The timeout in milliseconds for the two-character ESC keymap should be5436// adjusted according to your typing speed to prevent false positives.5437defineOption('insertModeEscKeysTimeout', 200, 'number');5438
5439CodeMirror.keyMap['vim-insert'] = {5440// TODO: override navigation keys so that Esc will cancel automatic5441// indentation from o, O, i_<CR>5442fallthrough: ['default'],5443attach: attachVimMap,5444detach: detachVimMap,5445call: cmKey5446};5447
5448CodeMirror.keyMap['vim-replace'] = {5449'Backspace': 'goCharLeft',5450fallthrough: ['vim-insert'],5451attach: attachVimMap,5452detach: detachVimMap,5453call: cmKey5454};5455
5456function executeMacroRegister(cm, vim, macroModeState, registerName) {5457var register = vimGlobalState.registerController.getRegister(registerName);5458if (registerName == ':') {5459// Read-only register containing last Ex command.5460if (register.keyBuffer[0]) {5461exCommandDispatcher.processCommand(cm, register.keyBuffer[0]);5462}5463macroModeState.isPlaying = false;5464return;5465}5466var keyBuffer = register.keyBuffer;5467var imc = 0;5468macroModeState.isPlaying = true;5469macroModeState.replaySearchQueries = register.searchQueries.slice(0);5470for (var i = 0; i < keyBuffer.length; i++) {5471var text = keyBuffer[i];5472var match, key;5473while (text) {5474// Pull off one command key, which is either a single character5475// or a special sequence wrapped in '<' and '>', e.g. '<Space>'.5476match = (/<\w+-.+?>|<\w+>|./).exec(text);5477key = match[0];5478text = text.substring(match.index + key.length);5479vimApi.handleKey(cm, key, 'macro');5480if (vim.insertMode) {5481var changes = register.insertModeChanges[imc++].changes;5482vimGlobalState.macroModeState.lastInsertModeChanges.changes =5483changes;5484repeatInsertModeChanges(cm, changes, 1);5485exitInsertMode(cm);5486}5487}5488}5489macroModeState.isPlaying = false;5490}5491
5492function logKey(macroModeState, key) {5493if (macroModeState.isPlaying) { return; }5494var registerName = macroModeState.latestRegister;5495var register = vimGlobalState.registerController.getRegister(registerName);5496if (register) {5497register.pushText(key);5498}5499}5500
5501function logInsertModeChange(macroModeState) {5502if (macroModeState.isPlaying) { return; }5503var registerName = macroModeState.latestRegister;5504var register = vimGlobalState.registerController.getRegister(registerName);5505if (register && register.pushInsertModeChanges) {5506register.pushInsertModeChanges(macroModeState.lastInsertModeChanges);5507}5508}5509
5510function logSearchQuery(macroModeState, query) {5511if (macroModeState.isPlaying) { return; }5512var registerName = macroModeState.latestRegister;5513var register = vimGlobalState.registerController.getRegister(registerName);5514if (register && register.pushSearchQuery) {5515register.pushSearchQuery(query);5516}5517}5518
5519/**5520* Listens for changes made in insert mode.
5521* Should only be active in insert mode.
5522*/
5523function onChange(cm, changeObj) {5524var macroModeState = vimGlobalState.macroModeState;5525var lastChange = macroModeState.lastInsertModeChanges;5526if (!macroModeState.isPlaying) {5527while(changeObj) {5528lastChange.expectCursorActivityForChange = true;5529if (lastChange.ignoreCount > 1) {5530lastChange.ignoreCount--;5531} else if (changeObj.origin == '+input' || changeObj.origin == 'paste'5532|| changeObj.origin === undefined /* only in testing */) {5533var selectionCount = cm.listSelections().length;5534if (selectionCount > 1)5535lastChange.ignoreCount = selectionCount;5536var text = changeObj.text.join('\n');5537if (lastChange.maybeReset) {5538lastChange.changes = [];5539lastChange.maybeReset = false;5540}5541if (text) {5542if (cm.state.overwrite && !/\n/.test(text)) {5543lastChange.changes.push([text]);5544} else {5545lastChange.changes.push(text);5546}5547}5548}5549// Change objects may be chained with next.5550changeObj = changeObj.next;5551}5552}5553}5554
5555/**5556* Listens for any kind of cursor activity on CodeMirror.
5557*/
5558function onCursorActivity(cm) {5559var vim = cm.state.vim;5560if (vim.insertMode) {5561// Tracking cursor activity in insert mode (for macro support).5562var macroModeState = vimGlobalState.macroModeState;5563if (macroModeState.isPlaying) { return; }5564var lastChange = macroModeState.lastInsertModeChanges;5565if (lastChange.expectCursorActivityForChange) {5566lastChange.expectCursorActivityForChange = false;5567} else {5568// Cursor moved outside the context of an edit. Reset the change.5569lastChange.maybeReset = true;5570}5571} else if (!cm.curOp.isVimOp) {5572handleExternalSelection(cm, vim);5573}5574}5575function handleExternalSelection(cm, vim) {5576var anchor = cm.getCursor('anchor');5577var head = cm.getCursor('head');5578// Enter or exit visual mode to match mouse selection.5579if (vim.visualMode && !cm.somethingSelected()) {5580exitVisualMode(cm, false);5581} else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) {5582vim.visualMode = true;5583vim.visualLine = false;5584CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});5585}5586if (vim.visualMode) {5587// Bind CodeMirror selection model to vim selection model.5588// Mouse selections are considered visual characterwise.5589var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0;5590var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0;5591head = offsetCursor(head, 0, headOffset);5592anchor = offsetCursor(anchor, 0, anchorOffset);5593vim.sel = {5594anchor: anchor,5595head: head5596};5597updateMark(cm, vim, '<', cursorMin(head, anchor));5598updateMark(cm, vim, '>', cursorMax(head, anchor));5599} else if (!vim.insertMode) {5600// Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse.5601vim.lastHPos = cm.getCursor().ch;5602}5603}5604
5605/** Wrapper for special keys pressed in insert mode */5606function InsertModeKey(keyName) {5607this.keyName = keyName;5608}5609
5610/**5611* Handles raw key down events from the text area.
5612* - Should only be active in insert mode.
5613* - For recording deletes in insert mode.
5614*/
5615function onKeyEventTargetKeyDown(e) {5616var macroModeState = vimGlobalState.macroModeState;5617var lastChange = macroModeState.lastInsertModeChanges;5618var keyName = CodeMirror.keyName(e);5619if (!keyName) { return; }5620function onKeyFound() {5621if (lastChange.maybeReset) {5622lastChange.changes = [];5623lastChange.maybeReset = false;5624}5625lastChange.changes.push(new InsertModeKey(keyName));5626return true;5627}5628if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {5629CodeMirror.lookupKey(keyName, 'vim-insert', onKeyFound);5630}5631}5632
5633/**5634* Repeats the last edit, which includes exactly 1 command and at most 1
5635* insert. Operator and motion commands are read from lastEditInputState,
5636* while action commands are read from lastEditActionCommand.
5637*
5638* If repeatForInsert is true, then the function was called by
5639* exitInsertMode to repeat the insert mode changes the user just made. The
5640* corresponding enterInsertMode call was made with a count.
5641*/
5642function repeatLastEdit(cm, vim, repeat, repeatForInsert) {5643var macroModeState = vimGlobalState.macroModeState;5644macroModeState.isPlaying = true;5645var isAction = !!vim.lastEditActionCommand;5646var cachedInputState = vim.inputState;5647function repeatCommand() {5648if (isAction) {5649commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand);5650} else {5651commandDispatcher.evalInput(cm, vim);5652}5653}5654function repeatInsert(repeat) {5655if (macroModeState.lastInsertModeChanges.changes.length > 0) {5656// For some reason, repeat cw in desktop VIM does not repeat5657// insert mode changes. Will conform to that behavior.5658repeat = !vim.lastEditActionCommand ? 1 : repeat;5659var changeObject = macroModeState.lastInsertModeChanges;5660repeatInsertModeChanges(cm, changeObject.changes, repeat);5661}5662}5663vim.inputState = vim.lastEditInputState;5664if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) {5665// o and O repeat have to be interlaced with insert repeats so that the5666// insertions appear on separate lines instead of the last line.5667for (var i = 0; i < repeat; i++) {5668repeatCommand();5669repeatInsert(1);5670}5671} else {5672if (!repeatForInsert) {5673// Hack to get the cursor to end up at the right place. If I is5674// repeated in insert mode repeat, cursor will be 1 insert5675// change set left of where it should be.5676repeatCommand();5677}5678repeatInsert(repeat);5679}5680vim.inputState = cachedInputState;5681if (vim.insertMode && !repeatForInsert) {5682// Don't exit insert mode twice. If repeatForInsert is set, then we5683// were called by an exitInsertMode call lower on the stack.5684exitInsertMode(cm);5685}5686macroModeState.isPlaying = false;5687}5688
5689function repeatInsertModeChanges(cm, changes, repeat) {5690function keyHandler(binding) {5691if (typeof binding == 'string') {5692CodeMirror.commands[binding](cm);5693} else {5694binding(cm);5695}5696return true;5697}5698var head = cm.getCursor('head');5699var visualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.visualBlock;5700if (visualBlock) {5701// Set up block selection again for repeating the changes.5702selectForInsert(cm, head, visualBlock + 1);5703repeat = cm.listSelections().length;5704cm.setCursor(head);5705}5706for (var i = 0; i < repeat; i++) {5707if (visualBlock) {5708cm.setCursor(offsetCursor(head, i, 0));5709}5710for (var j = 0; j < changes.length; j++) {5711var change = changes[j];5712if (change instanceof InsertModeKey) {5713CodeMirror.lookupKey(change.keyName, 'vim-insert', keyHandler);5714} else if (typeof change == "string") {5715cm.replaceSelection(change);5716} else {5717var start = cm.getCursor();5718var end = offsetCursor(start, 0, change[0].length);5719cm.replaceRange(change[0], start, end);5720cm.setCursor(end);5721}5722}5723}5724if (visualBlock) {5725cm.setCursor(offsetCursor(head, 0, 1));5726}5727}5728
5729resetVimGlobalState();5730return vimApi;5731};5732// Initialize Vim and make it available as an API.5733CodeMirror.Vim = Vim();5734});5735