_ = require 'underscore-plus' {MotionWithInput} = require './general-motions' SearchViewModel = require '../view-models/search-view-model' {Input} = require '../view-models/view-model' {$$, Point, Range} = require 'atom' class SearchBase extends MotionWithInput @currentSearch: null constructor: (@editorView, @vimState) -> super(@editorView, @vimState) Search.currentSearch = @ @reverse = @initiallyReversed = false repeat: (opts = {}) => reverse = opts.backwards if @initiallyReversed and reverse @reverse = false else @reverse = reverse or @initiallyReversed @ reversed: => @initiallyReversed = @reverse = true @ execute: (count=1) -> @scan() @match count, (pos) => @editor.setCursorBufferPosition(pos.range.start) select: (count=1) -> @scan() selectionStart = @getSelectionStart() @match count, (pos) => reversed = selectionStart.compare(pos.range.start) > 0 @editor.setSelectedBufferRange([selectionStart, pos.range.start], {reversed}) [true] getSelectionStart: -> cur = @editor.getCursorBufferPosition() {start, end} = @editor.getSelectedBufferRange() if start.compare(cur) is 0 then end else start match: (count, callback) -> pos = @matches[(count - 1) % @matches.length] if pos? callback(pos) else atom.beep() scan: -> term = @input.characters regexp = try new RegExp(term, 'g') catch new RegExp(_.escapeRegExp(term), 'g') cur = @editor.getCursorBufferPosition() matchPoints = [] iterator = (item) => matchPointItem = range: item.range matchPoints.push(matchPointItem) @editor.scan(regexp, iterator) previous = _.filter matchPoints, (point) => if @reverse point.range.start.compare(cur) < 0 else point.range.start.compare(cur) <= 0 after = _.difference(matchPoints, previous) after.push(previous...) after = after.reverse() if @reverse @matches = after class Search extends SearchBase constructor: (@editorView, @vimState) -> super(@editorView, @vimState) @viewModel = new SearchViewModel(@) Search.currentSearch = @ @reverse = @initiallyReversed = false compose: (input) -> super(input) @viewModel.value = @input.characters class SearchCurrentWord extends SearchBase @keywordRegex: null constructor: (@editorView, @vimState) -> super(@editorView, @vimState) Search.currentSearch = @ @reverse = @initiallyReversed = false # FIXME: This must depend on the current language defaultIsKeyword = "[@a-zA-Z0-9_\-]+" userIsKeyword = atom.config.get('vim-mode.iskeyword') @keywordRegex = new RegExp(userIsKeyword or defaultIsKeyword) @input = new Input(@getCurrentWordMatch()) getCurrentWord: (onRecursion=false) -> cursor = @editor.getCursor() wordRange = cursor.getCurrentWordBufferRange(wordRegex: @keywordRegex) characters = @editor.getTextInBufferRange(wordRange) # We are not standing on top of a word, let's try to # get to the next word and try again if characters.length is 0 and not onRecursion if @cursorIsOnEOF() "" else cursor.moveToNextWordBoundary(wordRegex: @keywordRegex) @getCurrentWord(true) else characters cursorIsOnEOF: -> cursor = @editor.getCursor() pos = cursor.getMoveNextWordBoundaryBufferPosition(wordRegex: @keywordRegex) eofPos = @editor.getEofBufferPosition() pos.row == eofPos.row && pos.column == eofPos.column getCurrentWordMatch: -> characters = @getCurrentWord() if characters.length > 0 if /\W/.test(characters) then "#{characters}\\b" else "\\b#{characters}\\b" else characters isComplete: -> true execute: (count=1) -> super(count) if @input.characters.length > 0 class BracketMatchingMotion extends SearchBase @keywordRegex: null constructor: (@editorView, @vimState) -> super(@editorView, @vimState) Search.currentSearch = @ @reverse = @initiallyReversed = false @characters = [')','(','}','{',']','['] @charactersMatching = ['(',')','{','}','[',']'] @reverseSearch = [true,false,true,false,true,false] # FIXME: This must depend on the current language @input = new Input(@getCurrentWordMatch()) getCurrentWord: (onRecursion=false) -> cursor = @editor.getCursor() tempPoint = cursor.getBufferPosition().toArray() @character = @editor.getTextInBufferRange([cursor.getBufferPosition(),new Point(tempPoint[0],tempPoint[1] + 1)]) @startUp = false; index = @characters.indexOf(@character) if index >= 0 @matching = @charactersMatching[index] @reverse = @reverseSearch[index] else @startUp = true @character getCurrentWordMatch: -> characters = @getCurrentWord() characters isComplete: -> true searchFor:(character) -> term = character regexp = new RegExp(_.escapeRegExp(term), 'g') cur = @editor.getCursorBufferPosition() matchPoints = [] iterator = (item) => matchPointItem = range: item.range matchPoints.push(matchPointItem) @editor.scan(regexp, iterator) previous = _.filter matchPoints, (point) => if @reverse point.range.start.compare(cur) < 0 else point.range.start.compare(cur) <= 0 if @reverse after = [] after.push(previous...) after = after.reverse() else after = _.difference(matchPoints, previous) matches = after matches select: (count=1) -> @scan() cur = if @startUp then @startUpPos else @editor.getCursorBufferPosition() @match count, (pos) => if @reverse tempPoint = cur.toArray() @editor.setSelectedBufferRange([pos.range.start, new Point(tempPoint[0],tempPoint[1] + 1)], {reversed: true}) else tempPoint = pos.range.start.toArray() @editor.setSelectedBufferRange([ cur, new Point(tempPoint[0],tempPoint[1] + 1)], {reversed: true}) [true] scan: -> if @startUp @startUpPos = @editor.getCursorBufferPosition() min = -1 iwin = -1 for i in [0..@characters.length - 1] matchesCharacter = @searchFor(@characters[i]) if matchesCharacter.length > 0 dst = matchesCharacter[0].range.start.toArray() if @startUpPos.toArray()[0] == dst[0] and @startUpPos.toArray()[1] < dst[1] if dst[1] < min or min == -1 line = dst[0] min = dst[1] iwin = i if iwin != -1 @editor.setCursorBufferPosition(new Point(line,min)) @character = @characters[iwin] @matching = @charactersMatching[iwin] @reverse = @reverseSearch[iwin] matchesCharacter = @searchFor(@character) matchesMatching = @searchFor(@matching) if matchesMatching.length == 0 @matches = [] else charIndex = 0; matchIndex = 0; counter = 1; winner = -1 if @reverse compVal = 1 else compVal = -1 while counter > 0 if matchIndex < matchesMatching.length and charIndex < matchesCharacter.length if matchesCharacter[charIndex].range.compare(matchesMatching[matchIndex].range) == compVal counter = counter + 1 charIndex = charIndex + 1 else counter = counter - 1 winner = matchIndex matchIndex = matchIndex + 1 else if matchIndex < matchesMatching.length counter = counter - 1 winner = matchIndex matchIndex = matchIndex + 1 else break retVal = [] if counter == 0 retVal.push(matchesMatching[winner]) @matches = retVal if @matches.length == 0 and @startUp @editor.setCursorBufferPosition(@startUpPos) execute: (count=1) -> super(count) if @input.characters.length > 0 module.exports = {Search, SearchCurrentWord,BracketMatchingMotion}