1 _ = require 'underscore-plus'
2 {MotionWithInput} = require './general-motions'
3 SearchViewModel = require '../view-models/search-view-model'
4 {Input} = require '../view-models/view-model'
5 {Point, Range} = require 'atom'
6 settings = require '../settings'
8 class SearchBase extends MotionWithInput
9 constructor: (@editor, @vimState, options = {}) ->
10 super(@editor, @vimState)
11 @reverse = @initiallyReversed = false
12 @updateCurrentSearch() unless options.dontUpdateCurrentSearch
15 @initiallyReversed = @reverse = true
16 @updateCurrentSearch()
19 moveCursor: (cursor, count=1) ->
20 ranges = @scan(cursor)
22 range = ranges[(count - 1) % ranges.length]
23 cursor.setBufferPosition(range.start)
28 return [] if @input.characters is ""
30 currentPosition = cursor.getBufferPosition()
32 [rangesBefore, rangesAfter] = [[], []]
33 @editor.scan @getSearchTerm(@input.characters), ({range}) =>
34 isBefore = if @reverse
35 range.start.compare(currentPosition) < 0
37 range.start.compare(currentPosition) <= 0
40 rangesBefore.push(range)
42 rangesAfter.push(range)
45 rangesAfter.concat(rangesBefore).reverse()
47 rangesAfter.concat(rangesBefore)
49 getSearchTerm: (term) ->
50 modifiers = {'g': true}
52 if not term.match('[A-Z]') and settings.useSmartcaseForSearch()
55 if term.indexOf('\\c') >= 0
56 term = term.replace('\\c', '')
59 modFlags = Object.keys(modifiers).join('')
62 new RegExp(term, modFlags)
64 new RegExp(_.escapeRegExp(term), modFlags)
66 updateCurrentSearch: ->
67 @vimState.globalVimState.currentSearch.reverse = @reverse
68 @vimState.globalVimState.currentSearch.initiallyReversed = @initiallyReversed
70 replicateCurrentSearch: ->
71 @reverse = @vimState.globalVimState.currentSearch.reverse
72 @initiallyReversed = @vimState.globalVimState.currentSearch.initiallyReversed
74 class Search extends SearchBase
75 constructor: (@editor, @vimState) ->
76 super(@editor, @vimState)
77 @viewModel = new SearchViewModel(this)
81 @initiallyReversed = @reverse = true
82 @updateCurrentSearch()
87 @viewModel.update(@initiallyReversed)
89 class SearchCurrentWord extends SearchBase
92 constructor: (@editor, @vimState) ->
93 super(@editor, @vimState)
95 # FIXME: This must depend on the current language
96 defaultIsKeyword = "[@a-zA-Z0-9_\-]+"
97 userIsKeyword = atom.config.get('vim-mode.iskeyword')
98 @keywordRegex = new RegExp(userIsKeyword or defaultIsKeyword)
100 searchString = @getCurrentWordMatch()
101 @input = new Input(searchString)
102 @vimState.pushSearchHistory(searchString) unless searchString is @vimState.getSearchHistoryItem()
105 cursor = @editor.getLastCursor()
106 wordStart = cursor.getBeginningOfCurrentWordBufferPosition(wordRegex: @keywordRegex, allowPrevious: false)
107 wordEnd = cursor.getEndOfCurrentWordBufferPosition (wordRegex: @keywordRegex, allowNext: false)
108 cursorPosition = cursor.getBufferPosition()
110 if wordEnd.column is cursorPosition.column
111 # either we don't have a current word, or it ends on cursor, i.e. precedes it, so look for the next one
112 wordEnd = cursor.getEndOfCurrentWordBufferPosition (wordRegex: @keywordRegex, allowNext: true)
113 return "" if wordEnd.row isnt cursorPosition.row # don't look beyond the current line
115 cursor.setBufferPosition wordEnd
116 wordStart = cursor.getBeginningOfCurrentWordBufferPosition(wordRegex: @keywordRegex, allowPrevious: false)
118 cursor.setBufferPosition wordStart
120 @editor.getTextInBufferRange([wordStart, wordEnd])
122 cursorIsOnEOF: (cursor) ->
123 pos = cursor.getNextWordBoundaryBufferPosition(wordRegex: @keywordRegex)
124 eofPos = @editor.getEofBufferPosition()
125 pos.row is eofPos.row and pos.column is eofPos.column
127 getCurrentWordMatch: ->
128 characters = @getCurrentWord()
129 if characters.length > 0
130 if /\W/.test(characters) then "#{characters}\\b" else "\\b#{characters}\\b"
136 execute: (count=1) ->
137 super(count) if @input.characters.length > 0
139 OpenBrackets = ['(', '{', '[']
140 CloseBrackets = [')', '}', ']']
141 AnyBracket = new RegExp(OpenBrackets.concat(CloseBrackets).map(_.escapeRegExp).join("|"))
143 class BracketMatchingMotion extends SearchBase
144 operatesInclusively: true
148 searchForMatch: (startPosition, reverse, inCharacter, outCharacter) ->
150 point = startPosition.copy()
151 lineLength = @editor.lineTextForBufferRow(point.row).length
152 eofPosition = @editor.getEofBufferPosition().translate([0, 1])
153 increment = if reverse then -1 else 1
156 character = @characterAt(point)
157 depth++ if character is inCharacter
158 depth-- if character is outCharacter
160 return point if depth is 0
162 point.column += increment
164 return null if depth < 0
165 return null if point.isEqual([0, -1])
166 return null if point.isEqual(eofPosition)
170 lineLength = @editor.lineTextForBufferRow(point.row).length
171 point.column = lineLength - 1
172 else if point.column >= lineLength
174 lineLength = @editor.lineTextForBufferRow(point.row).length
177 characterAt: (position) ->
178 @editor.getTextInBufferRange([position, position.translate([0, 1])])
180 getSearchData: (position) ->
181 character = @characterAt(position)
182 if (index = OpenBrackets.indexOf(character)) >= 0
183 [character, CloseBrackets[index], false]
184 else if (index = CloseBrackets.indexOf(character)) >= 0
185 [character, OpenBrackets[index], true]
189 moveCursor: (cursor) ->
190 startPosition = cursor.getBufferPosition()
192 [inCharacter, outCharacter, reverse] = @getSearchData(startPosition)
195 restOfLine = [startPosition, [startPosition.row, Infinity]]
196 @editor.scanInBufferRange AnyBracket, restOfLine, ({range, stop}) ->
197 startPosition = range.start
200 [inCharacter, outCharacter, reverse] = @getSearchData(startPosition)
202 return unless inCharacter?
204 if matchPosition = @searchForMatch(startPosition, reverse, inCharacter, outCharacter)
205 cursor.setBufferPosition(matchPosition)
207 class RepeatSearch extends SearchBase
208 constructor: (@editor, @vimState) ->
209 super(@editor, @vimState, dontUpdateCurrentSearch: true)
210 @input = new Input(@vimState.getSearchHistoryItem(0) ? "")
211 @replicateCurrentSearch()
216 @reverse = not @initiallyReversed
220 module.exports = {Search, SearchCurrentWord, BracketMatchingMotion, RepeatSearch}