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