]> git.r.bdr.sh - rbdr/dotfiles/blob - atom/packages/vim-mode/lib/motions/search-motion.coffee
Update prompt to show ruby & node envs
[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 constructor: (@editor, @vimState, options = {}) ->
10 super(@editor, @vimState)
11 @reverse = @initiallyReversed = false
12 @updateCurrentSearch() unless options.dontUpdateCurrentSearch
13
14 reversed: =>
15 @initiallyReversed = @reverse = true
16 @updateCurrentSearch()
17 this
18
19 moveCursor: (cursor, count=1) ->
20 ranges = @scan(cursor)
21 if ranges.length > 0
22 range = ranges[(count - 1) % ranges.length]
23 cursor.setBufferPosition(range.start)
24 else
25 atom.beep()
26
27 scan: (cursor) ->
28 return [] if @input.characters is ""
29
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 @updateViewModel()
79
80 reversed: =>
81 @initiallyReversed = @reverse = true
82 @updateCurrentSearch()
83 @updateViewModel()
84 this
85
86 updateViewModel: ->
87 @viewModel.update(@initiallyReversed)
88
89 class SearchCurrentWord extends SearchBase
90 @keywordRegex: null
91
92 constructor: (@editor, @vimState) ->
93 super(@editor, @vimState)
94
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)
99
100 searchString = @getCurrentWordMatch()
101 @input = new Input(searchString)
102 @vimState.pushSearchHistory(searchString) unless searchString is @vimState.getSearchHistoryItem()
103
104 getCurrentWord: ->
105 cursor = @editor.getLastCursor()
106 wordStart = cursor.getBeginningOfCurrentWordBufferPosition(wordRegex: @keywordRegex, allowPrevious: false)
107 wordEnd = cursor.getEndOfCurrentWordBufferPosition (wordRegex: @keywordRegex, allowNext: false)
108 cursorPosition = cursor.getBufferPosition()
109
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
114
115 cursor.setBufferPosition wordEnd
116 wordStart = cursor.getBeginningOfCurrentWordBufferPosition(wordRegex: @keywordRegex, allowPrevious: false)
117
118 cursor.setBufferPosition wordStart
119
120 @editor.getTextInBufferRange([wordStart, wordEnd])
121
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
126
127 getCurrentWordMatch: ->
128 characters = @getCurrentWord()
129 if characters.length > 0
130 if /\W/.test(characters) then "#{characters}\\b" else "\\b#{characters}\\b"
131 else
132 characters
133
134 isComplete: -> true
135
136 execute: (count=1) ->
137 super(count) if @input.characters.length > 0
138
139 OpenBrackets = ['(', '{', '[']
140 CloseBrackets = [')', '}', ']']
141 AnyBracket = new RegExp(OpenBrackets.concat(CloseBrackets).map(_.escapeRegExp).join("|"))
142
143 class BracketMatchingMotion extends SearchBase
144 operatesInclusively: true
145
146 isComplete: -> true
147
148 searchForMatch: (startPosition, reverse, inCharacter, outCharacter) ->
149 depth = 0
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
154
155 loop
156 character = @characterAt(point)
157 depth++ if character is inCharacter
158 depth-- if character is outCharacter
159
160 return point if depth is 0
161
162 point.column += increment
163
164 return null if depth < 0
165 return null if point.isEqual([0, -1])
166 return null if point.isEqual(eofPosition)
167
168 if point.column < 0
169 point.row--
170 lineLength = @editor.lineTextForBufferRow(point.row).length
171 point.column = lineLength - 1
172 else if point.column >= lineLength
173 point.row++
174 lineLength = @editor.lineTextForBufferRow(point.row).length
175 point.column = 0
176
177 characterAt: (position) ->
178 @editor.getTextInBufferRange([position, position.translate([0, 1])])
179
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]
186 else
187 []
188
189 moveCursor: (cursor) ->
190 startPosition = cursor.getBufferPosition()
191
192 [inCharacter, outCharacter, reverse] = @getSearchData(startPosition)
193
194 unless inCharacter?
195 restOfLine = [startPosition, [startPosition.row, Infinity]]
196 @editor.scanInBufferRange AnyBracket, restOfLine, ({range, stop}) ->
197 startPosition = range.start
198 stop()
199
200 [inCharacter, outCharacter, reverse] = @getSearchData(startPosition)
201
202 return unless inCharacter?
203
204 if matchPosition = @searchForMatch(startPosition, reverse, inCharacter, outCharacter)
205 cursor.setBufferPosition(matchPosition)
206
207 class RepeatSearch extends SearchBase
208 constructor: (@editor, @vimState) ->
209 super(@editor, @vimState, dontUpdateCurrentSearch: true)
210 @input = new Input(@vimState.getSearchHistoryItem(0) ? "")
211 @replicateCurrentSearch()
212
213 isComplete: -> true
214
215 reversed: ->
216 @reverse = not @initiallyReversed
217 this
218
219
220 module.exports = {Search, SearchCurrentWord, BracketMatchingMotion, RepeatSearch}