]>
Commit | Line | Data |
---|---|---|
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} |