aboutsummaryrefslogtreecommitdiff
path: root/atom/packages/vim-mode/lib/motions/search-motion.coffee
diff options
context:
space:
mode:
authorBen Beltran <ben@nsovocal.com>2015-07-10 11:12:25 -0500
committerBen Beltran <ben@nsovocal.com>2015-07-10 11:12:25 -0500
commit24c7594d62d8d7fbbcdb64b11ce4adc5d8e6991a (patch)
treeded312222bb108923da1820ba40b04d710d20e5b /atom/packages/vim-mode/lib/motions/search-motion.coffee
parenteb786e82d170e2abc351a432ade616d6ecdeeb6b (diff)
Adds atom
Diffstat (limited to 'atom/packages/vim-mode/lib/motions/search-motion.coffee')
-rw-r--r--atom/packages/vim-mode/lib/motions/search-motion.coffee210
1 files changed, 210 insertions, 0 deletions
diff --git a/atom/packages/vim-mode/lib/motions/search-motion.coffee b/atom/packages/vim-mode/lib/motions/search-motion.coffee
new file mode 100644
index 0000000..11761ad
--- /dev/null
+++ b/atom/packages/vim-mode/lib/motions/search-motion.coffee
@@ -0,0 +1,210 @@
+_ = require 'underscore-plus'
+{MotionWithInput} = require './general-motions'
+SearchViewModel = require '../view-models/search-view-model'
+{Input} = require '../view-models/view-model'
+{Point, Range} = require 'atom'
+settings = require '../settings'
+
+class SearchBase extends MotionWithInput
+ operatesInclusively: false
+
+ constructor: (@editor, @vimState, options = {}) ->
+ super(@editor, @vimState)
+ @reverse = @initiallyReversed = false
+ @updateCurrentSearch() unless options.dontUpdateCurrentSearch
+
+ reversed: =>
+ @initiallyReversed = @reverse = true
+ @updateCurrentSearch()
+ this
+
+ moveCursor: (cursor, count=1) ->
+ ranges = @scan(cursor)
+ if ranges.length > 0
+ range = ranges[(count - 1) % ranges.length]
+ cursor.setBufferPosition(range.start)
+ else
+ atom.beep()
+
+ scan: (cursor) ->
+ currentPosition = cursor.getBufferPosition()
+
+ [rangesBefore, rangesAfter] = [[], []]
+ @editor.scan @getSearchTerm(@input.characters), ({range}) =>
+ isBefore = if @reverse
+ range.start.compare(currentPosition) < 0
+ else
+ range.start.compare(currentPosition) <= 0
+
+ if isBefore
+ rangesBefore.push(range)
+ else
+ rangesAfter.push(range)
+
+ if @reverse
+ rangesAfter.concat(rangesBefore).reverse()
+ else
+ rangesAfter.concat(rangesBefore)
+
+ getSearchTerm: (term) ->
+ modifiers = {'g': true}
+
+ if not term.match('[A-Z]') and settings.useSmartcaseForSearch()
+ modifiers['i'] = true
+
+ if term.indexOf('\\c') >= 0
+ term = term.replace('\\c', '')
+ modifiers['i'] = true
+
+ modFlags = Object.keys(modifiers).join('')
+
+ try
+ new RegExp(term, modFlags)
+ catch
+ new RegExp(_.escapeRegExp(term), modFlags)
+
+ updateCurrentSearch: ->
+ @vimState.globalVimState.currentSearch.reverse = @reverse
+ @vimState.globalVimState.currentSearch.initiallyReversed = @initiallyReversed
+
+ replicateCurrentSearch: ->
+ @reverse = @vimState.globalVimState.currentSearch.reverse
+ @initiallyReversed = @vimState.globalVimState.currentSearch.initiallyReversed
+
+class Search extends SearchBase
+ constructor: (@editor, @vimState) ->
+ super(@editor, @vimState)
+ @viewModel = new SearchViewModel(this)
+
+class SearchCurrentWord extends SearchBase
+ @keywordRegex: null
+
+ constructor: (@editor, @vimState) ->
+ super(@editor, @vimState)
+
+ # 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)
+
+ searchString = @getCurrentWordMatch()
+ @input = new Input(searchString)
+ @vimState.pushSearchHistory(searchString) unless searchString is @vimState.getSearchHistoryItem()
+
+ getCurrentWord: ->
+ cursor = @editor.getLastCursor()
+ wordStart = cursor.getBeginningOfCurrentWordBufferPosition(wordRegex: @keywordRegex, allowPrevious: false)
+ wordEnd = cursor.getEndOfCurrentWordBufferPosition (wordRegex: @keywordRegex, allowNext: false)
+ cursorPosition = cursor.getBufferPosition()
+
+ if wordEnd.column is cursorPosition.column
+ # either we don't have a current word, or it ends on cursor, i.e. precedes it, so look for the next one
+ wordEnd = cursor.getEndOfCurrentWordBufferPosition (wordRegex: @keywordRegex, allowNext: true)
+ return "" if wordEnd.row isnt cursorPosition.row # don't look beyond the current line
+
+ cursor.setBufferPosition wordEnd
+ wordStart = cursor.getBeginningOfCurrentWordBufferPosition(wordRegex: @keywordRegex, allowPrevious: false)
+
+ cursor.setBufferPosition wordStart
+
+ @editor.getTextInBufferRange([wordStart, wordEnd])
+
+ cursorIsOnEOF: (cursor) ->
+ pos = cursor.getNextWordBoundaryBufferPosition(wordRegex: @keywordRegex)
+ eofPos = @editor.getEofBufferPosition()
+ pos.row is eofPos.row and pos.column is 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
+
+OpenBrackets = ['(', '{', '[']
+CloseBrackets = [')', '}', ']']
+AnyBracket = new RegExp(OpenBrackets.concat(CloseBrackets).map(_.escapeRegExp).join("|"))
+
+class BracketMatchingMotion extends SearchBase
+ operatesInclusively: true
+
+ isComplete: -> true
+
+ searchForMatch: (startPosition, reverse, inCharacter, outCharacter) ->
+ depth = 0
+ point = startPosition.copy()
+ lineLength = @editor.lineTextForBufferRow(point.row).length
+ eofPosition = @editor.getEofBufferPosition().translate([0, 1])
+ increment = if reverse then -1 else 1
+
+ loop
+ character = @characterAt(point)
+ depth++ if character is inCharacter
+ depth-- if character is outCharacter
+
+ return point if depth is 0
+
+ point.column += increment
+
+ return null if depth < 0
+ return null if point.isEqual([0, -1])
+ return null if point.isEqual(eofPosition)
+
+ if point.column < 0
+ point.row--
+ lineLength = @editor.lineTextForBufferRow(point.row).length
+ point.column = lineLength - 1
+ else if point.column >= lineLength
+ point.row++
+ lineLength = @editor.lineTextForBufferRow(point.row).length
+ point.column = 0
+
+ characterAt: (position) ->
+ @editor.getTextInBufferRange([position, position.translate([0, 1])])
+
+ getSearchData: (position) ->
+ character = @characterAt(position)
+ if (index = OpenBrackets.indexOf(character)) >= 0
+ [character, CloseBrackets[index], false]
+ else if (index = CloseBrackets.indexOf(character)) >= 0
+ [character, OpenBrackets[index], true]
+ else
+ []
+
+ moveCursor: (cursor) ->
+ startPosition = cursor.getBufferPosition()
+
+ [inCharacter, outCharacter, reverse] = @getSearchData(startPosition)
+
+ unless inCharacter?
+ restOfLine = [startPosition, [startPosition.row, Infinity]]
+ @editor.scanInBufferRange AnyBracket, restOfLine, ({range, stop}) ->
+ startPosition = range.start
+ stop()
+
+ [inCharacter, outCharacter, reverse] = @getSearchData(startPosition)
+
+ return unless inCharacter?
+
+ if matchPosition = @searchForMatch(startPosition, reverse, inCharacter, outCharacter)
+ cursor.setBufferPosition(matchPosition)
+
+class RepeatSearch extends SearchBase
+ constructor: (@editor, @vimState) ->
+ super(@editor, @vimState, dontUpdateCurrentSearch: true)
+ @input = new Input(@vimState.getSearchHistoryItem(0) ? "")
+ @replicateCurrentSearch()
+
+ isComplete: -> true
+
+ reversed: ->
+ @reverse = not @initiallyReversed
+ this
+
+
+module.exports = {Search, SearchCurrentWord, BracketMatchingMotion, RepeatSearch}