]> git.r.bdr.sh - rbdr/dotfiles/blob - atom/packages/vim-mode/lib/motions/search-motion.coffee
11761ad8182f5911192bf80478fed034ee081ac3
[rbdr/dotfiles] / atom / packages / vim-mode / lib / motions / search-motion.coffee
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'
7
8 class SearchBase extends MotionWithInput
9 operatesInclusively: false
10
11 constructor: (@editor, @vimState, options = {}) ->
12 super(@editor, @vimState)
13 @reverse = @initiallyReversed = false
14 @updateCurrentSearch() unless options.dontUpdateCurrentSearch
15
16 reversed: =>
17 @initiallyReversed = @reverse = true
18 @updateCurrentSearch()
19 this
20
21 moveCursor: (cursor, count=1) ->
22 ranges = @scan(cursor)
23 if ranges.length > 0
24 range = ranges[(count - 1) % ranges.length]
25 cursor.setBufferPosition(range.start)
26 else
27 atom.beep()
28
29 scan: (cursor) ->
30 currentPosition = cursor.getBufferPosition()
31
32 [rangesBefore, rangesAfter] = [[], []]
33 @editor.scan @getSearchTerm(@input.characters), ({range}) =>
34 isBefore = if @reverse
35 range.start.compare(currentPosition) < 0
36 else
37 range.start.compare(currentPosition) <= 0
38
39 if isBefore
40 rangesBefore.push(range)
41 else
42 rangesAfter.push(range)
43
44 if @reverse
45 rangesAfter.concat(rangesBefore).reverse()
46 else
47 rangesAfter.concat(rangesBefore)
48
49 getSearchTerm: (term) ->
50 modifiers = {'g': true}
51
52 if not term.match('[A-Z]') and settings.useSmartcaseForSearch()
53 modifiers['i'] = true
54
55 if term.indexOf('\\c') >= 0
56 term = term.replace('\\c', '')
57 modifiers['i'] = true
58
59 modFlags = Object.keys(modifiers).join('')
60
61 try
62 new RegExp(term, modFlags)
63 catch
64 new RegExp(_.escapeRegExp(term), modFlags)
65
66 updateCurrentSearch: ->
67 @vimState.globalVimState.currentSearch.reverse = @reverse
68 @vimState.globalVimState.currentSearch.initiallyReversed = @initiallyReversed
69
70 replicateCurrentSearch: ->
71 @reverse = @vimState.globalVimState.currentSearch.reverse
72 @initiallyReversed = @vimState.globalVimState.currentSearch.initiallyReversed
73
74 class Search extends SearchBase
75 constructor: (@editor, @vimState) ->
76 super(@editor, @vimState)
77 @viewModel = new SearchViewModel(this)
78
79 class SearchCurrentWord extends SearchBase
80 @keywordRegex: null
81
82 constructor: (@editor, @vimState) ->
83 super(@editor, @vimState)
84
85 # FIXME: This must depend on the current language
86 defaultIsKeyword = "[@a-zA-Z0-9_\-]+"
87 userIsKeyword = atom.config.get('vim-mode.iskeyword')
88 @keywordRegex = new RegExp(userIsKeyword or defaultIsKeyword)
89
90 searchString = @getCurrentWordMatch()
91 @input = new Input(searchString)
92 @vimState.pushSearchHistory(searchString) unless searchString is @vimState.getSearchHistoryItem()
93
94 getCurrentWord: ->
95 cursor = @editor.getLastCursor()
96 wordStart = cursor.getBeginningOfCurrentWordBufferPosition(wordRegex: @keywordRegex, allowPrevious: false)
97 wordEnd = cursor.getEndOfCurrentWordBufferPosition (wordRegex: @keywordRegex, allowNext: false)
98 cursorPosition = cursor.getBufferPosition()
99
100 if wordEnd.column is cursorPosition.column
101 # either we don't have a current word, or it ends on cursor, i.e. precedes it, so look for the next one
102 wordEnd = cursor.getEndOfCurrentWordBufferPosition (wordRegex: @keywordRegex, allowNext: true)
103 return "" if wordEnd.row isnt cursorPosition.row # don't look beyond the current line
104
105 cursor.setBufferPosition wordEnd
106 wordStart = cursor.getBeginningOfCurrentWordBufferPosition(wordRegex: @keywordRegex, allowPrevious: false)
107
108 cursor.setBufferPosition wordStart
109
110 @editor.getTextInBufferRange([wordStart, wordEnd])
111
112 cursorIsOnEOF: (cursor) ->
113 pos = cursor.getNextWordBoundaryBufferPosition(wordRegex: @keywordRegex)
114 eofPos = @editor.getEofBufferPosition()
115 pos.row is eofPos.row and pos.column is eofPos.column
116
117 getCurrentWordMatch: ->
118 characters = @getCurrentWord()
119 if characters.length > 0
120 if /\W/.test(characters) then "#{characters}\\b" else "\\b#{characters}\\b"
121 else
122 characters
123
124 isComplete: -> true
125
126 execute: (count=1) ->
127 super(count) if @input.characters.length > 0
128
129 OpenBrackets = ['(', '{', '[']
130 CloseBrackets = [')', '}', ']']
131 AnyBracket = new RegExp(OpenBrackets.concat(CloseBrackets).map(_.escapeRegExp).join("|"))
132
133 class BracketMatchingMotion extends SearchBase
134 operatesInclusively: true
135
136 isComplete: -> true
137
138 searchForMatch: (startPosition, reverse, inCharacter, outCharacter) ->
139 depth = 0
140 point = startPosition.copy()
141 lineLength = @editor.lineTextForBufferRow(point.row).length
142 eofPosition = @editor.getEofBufferPosition().translate([0, 1])
143 increment = if reverse then -1 else 1
144
145 loop
146 character = @characterAt(point)
147 depth++ if character is inCharacter
148 depth-- if character is outCharacter
149
150 return point if depth is 0
151
152 point.column += increment
153
154 return null if depth < 0
155 return null if point.isEqual([0, -1])
156 return null if point.isEqual(eofPosition)
157
158 if point.column < 0
159 point.row--
160 lineLength = @editor.lineTextForBufferRow(point.row).length
161 point.column = lineLength - 1
162 else if point.column >= lineLength
163 point.row++
164 lineLength = @editor.lineTextForBufferRow(point.row).length
165 point.column = 0
166
167 characterAt: (position) ->
168 @editor.getTextInBufferRange([position, position.translate([0, 1])])
169
170 getSearchData: (position) ->
171 character = @characterAt(position)
172 if (index = OpenBrackets.indexOf(character)) >= 0
173 [character, CloseBrackets[index], false]
174 else if (index = CloseBrackets.indexOf(character)) >= 0
175 [character, OpenBrackets[index], true]
176 else
177 []
178
179 moveCursor: (cursor) ->
180 startPosition = cursor.getBufferPosition()
181
182 [inCharacter, outCharacter, reverse] = @getSearchData(startPosition)
183
184 unless inCharacter?
185 restOfLine = [startPosition, [startPosition.row, Infinity]]
186 @editor.scanInBufferRange AnyBracket, restOfLine, ({range, stop}) ->
187 startPosition = range.start
188 stop()
189
190 [inCharacter, outCharacter, reverse] = @getSearchData(startPosition)
191
192 return unless inCharacter?
193
194 if matchPosition = @searchForMatch(startPosition, reverse, inCharacter, outCharacter)
195 cursor.setBufferPosition(matchPosition)
196
197 class RepeatSearch extends SearchBase
198 constructor: (@editor, @vimState) ->
199 super(@editor, @vimState, dontUpdateCurrentSearch: true)
200 @input = new Input(@vimState.getSearchHistoryItem(0) ? "")
201 @replicateCurrentSearch()
202
203 isComplete: -> true
204
205 reversed: ->
206 @reverse = not @initiallyReversed
207 this
208
209
210 module.exports = {Search, SearchCurrentWord, BracketMatchingMotion, RepeatSearch}