├── README ├── autoload ├── EnhancedJumps.vim └── EnhancedJumps │ ├── Changes.vim │ └── Common.vim ├── doc └── EnhancedJumps.txt └── plugin └── EnhancedJumps.vim /README: -------------------------------------------------------------------------------- 1 | This is a mirror of http://www.vim.org/scripts/script.php?script_id=2695 2 | 3 | DESCRIPTION 4 | This plugin enhances the built-in CTRL-I / CTRL-O jump commands: 5 | - After a jump, the line, column and text of the next jump target are printed: 6 | next: 3,9 ENHANCED JUMPS by Ingo Karkat 7 | - An error message and the valid range for jumps in that direction is printed 8 | if a jump outside the jump list is attempted: 9 | Only 8 older jump positions. 10 | - In case the next jump would move to another buffer, only a warning is 11 | printed at the first attempt: 12 | next: EnhancedJumps.vim 13 | The jump to another buffer is only done if the same jump command is repeated 14 | once more immediately afterwards; like this: Pressing CTRL-O, noticing the 15 | warning, then quickly pressing CTRL-O again to overcome the warning. 16 | With this, you can eagerly jump around the current buffer. Because you will 17 | be warned when a jump would move to another buffer, you're much less likely 18 | to get lost. 19 | 20 | In addition to the enhanced jump commands, separate special mappings are 21 | available that restrict the jump targets to only local locations (in the same 22 | buffer) and remote locations (only in other buffers). 23 | 24 | 25 | This plugin enhances the built-in g;/g, change list jump commands so that 26 | they skip jumps to previous / later edit locations that lie within the 27 | currently visible range. This makes it easier to navigate between distant 28 | edits in a large buffer, without having to manually skip over all the local 29 | changes. 30 | 31 | RELATED WORKS 32 | - back_to_recent_buffer.vim (vimscript #4290) implements going back to the 33 | previous buffer. 34 | 35 | USAGE 36 | Simply use the CTRL-O and CTRL-I commands to go to an older / newer cursor 37 | position in the jump list. When a warning "next: {file}" is echoed, quickly 38 | repeat the jump command to move to that buffer (a [count] need to not be 39 | typed again; if you do include the [count], it must be the same as before). 40 | If you do not want to move to that buffer, just ignore the warning, and 41 | continue browsing the current buffer. On the next jump attempt, the warning 42 | will be repeated. 43 | 44 | g, g Go to [count] older / newer cursor position in the 45 | current buffer. Jumps to other buffers are not 46 | considered. Useful when you mainly edited one file, 47 | briefly jumped to another, and now want to recall 48 | older positions without considering the other file. 49 | 50 | , 51 | Go to [count] older / newer cursor position in another 52 | buffer. Jumps inside the current buffer are not 53 | considered. Useful for recalling previously visited 54 | buffers without going through all local positions. 55 | Regardless of the jump direction, the last jump 56 | position in a buffer is used when there are multiple 57 | subsequent jumps in a buffer. 58 | 59 | Simply use the g; and g, commands to go to an older / newer far change 60 | position in the change list. 61 | 62 | g; Go to [count] older far change (that lies outside the 63 | currently visible range and is more than the current 64 | window height lines away from the previous change or 65 | the last accepted change). 66 | If [count] is larger than the number of older far 67 | change positions go to the oldest far change. 68 | If there is no older far change, go to the [count] 69 | older near change; i.e. fall back to the original 70 | behavior. 71 | If there is no older change an error message is given. 72 | (not a motion command) 73 | g, Go to [count] newer far change. 74 | Just like g; but in the opposite direction. 75 | -------------------------------------------------------------------------------- /autoload/EnhancedJumps.vim: -------------------------------------------------------------------------------- 1 | " EnhancedJumps.vim: Enhanced jump list navigation commands. 2 | " 3 | " DEPENDENCIES: 4 | " - EnhancedJumps/Common.vim autoload script 5 | " - ingo/avoidprompt.vim autoload script 6 | " - ingo/msg.vim autoload script 7 | " - ingo/record.vim autoload script 8 | " 9 | " Copyright: (C) 2009-2014 Ingo Karkat 10 | " The VIM LICENSE applies to this script; see ':help copyright'. 11 | " 12 | " Maintainer: Ingo Karkat 13 | " 14 | " REVISION DATE REMARKS 15 | " 3.02.019 29-Sep-2014 Add g:EnhancedJumps_CaptureJumpMessages 16 | " configuration to turn off the capturing of the 17 | " messages during the jump, as the used :redir may 18 | " cause errors with another, concurrent capture. 19 | " 3.02.018 30-May-2014 Use ingo#record#Position(). 20 | " 3.02.017 05-May-2014 Use ingo#msg#WarningMsg(). 21 | " 3.01.016 14-Jun-2013 Use ingo/msg.vim. 22 | " 3.01.015 07-Jun-2013 Move EchoWithoutScrolling.vim into ingo-library. 23 | " 3.00.014 08-Feb-2012 Move common shared functions to 24 | " EnhancedJumps/Common.vim autoload script to 25 | " allow re-use by new EnhancedJumps/Changes.vim. 26 | " 2.00.013 20-Sep-2011 Split off autoload script. 27 | " 2.00.012 14-Sep-2011 Make s:ParseJumpLine() return object to allow 28 | " easier access to attributes without array 29 | " slicing. 30 | " Differentiate between (given) count and 31 | " jumpCount when filtering. 32 | " Make remote jumps move to individual, different 33 | " files, so that remote jumps with [count] work as 34 | " expected. 35 | " s:DoJump() must now check for count = 0 because 36 | " of filtering. 37 | " Add filter name to all user messages. 38 | " Redefine l:jumps to only contain the jumps in 39 | " the jump direction via 40 | " s:SliceJumpsInDirection(). This obviates the 41 | " index arithmetic, duplicated checks for current 42 | " index marker, and enhances the filter 43 | " performance, because the unnecessary part in the 44 | " opposite direction doesn't need to be processed. 45 | " Make newer remote jumps also jump to the latest 46 | " file position, as this is more useful. 47 | " FIX: By just considering file names, 48 | " s:RemoveDuplicateSubsequentFiles() collapsed 49 | " jumps where there was a local jump in between. 50 | " Now also checking for sequential jump count. 51 | " Implement "next jump" message also for remote 52 | " jumps by capturing the file jump message(s) 53 | " (BufRead autocmds may be triggered and print 54 | " messages, such as the IndentConsistencyCop 55 | " plugin). Concatenate in one message line, or use 56 | " a larger 'cmdheight' value. The tricky thing for 57 | " the "remote" filter is that the following jump 58 | " information can become wrong in an A->B->A 59 | " scenario. 60 | " 2.00.011 13-Sep-2011 Implement "local jumps" and "remote jumps" 61 | " varieties: 62 | " Change signature of s:IsJumpInCurrentBuffer() to 63 | " be suitable for directly accepting 64 | " s:ParseJumpLine() results. 65 | " 1.14.010 13-Sep-2011 Better way to beep. 66 | " 1.13.009 16-Jul-2010 BUG: Jump opened fold at current position when 67 | " "No newer/older jump position" error occurred. 68 | " Now checking whether the jump actually was 69 | " successful in s:DoJump(), and not just relying 70 | " on the Vim error that only occurs when there's 71 | " an invalid jump position. 72 | " 1.12.008 17-Jul-2009 BF: Trailing space after the command to open the 73 | " folds accidentally moved cursor one position to 74 | " the right of the jump target. 75 | " 1.11.007 14-Jul-2009 BF: A '^\)' string caused "E55: Unmatched \)" 76 | " because the '\^\p' regexp fragment would only 77 | " match the first half of the text's escaped 78 | " backslash and thus sabotage the escaping. Now 79 | " explicitly matching an escaped backslash (\\) as 80 | " an alternative to the \p atom. 81 | " 1.10.006 06-Jul-2009 BF: Folds at the jump target must be explicitly 82 | " opened; inside a mapping / :normal CTRL-I/O 83 | " behave like [nN*#]. 84 | " 1.10.005 01-Jul-2009 ENH: To overcome the next buffer warning, a 85 | " previously given [count] need not be specified 86 | " again. A jump command with a different [count] 87 | " than last time now is treated as a separate jump 88 | " command and thus doesn't overcome the next 89 | " buffer warning. 90 | " Factored out s:GetJumps(), s:GetCurrentIndex() 91 | " and s:GetCount() to reduce the size of s:Jump(). 92 | " 1.00.004 01-Jul-2009 Renamed to EnhancedJumps.vim. 93 | " BF: Empty jump text matched any line in the 94 | " current buffer; but it must match an empty line 95 | " to belong to the current buffer. 96 | " BF: An unnamed buffer was simply listed as 97 | " "next: ", now listed as "next: [No name]". 98 | " BF: s:IsJumpInCurrentBuffer() regexp didn't 99 | " consider that ^X could stand for either a 100 | " non-printable char or the literal ^X sequence. 101 | " 003 29-Jun-2009 BF: Fixed missing next jump indication by 102 | " executing the jump command before the :echo (and 103 | " sometimes doing a :redraw before the :echo). 104 | " 002 28-Jun-2009 ENH: After a jump, the line, column and text of 105 | " the next jump target are printed. The text of 106 | " jumps inside the current buffer are highlighted 107 | " like in the :jumps output. 108 | " 001 27-Jun-2009 file creation 109 | let s:save_cpo = &cpo 110 | set cpo&vim 111 | 112 | function! s:FilterDuplicateSubsequentFiles( jumps, isNewer ) 113 | "****D echo join(a:jumps, "\n") 114 | " Always jump to the latest file position, also when jumping to newer files. 115 | " This way, the same position is maintained when jumping older and newer. 116 | " Otherwise, the newer jumps would always start at the top of the file (or 117 | " remembered file position) - not very useful. 118 | let l:uniqueJumps = [] 119 | let l:prevParsedJump = EnhancedJumps#Common#ParseJumpLine('') 120 | for l:i in (a:isNewer ? 121 | \ range(len(a:jumps) - 1, 0, -1) : 122 | \ range(len(a:jumps)) 123 | \) 124 | let l:currentParsedJump = EnhancedJumps#Common#ParseJumpLine(a:jumps[l:i]) 125 | " Include the current jump if it's a different file or there are other 126 | " local jumps in between (i.e. the jump counts are not sequential). 127 | if l:currentParsedJump.text !=# l:prevParsedJump.text || 128 | \ l:currentParsedJump.count != (l:prevParsedJump.count + (a:isNewer ? -1 : 1)) 129 | if a:isNewer 130 | call insert(l:uniqueJumps, a:jumps[l:i], 0) 131 | else 132 | call add(l:uniqueJumps, a:jumps[l:i]) 133 | endif 134 | endif 135 | let l:prevParsedJump = l:currentParsedJump 136 | endfor 137 | "****D echo "****\n" join(l:uniqueJumps, "\n") 138 | return l:uniqueJumps 139 | endfunction 140 | function! s:FilterJumps( jumps, filter, isNewer ) 141 | if empty(a:filter) 142 | return a:jumps 143 | elseif a:filter ==# 'local' 144 | return filter(a:jumps, 's:IsJumpInCurrentBuffer(EnhancedJumps#Common#ParseJumpLine(v:val))') 145 | elseif a:filter ==# 'remote' 146 | return s:FilterDuplicateSubsequentFiles( 147 | \ filter(a:jumps, '! s:IsJumpInCurrentBuffer(EnhancedJumps#Common#ParseJumpLine(v:val))'), 148 | \ a:isNewer 149 | \) 150 | else 151 | throw 'ASSERT: Unknown filter type ' . string(a:filter) 152 | endif 153 | endfunction 154 | function! s:GetCount() 155 | " Determine whether this is a repetition of the same jump command that got 156 | " stuck on the warning about jumping into another buffer. 157 | let l:wasStopped = (exists('t:lastJumpCommandCount') && t:lastJumpCommandCount) 158 | if l:wasStopped 159 | " If no [count] is given on this repetition, re-use the [count] 160 | " from the initial jump command that got stuck on the warning. 161 | return (v:count ? v:count1 : t:lastJumpCommandCount) 162 | else 163 | " This isn't a repetition; use the supplied [count]. 164 | return v:count1 165 | endif 166 | endfunction 167 | function! s:BufferName( jumpText ) 168 | return (empty(a:jumpText) ? '[No name]' : a:jumpText) 169 | endfunction 170 | function! s:WasLastStop( current, record ) 171 | return (! empty(a:current) && ! empty(a:record) && a:current[0:-2] == a:record[0:-2]) && (a:current[-1] - a:record[-1] <= (g:stopFirstAndNotifyTimeoutLen / 1000)) 172 | endfunction 173 | function! s:IsInvalid( text ) 174 | if a:text ==# '-invalid-' 175 | " Though invalid jumps are caused by marks in another (modified) file, 176 | " treat them as belonging to the current buffer; after all, Vim doesn't 177 | " move to that file, and just prints the "E19: Mark has invalid line 178 | " number" error. 179 | return 1 180 | endif 181 | endfunction 182 | function! s:IsJumpInCurrentBuffer( parsedJump ) 183 | if empty(a:parsedJump.text) 184 | " In case there is no jump text, the corresponding line in the current 185 | " buffer also should be empty. 186 | let l:regexp = '^$' 187 | else 188 | " The jump text omits any indent, may be truncated and has non-printable 189 | " characters rendered as ^X (so any ^X substring may either represent a 190 | " non-printable single character or the literal two-character ^X 191 | " sequence). The regexp has to consider this. 192 | let l:regexp = '\V' . substitute(escape(a:parsedJump.text, '\'), '\^\%(\\\\\|\p\)', '\\%(\0\\|\\.\\)', 'g') 193 | endif 194 | "****D echomsg '****' l:regexp 195 | return getline(a:parsedJump.lnum) =~# l:regexp 196 | endfunction 197 | function! s:DoJump( count, isNewer ) 198 | if a:count == 0 199 | execute "normal! \\\" 200 | return 0 201 | endif 202 | 203 | try 204 | " There's just a beep when there's no newer/older jump position; this is 205 | " not a Vim error, so no exception is thrown. 206 | " We check the position before and after the jump to detect its success 207 | " in all cases. 208 | let l:originalPosition = ingo#record#Position(0) 209 | execute 'normal!' a:count . (a:isNewer ? "\" : "\") 210 | if ingo#record#Position(0) == l:originalPosition 211 | return 0 212 | endif 213 | 214 | " When typed, CTRL-I/O open the fold at the jump target, but inside a 215 | " mapping or :normal this must be done explicitly via 'zv'. 216 | normal! zv 217 | 218 | return 1 219 | catch /^Vim\%((\a\+)\)\=:/ 220 | " A Vim error occurs when there's an invalid jump position. 221 | call ingo#msg#VimExceptionMsg() 222 | return 0 223 | endtry 224 | endfunction 225 | function! s:Echo( fileJumpMessages, message ) 226 | if empty(a:fileJumpMessages) 227 | echo a:message 228 | elseif &cmdheight > 1 || len(a:fileJumpMessages) > 1 229 | for l:message in a:fileJumpMessages 230 | echomsg l:message 231 | endfor 232 | echo a:message 233 | else 234 | echomsg a:fileJumpMessages[0] . ' ' 235 | echon a:message 236 | endif 237 | endfunction 238 | function! s:EchoFollowingMessage( followingJump, jumpDirection, filterName, fileJumpMessages ) 239 | let l:following = EnhancedJumps#Common#ParseJumpLine(a:followingJump) 240 | if empty(a:followingJump) 241 | redraw 242 | call s:Echo(a:fileJumpMessages, printf('No %s%s jump position', a:jumpDirection, a:filterName)) 243 | elseif s:IsInvalid(l:following.text) 244 | redraw 245 | call s:Echo(a:fileJumpMessages, printf('Next%s jump position is invalid', a:filterName)) 246 | elseif s:IsJumpInCurrentBuffer(l:following) 247 | let l:header = printf('next%s: %d,%d ', a:filterName, l:following.lnum, l:following.col) 248 | call s:Echo(a:fileJumpMessages, l:header) 249 | echohl Directory 250 | echon ingo#avoidprompt#Truncate(l:following.text, strlen(l:header)) | " l:header is printable ASCII-only, so can use strlen() for text width. 251 | echohl None 252 | else 253 | call s:Echo(a:fileJumpMessages, ingo#avoidprompt#Truncate(printf('next%s: %s', a:filterName, s:BufferName(l:following.text)))) 254 | endif 255 | endfunction 256 | function! EnhancedJumps#Jump( isNewer, filter ) 257 | let l:filterName = (empty(a:filter) ? '' : ' ' . a:filter) 258 | let l:jumpDirection = (a:isNewer ? 'newer' : 'older') 259 | 260 | let l:jumps = s:FilterJumps(EnhancedJumps#Common#SliceJumpsInDirection(EnhancedJumps#Common#GetJumps('jumps'), a:isNewer), a:filter, a:isNewer) 261 | let l:count = s:GetCount() 262 | 263 | let l:targetJump = get(l:jumps, l:count - 1, '') 264 | let l:followingJump = get(l:jumps, l:count, '') 265 | "****D echomsg '****' l:targetJump 266 | "****D echomsg '****' l:followingJump 267 | " In case of filtering the count for the jump command does not correspond to 268 | " the given count and must be retrieved from the jump line. 269 | let l:jumpCount = (empty(a:filter) ? l:count : EnhancedJumps#Common#ParseJumpLine(l:targetJump).count) 270 | "****D echomsg '****' l:count l:jumpCount 271 | if empty(l:targetJump) 272 | let l:countMax = len(l:jumps) 273 | if l:countMax == 0 274 | call ingo#msg#ErrorMsg(printf('No %s%s jump position', l:jumpDirection, l:filterName)) 275 | else 276 | call ingo#msg#ErrorMsg(printf('Only %d %s%s jump position%s', l:countMax, l:jumpDirection, l:filterName, (l:countMax > 1 ? 's' : ''))) 277 | endif 278 | 279 | " We still execute the actual jump command, even though we've determined 280 | " that it won't work. The jump command will still cause the customary 281 | " beep. 282 | call s:DoJump(l:jumpCount, a:isNewer) 283 | else 284 | let l:target = EnhancedJumps#Common#ParseJumpLine(l:targetJump) 285 | if s:IsInvalid(l:target.text) 286 | " Do nothing here, the jump command will print an error. 287 | call s:DoJump(l:jumpCount, a:isNewer) 288 | elseif s:IsJumpInCurrentBuffer(l:target) 289 | " To avoid that the jump command's output overwrites the indication 290 | " of the next jump position, the jump command is executed first and 291 | " the indication only printed if the jump didn't cause an error. 292 | if s:DoJump(l:jumpCount, a:isNewer) 293 | call s:EchoFollowingMessage(l:followingJump, l:jumpDirection, l:filterName, '') 294 | endif 295 | else 296 | " The next jump would move to another buffer. Stop and notify first, 297 | " and only execute the jump if the same jump command (either 298 | " repeating the original [count] or completely omitting it) is 299 | " executed once more immediately afterwards. 300 | let l:isSameCountAsLast = (! v:count || (exists('t:lastJumpCommandCount') && t:lastJumpCommandCount == v:count1)) 301 | let l:wasLastJumpBufferStop = l:isSameCountAsLast && 302 | \ exists('t:lastJumpBufferStop') && 303 | \ s:WasLastStop([a:isNewer, winnr(), l:target.text, localtime()], t:lastJumpBufferStop) 304 | if l:wasLastJumpBufferStop || ! empty(a:filter) 305 | if g:EnhancedJumps_CaptureJumpMessages 306 | redir => l:fileJumpCapture 307 | silent call s:DoJump(l:jumpCount, a:isNewer) 308 | redir END 309 | else 310 | let l:fileJumpCapture = '' 311 | call s:DoJump(l:jumpCount, a:isNewer) 312 | endif 313 | 314 | if a:filter ==# 'remote' 315 | " After the jump to another file, the filtered list for 316 | " remote files becomes wrong in case the following file is 317 | " the same as the original file (i.e. A(original) -> B(jump) 318 | " -> A(following)), because that jump was initially filtered 319 | " out. To correctly determine the following jump, we must 320 | " re-query and re-filter the jumps. 321 | " In addition, the file paths to the file may have changed 322 | " due to changes in CWD / 'autochdir'. 323 | let l:followingJump = get( 324 | \ s:FilterJumps(EnhancedJumps#Common#SliceJumpsInDirection(EnhancedJumps#Common#GetJumps('jumps'), a:isNewer), a:filter, a:isNewer), 325 | \ 0, '' 326 | \) 327 | endif 328 | 329 | call s:EchoFollowingMessage(l:followingJump, l:jumpDirection, l:filterName, 330 | \ filter( 331 | \ split(l:fileJumpCapture, "\n"), 332 | \ '! empty(v:val)' 333 | \ ) 334 | \) 335 | else 336 | " Memorize the current jump command, context, target and time 337 | " (except for the [count], which is stored separately) to be 338 | " able to detect the same jump command. 339 | let t:lastJumpBufferStop = [a:isNewer, winnr(), l:target.text, localtime()] 340 | 341 | " Memorize the given [count] to detect the same jump command, 342 | " and that it need not be specified on the repetition of the 343 | " jump command to overcome the warning. 344 | let t:lastJumpCommandCount = l:count 345 | 346 | call ingo#msg#WarningMsg(printf('next%s: %s', l:filterName, s:BufferName(l:target.text))) 347 | " Signal edge case via beep. 348 | execute "normal! \\\" 349 | 350 | " We stop here, and do not execute the actual jump command. 351 | return 352 | endif 353 | endif 354 | endif 355 | 356 | let t:lastJumpBufferStop = [a:isNewer, winnr(), '', 0] 357 | let t:lastJumpCommandCount = 0 " This is no repetition. 358 | endfunction 359 | 360 | let &cpo = s:save_cpo 361 | unlet s:save_cpo 362 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 363 | -------------------------------------------------------------------------------- /autoload/EnhancedJumps/Changes.vim: -------------------------------------------------------------------------------- 1 | " Changes.vim: Enhanced change list navigation commands. 2 | " 3 | " DEPENDENCIES: 4 | " - EnhancedJumps/Common.vim autoload script 5 | " - ingo/msg.vim autoload script 6 | " - ingo/window/dimensions.vim autoload script 7 | " 8 | " Copyright: (C) 2012-2014 Ingo Karkat 9 | " The VIM LICENSE applies to this script; see ':help copyright'. 10 | " 11 | " Maintainer: Ingo Karkat 12 | " 13 | " REVISION DATE REMARKS 14 | " 3.02.006 05-May-2014 Use ingo#msg#WarningMsg(). 15 | " 3.01.005 14-Jun-2013 Use ingo/msg.vim. 16 | " 3.01.004 05-Jun-2013 Handle it when the :changes command sometimes 17 | " outputs just the header without a following ">" 18 | " marker by catching the plugin exception in 19 | " EnhancedJumps#Changes#GetJumps() and returning 20 | " an empty List instead. This will cause the 21 | " callers to fall back on the default g; / g, 22 | " commands, which will then report the "E664: 23 | " changelist is empty" error. 24 | " 3.01.003 08-Apr-2013 Move ingowindow.vim functions into ingo-library. 25 | " 3.00.002 09-Feb-2012 Modify s:FilterNearJumps() algorithm. 26 | " 001 08-Feb-2012 file creation 27 | let s:save_cpo = &cpo 28 | set cpo&vim 29 | 30 | function! s:abs( num1, num2 ) 31 | let l:difference = a:num1 - a:num2 32 | return (l:difference >= 0 ? l:difference : -1 * l:difference) 33 | endfunction 34 | function! s:FilterNearJumps( jumps, startLnum, endLnum, nearHeight ) 35 | "****D echomsg '####' a:startLnum a:endLnum 36 | let l:farJumps = [] 37 | let l:prevParsedJump = EnhancedJumps#Common#ParseJumpLine('') 38 | let l:prevParsedJump.lnum = -1 * (a:nearHeight + 1) 39 | let l:lastLnum = 0 40 | 41 | for l:i in range(len(a:jumps)) 42 | let l:currentParsedJump = EnhancedJumps#Common#ParseJumpLine(a:jumps[l:i]) 43 | " Include the current jump if it's outside the currently visible range, 44 | " and more than a:nearHeight lines away from the previous jump or from 45 | " the last accepted jump. 46 | "****D echomsg '****' l:currentParsedJump.lnum l:prevParsedJump.lnum l:lastLnum 47 | if ( 48 | \ l:currentParsedJump.lnum < a:startLnum || 49 | \ l:currentParsedJump.lnum > a:endLnum 50 | \) && ( 51 | \ s:abs(l:currentParsedJump.lnum, l:prevParsedJump.lnum) > a:nearHeight || 52 | \ s:abs(l:currentParsedJump.lnum, l:lastLnum) > a:nearHeight 53 | \) 54 | call add(l:farJumps, a:jumps[l:i]) 55 | let l:lastLnum = l:currentParsedJump.lnum 56 | "****D echomsg '**** accept' 57 | endif 58 | let l:prevParsedJump = l:currentParsedJump 59 | endfor 60 | "****D echo "****\n" join(l:farJumps, "\n") 61 | return l:farJumps 62 | endfunction 63 | function! EnhancedJumps#Changes#GetJumps( isNewer ) 64 | let [l:startLnum, l:endLnum] = ingo#window#dimensions#DisplayedLines() 65 | let l:nearHeight = winheight(0) 66 | 67 | try 68 | return s:FilterNearJumps( 69 | \ EnhancedJumps#Common#SliceJumpsInDirection( 70 | \ EnhancedJumps#Common#GetJumps('changes'), 71 | \ a:isNewer 72 | \ ), 73 | \ l:startLnum, l:endLnum, l:nearHeight 74 | \) 75 | catch /^EnhancedJumps:/ 76 | return [] 77 | endtry 78 | endfunction 79 | 80 | function! s:warn( warningmsg ) 81 | redraw " After the jump, a redraw is pending. Do it now or the message may vanish. 82 | call ingo#msg#WarningMsg(a:warningmsg) 83 | endfunction 84 | function! s:DoJump( count, isNewer ) 85 | if a:count == 0 86 | execute "normal! \\\" 87 | return 0 88 | endif 89 | "****D echomsg '****' a:count . (a:isNewer ? 'g,' : 'g;') 90 | try 91 | execute 'normal!' a:count . (a:isNewer ? 'g,' : 'g;') 92 | 93 | " When typed, g,/g; open the fold at the jump target, but inside a 94 | " mapping or :normal this must be done explicitly via 'zv'. 95 | normal! zv 96 | 97 | return 1 98 | catch /^Vim\%((\a\+)\)\=:/ 99 | " A Vim error occurs when already at the start / end of the changelist. 100 | call ingo#msg#VimExceptionMsg() 101 | return 0 102 | endtry 103 | endfunction 104 | 105 | function! EnhancedJumps#Changes#Jump( isNewer, isFallbackToNearChanges ) 106 | let l:jumpDirection = (a:isNewer ? 'newer' : 'older') 107 | let l:count = v:count1 108 | let l:jumps = EnhancedJumps#Changes#GetJumps(a:isNewer) 109 | if empty(l:jumps) 110 | if a:isFallbackToNearChanges 111 | " Perform the [count]'th near jump. 112 | if s:DoJump(l:count, a:isNewer) 113 | " Only print the warning when the jump was successful; it may 114 | " have already errored out with "At start / end of changelist". 115 | call s:warn(printf('No %s far change', l:jumpDirection)) 116 | endif 117 | else 118 | call ingo#msg#ErrorMsg(printf('No %s far change', l:jumpDirection)) 119 | " Still execute the a zero-jump command to cause the customary beep. 120 | call s:DoJump(0, a:isNewer) 121 | endif 122 | 123 | return 124 | endif 125 | 126 | "****D for j in l:jumps | echomsg j | endfor 127 | let l:isFallbackNearJump = 0 128 | let l:targetJump = get(l:jumps, l:count - 1, '') 129 | if empty(l:targetJump) 130 | " Jump to the last available far jump, like the original 999g, jumps to 131 | " the last change. 132 | let l:targetJump = get(l:jumps, -1, '') 133 | let l:isFallbackNearJump = 1 134 | endif 135 | 136 | " Because of filtering the count for the jump command does not correspond to 137 | " the given count and must be retrieved from the jump line. 138 | let l:jumpCount = EnhancedJumps#Common#ParseJumpLine(l:targetJump).count 139 | "****D echomsg '****' l:count l:jumpCount 140 | if s:DoJump(l:jumpCount, a:isNewer) && l:isFallbackNearJump 141 | " Only print the warning when the jump was successful; it may 142 | " have already errored out with "At start / end of changelist". 143 | call s:warn(printf('No more %d %s far changes', l:count, l:jumpDirection)) 144 | endif 145 | endfunction 146 | 147 | let &cpo = s:save_cpo 148 | unlet s:save_cpo 149 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 150 | -------------------------------------------------------------------------------- /autoload/EnhancedJumps/Common.vim: -------------------------------------------------------------------------------- 1 | " Common.vim: Shared functionality for dealing with jump lists. 2 | " 3 | " DEPENDENCIES: 4 | " 5 | " Copyright: (C) 2012-2014 Ingo Karkat 6 | " The VIM LICENSE applies to this script; see ':help copyright'. 7 | " 8 | " Maintainer: Ingo Karkat 9 | " 10 | " REVISION DATE REMARKS 11 | " 3.01.002 05-Jun-2013 Change assertion into plugin exception, since 12 | " the :changes command sometimes outputs just the 13 | " header without a following ">" marker. 14 | " 3.00.001 08-Feb-2012 file creation from autoload/EnhancedJumps.vim. 15 | let s:save_cpo = &cpo 16 | set cpo&vim 17 | 18 | function! EnhancedJumps#Common#GetJumps( command ) 19 | redir => l:jumpsOutput 20 | silent! execute a:command 21 | redir END 22 | redraw " This is necessary because of the :redir done earlier. 23 | 24 | return split(l:jumpsOutput, "\n")[1:] " The first line contains the header. 25 | endfunction 26 | function! s:GetCurrentIndex( jumps ) 27 | let l:currentIndex = -1 28 | " Note: The linear search starts from the end because it's more likely that 29 | " the user hasn't navigated to the oldest entries in the jump list. 30 | for l:i in reverse(range(len(a:jumps))) 31 | if a:jumps[l:i][0] ==# '>' 32 | let l:currentIndex = l:i 33 | break 34 | endif 35 | endfor 36 | if l:currentIndex < 0 37 | " XXX: Sometimes, the :changes command just outputs the "change line col 38 | " text" line, without a ">" line following. 39 | throw 'EnhancedJumps: jump list does not contain > marker' 40 | endif 41 | return l:currentIndex 42 | endfunction 43 | function! EnhancedJumps#Common#SliceJumpsInDirection( jumps, isNewer ) 44 | "****************************************************************************** 45 | "* PURPOSE: 46 | " From the list of jumps, keep only those following the current index in the 47 | " direction of jump, and reverse older jumps so that the jump index directly 48 | " corresponds to the count of the jump. 49 | "* ASSUMPTIONS / PRECONDITIONS: 50 | " None. 51 | "* EFFECTS / POSTCONDITIONS: 52 | " None. 53 | "* INPUTS: 54 | " a:jumps List of jump lines from :jumps or :changes command. 55 | " a:isNewer Flag whether the jump is to newer jumps. 56 | "* RETURN VALUES: 57 | " Rearranged slice of jumps; the jump index corresponds to the jump count. 58 | "****************************************************************************** 59 | let l:currentIndex = s:GetCurrentIndex(a:jumps) 60 | if a:isNewer 61 | return a:jumps[(l:currentIndex + 1) : ] 62 | else 63 | return (l:currentIndex == 0 ? [] : reverse(a:jumps[ : (l:currentIndex - 1)])) 64 | endif 65 | endfunction 66 | 67 | function! EnhancedJumps#Common#ParseJumpLine( jumpLine ) 68 | " Parse one line of output from :jumps into object with count, lnum, col, text. 69 | let l:parseResult = matchlist(a:jumpLine, '^>\?\s*\(\d\+\)\s\+\(\d\+\)\s\+\(\d\+\)\s\+\(.*\)$') 70 | return { 71 | \ 'count': get(l:parseResult, 1, 0), 72 | \ 'lnum' : get(l:parseResult, 2, 0), 73 | \ 'col' : get(l:parseResult, 3, 0), 74 | \ 'text' : get(l:parseResult, 4, '') 75 | \} 76 | endfunction 77 | 78 | let &cpo = s:save_cpo 79 | unlet s:save_cpo 80 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 81 | -------------------------------------------------------------------------------- /doc/EnhancedJumps.txt: -------------------------------------------------------------------------------- 1 | *EnhancedJumps.txt* Enhanced jump and change list navigation commands. 2 | 3 | ENHANCED JUMPS by Ingo Karkat 4 | *EnhancedJumps.vim* 5 | description |EnhancedJumps-description| 6 | usage |EnhancedJumps-usage| 7 | installation |EnhancedJumps-installation| 8 | configuration |EnhancedJumps-configuration| 9 | limitations |EnhancedJumps-limitations| 10 | known problems |EnhancedJumps-known-problems| 11 | todo |EnhancedJumps-todo| 12 | history |EnhancedJumps-history| 13 | 14 | ============================================================================== 15 | DESCRIPTION *EnhancedJumps-description* 16 | 17 | This plugin enhances the built-in |CTRL-I|/|CTRL-O| jump commands: 18 | - After a jump, the line, column and text of the next jump target are printed: 19 | next: 3,9 ENHANCED JUMPS by Ingo Karkat ~ 20 | - An error message and the valid range for jumps in that direction is printed 21 | if a jump outside the jump list is attempted: 22 | Only 8 older jump positions. ~ 23 | - In case the next jump would move to another buffer, only a warning is 24 | printed at the first attempt: 25 | next: EnhancedJumps.vim ~ 26 | The jump to another buffer is only done if the same jump command is repeated 27 | once more immediately afterwards; like this: Pressing CTRL-O, noticing the 28 | warning, then quickly pressing CTRL-O again to overcome the warning. 29 | With this, you can eagerly jump around the current buffer. Because you will 30 | be warned when a jump would move to another buffer, you're much less likely 31 | to get lost. 32 | 33 | In addition to the enhanced jump commands, separate special mappings are 34 | available that restrict the jump targets to only local locations (in the same 35 | buffer) and remote locations (only in other buffers). 36 | 37 | 38 | This plugin enhances the built-in |g;|/|g,| change list jump commands so that 39 | they skip jumps to previous / later edit locations that lie within the 40 | currently visible range. This makes it easier to navigate between distant 41 | edits in a large buffer, without having to manually skip over all the local 42 | changes. 43 | 44 | RELATED WORKS * 45 | 46 | - back_to_recent_buffer.vim (vimscript #4290) implements going back to the 47 | previous buffer. 48 | 49 | ============================================================================== 50 | USAGE *EnhancedJumps-usage* 51 | 52 | Simply use the |CTRL-O| and |CTRL-I| commands to go to an older / newer cursor 53 | position in the jump list. When a warning "next: {file}" is echoed, quickly 54 | repeat the jump command to move to that buffer (a [count] need to not be 55 | typed again; if you do include the [count], it must be the same as before). 56 | If you do not want to move to that buffer, just ignore the warning, and 57 | continue browsing the current buffer. On the next jump attempt, the warning 58 | will be repeated. 59 | 60 | *g_CTRL-O* *g_CTRL-I* 61 | g, g Go to [count] older / newer cursor position in the 62 | current buffer. Jumps to other buffers are not 63 | considered. Useful when you mainly edited one file, 64 | briefly jumped to another, and now want to recall 65 | older positions without considering the other file. 66 | 67 | *CTRL-O* *CTRL-I* 68 | , 69 | Go to [count] older / newer cursor position in another 70 | buffer. Jumps inside the current buffer are not 71 | considered. Useful for recalling previously visited 72 | buffers without going through all local positions. 73 | Regardless of the jump direction, the last jump 74 | position in a buffer is used when there are multiple 75 | subsequent jumps in a buffer. 76 | 77 | 78 | Simply use the |g;| and |g,| commands to go to an older / newer far change 79 | position in the change list. 80 | 81 | g; Go to [count] older far change (that lies outside the 82 | currently visible range and is more than the current 83 | window height lines away from the previous change or 84 | the last accepted change). 85 | If [count] is larger than the number of older far 86 | change positions go to the oldest far change. 87 | If there is no older far change, go to the [count] 88 | older near change; i.e. fall back to the original 89 | behavior. 90 | If there is no older change an error message is given. 91 | (not a motion command) 92 | g, Go to [count] newer far change. 93 | Just like |g;| but in the opposite direction. 94 | 95 | ============================================================================== 96 | INSTALLATION *EnhancedJumps-installation* 97 | 98 | This script is packaged as a |vimball|. If you have the "gunzip" decompressor 99 | in your PATH, simply edit the *.vmb.gz package in Vim; otherwise, decompress 100 | the archive first, e.g. using WinZip. Inside Vim, install by sourcing the 101 | vimball or via the |:UseVimball| command. > 102 | vim EnhancedJumps*.vmb.gz 103 | :so % 104 | To uninstall, use the |:RmVimball| command. 105 | 106 | DEPENDENCIES *EnhancedJumps-dependencies* 107 | 108 | - Requires Vim 7.0 or higher. 109 | - Requires the |ingo-library.vim| plugin (vimscript #4433), version 1.020 or 110 | higher. 111 | 112 | ============================================================================== 113 | CONFIGURATION *EnhancedJumps-configuration* 114 | 115 | For a permanent configuration, put the following commands into your |vimrc|: 116 | 117 | *g:stopFirstAndNotifyTimeoutLen* 118 | The time span in which the jump command must be repeated to overcome the 119 | warning about jumping into another buffer defaults to (roughly) two seconds. 120 | To change the timeout, set a different value (in milliseconds): > 121 | let g:stopFirstAndNotifyTimeoutLen = 2000 122 | < 123 | *g:EnhancedJumps_CaptureJumpMessages* 124 | Jumps to another buffer happen with "redir => l:fileJumpCapture". If other 125 | plugins triggered by the (e.g. BufWinEnter) event do another :redir, this 126 | causes an error, because nested redirs are prohibited. You can avoid this 127 | problem by turning off the capture of jump messages: > 128 | let g:EnhancedJumps_CaptureJumpMessages = 0 129 | < 130 | *EnhancedJumps-remap* 131 | If you do not want to override the built-in jump commands and use separate 132 | mappings, or change the special additional mappings, map your keys to the 133 | ... mapping targets _before_ sourcing the script (e.g. in your |vimrc|). > 134 | nmap { EnhancedJumpsOlder 135 | nmap } EnhancedJumpsNewer 136 | nmap g{ EnhancedJumpsLocalOlder 137 | nmap g} EnhancedJumpsLocalNewer 138 | nmap { EnhancedJumpsRemoteOlder 139 | nmap } EnhancedJumpsRemoteNewer 140 | 141 | For the change list jump commands, you can choose between two alternatives, 142 | the default one that falls back to near changes when there are no far changes > 143 | nmap z; EnhancedJumpsFarFallbackChangeOlder 144 | nmap z, EnhancedJumpsFarFallbackChangeNewer 145 | and a pure "far jumps" variant: > 146 | nmap z; EnhancedJumpsFarChangeOlder 147 | nmap z, EnhancedJumpsFarChangeNewer 148 | < 149 | To disable the special additional mappings: > 150 | nmap DisableEnhancedJumpsLocalOlder EnhancedJumpsLocalOlder 151 | nmap DisableEnhancedJumpsLocalNewer EnhancedJumpsLocalNewer 152 | nmap DisableEnhancedJumpsRemoteOlder EnhancedJumpsRemoteOlder 153 | nmap DisableEnhancedJumpsRemoteNewer EnhancedJumpsRemoteNewer 154 | < 155 | ============================================================================== 156 | LIMITATIONS *EnhancedJumps-limitations* 157 | 158 | - If the jump target is an empty line in another unnamed buffer, and the 159 | current buffer contains an empty line at the target line, the jump outside 160 | the current buffer is not detected, and no warning given. 161 | - If the jump target is in another buffer, and the current buffer contains 162 | that buffer's filespec at the target line, the jump outside the current 163 | buffer is not detected, and no warning given. Same for empty buffers, viz. 164 | "[No Name]". 165 | 166 | KNOWN PROBLEMS *EnhancedJumps-known-problems* 167 | 168 | TODO *EnhancedJumps-todo* 169 | 170 | IDEAS *EnhancedJumps-ideas* 171 | 172 | ============================================================================== 173 | HISTORY *EnhancedJumps-history* 174 | 175 | 3.02 29-Sep-2014 176 | - Add g:EnhancedJumps_CaptureJumpMessages configuration to turn off the 177 | capturing of the messages during the jump, as the used :redir may cause 178 | errors with another, concurrent capture. This was first reported by 179 | by Alexey Radkov on 06-Jul-2012 (conflict with the Recover.vim plugin that 180 | was fixed in that plugin), now again by Maxim Gonchar (conflict with 181 | Vimfiler plugin). 182 | - Use ingo#msg#WarningMsg(). 183 | - Use ingo#record#Position(). 184 | *** You need to update to ingo-library (vimscript #4433) version 1.020! *** 185 | 186 | 3.01 19-Nov-2013 187 | - Handle it when the :changes command sometimes outputs just the header 188 | without a following ">" marker by catching the plugin exception in 189 | EnhancedJumps#Changes#GetJumps() and returning an empty List instead. This 190 | will cause the callers to fall back on the default g; / g, commands, which 191 | will then report the "E664: changelist is empty" error. 192 | - Add dependency to ingo-library (vimscript #4433). *** You need to separately 193 | install ingo-library (vimscript #4433) version 1.008 (or higher)! *** 194 | 195 | 3.00 09-Feb-2012 196 | Implement enhanced change list navigation commands g; / g, that skip jumps to 197 | previous / later edit locations that lie within the currently visible range. 198 | 199 | 2.00 20-Sep-2011 200 | - Implement "local jumps" and "remote jumps" special mappings. 201 | - Restructure internal jump list representation to get rid of index 202 | arithmetic, duplicated checks for current index in, and enhance filter 203 | performance. 204 | - Split off functions to separate autoload script to help speed up Vim 205 | startup. 206 | 207 | 1.13 16-Jul-2010 208 | BUG: Jump opened fold at current position when "No newer/older jump position" 209 | error occurred. 210 | 211 | 1.12 17-Jul-2009 212 | BF: Trailing space after the command to open the folds accidentally moved 213 | cursor one position to the right of the jump target. 214 | 215 | 1.11 14-Jul-2009 216 | BF: A '^\)' in the jump text caused "E55: Unmatched \)" 217 | 218 | 1.10 06-Jul-2009 219 | - ENH: To overcome the next buffer warning, a previously given [count] need 220 | not be specified again. A jump command with a different [count] than last 221 | time now is treated as a separate jump command and thus doesn't overcome the 222 | next buffer warning. 223 | - BF: Folds at the jump target must be explicitly opened. 224 | 225 | 1.00 01-Jul-2009 226 | First published version. 227 | 228 | ============================================================================== 229 | Copyright: (C) 2009-2014 Ingo Karkat 230 | The VIM LICENSE applies to this plugin; see |copyright|. 231 | 232 | Maintainer: Ingo Karkat 233 | ============================================================================== 234 | vim:tw=78:ts=8:ft=help:norl: 235 | -------------------------------------------------------------------------------- /plugin/EnhancedJumps.vim: -------------------------------------------------------------------------------- 1 | " EnhancedJumps.vim: Enhanced jump list navigation commands. 2 | " 3 | " DEPENDENCIES: 4 | " - Requires Vim 7.0 or higher. 5 | " - EnhancedJumps.vim autoload script. 6 | " 7 | " Copyright: (C) 2009-2014 Ingo Karkat 8 | " The VIM LICENSE applies to this script; see ':help copyright'. 9 | " 10 | " Maintainer: Ingo Karkat 11 | " 12 | " REVISION DATE REMARKS 13 | " 3.02.003 29-Sep-2014 Add g:EnhancedJumps_CaptureJumpMessages 14 | " configuration to turn off the capturing of the 15 | " messages during the jump, as the used :redir may 16 | " cause errors with another, concurrent capture. 17 | " 3.00.002 08-Feb-2012 Add mappings for jumps to far changes. 18 | " 2.00.001 27-Jun-2009 Split off autoload script. 19 | " file creation 20 | 21 | " Avoid installing twice or when in unsupported Vim version. 22 | if exists('g:loaded_EnhancedJumps') || (v:version < 700) 23 | finish 24 | endif 25 | let g:loaded_EnhancedJumps = 1 26 | 27 | "- configuration -------------------------------------------------------------- 28 | 29 | if ! exists('g:stopFirstAndNotifyTimeoutLen') 30 | let g:stopFirstAndNotifyTimeoutLen = 2000 31 | endif 32 | if ! exists('g:EnhancedJumps_CaptureJumpMessages') 33 | let g:EnhancedJumps_CaptureJumpMessages = 1 34 | endif 35 | 36 | 37 | "- mappings ------------------------------------------------------------------- 38 | 39 | nnoremap EnhancedJumpsOlder :call EnhancedJumps#Jump(0,'') 40 | nnoremap EnhancedJumpsNewer :call EnhancedJumps#Jump(1,'') 41 | nnoremap EnhancedJumpsLocalOlder :call EnhancedJumps#Jump(0,'local') 42 | nnoremap EnhancedJumpsLocalNewer :call EnhancedJumps#Jump(1,'local') 43 | nnoremap EnhancedJumpsRemoteOlder :call EnhancedJumps#Jump(0,'remote') 44 | nnoremap EnhancedJumpsRemoteNewer :call EnhancedJumps#Jump(1,'remote') 45 | 46 | nnoremap EnhancedJumpsFarChangeOlder :call EnhancedJumps#Changes#Jump(0,0) 47 | nnoremap EnhancedJumpsFarChangeNewer :call EnhancedJumps#Changes#Jump(1,0) 48 | nnoremap EnhancedJumpsFarFallbackChangeOlder :call EnhancedJumps#Changes#Jump(0,1) 49 | nnoremap EnhancedJumpsFarFallbackChangeNewer :call EnhancedJumps#Changes#Jump(1,1) 50 | 51 | if ! hasmapto('EnhancedJumpsOlder', 'n') 52 | nmap EnhancedJumpsOlder 53 | endif 54 | if ! hasmapto('EnhancedJumpsNewer', 'n') 55 | nmap EnhancedJumpsNewer 56 | endif 57 | if ! hasmapto('EnhancedJumpsLocalOlder', 'n') 58 | nmap g EnhancedJumpsLocalOlder 59 | endif 60 | if ! hasmapto('EnhancedJumpsLocalNewer', 'n') 61 | nmap g EnhancedJumpsLocalNewer 62 | endif 63 | if ! hasmapto('EnhancedJumpsRemoteOlder', 'n') 64 | nmap EnhancedJumpsRemoteOlder 65 | endif 66 | if ! hasmapto('EnhancedJumpsRemoteNewer', 'n') 67 | nmap EnhancedJumpsRemoteNewer 68 | endif 69 | 70 | if ! hasmapto('EnhancedJumpsFarFallbackChangeOlder', 'n') 71 | nmap g; EnhancedJumpsFarFallbackChangeOlder 72 | endif 73 | if ! hasmapto('EnhancedJumpsFarFallbackChangeNewer', 'n') 74 | nmap g, EnhancedJumpsFarFallbackChangeNewer 75 | endif 76 | 77 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 78 | --------------------------------------------------------------------------------