diff --git a/lib/motions/search-motion.coffee b/lib/motions/search-motion.coffee index 0c05cd83..9e44681d 100644 --- a/lib/motions/search-motion.coffee +++ b/lib/motions/search-motion.coffee @@ -8,10 +8,10 @@ class SearchBase extends MotionWithInput operatesInclusively: false @currentSearch: null - constructor: (@editor, @vimState) -> + constructor: (@editor, @vimState, @reverse=false) -> super(@editor, @vimState) Search.currentSearch = @ - @reverse = @initiallyReversed = false + @initiallyReversed = @reverse repeat: (opts = {}) => reverse = opts.backwards @@ -21,10 +21,6 @@ class SearchBase extends MotionWithInput @reverse = reverse or @initiallyReversed @ - reversed: => - @initiallyReversed = @reverse = true - @ - moveCursor: (cursor, count=1) -> ranges = @scan(cursor) if ranges.length > 0 @@ -71,11 +67,10 @@ class SearchBase extends MotionWithInput new RegExp(_.escapeRegExp(term), modFlags) class Search extends SearchBase - constructor: (@editor, @vimState) -> - super(@editor, @vimState) + constructor: (@editor, @vimState, @reverse=false) -> + super(@editor, @vimState, @reverse) @viewModel = new SearchViewModel(@) Search.currentSearch = @ - @reverse = @initiallyReversed = false compose: (input) -> super(input) @@ -84,10 +79,9 @@ class Search extends SearchBase class SearchCurrentWord extends SearchBase @keywordRegex: null - constructor: (@editor, @vimState) -> - super(@editor, @vimState) + constructor: (@editor, @vimState, @reverse=false) -> + super(@editor, @vimState, @reverse) Search.currentSearch = @ - @reverse = @initiallyReversed = false # FIXME: This must depend on the current language defaultIsKeyword = "[@a-zA-Z0-9_\-]+" diff --git a/lib/view-models/search-view-model.coffee b/lib/view-models/search-view-model.coffee index 9f30066e..36364fee 100644 --- a/lib/view-models/search-view-model.coffee +++ b/lib/view-models/search-view-model.coffee @@ -3,14 +3,15 @@ module.exports = class SearchViewModel extends ViewModel constructor: (@searchMotion) -> - super(@searchMotion, class: 'search') + @prefixChar = if @searchMotion.initiallyReversed then '?' else '/' + super(@searchMotion, class: 'search', prefixChar: @prefixChar) @historyIndex = -1 @view.editor.on('core:move-up', @increaseHistorySearch) @view.editor.on('core:move-down', @decreaseHistorySearch) restoreHistory: (index) -> - @view.editor.setText(@history(index).value) + @view.editor.setText(@prefixChar + @history(index).value) history: (index) -> @vimState.getSearchHistoryItem(index) @@ -24,7 +25,7 @@ class SearchViewModel extends ViewModel if @historyIndex <= 0 # get us back to a clean slate @historyIndex = -1 - @view.editor.setText('') + @view.editor.setText(@prefixChar) else @historyIndex -= 1 @restoreHistory(@historyIndex) diff --git a/lib/view-models/view-model.coffee b/lib/view-models/view-model.coffee index c0a2e4da..25c75104 100644 --- a/lib/view-models/view-model.coffee +++ b/lib/view-models/view-model.coffee @@ -23,6 +23,7 @@ class ViewModel # # - singleChar {Boolean} - tells the view whether it should only listen for a single # character or an entire string + # - prefixChar {Char} - to be shown in the beginning constructor: (@operation, opts={}) -> {@editor, @vimState} = @operation diff --git a/lib/view-models/vim-command-mode-input-view.coffee b/lib/view-models/vim-command-mode-input-view.coffee index f4e966e5..c30c9287 100644 --- a/lib/view-models/vim-command-mode-input-view.coffee +++ b/lib/view-models/vim-command-mode-input-view.coffee @@ -16,15 +16,19 @@ class VimCommandModeInputView extends View @singleChar = opts.singleChar @defaultText = opts.defaultText ? '' + @prefixChar = opts.prefixChar ? '' @panel = atom.workspace.addBottomPanel(item: this, priority: 100) @focus() @handleEvents() + @editor.setText @prefixChar handleEvents: -> if @singleChar? @editor.find('input').on 'textInput', @autosubmit + if @prefixChar != '' + @editor.find('input').on 'keyup', @checkPrefix @editor.on 'core:confirm', @confirm @editor.on 'core:cancel', @cancel @editor.find('input').on 'blur', @cancel @@ -32,6 +36,8 @@ class VimCommandModeInputView extends View stopHandlingEvents: -> if @singleChar? @editor.find('input').off 'textInput', @autosubmit + if @prefixChar != '' + @editor.find('input').off 'keyup', @checkPrefix @editor.off 'core:confirm', @confirm @editor.off 'core:cancel', @cancel @editor.find('input').off 'blur', @cancel @@ -40,10 +46,19 @@ class VimCommandModeInputView extends View @editor.setText(event.originalEvent.data) @confirm() + checkPrefix: => + text = @editor.getText() + if text.length < 1 || text[0] != @prefixChar + @cancel() + confirm: => - @value = @editor.getText() or @defaultText - @viewModel.confirm(@) - @remove() + text = @editor.getText() + if @prefixChar != '' && !(text && text.length > 0 && text[0] == @prefixChar) + @cancel() + else + @value = if text == @prefixChar then @defaultText else if @prefixChar == '' then text else text.substr(1) + @viewModel.confirm(@) + @remove() focus: => @editorContainer.find('.editor').focus() diff --git a/lib/vim-state.coffee b/lib/vim-state.coffee index 66462daf..827192b0 100644 --- a/lib/vim-state.coffee +++ b/lib/vim-state.coffee @@ -152,11 +152,11 @@ class VimState 'repeat-find': (e) => @currentFind.repeat() if @currentFind? 'repeat-find-reverse': (e) => @currentFind.repeat(reverse: true) if @currentFind? 'replace': (e) => new Operators.Replace(@editor, @) - 'search': (e) => new Motions.Search(@editor, @) - 'reverse-search': (e) => (new Motions.Search(@editor, @)).reversed() - 'search-current-word': (e) => new Motions.SearchCurrentWord(@editor, @) + 'search': (e) => new Motions.Search(@editor, @, false) + 'reverse-search': (e) => new Motions.Search(@editor, @, true) + 'search-current-word': (e) => new Motions.SearchCurrentWord(@editor, @, false) + 'reverse-search-current-word': (e) => new Motions.SearchCurrentWord(@editor, @, true) 'bracket-matching-motion': (e) => new Motions.BracketMatchingMotion(@editor,@) - 'reverse-search-current-word': (e) => (new Motions.SearchCurrentWord(@editor, @)).reversed() # Private: Register multiple command handlers via an {Object} that maps # command names to command handler functions. diff --git a/spec/motions-spec.coffee b/spec/motions-spec.coffee index e765dead..0c23c266 100644 --- a/spec/motions-spec.coffee +++ b/spec/motions-spec.coffee @@ -956,7 +956,7 @@ describe "Motions", -> it "moves the cursor to the specified search pattern", -> keydown('/') - editor.commandModeInputView.editor.setText 'def' + editor.commandModeInputView.editor.setText '/def' editor.commandModeInputView.editor.trigger 'core:confirm' expect(editor.getCursorBufferPosition()).toEqual [1, 0] @@ -965,7 +965,7 @@ describe "Motions", -> it "loops back around", -> editor.setCursorBufferPosition([3, 0]) keydown('/') - editor.commandModeInputView.editor.setText 'def' + editor.commandModeInputView.editor.setText '/def' editor.commandModeInputView.editor.trigger 'core:confirm' expect(editor.getCursorBufferPosition()).toEqual [1, 0] @@ -973,7 +973,7 @@ describe "Motions", -> it "uses a valid regex as a regex", -> keydown('/') # Cycle through the 'abc' on the first line with a character pattern - editor.commandModeInputView.editor.setText '[abc]' + editor.commandModeInputView.editor.setText '/[abc]' editor.commandModeInputView.editor.trigger 'core:confirm' expect(editor.getCursorBufferPosition()).toEqual [0, 1] keydown('n') @@ -983,7 +983,7 @@ describe "Motions", -> # Go straight to the literal [abc editor.setText("abc\n[abc]\n") keydown('/') - editor.commandModeInputView.editor.setText '[abc' + editor.commandModeInputView.editor.setText '/[abc' editor.commandModeInputView.editor.trigger 'core:confirm' expect(editor.getCursorBufferPosition()).toEqual [1, 0] keydown('n') @@ -993,7 +993,7 @@ describe "Motions", -> editor.setText('one two three') keydown('v') keydown('/') - editor.commandModeInputView.editor.setText 'th' + editor.commandModeInputView.editor.setText '/th' editor.commandModeInputView.editor.trigger 'core:confirm' expect(editor.getCursorBufferPosition()).toEqual [0, 9] keydown('d') @@ -1003,7 +1003,7 @@ describe "Motions", -> editor.setText('line1\nline2\nline3') keydown('v') keydown('/') - editor.commandModeInputView.editor.setText 'line' + editor.commandModeInputView.editor.setText '/line' editor.commandModeInputView.editor.trigger 'core:confirm' {start, end} = editor.getSelectedBufferRange() expect(start.row).toEqual 0 @@ -1020,21 +1020,21 @@ describe "Motions", -> keydown('/') it "works in case sensitive mode", -> - editor.commandModeInputView.editor.setText 'ABC' + editor.commandModeInputView.editor.setText '/ABC' editor.commandModeInputView.editor.trigger 'core:confirm' expect(editor.getCursorBufferPosition()).toEqual [2, 0] keydown('n') expect(editor.getCursorBufferPosition()).toEqual [2, 0] it "works in case insensitive mode", -> - editor.commandModeInputView.editor.setText '\\cAbC' + editor.commandModeInputView.editor.setText '/\\cAbC' editor.commandModeInputView.editor.trigger 'core:confirm' expect(editor.getCursorBufferPosition()).toEqual [1, 0] keydown('n') expect(editor.getCursorBufferPosition()).toEqual [2, 0] it "works in case insensitive mode wherever \\c is", -> - editor.commandModeInputView.editor.setText 'AbC\\c' + editor.commandModeInputView.editor.setText '/AbC\\c' editor.commandModeInputView.editor.trigger 'core:confirm' expect(editor.getCursorBufferPosition()).toEqual [1, 0] keydown('n') @@ -1042,7 +1042,7 @@ describe "Motions", -> it "uses case insensitive search if useSmartcaseForSearch is true and searching lowercase", -> atom.config.set 'vim-mode.useSmartcaseForSearch', true - editor.commandModeInputView.editor.setText 'abc' + editor.commandModeInputView.editor.setText '/abc' editor.commandModeInputView.editor.trigger 'core:confirm' expect(editor.getCursorBufferPosition()).toEqual [1, 0] keydown('n') @@ -1050,7 +1050,7 @@ describe "Motions", -> it "uses case sensitive search if useSmartcaseForSearch is true and searching uppercase", -> atom.config.set 'vim-mode.useSmartcaseForSearch', true - editor.commandModeInputView.editor.setText 'ABC' + editor.commandModeInputView.editor.setText '/ABC' editor.commandModeInputView.editor.trigger 'core:confirm' expect(editor.getCursorBufferPosition()).toEqual [2, 0] keydown('n') @@ -1063,7 +1063,7 @@ describe "Motions", -> beforeEach -> keydown('/') - editor.commandModeInputView.editor.setText 'def' + editor.commandModeInputView.editor.setText '/def' editor.commandModeInputView.editor.trigger 'core:confirm' describe "the n keybinding", -> @@ -1083,14 +1083,14 @@ describe "Motions", -> it "composes with operators", -> keydown('d') keydown('/') - editor.commandModeInputView.editor.setText('def') + editor.commandModeInputView.editor.setText('/def') editor.commandModeInputView.editor.trigger('core:confirm') expect(editor.getText()).toEqual "def\nabc\ndef\n" it "repeats correctly with operators", -> keydown('d') keydown('/') - editor.commandModeInputView.editor.setText('def') + editor.commandModeInputView.editor.setText('/def') editor.commandModeInputView.editor.trigger('core:confirm') keydown('.') @@ -1099,14 +1099,14 @@ describe "Motions", -> describe "when reversed as ?", -> it "moves the cursor backwards to the specified search pattern", -> keydown('?') - editor.commandModeInputView.editor.setText('def') + editor.commandModeInputView.editor.setText('?def') editor.commandModeInputView.editor.trigger('core:confirm') expect(editor.getCursorBufferPosition()).toEqual [3, 0] describe "repeating", -> beforeEach -> keydown('?') - editor.commandModeInputView.editor.setText('def') + editor.commandModeInputView.editor.setText('?def') editor.commandModeInputView.editor.trigger('core:confirm') describe 'the n keybinding', -> @@ -1124,34 +1124,34 @@ describe "Motions", -> describe "using search history", -> beforeEach -> keydown('/') - editor.commandModeInputView.editor.setText('def') + editor.commandModeInputView.editor.setText('/def') editor.commandModeInputView.editor.trigger('core:confirm') expect(editor.getCursorBufferPosition()).toEqual [1, 0] keydown('/') - editor.commandModeInputView.editor.setText('abc') + editor.commandModeInputView.editor.setText('/abc') editor.commandModeInputView.editor.trigger('core:confirm') expect(editor.getCursorBufferPosition()).toEqual [2, 0] it "allows searching history in the search field", -> keydown('/') editor.commandModeInputView.editor.trigger('core:move-up') - expect(editor.commandModeInputView.editor.getText()).toEqual('abc') + expect(editor.commandModeInputView.editor.getText()).toEqual('/abc') editor.commandModeInputView.editor.trigger('core:move-up') - expect(editor.commandModeInputView.editor.getText()).toEqual('def') + expect(editor.commandModeInputView.editor.getText()).toEqual('/def') editor.commandModeInputView.editor.trigger('core:move-up') - expect(editor.commandModeInputView.editor.getText()).toEqual('def') + expect(editor.commandModeInputView.editor.getText()).toEqual('/def') it "resets the search field to empty when scrolling back", -> keydown('/') editor.commandModeInputView.editor.trigger('core:move-up') - expect(editor.commandModeInputView.editor.getText()).toEqual('abc') + expect(editor.commandModeInputView.editor.getText()).toEqual('/abc') editor.commandModeInputView.editor.trigger('core:move-up') - expect(editor.commandModeInputView.editor.getText()).toEqual('def') + expect(editor.commandModeInputView.editor.getText()).toEqual('/def') editor.commandModeInputView.editor.trigger('core:move-down') - expect(editor.commandModeInputView.editor.getText()).toEqual('abc') + expect(editor.commandModeInputView.editor.getText()).toEqual('/abc') editor.commandModeInputView.editor.trigger('core:move-down') - expect(editor.commandModeInputView.editor.getText()).toEqual '' + expect(editor.commandModeInputView.editor.getText()).toEqual '/' describe "the * keybinding", -> beforeEach ->