From 4cf7eec4b88c9e01405e9b58284c393997401b43 Mon Sep 17 00:00:00 2001 From: garcgt Date: Mon, 1 Sep 2014 05:47:36 +0200 Subject: [PATCH] jump motions (ctrl-o / ctrl-i) --- docs/motions.md | 2 + keymaps/vim-mode.cson | 3 + lib/jumplist.coffee | 59 ++++++++++++ lib/motions/general-motions.coffee | 16 +++- lib/motions/search-motion.coffee | 1 + lib/vim-mode.coffee | 2 + lib/vim-state.coffee | 2 + spec/motions-spec.coffee | 144 +++++++++++++++++++++++++++++ 8 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 lib/jumplist.coffee diff --git a/docs/motions.md b/docs/motions.md index 718a556b..e5a5013e 100644 --- a/docs/motions.md +++ b/docs/motions.md @@ -33,3 +33,5 @@ * [T](http://vimhelp.appspot.com/motion.txt.html#T) * [;](http://vimhelp.appspot.com/motion.txt.html#%3B) * [,](http://vimhelp.appspot.com/motion.txt.html#%2C) +* [ctrl-o](http://vimhelp.appspot.com/motion.txt.html#CTRL-O) +* [ctrl-i](http://vimhelp.appspot.com/motion.txt.html#CTRL-I) diff --git a/keymaps/vim-mode.cson b/keymaps/vim-mode.cson index ff2475fb..561c87e8 100644 --- a/keymaps/vim-mode.cson +++ b/keymaps/vim-mode.cson @@ -87,6 +87,9 @@ 'g t': 'pane:show-next-item' 'g T': 'pane:show-previous-item' + 'ctrl-o': 'vim-mode:jump-older-pos' + 'ctrl-i': 'vim-mode:jump-newer-pos' + 'm': 'vim-mode:mark' '`': 'vim-mode:move-to-mark-literal' '\'': 'vim-mode:move-to-mark' diff --git a/lib/jumplist.coffee b/lib/jumplist.coffee new file mode 100644 index 00000000..4b762aeb --- /dev/null +++ b/lib/jumplist.coffee @@ -0,0 +1,59 @@ + +module.exports = +class JumpList + constructor: (@maxEntries = 100) -> + @list = [] + @pointer = -1 + @pending = false + + addJump: (editor) -> + @list.splice(@pointer, 1) unless @pointer == -1 + entry = new JumpListEntry(editor) + @list.unshift entry unless entry.isEqual(@list[0]) + @list.splice(0, @maxEntries) if @list.length > @maxEntries + @pointer = -1 + + moveToOlderPos: (editor, count=1) -> + return if @pending + if @pointer == -1 + @addJump editor + @pointer = 0 + @_moveToPos editor, count + + moveToNewerPos: (editor, count=1) -> + return if @pending + @_moveToPos editor, -count + + _moveToPos: (editor, inc) -> + dst = @pointer+inc + if dst < 0 + dst = 0 + if dst >= @list.length + dst = @list.length-1 + if dst != @pointer + @pending = true + @list[dst].restoreCursor editor, (err, dstEditor) => + @pointer = dst + @pending = false + + +class JumpListEntry + constructor: (editor) -> + {@row, @column} = editor.getCursorBufferPosition() + @uri = editor.getUri() + + isEqual: (o) -> + o && + o.row == @row && + o.column == @column && + o.uri == @uri + + restoreCursor: (editor, cb) -> + if editor.getUri() == @uri + editor.setCursorBufferPosition [@row, @column] + cb?(null, editor) + else + atom.workspace.open(@uri, { + initialLine: @row, + initialColumn: @column + }).nodeify cb diff --git a/lib/motions/general-motions.coffee b/lib/motions/general-motions.coffee index fd9ea053..fee8c3f8 100644 --- a/lib/motions/general-motions.coffee +++ b/lib/motions/general-motions.coffee @@ -342,6 +342,7 @@ class MoveToEndOfWholeWord extends Motion class MoveToNextParagraph extends Motion execute: (count=1) -> + atom.workspace.vimState.jumpList.addJump @editor _.times count, => @editor.setCursorScreenPosition(@nextPosition()) @@ -369,6 +370,7 @@ class MoveToNextParagraph extends Motion class MoveToPreviousParagraph extends Motion execute: (count=1) -> + atom.workspace.vimState.jumpList.addJump @editor _.times count, => @editor.setCursorScreenPosition(@previousPosition()) @@ -428,6 +430,7 @@ class MoveToLine extends Motion new Range(startPoint, endPoint) setCursorPosition: (count) -> + atom.workspace.vimState.jumpList.addJump @editor @editor.setCursorBufferPosition([@getDestinationRow(count), 0]) getDestinationRow: (count) -> @@ -439,6 +442,7 @@ class MoveToScreenLine extends MoveToLine super(@editor, @vimState) setCursorPosition: (count) -> + atom.workspace.vimState.jumpList.addJump @editor @editor.setCursorScreenPosition([@getDestinationRow(count), 0]) class MoveToBeginningOfLine extends Motion @@ -544,11 +548,21 @@ class MoveToMiddleOfScreen extends MoveToScreenLine height = lastScreenRow - firstScreenRow Math.floor(firstScreenRow + (height / 2)) +class MoveToOlderJumpPos extends Motion + execute: (count=1) -> + atom.workspace.vimState.jumpList.moveToOlderPos @editor, count + +class MoveToNewerJumpPos extends Motion + execute: (count=1) -> + atom.workspace.vimState.jumpList.moveToNewerPos @editor, count + + module.exports = { Motion, MotionWithInput, CurrentSelection, MoveLeft, MoveRight, MoveUp, MoveDown, MoveToPreviousWord, MoveToPreviousWholeWord, MoveToNextWord, MoveToNextWholeWord, MoveToEndOfWord, MoveToNextParagraph, MoveToPreviousParagraph, MoveToLine, MoveToBeginningOfLine, MoveToFirstCharacterOfLineUp, MoveToFirstCharacterOfLineDown, MoveToFirstCharacterOfLine, MoveToLastCharacterOfLine, MoveToStartOfFile, MoveToTopOfScreen, - MoveToBottomOfScreen, MoveToMiddleOfScreen, MoveToEndOfWholeWord, MotionError + MoveToBottomOfScreen, MoveToMiddleOfScreen, MoveToEndOfWholeWord, MotionError, + MoveToOlderJumpPos, MoveToNewerJumpPos } diff --git a/lib/motions/search-motion.coffee b/lib/motions/search-motion.coffee index 64415c95..c5b42588 100644 --- a/lib/motions/search-motion.coffee +++ b/lib/motions/search-motion.coffee @@ -26,6 +26,7 @@ class SearchBase extends MotionWithInput execute: (count=1) -> @scan() @match count, (pos) => + atom.workspace.vimState.jumpList.addJump @editor @editor.setCursorBufferPosition(pos.range.start) select: (count=1) -> diff --git a/lib/vim-mode.coffee b/lib/vim-mode.coffee index 69b1a6f3..98161e28 100644 --- a/lib/vim-mode.coffee +++ b/lib/vim-mode.coffee @@ -1,4 +1,5 @@ VimState = require './vim-state' +JumpList = require './jumplist' module.exports = configDefaults: @@ -9,6 +10,7 @@ module.exports = atom.workspace.vimState ||= {} atom.workspace.vimState.registers ||= {} atom.workspace.vimState.searchHistory ||= [] + atom.workspace.vimState.jumpList ||= new JumpList() activate: (state) -> @_initializeWorkspaceState() diff --git a/lib/vim-state.coffee b/lib/vim-state.coffee index 3feecaa8..c9e85b19 100644 --- a/lib/vim-state.coffee +++ b/lib/vim-state.coffee @@ -182,6 +182,8 @@ class VimState 'focus-pane-view-above': => new Panes.FocusPaneViewAbove() 'focus-pane-view-below': => new Panes.FocusPaneViewBelow() 'focus-previous-pane-view': => new Panes.FocusPreviousPaneView() + 'jump-older-pos': (e) => new Motions.MoveToOlderJumpPos(@editor, @) + 'jump-newer-pos': (e) => new Motions.MoveToNewerJumpPos(@editor, @) 'move-to-mark': (e) => new Motions.MoveToMark(@editorView, @) 'move-to-mark-literal': (e) => new Motions.MoveToMark(@editorView, @, false) 'mark': (e) => new Operators.Mark(@editorView, @) diff --git a/spec/motions-spec.coffee b/spec/motions-spec.coffee index a503e683..95880546 100644 --- a/spec/motions-spec.coffee +++ b/spec/motions-spec.coffee @@ -1551,3 +1551,147 @@ describe "Motions", -> keydown('%') expect(editor.getCursorScreenPosition()).toEqual [0, 60] expect(editor.getText()).toEqual "( ( ) )--{ text in here; and a function call(with parameters) }\n" + + + describe 'jumplist behaviour', -> + + keydownSeq = (seq) -> keydown(k) for k in seq + + describe 'empty jumplist', -> + + beforeEach -> + editor.setText( ('01234567890' for i in [1..10]).join('\n') ) + editor.setCursorScreenPosition([5, 5]) + + it 'does nothing, ctrl-o', -> + keydown('o', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [5, 5] + + it 'does nothing, ctrl-i', -> + keydown('i', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [5, 5] + + describe 'ctrl-o / ctrl-i', -> + + beforeEach -> + editor.setText( ('01234567890' for i in [1..10]).join('\n') ) + editor.setCursorScreenPosition([5, 5]) + # add some jumps to jumplist (it doesn't matter how) + keydownSeq('7gg') + keydownSeq('gg') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + it 'jumps to older positions', -> + keydown('o', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [6, 0] + keydown('o', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [5, 5] + keydown('o', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [5, 5] + + it 'jumps to newer positions', -> + keydown('o', ctrl:true) + keydown('o', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [5, 5] + keydown('i', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [6, 0] + keydown('i', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + keydown('i', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + it 'jumps to older positions by count', -> + keydown('2') + keydown('o', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [5, 5] + + it 'jumps to older positions by count (count bigger than list)', -> + keydown('999') + keydown('o', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [5, 5] + + it 'jumps to newer positions by count', -> + keydown('o', ctrl:true) + keydown('o', ctrl:true) + keydown('2') + keydown('i', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + it 'jumps to newer positions by count (count bigger than list)', -> + keydown('o', ctrl:true) + keydown('o', ctrl:true) + keydown('999') + keydown('i', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + + describe 'an entry is created in the jumplist', -> + + beforeEach -> + editor.setText( ('01234567890' for i in [1..10]).join('\n') ) + editor.setCursorScreenPosition([5, 5]) + + expectJumpAdded = (positionBeforeJump = [5, 5]) -> + expect(editor.getCursorScreenPosition()).not.toEqual positionBeforeJump + keydown('o', ctrl:true) + expect(editor.getCursorScreenPosition()).toEqual positionBeforeJump + + it 'before moving to a line', -> + keydownSeq('2gg') + expectJumpAdded() + + it 'before moving to the top of the buffer', -> + keydownSeq('gg') + expectJumpAdded() + + it 'before moving to the bottom of the buffer', -> + keydown('G', {shift:true}) + expectJumpAdded() + + it 'before moving to the bottom of the screen', -> + keydown('L', {shift:true}) + expectJumpAdded() + + it 'before moving to the top of the screen', -> + keydown('L', {shift:true}) + expectJumpAdded() + + it 'before moving to the middle of the screen', -> + keydown('M', {shift:true}) + expectJumpAdded() + + it 'before doing a text search', -> + keydown('/') + editor.commandModeInputView.editor.setText '0123' + editor.commandModeInputView.editor.trigger 'core:confirm' + expectJumpAdded() + + it 'before doing a reverse text search', -> + keydown('?') + editor.commandModeInputView.editor.setText '0123' + editor.commandModeInputView.editor.trigger 'core:confirm' + expectJumpAdded() + + it 'before moving to the next paragraph', -> + keydown('}') + expectJumpAdded() + + it 'before moving to the prevous paragraph', -> + keydown('{') + expectJumpAdded() + + it 'before moving to the matching bracket', -> + editor.setText('a(c)b') + editor.setCursorScreenPosition([0, 3]) + keydown('%') + expectJumpAdded([0, 3]) + + + describe 'jumplist between buffers', -> + + beforeEach -> + + it 'jumps back to position in other buffer', -> throw 'TODO test' + it 'jumps forward to position in other buffer', -> throw 'TODO test' + it 'adds jump to jumplist when switching or opening buffers', -> throw 'TODO test and implementation' + it 'jumps between new unsaved buffers (no editor uri)', -> throw 'TODO test and implementation'