From 267621eb022f649a68a4104bcd00a3caf1dafc99 Mon Sep 17 00:00:00 2001 From: "Bartlomiej P." Date: Sat, 26 Mar 2011 00:00:00 +0000 Subject: [PATCH] Version 0.49 Many fixes. PJ works now in 2D (whole visible window, not only current line). --- README | 19 +- doc/PreciseJump.txt | 150 ++++++++++++++ plugin/PreciseJump.vim | 443 ++++++++++++++++++++++++++++++----------- 3 files changed, 485 insertions(+), 127 deletions(-) create mode 100644 doc/PreciseJump.txt diff --git a/README b/README index 5c4fd30..aea3811 100644 --- a/README +++ b/README @@ -1,9 +1,9 @@ This is a mirror of http://www.vim.org/scripts/script.php?script_id=3437 -PreciseJump aka '[count] motion' on drugs +PreciseJump - script to ease on-screen motion I find - line-motion + motion not very practical for bigger . I usually don't know exactly how many times I want the motion to be repeated - I just see place on the screen where I want to jump to. @@ -12,14 +12,19 @@ This script is addressing this inconvenience. When the motion key is pressed, possible jump targets are highlighted. You just have to type character that appeared on desired place. -i.e: If you press new '_fx' you get all 'x' characters in the line highlighted and changed to -some unique chars. Pressing the highlighted char will take the cursor to place you want. +i.e: If you press '_fx' you get all 'x' characters that are visible in current window highlighted and changed to some unique chars. Pressing the highlighted char will quickly take the cursor to place you want. For little demo you can see: -http://i258.photobucket.com/albums/hh246/otokan/WATCH_ME.gif + http://i258.photobucket.com/albums/hh246/otokan/PJ_animation.gif -Plugin definines following mapping: _f , _w , _e , _t . +Plugin definines following mappings: +_f - jump to given char in whole window +_F - jump to given char in a line This script is usable right now, but keep in mind that it still is being developed. -I will apriciate any comments. + +If you have any comments, have found bugs, know how to make something better + - let me know. + +Special thanks to: Filip, Adam, Sergey, John, Jim diff --git a/doc/PreciseJump.txt b/doc/PreciseJump.txt new file mode 100644 index 0000000..bb0fd04 --- /dev/null +++ b/doc/PreciseJump.txt @@ -0,0 +1,150 @@ + +============================================================================== +*PreciseJump* - script to ease on-screen motion + +version: 0.49 - 2011-03-26 + +author: Bartlomiej Podolak + + +If you have any comments, have found bugs, know how to make something better + - let me know. + +============================================================================== + +PreciseJump Script to ease on screen motion + +1. Overview |overview| +2. Requirements |requirements| +3. Installation |installation| +4. Configuration |configuration| + +============================================================================== +1. Overview *overview* + +I find + motion +not very practical for bigger . + +I usually don't know exactly how many times I want the motion to be repeated - +I just see place on the screen where I want to jump to. + +This script is addressing this inconvenience. + +When the motion key is pressed, possible jump targets are highlighted. You just +have to type character that appeared on desired place. + +i.e: +If you press new '_fx' you get all 'x' characters on the screen highlighted and +changed to some unique chars. Pressing the highlighted character will take the +cursor to place you want. + +If there are too many possible target jumps on the screen target choosing is +done in two phases - you have to choose the group character first, then - as +targets change - you can select desired jump location. + + +============================================================================== +2. Requirements *requirements* + + - required vim 7.2+ + - Vi-compatible mode must be off + + +============================================================================== +3. Installation *installation* + +Copy PreciseJump.vim file to your plugin directory and PreciseJump.txt to +your documentation directory. + + +============================================================================== +4. Configuration *configuration* + + +====== +4.1 Target characters + +You can choose what characters will be used as jump targets and in which order +they are used. + +Just set g:PreciseJump_target_keys variable. + +It's value should be a string containing all characters that can be used as target +characters. By default its value is as below + +abcdefghijklmnopqrstuwxz123456789[];'\,./ABCDEFGHIJKLMNOPQRSTUWXZ{}:"|<>?!@#$%^&*()_+ + +====== +4.2 Target highlight color + +Variable g:PreciseJump_match_target_hi contains names of highlight group that +will be used to mark jump targets. By default it is the group PreciseJumpTarget +defined in the script (red background, yellow foreground) + + +====== +4.3 PreciseJump invocation + +To invoke PreciseJump you have to call PreciseJump function. + +:call PreciseJump(re, lines_prev, lines_next, vismode) + +where + re - regular expression that will match desired targets + + lines_prev - number - in how many lines above to look for jump targets + -1 means - look as far as visible + + lines_next - number - in how many lines below to look for jump targets + -1 means - look as far as visible + + vismode - 1 if function is called in visual mode, 0 otherwise + +For example: + :call PreciseJump('x', 0, 0, 0) + will highlight all 'x' character in the current line + + :call PreciseJump('\<.', -1, -1, 0) + will highlight all beginning of words on the whole window + + +Some helper functions are defined: + +PreciseJumpF(lines_prev, lines_next, vismode) + asks for a character and treat it as jump target - simulates 'f' command + +PreciseJumpT(lines_prev, lines_next, vismode) + asks for a character and treat it as jump target - simulates 't' command + +PreciseJumpW(lines_prev, lines_next, vismode) + highlights beginning of the words - simulates 'w' command + +PreciseJumpE(lines_prev, lines_next, vismode) + highlights ending of the words - simulates 'e' command + + +You may define your own functions with configuration you find convenient. + +====== +4.4 Mappings + +PreciseJump defines two mappings: + + + _F - calls PreciseJumpF(0, 0, 0) + simulates f command on current line + + _f - calls PreciseJumpF(-1, -1, 0) + simulates f command in whole window + +(personaly I have them mapped to 'F' and 'f' respectively) + +When you are defining visual mode mapping you have to prepend the +function call with key. For example: + + :vmap _f :call PreciseJumpF(-1, -1, 1) + +============================================================================== + +vim:tw=78:ts=8:ft=help:norl: diff --git a/plugin/PreciseJump.vim b/plugin/PreciseJump.vim index 471444d..4872c78 100644 --- a/plugin/PreciseJump.vim +++ b/plugin/PreciseJump.vim @@ -1,168 +1,371 @@ " -" PreciseJump aka '[count] motion' on drugs -" -" required vim 7.2+ +" PreciseJump - script to ease on-screen motion +" version: 0.49 - 2011-03-26 " " author: Bartlomiej Podolak - -" -" TODO: W over multiple lines ? -" TODO: find & highlight only on visible text - not whole line ? -" TODO: customise target markers 'a'..'z' vs. 1 .. 9 vs. most convinient keys -" on keyboard -" TODO: make it work as custom motion (:omap) -" TODO: handle Jump('.') better; maximum numer of marked targets? -" TODO: investigate what happens when user doesn't have highilithasdfasdts on -" TODO: should we search for a match in whole line or starting from cursor -" position - -" TODO: function shouldn't affect undo list -" DONE: make it work on 'not modifable' buffers -" DONE: ctrl-c during Fc execution can leave a mess -" DONE: check if match_rule can contain \\%_l only once " -if exists('s:mdod_loaded') +if exists('g:PreciseJump_loaded') || &cp || version < 702 finish endif -let s:mdod_loaded = 1 +let g:PreciseJump_loaded = 1 + +"{{{xxxxxxxx CONFIGURATION xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +if !exists('g:PreciseJump_target_keys') + let g:PreciseJump_target_keys = '' + let g:PreciseJump_target_keys .= 'abcdefghijklmnopqrstuwxz' + let g:PreciseJump_target_keys .= '123456789' + let g:PreciseJump_target_keys .= "[];'\,./" + let g:PreciseJump_target_keys .= 'ABCDEFGHIJKLMNOPQRSTUWXZ' + let g:PreciseJump_target_keys .= '{}:"|<>?' + let g:PreciseJump_target_keys .= '!@#$%^&*()_+' +endif -if version >= 702 +hi PreciseJumpTarget ctermfg=yellow ctermbg=red cterm=bold gui=bold guibg=Red guifg=yellow -if !exists('g:cmodMatchTargetHi') - let g:cmodMatchTargetHi = 'ErrorMsg' +if !exists('g:PreciseJump_match_target_hi') + let g:PreciseJump_match_target_hi = 'PreciseJumpTarget' endif -if !exists('g:cmodMatchTargetShade') - let g:cmodMatchTargetShade = 'String' + +nmap _F :call PreciseJumpF(0, 0, 0) +vmap _F :call PreciseJumpF(0, 0, 1) +omap _F :call PreciseJumpF(0, 0, 0) + +nmap _f :call PreciseJumpF(-1, -1, 0) +vmap _f :call PreciseJumpF(-1, -1, 1) +omap _f :call PreciseJumpF(-1, -1, 0) + +"nmap _t :call PreciseJumpT(-1, -1, 0) +"vmap _t :call PreciseJumpT(-1, -1, 1) +"omap _t :call PreciseJumpT(-1, -1, 0) +" +"nmap _w :call PreciseJumpW(-1, -1, 0) +"vmap _w :call PreciseJumpW(-1, -1, 1) +"omap _w :call PreciseJumpW(-1, -1, 0) +" +"nmap _e :call PreciseJumpE(-1, -1, 0) +"vmap _e :call PreciseJumpE(-1, -1, 1) +"omap _e :call PreciseJumpE(-1, -1, 0) + +if exists('g:PreciseJump_I_am_brave') + nmap F :call PreciseJumpF(0, 0, 0) + vmap F :call PreciseJumpF(0, 0, 1) + omap F v:call PreciseJumpF(0, 0, 0) + + nmap f :call PreciseJumpF(-1, -1, 0) + vmap f :call PreciseJumpF(-1, -1, 1) + omap f v:call PreciseJumpF(-1, -1, 0) + + nmap t :call PreciseJumpT(-1, -1, 0) + vmap t :call PreciseJumpT(-1, -1, 1) + omap t v:call PreciseJumpT(-1, -1, 0) endif -let g:cmodShade = 0 +" +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +"}}} +"{{{ initialization stuff +let s:index_to_key = split(g:PreciseJump_target_keys, '\zs') +let s:key_to_index = {} + +let index = 0 +for i in s:index_to_key + let s:key_to_index[i] = index + let index += 1 +endfor +"}}} -function! JumpW() - call Jump('\s\@<=[^\t\n ]', 1) +"{{{ motion functions +function! PreciseJumpW(lines_prev, lines_next, vismode) + call PreciseJump('\<.', a:lines_prev, a:lines_next, a:vismode) endfunction -function! JumpE() - call Jump('[^\t\n ][\t\n ]', 1) +function! PreciseJumpE(lines_prev, lines_next, vismode) + call PreciseJump('.\>', a:lines_prev, a:lines_next, a:vismode) endfunction -function! JumpF() +function! PreciseJumpF(lines_prev, lines_next, vismode) let re = nr2char( getchar() ) - call Jump(re, 1) + let re = escape(re, '.$^~') + redraw + call PreciseJump('\C'.re, a:lines_prev, a:lines_next, a:vismode) endfunction -function! JumpT() - let re = '.' . nr2char( getchar() ) - call Jump(re, 1) +function! PreciseJumpT(lines_prev, lines_next, vismode) + let re = escape( nr2char(getchar()), '.$^~') + let re = '.' . re + redraw + call PreciseJump('\C'.re, a:lines_prev, a:lines_next, a:vismode) endfunction +"}}} -" jump to n-th char in current line -function! JumpToChar(n) +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +" jump to l-th line and c-th column +"{{{ function! s:JumpToCoords(l, c, vismode) +function! s:JumpToCoords(l, c, vismode) let ve = &virtualedit setl virtualedit="" + if a:vismode + execute "normal! gv" + endif + execute "normal! " . a:l . "gg" normal! "0|" - let n = a:n - 1 - execute "normal! " . n . "l" + if a:c > 1 + execute "normal! " . (a:c - 1) . "l" + endif execute "silent setl virtualedit=" . ve + echo "Jumping to [" . a:l . ", " . a:c . "]" endfunction +"}}} -" return lists of targets where re matches in line -function! FindTargets(re, line, start_column) +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +" function returns list of places ([line, column]), +" that match regular expression 're' +" in lines of numbers from list 'line_numbers' +"{{{ function! s:FindTargets(re, line_numbers) +function! s:FindTargets(re, line_numbers) let targets = [] - let column = match(a:line, a:re, a:start_column) - while column != -1 - call add(targets, column + 1) - let column = match(a:line, a:re, column + 1) - endwhile + for l in a:line_numbers + let n = 1 + let match_start = match(getline(l), a:re, 0, 1) + while match_start != -1 + call add(targets, [l, match_start + 1]) + let n += 1 + let match_start = match(getline(l), a:re, 0, n) + endwhile + endfor return targets endfunction +"}}} -function! Jump(re, ...) - let targets = {} - let start_ch = char2nr('a') - let re = a:re +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +" split 'list' into groups (list of lists) of +" 'group_size' length +"{{{ function! s:SplitListIntoGroups(list, group_size) +function! s:SplitListIntoGroups(list, group_size) + let groups = [] + let i = 0 + while i < len(a:list) + call add(groups, a:list[i : i + a:group_size - 1]) + let i += a:group_size + endwhile + return groups +endfunction +"}}} - let org_line = getline('.') - let line_arr = split(org_line, '\zs') - let iter_count = 0 - let start_column = ( a:0 > 0 ? col('.') : 0 ) "FIXME +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +"{{{ function! s:GetLinesFromCoordList(list) +function! s:GetLinesFromCoordList(list) + let lines_seen = {} + let lines_no = [] + for [l, c] in a:list + if !has_key(lines_seen, l) + call add(lines_no, l) + let lines_seen[l] = 1 + endif + endfor + return lines_no +endfunction +"}}} - let targets_list = FindTargets(re, org_line, start_column) +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +"{{{ function! s:CreateHighlightRegex(coords) +function! s:CreateHighlightRegex(coords) + let tmp = [] + for [l, c] in s:Flatten(a:coords) + call add(tmp, '\%' . l . 'l\%' . c . 'c') + endfor + return join(tmp, '\|') +endfunction +"}}} - " create targets hash + line with markers - for t in targets_list - let targets[ nr2char(start_ch + iter_count) ] = t - let line_arr[t - 1] = nr2char(start_ch + iter_count) - let iter_count += 1 +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +"{{{ function! s:Flatten(list) +function! s:Flatten(list) + let res = [] + for elem in a:list + call extend(res, elem) endfor + return res +endfunction +"}}} - if len(targets) == 1 - call JumpToChar(targets[nr2char(start_ch)]) - elseif len(targets) > 0 - let match_rule = join( map( values(targets), ' "\\%" . v:val . "c" ' ), '\|' ) - let match_rule = '\%' . line('.') . 'l\(' . match_rule . '\)' +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +" get a list of coordinates groups [ [ [1,2], [2,5] ], [ [2,2] ] ] +" get a list of coordinates groups [ [ [1,2], [2,5] ] ] +"{{{ function! s:AskForTarget(group) +function! s:AskForTarget(groups) abort + let single_group = ( len(a:groups) == 1 ? 1 : 0 ) - " fun - if g:cmodShade - let shade_rule = '\%<' . line('.') . 'l\|\%>' . line('.') . 'l' + " how many targets there is + let targets_count = single_group ? len(a:groups[0]) : len(a:groups) + + if single_group && targets_count == 1 + return a:groups[0][0] + endif + + " which lines need to be changed + let lines = s:GetLinesFromCoordList(s:Flatten(a:groups)) + + " creating copy of lines to be changed + let lines_with_markers = {} + for l in lines + let lines_with_markers[l] = split(getline(l), '\zs') + endfor + + " adding markers to lines + let gr = 0 " group no + for group in a:groups + let el = 0 " element in group no + for [l, c] in group + " highlighting with group mark or target mark + let lines_with_markers[l][c - 1] = s:index_to_key[ single_group ? el : gr ] + let el += 1 + endfor + let gr += 1 + endfor + + " create highlight + let hi_regex = s:CreateHighlightRegex(a:groups) + + " + let user_char = '' + let modifiable = &modifiable + let readonly = &readonly + + try + let match_id = matchadd(g:PreciseJump_match_target_hi, hi_regex, -1) + if modifiable == 0 + silent setl modifiable + endif + if readonly == 1 + silent setl noreadonly endif - let c = '' - let modifiable = &modifiable - let readonly = &readonly - try - let match_id = matchadd(g:cmodMatchTargetHi, match_rule, -1) - if g:cmodShade - let shade_id = matchadd(g:cmodMatchTargetShade, shade_rule, -1) - endif - if modifiable == 0 - silent setl modifiable - endif - if readonly == 1 - silent setl noreadonly - endif - call setline('.', join(line_arr, '')) - redraw - echo "Fc(" . re . ") [" . join( keys(targets), '') . "] ?" - let c = nr2char( getchar() ) - finally - "call setline('.', org_line) - silent normal! u - call matchdelete(match_id) - if g:cmodShade - call matchdelete(shade_id) - endif - if c != '' && has_key(targets, c) - call JumpToChar(targets[c]) - endif - if modifiable == 0 - silent setl nomodifiable - endif - if readonly == 1 - silent setl readonly + for [lnum, line_arr] in items(lines_with_markers) + call setline(lnum, join(line_arr, '')) + endfor + redraw + if single_group + echo "target char>" + else + echo "group char>" + endif + let user_char = nr2char( getchar() ) + redraw + finally + normal! u + normal  + + call matchdelete(match_id) + redraw + if modifiable == 0 + silent setl nomodifiable + endif + if readonly == 1 + silent setl readonly + endif + + if ! has_key(s:key_to_index, user_char) || s:key_to_index[user_char] >= targets_count + return [] + else + if single_group + if ! has_key(s:key_to_index, user_char) + return [] + else + return a:groups[0][ s:key_to_index[user_char] ] " returning coordinates + endif + else + return s:AskForTarget( [ a:groups[ s:key_to_index[user_char] ] ] ) endif - endtry - else - endif - echo + endif + endtry +endfunction +"}}} + +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +"{{{ function! s:LinesAllSequential() +function! s:LinesAllSequential() + return filter( range(line('w0'), line('w$')), 'foldclosed(v:val) == -1' ) +endfunction +" }}} + +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +"{{{ function! s:LinesSurrondingAll(a:surrounding_lines) +function! s:LinesSurrondingAll(surrounding_lines) + let cur_line = line('.') + let line_numbers = [ cur_line ] + let i = 1 + while 1 + let leave = 1 + if cur_line - i >= line('w0') + call add(line_numbers, cur_line - i) + let leave = 0 + endif + + if cur_line + i <= line('w$') + call add(line_numbers, cur_line + i) + let leave = 0 + endif + + if leave + break + endif + let i += 1 + endwhile + return line_numbers +endfunction +"}}} + +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +"{{{ function! s:LinesInRange(lines_prev, lines_next) +function! s:LinesInRange(lines_prev, lines_next) + let all_lines = filter( range(line('w0'), line('w$')), 'foldclosed(v:val) == -1' ) + let current = index(all_lines, line('.')) + + let lines_prev = a:lines_prev == -1 ? current : a:lines_prev + let lines_next = a:lines_next == -1 ? len(all_lines) : a:lines_next + + let lines_prev_i = max( [0, current - lines_prev] ) + let lines_next_i = min( [len(all_lines), current + lines_next] ) + + return all_lines[ lines_prev_i : lines_next_i ] endfunction +"}}} -nmap _f :call JumpF() -nmap _w :call JumpW() -nmap _e :call JumpE() -nmap _t :call JumpT() +"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +"{{{ function! PreciseJump() +function! PreciseJump(re, lines_prev, lines_next, vismode) + let group_size = len(s:index_to_key) + let lnums = s:LinesInRange(a:lines_prev, a:lines_next) -" Are you brave enough to uncomment those? -"nmap f :call JumpF() -"nmap w :call JumpW() -"nmap e :call JumpE() -"nmap t :call JumpT() + let targets = s:FindTargets(a:re, lnums) + if len(targets) == 0 + echo "No targets found" + return + endif + + let groups = s:SplitListIntoGroups( targets, group_size ) + + " too many targets; showing only first ones + if len(groups) > group_size + echo "Showing only first targets" + let groups = groups[0 : group_size - 1] + endif + + let coords = s:AskForTarget(groups) + + if len(coords) != 2 + echo "Cancelled" + return + else + call s:JumpToCoords(coords[0], coords[1], a:vismode) + endif +endfunction +"}}} -"" !! EXPERIMENTAL !! -" omap _f :call JumpF() -" omap _w :call JumpW() -" omap _t :call JumpT() +finish -endif " if version >= 702