├── CHECKLIST.md ├── README.md ├── Rakefile ├── VERSION ├── autoload ├── textmanip.vim └── textmanip │ ├── area.vim │ ├── helper.vim │ ├── options.vim │ ├── pos.vim │ ├── register.vim │ └── util.vim ├── doc └── textmanip.txt └── plugin └── textmanip.vim /CHECKLIST.md: -------------------------------------------------------------------------------- 1 | CeckList: 2 | ===================== 3 | restore original vim options 4 | restore original visual mode 5 | restore original cursor pos including where 'o'posit pos in visual mode. 6 | count aware 7 | undoable for continuous move by one 'undo' command. 8 | care when move across corner( TOF,EOF, BOL, EOL ) 9 | - by adjusting cursor to appropriate value 10 | u => TOF 11 | d => EOF 12 | r => EOL(but be care this!) 13 | l => BOF 14 | 15 | Supported: [O: Finish][X: Not Yet][P: Partially impremented] 16 | * normal_mode: 17 | [O] duplicate line to above, below 18 | 19 | * visual_line('V', or multiline 'v': 20 | [O] duplicate line to above, below 21 | [O] move righ/left 22 | [O] undoable/count 23 | 24 | * visual_block(C-v): 25 | [O] move selected block to up/down/right/left. 26 | ( but not multibyte char aware ). 27 | [X] count support, not undoable 28 | 29 | Test 30 | ================================== 31 | 111111|BBBBBB|111111 32 | 000000|AAAAAA|000000 33 | 666665|FFFFFF|666666 34 | 777777|CCCCCC|777777 35 | 888888|DDDDDD|888888 36 | 222222|000000|222222 37 | 555556|000000|555555 38 | 333333|000000|333333 39 | 444444|000000|444444 40 | 000000|000000|000000 41 | 111111|000000|111111 42 | 333333|NNNNNN|333333 43 | 444444|OOOOOO|444444 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Move/Duplicate text intuitively. 2 | * Move selected lines or block area to specified direction ( up/down/right/left ). 3 | * Duplicate selected lines or block to specified direction ( up/down/right/left ). 4 | * Two mode: inesrt or replace 5 | * Count support 6 | * Keep original cursor position (include 'o'ther pos in visualmode!) while moving / duplicating. 7 | * Undo with one 'u' by undojoining. 8 | 9 | ![Example](https://github.com/t9md/t9md/blob/master/img/vim-textmanip_anime.gif?raw=true) 10 | ### [help](https://github.com/t9md/vim-textmanip/blob/master/doc/textmanip.txt) 11 | 12 | # Configuration example 13 | 14 | ### GUI macvim ( which I use now ) 15 | 16 | ```Vim 17 | xmap (textmanip-duplicate-down) 18 | nmap (textmanip-duplicate-down) 19 | xmap (textmanip-duplicate-up) 20 | nmap (textmanip-duplicate-up) 21 | 22 | xmap (textmanip-move-down) 23 | xmap (textmanip-move-up) 24 | xmap (textmanip-move-left) 25 | xmap (textmanip-move-right) 26 | 27 | " toggle insert/replace with 28 | nmap (textmanip-toggle-mode) 29 | xmap (textmanip-toggle-mode) 30 | 31 | " use allow key to force replace movement 32 | xmap (textmanip-move-up-r) 33 | xmap (textmanip-move-down-r) 34 | xmap (textmanip-move-left-r) 35 | xmap (textmanip-move-right-r) 36 | ``` 37 | 38 | ### gVim 39 | 40 | ```Vim 41 | xmap (textmanip-duplicate-down) 42 | nmap (textmanip-duplicate-down) 43 | xmap (textmanip-duplicate-up) 44 | nmap (textmanip-duplicate-up) 45 | 46 | xmap (textmanip-move-down) 47 | xmap (textmanip-move-up) 48 | xmap (textmanip-move-left) 49 | xmap (textmanip-move-right) 50 | 51 | " toggle insert/replace with 52 | nmap (textmanip-toggle-mode) 53 | xmap (textmanip-toggle-mode) 54 | 55 | " use allow key to force replace movement 56 | xmap (textmanip-move-up-r) 57 | xmap (textmanip-move-down-r) 58 | xmap (textmanip-move-left-r) 59 | xmap (textmanip-move-right-r) 60 | ``` 61 | 62 | ### vim on terminal 63 | 64 | ```Vim 65 | xmap d (textmanip-duplicate-down) 66 | nmap d (textmanip-duplicate-down) 67 | xmap D (textmanip-duplicate-up) 68 | nmap D (textmanip-duplicate-up) 69 | 70 | xmap (textmanip-move-down) 71 | xmap (textmanip-move-up) 72 | xmap (textmanip-move-left) 73 | xmap (textmanip-move-right) 74 | 75 | " toggle insert/replace with 76 | nmap (textmanip-toggle-mode) 77 | xmap (textmanip-toggle-mode) 78 | ``` 79 | 80 | ### keymap advanced macvim (this is my current configuration). 81 | 82 | ```Vim 83 | nmap (textmanip-blank-below) 84 | nmap (textmanip-blank-above) 85 | xmap (textmanip-blank-below) 86 | xmap (textmanip-blank-above) 87 | 88 | nmap (textmanip-duplicate-up) 89 | nmap (textmanip-duplicate-down) 90 | xmap (textmanip-duplicate-up) 91 | xmap (textmanip-duplicate-down) 92 | 93 | xmap (textmanip-move-up) 94 | xmap (textmanip-move-down) 95 | xmap (textmanip-move-left) 96 | xmap (textmanip-move-right) 97 | 98 | xmap (textmanip-duplicate-up) 99 | xmap (textmanip-duplicate-down) 100 | xmap (textmanip-duplicate-left) 101 | xmap (textmanip-duplicate-right) 102 | 103 | xmap (textmanip-move-up-r) 104 | xmap (textmanip-move-down-r) 105 | xmap (textmanip-move-left-r) 106 | xmap (textmanip-move-right-r) 107 | 108 | nmap (textmanip-toggle-mode) 109 | xmap (textmanip-toggle-mode) 110 | ``` 111 | 112 | # Experimental hook/helper 113 | 114 | Currently only `finish` hook point is supported. 115 | `finish` is called just before manipulation finish. 116 | If you want to additional text manipulation, you can start from following example. 117 | The `hook` must accept one argument(`tm` in this example), which is texmanip app instance itself. 118 | 119 | ```vim 120 | let g:textmanip_hooks = {} 121 | function! g:textmanip_hooks.finish(tm) 122 | let tm = a:tm 123 | let helper = textmanip#helper#get() 124 | if tm.linewise 125 | " if filetype is `html` automatically indent 126 | if &ft ==# 'html' 127 | call helper.indent(tm) 128 | endif 129 | else 130 | " When blockwise move/duplicate, remove trailing white space. 131 | " To use this feature without feeling counterintuitive, 132 | " I recommend you to ':set virtualedit=block', 133 | call helper.remove_trailing_WS(tm) 134 | endif 135 | endfunction 136 | ``` 137 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc "zip" 2 | task :zip do 3 | version = File.read("VERSION").chomp 4 | dirname = File.basename( File.dirname(File.expand_path(__FILE__))) 5 | zipname = "#{dirname}-#{version}.zip" 6 | sh "zip -r #{zipname} README.md autoload doc plugin -x doc/tags" 7 | end 8 | 9 | desc "release" 10 | task :release => [:tag, :zip] 11 | 12 | desc "tag" 13 | task :tag do 14 | version = File.read("VERSION").chomp 15 | ver_s = "v#{version}" 16 | sh "git tag -a #{ver_s} -m '#{ver_s}'" 17 | sh "git push -u origin master #{ver_s}" 18 | end 19 | 20 | desc "versub" 21 | task :versub do 22 | version = File.read("VERSION").chomp 23 | files = Dir.glob('{doc,autoload,plugin}/**').select do |f| 24 | File.file? f 25 | end 26 | files.delete('doc/tags') 27 | files.each do |fname| 28 | lines = File.readlines(fname) 29 | lines.map! { |l| l.sub(/Version: (.*)/, "Version: #{version}") } 30 | File.open(fname,'w') {|f| f.puts lines } 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.5 2 | -------------------------------------------------------------------------------- /autoload/textmanip.vim: -------------------------------------------------------------------------------- 1 | " Util: 2 | let s:_ = textmanip#util#get() 3 | 4 | function! s:getpos(mode) "{{{1 5 | if a:mode ==# 'n' 6 | let s = getpos('.') 7 | return [s, s] 8 | endif 9 | exe 'normal! gvo' | let s = getpos('.') | exe "normal! \" 10 | exe 'normal! gvo' | let e = getpos('.') | exe "normal! \" 11 | return [s, e] 12 | endfunction 13 | "}}} 14 | 15 | " Main: 16 | let s:TM = {} 17 | 18 | function! s:TM.start(action, dir, mode, emode) "{{{1 19 | if &modifiable == 0 20 | return 21 | endif 22 | try 23 | let opts = { '&virtualedit': 'all' } 24 | if a:action ==# 'move1' 25 | let opts['&shiftwidth'] = 1 26 | elseif g:textmanip_move_ignore_shiftwidth 27 | let opts['&shiftwidth'] = g:textmanip_move_shiftwidth 28 | endif 29 | let options = textmanip#options#replace(opts) 30 | 31 | let env = { 32 | \ "action": a:action ==# 'move1' ? 'move' : a:action, 33 | \ "dir": a:dir, 34 | \ "mode": a:mode ==# 'x' ? visualmode() : a:mode, 35 | \ "emode": (a:emode ==# 'auto') ? g:textmanip_current_mode : a:emode, 36 | \ "count": v:count1, 37 | \ } 38 | call self.init(env) 39 | call self.manip() 40 | 41 | catch /STOP/ 42 | if g:textmanip_debug 43 | " call Plog(v:exception) 44 | endif 45 | catch /FINISH/ 46 | finally 47 | call self.register.restore() 48 | call options.restore() 49 | endtry 50 | endfunction 51 | 52 | function! s:TM.init(env) "{{{1 53 | let [_s, _e] = s:getpos(a:env.mode) 54 | let s = textmanip#pos#new(_s) 55 | let e = textmanip#pos#new(_e) 56 | let self.env = a:env 57 | let self.toward = s:_.toward(self.env.dir) 58 | let self.register = textmanip#register#use('x') 59 | 60 | " [ 1 ] [ 2 ] [ 3 ] [ 4 ] 61 | " s----+----+ e----+----+ +----+----s +----+----e 62 | " | | | | | | | | | | | | 63 | " +----+----+ +----+----+ +----+----+ +----+----+ 64 | " | | | | | | | | | | | | 65 | " +----+----e +----+----s e----+----+ s----+----+ 66 | " 67 | let [T, B, L, R] = 68 | \ 1 && (s.line <= e.line) && (s.colm <= e.colm) ? [ s, e, s, e ] : 69 | \ 2 && (s.line >= e.line) && (s.colm >= e.colm) ? [ e, s, e, s ] : 70 | \ 3 && (s.line <= e.line) && (s.colm >= e.colm) ? [ s, e, e, s ] : 71 | \ 4 && (s.line >= e.line) && (s.colm <= e.colm) ? [ e, s, s, e ] : 72 | \ throw 73 | let self.pos = { 'S': s, 'E': e, '^': T, 'v': B, '<': L, '>': R, } 74 | 75 | " Preserve original height and width 76 | let self.height = self.pos['v'].line - self.pos['^'].line + 1 77 | let self.width = self.pos['>'].colm - self.pos['<'].colm + 1 78 | let self.linewise = self.is_linewise() 79 | let self.continuous = get(b:, "textmanip_status", {}) == self.state() 80 | return self 81 | endfunction 82 | 83 | function! s:TM.manip() "{{{1 84 | call self[self.env.action]() 85 | endfunction 86 | 87 | function! s:TM.is_linewise() "{{{1 88 | return 89 | \ (self.env.mode ==# 'V' ) || 90 | \ (self.env.mode ==# 'n' ) || 91 | \ (self.env.mode ==# 'v' && self.height > 1) 92 | endfunction 93 | 94 | function! s:TM.finish(...) "{{{1 95 | call call(self.select, a:000, self) 96 | 97 | if exists('*g:textmanip_hooks.finish') 98 | call g:textmanip_hooks['finish'](self) 99 | endif 100 | 101 | if self.env.action ==# 'move' 102 | let b:textmanip_status = self.state() 103 | endif 104 | 105 | if self.env.mode ==# 'v' 106 | execute 'normal! v' 107 | throw 'FINISH' 108 | endif 109 | 110 | if self.env.mode ==# 'n' 111 | execute "normal! \" 112 | if self.env.action ==# 'duplicate' 113 | call self.pos[self.env.dir].set_cursor() 114 | endif 115 | endif 116 | 117 | throw 'FINISH' 118 | endfunction 119 | 120 | function! s:TM.stop(desc, expr) "{{{1 121 | if !a:expr 122 | return 123 | endif 124 | if self.env.mode !=# 'n' 125 | normal! gv 126 | endif 127 | throw 'STOP: ' . a:desc 128 | endfunction 129 | 130 | function! s:TM.select(...) "{{{1 131 | if a:0 132 | call call(self.move_pos, a:000, self) 133 | endif 134 | silent execute "normal! \" 135 | call self.pos.S.set_cursor() 136 | execute 'normal! ' . (self.linewise ? 'V' : "\") 137 | call self.pos.E.set_cursor() 138 | return self 139 | endfunction 140 | 141 | function! s:TM.yank(...) "{{{1 142 | call call(self.select, a:000, self) 143 | call self.register.yank() 144 | return self 145 | endfunction 146 | 147 | function! s:TM.paste(...) "{{{1 148 | call call(self.select, a:000, self) 149 | call self.register.paste() 150 | return self 151 | endfunction 152 | 153 | function! s:TM.modify() "{{{1 154 | let action = self.env.action ==# 'move' ? 'rotate' : self.env.action 155 | let args = [self.register.content] 156 | 157 | if action ==# 'rotate' && self.env.emode ==# 'replace' 158 | if ! self.continuous 159 | let initial = self.linewise ? [''] : [repeat(' ', self.width)] 160 | let b:textmanip_replaced = textmanip#area#new(repeat(initial, self.height)) 161 | endif 162 | let args += [b:textmanip_replaced] 163 | endif 164 | let self.register.content = 165 | \ call('textmanip#area#new', args)[action](self.env.dir, self.env.count).data() 166 | return self 167 | endfunction 168 | 169 | function! s:TM.move_pos(opes) "{{{1 170 | let vars = { 'c': self.env.count, 'h': self.height, 'w': self.width, 'SW': &sw } 171 | for ope in s:_.toList(a:opes) 172 | let pos = ope[0] 173 | let _ope = split(ope[1:], '\v\s*:\s*', 1) 174 | call map(_ope, 's:_.template(v:val, vars)') 175 | call self.pos[pos].move(_ope) 176 | endfor 177 | return self 178 | endfunction 179 | 180 | function! s:TM.insert_blank(dir, num) "{{{1 181 | let where = { 182 | \ '^': self.pos['^'].line-1, 'v': self.pos['v'].line, 183 | \ '<': self.pos['<'].colm-1, '>': self.pos['>'].colm, 184 | \ }[a:dir] 185 | if self.toward ==# '^v' 186 | call append(where, map(range(a:num), '""')) 187 | else 188 | let lines = map(getline(self.pos['^'].line, self.pos['v'].line), 189 | \ 'v:val[0 : where-1] . repeat(" ", a:num) . v:val[ where : ]') 190 | call setline(self.pos['^'].line, lines) 191 | endif 192 | return self 193 | endfunction 194 | 195 | function! s:TM.state() "{{{1 196 | " should not depend current visual selction to keep selection state 197 | " unchanged. So need to extract rectangle region from colum. 198 | let content = getline(self.pos['^'].line, self.pos['v'].line) 199 | if !self.linewise 200 | call map(content, 'v:val[ self.pos["<"].colm - 1 : self.pos[">"].colm - 1]') 201 | endif 202 | return { 203 | \ 'emode': self.env.emode, 204 | \ 'line_top': self.pos['^'].line, 205 | \ 'line_bottom': self.pos['v'].line, 206 | \ 'len': len(content), 207 | \ 'content': content, 208 | \ } 209 | endfunction 210 | "}}} 211 | 212 | " Action: 213 | function! s:TM.move() "{{{1 214 | let [dir, c, emode] = [self.env.dir, self.env.count, self.env.emode] 215 | 216 | if dir ==# '^' 217 | call self.stop('Topmost line', self.pos['^'].line ==# 1) 218 | let self.env.count = min([self.pos['^'].line - 1 , c]) 219 | endif 220 | if dir ==# '<' 221 | if self.linewise 222 | let content = self.yank().register.content 223 | call self.stop('No empty space to <', empty(filter(content, "v:val =~# '^\\s'"))) 224 | else 225 | call self.stop('Leftmost cursor', self.pos['<'].colm ==# 1) 226 | let self.env.count = min([self.pos['<'].colm - 1 , c]) 227 | endif 228 | endif 229 | 230 | if self.continuous 231 | silent! undojoin 232 | endif 233 | 234 | if self.toward ==# '<>' && self.linewise 235 | " a:dir is '<' or '>', yes its valid Vim operator! so I can pass as-is 236 | execute self.pos['^'].line . ',' . self.pos['v'].line . repeat(dir,c) 237 | let _last = { 238 | \ '>': ['^ :+(SW * c)', 'v :+(SW * c)'], 239 | \ '<': ['^ :-(SW * c)', 'v :-(SW * c)'], 240 | \ }[dir] 241 | call self.finish(_last) 242 | endif 243 | 244 | if dir ==# 'v' " Extend EOF if needed 245 | let amount = (self.pos['v'].line + c) - line('$') 246 | if amount > 0 247 | call append(line('$'), map(range(amount), '""')) 248 | endif 249 | endif 250 | 251 | let [ _yank, _last ] = { 252 | \ "^": ['^ -c: ', 'v -c: '], 253 | \ "v": ['v +c: ', '^ +c: '], 254 | \ ">": ['> :+c', '< :+c'], 255 | \ "<": ['< :-c', '> :-c'], 256 | \ }[dir] 257 | call self 258 | \.yank(_yank).modify().paste().finish(_last) 259 | endfunction 260 | 261 | function! s:TM.duplicate() "{{{1 262 | let action = 'duplicate' 263 | let [dir, c, emode] = [self.env.dir, self.env.count, self.env.emode] 264 | call self.yank() 265 | 266 | if self.toward ==# '<>' && self.linewise 267 | let self.env.count += 1 268 | call self.modify().paste().finish() 269 | endif 270 | 271 | if emode ==# 'insert' 272 | call self.insert_blank(dir, self[self.toward ==# '^v' ? 'height' : 'width'] * c) 273 | let _paste = { 274 | \ "^": ['v +h*(c-1): ' ], 275 | \ "v": ['^ +h : ', 'v +(h*c): '], 276 | \ ">": ['< :+w ', '> :+(w*c)'], 277 | \ "<": ['> :+w*(c-1)' ], 278 | \ }[dir] 279 | else 280 | " replace 281 | if dir ==# '^' 282 | call self.stop('No enough space to duplicate to ^', self.pos['^'].line - 1 < self.height) 283 | let self.env.count = min([ (self.pos['^'].line - 1) / self.height, c ]) 284 | elseif dir ==# '<' 285 | call self.stop('No enough space to duplicate to <', self.pos['<'].colm - 1 < self.width) 286 | let self.env.count = min([ (self.pos['<'].colm - 1) / self.width, c ]) 287 | endif 288 | 289 | let _paste = { 290 | \ "^": ['^ -(h*c): ', 'v -h : '], 291 | \ "v": ['^ +h : ', 'v +(h*c): '], 292 | \ ">": ['< :+w', '> :+(w*c)'], 293 | \ "<": ['> :-w', '< :-(w*c)'], 294 | \ }[dir] 295 | endif 296 | call self.modify().paste(_paste).finish() 297 | endfunction 298 | 299 | function! s:TM.blank() "{{{1 300 | call self.insert_blank(self.env.dir, self.env.count) 301 | " simpley 'normal! gv' is enough tough, I choose, 302 | " instruction and select() pattern to be consistent 303 | " to other action. 304 | let _last = { 305 | \ "^": [ '^ +c:', 'v +c:'], 306 | \ "v": [ 'v : '], 307 | \ }[self.env.dir] 308 | call self.finish(_last) 309 | endfunction 310 | "}}} 311 | 312 | " API: 313 | function! textmanip#start(...) "{{{1 314 | return call(s:TM.start, a:000, s:TM) 315 | endfunction 316 | 317 | function! textmanip#mode(...) "{{{1 318 | if a:0 ==# 0 319 | return g:textmanip_current_mode 320 | endif 321 | 322 | let g:textmanip_current_mode = 323 | \ g:textmanip_current_mode ==# 'insert' ? 'replace' : 'insert' 324 | echo "textmanip-mode: " . g:textmanip_current_mode 325 | endfunction 326 | "}}} 327 | " vim: foldmethod=marker 328 | -------------------------------------------------------------------------------- /autoload/textmanip/area.vim: -------------------------------------------------------------------------------- 1 | let s:_ = textmanip#util#get() 2 | 3 | function! s:height(val) "{{{1 4 | return len(a:val) 5 | endfunction 6 | 7 | function! s:width(val) "{{{1 8 | return len(a:val[0]) 9 | endfunction 10 | "}}} 11 | 12 | let s:Area = {} 13 | function! s:Area.new(data, ...) "{{{1 14 | " data is array of string 15 | " ex) [ 'string1', 'string2'...] 16 | " 17 | " and `_data` is only state this object keep. 18 | " So we dont' need deepcopy, shallow copy is ok here. 19 | let o = copy(self) 20 | let o._data = a:data 21 | if a:0 22 | let o._overlapped = a:1 23 | endif 24 | return o 25 | endfunction 26 | 27 | function! s:Area.data(...) "{{{1 28 | if a:0 29 | let self._data = a:1 30 | endif 31 | return self._data 32 | endfunction 33 | 34 | function! s:Area.height() "{{{1 35 | return len(self.data()) 36 | endfunction 37 | 38 | function! s:Area.is_empty() "{{{1 39 | return empty(self.data()) 40 | endfunction 41 | 42 | function! s:Area.width() "{{{1 43 | " assume all data have same width, so this function is useless! 44 | return len(self.data()[0]) 45 | endfunction 46 | 47 | function! s:Area.dump() "{{{1 48 | return PP(self.data()) 49 | endfunction 50 | 51 | " add 52 | function! s:Area.add(dir, val) "{{{1 53 | let lis = type(a:val) ==# 3 ? a:val : [a:val] 54 | 55 | if a:dir ==# '^' 56 | call self.data(lis + self.data()) 57 | return self 58 | endif 59 | 60 | if a:dir ==# 'v' 61 | call self.data(self.data() + lis) 62 | return self 63 | endif 64 | 65 | if self.is_empty() 66 | call self.data(lis) 67 | return self 68 | endif 69 | 70 | if a:dir ==# '<' 71 | cal map(self.data(), 'lis[v:key] . v:val') 72 | return self 73 | endif 74 | 75 | if a:dir ==# '>' 76 | call map(self.data(), 'v:val . lis[v:key]') 77 | return self 78 | endif 79 | 80 | throw 'never happen!' 81 | endfunction 82 | 83 | 84 | " cut 85 | function! s:Area.cut(dir, n) "{{{1 86 | " n: number of cut 87 | 88 | if a:dir ==# '^' 89 | let end = min([a:n, self.height()]) - 1 90 | return remove(self.data(), 0, end) 91 | endif 92 | 93 | if a:dir ==# 'v' 94 | let last = self.height() 95 | return remove(self.data(), last-a:n, last-1) 96 | endif 97 | 98 | if a:dir ==# '<' 99 | let R = map(copy(self.data()), 'v:val[ : a:n-1]') 100 | call map(self.data(), 'v:val[a:n :]') 101 | return R 102 | endif 103 | 104 | if a:dir ==# '>' 105 | let R = map(copy(self.data()), 'v:val[-a:n : -1]') 106 | call map(self.data(), 'v:val[:-a:n-1]') 107 | return R 108 | endif 109 | 110 | throw 'never happen!' 111 | endfunction 112 | 113 | " swap 114 | function! s:Area.swap(dir, val) "{{{1 115 | let n = s:_.toward(a:dir) ==# '^v' ? s:height(a:val) : s:width(a:val) 116 | let R = self.cut(a:dir, n) 117 | call self.add(a:dir, a:val) 118 | return R 119 | endfunction 120 | 121 | " pushout 122 | function! s:Area.pushout(dir, val) "{{{1 123 | let n = s:_.toward(a:dir) ==# '^v' ? s:height(a:val) : s:width(a:val) 124 | call self.add(a:dir, a:val) 125 | return self.cut(s:_.opposite(a:dir), n) 126 | endfunction 127 | 128 | " rotate 129 | function! s:Area.rotate(dir, n) 130 | let _cut = self.cut(a:dir, a:n) 131 | 132 | if has_key(self, '_overlapped') 133 | let _cut = self._overlapped.pushout(a:dir, _cut) 134 | endif 135 | 136 | call self.add(s:_.opposite(a:dir), _cut) 137 | return self 138 | endfunction 139 | 140 | " duplcate vertical/horizontal(=side) 141 | function! s:Area.duplicate(dir, n) "{{{1 142 | if a:dir =~# '\^\|v' " vertical 143 | call self.data(repeat(self.data(), a:n)) 144 | return self 145 | endif 146 | 147 | if a:dir =~# '>\|<' " horizontal 148 | " horizontal, map have side effect, so no need to updata with data() 149 | call map(self.data(), 'repeat(v:val, a:n)') 150 | return self 151 | endif 152 | throw 'never happen!' 153 | endfunction 154 | "}}} 155 | 156 | " API: 157 | function! textmanip#area#new(...) "{{{1 158 | return call(s:Area.new, a:000, s:Area) 159 | endfunction 160 | "}}} 161 | " vim: foldmethod=marker 162 | -------------------------------------------------------------------------------- /autoload/textmanip/helper.vim: -------------------------------------------------------------------------------- 1 | let s:helper = {} 2 | 3 | function! s:helper.indent(tm) 4 | normal! = 5 | call a:tm.select() 6 | endfunction 7 | 8 | function! s:helper.remove_trailing_WS(tm) 9 | let tm = a:tm 10 | let [line_s, line_e] = [tm.pos['^'].line, tm.pos['v'].line] 11 | if !tm.linewise 12 | let cmd = printf('%d,%ds!\v\s+$!!', line_s, line_e) 13 | silent! execute cmd 14 | endif 15 | call a:tm.select() 16 | endfunction 17 | 18 | function! textmanip#helper#get() 19 | return s:helper 20 | endfunction 21 | -------------------------------------------------------------------------------- /autoload/textmanip/options.vim: -------------------------------------------------------------------------------- 1 | let s:Options = {} 2 | 3 | function! s:Options.new() "{{{1 4 | let self._opts = {} 5 | return copy(self) 6 | endfunction 7 | 8 | function! s:Options.replace(opts) "{{{1 9 | let curbuf = fnameescape(bufname('')) 10 | for [name, val] in items(a:opts) 11 | let self._opts[name] = getbufvar(curbuf, name) 12 | call setbufvar(curbuf, name, val) 13 | endfor 14 | return self 15 | endfunction 16 | 17 | function! s:Options.restore() "{{{1 18 | for [name, val] in items(self._opts) 19 | call setbufvar(fnameescape(bufname('')), name, val) 20 | endfor 21 | let self._opts = {} 22 | return self 23 | endfunction 24 | "}}} 25 | 26 | " Api: 27 | function! textmanip#options#replace(...) "{{{1 28 | let opts = s:Options.new() 29 | return call(opts.replace, a:000, opts) 30 | endfunction 31 | "}}} 32 | " vim: foldmethod=marker 33 | -------------------------------------------------------------------------------- /autoload/textmanip/pos.vim: -------------------------------------------------------------------------------- 1 | let s:Pos = {} 2 | 3 | function! s:Pos.new(pos) "{{{1 4 | " pos is result of `getpos()` 5 | 6 | " getpos() return [bufnum, lnum, col, off] 7 | " off is offset from actual col when virtual edit(ve) mode, 8 | " so, to respect ve position, we sum "col" + "off" 9 | 10 | let self.line = a:pos[1] 11 | let self.colm = a:pos[2] + a:pos[3] 12 | return copy(self) 13 | endfunction 14 | 15 | function! s:Pos.pos() "{{{1 16 | return [self.line, self.colm] 17 | endfunction 18 | 19 | function! s:Pos.set_cursor() "{{{1 20 | call cursor(self.pos()) 21 | endfunction 22 | 23 | function! s:Pos.move(ope) "{{{1 24 | let self.line = max([1, eval(self.line . a:ope[0])]) 25 | let self.colm = max([1, eval(self.colm . a:ope[1])]) 26 | return self 27 | endfunction 28 | 29 | function! textmanip#pos#new(pos) "{{{1 30 | " pos = [line, col] 31 | return s:Pos.new(a:pos) 32 | endfunction 33 | " vim: foldmethod=marker 34 | -------------------------------------------------------------------------------- /autoload/textmanip/register.vim: -------------------------------------------------------------------------------- 1 | let s:Register = {} 2 | 3 | function! s:Register.use(reg) "{{{1 4 | let self.name = a:reg 5 | let self.content = [] 6 | let self.type = '' 7 | let self._modified = 0 8 | let self._org = { 9 | \ "content": getreg(a:reg, 1), 10 | \ "type": getregtype(a:reg) 11 | \ } 12 | return copy(self) 13 | endfunction 14 | 15 | function! s:Register.yank() "{{{1 16 | silent execute 'normal! "' . self.name . 'y' 17 | let self.type = getregtype(self.name) 18 | let self.content = split(getreg(self.name), "\n", 1) 19 | if self.type ==# 'V' 20 | call remove(self.content, -1) 21 | endif 22 | return self 23 | endfunction 24 | 25 | function! s:Register.paste() "{{{1 26 | call setreg(self.name, self.content, self.type) 27 | silent execute 'normal! "' . self.name . 'p' 28 | endfunction 29 | 30 | function! s:Register.restore() "{{{1 31 | if self._modified 32 | call setreg(self.name, self._org.content, self._org.type) 33 | endif 34 | let self._org = {} 35 | endfunction 36 | "}}} 37 | 38 | " API: 39 | function! textmanip#register#use(...) "{{{1 40 | return call(s:Register.use, a:000, s:Register) 41 | endfunction 42 | " vim: foldmethod=marker 43 | -------------------------------------------------------------------------------- /autoload/textmanip/util.vim: -------------------------------------------------------------------------------- 1 | function! s:SID() "{{{1 2 | let fullname = expand("") 3 | return matchstr(fullname, '\d\+_') 4 | endfunction 5 | "}}} 6 | let s:sid = s:SID() 7 | 8 | function! s:_opposite_init() "{{{1 9 | let data = [ 10 | \ [ '>', '<' ], 11 | \ [ '^', 'v' ], 12 | \ ] 13 | 14 | let R = {} 15 | for [ v1, v2 ] in data 16 | let R[v1] = v2 17 | let R[v2] = v1 18 | if v1 =~# '\u' 19 | let _v1 = tolower(v1) 20 | let _v2 = tolower(v2) 21 | let R[_v1] = _v2 22 | let R[_v2] = _v1 23 | endif 24 | endfor 25 | return R 26 | endfunction "}}} 27 | let s:opposite_data = s:_opposite_init() 28 | 29 | function! s:opposite(char) "{{{1 30 | return get(s:opposite_data, a:char) 31 | endfunction 32 | 33 | function! s:toward(dir) "{{{1 34 | return 35 | \ a:dir =~# '\^\|v' ? '^v' : 36 | \ a:dir =~# '>\|<' ? '<>' : throw 37 | endfunction 38 | 39 | function! s:template(string, vars) "{{{1 40 | let pattern = '\v(' . join(keys(a:vars), '|') . ')' 41 | return substitute(a:string, pattern,'\=a:vars[submatch(1)]', 'g') 42 | endfunction 43 | 44 | function! s:define_type_checker() "{{{1 45 | " dynamically define s:isNumber(v) etc.. 46 | let types = { 47 | \ "Number": 0, 48 | \ "String": 1, 49 | \ "Funcref": 2, 50 | \ "List": 3, 51 | \ "Dictionary": 4, 52 | \ "Float": 5, 53 | \ } 54 | 55 | for [type, number] in items(types) 56 | let s = '' 57 | let s .= 'function! s:is' . type . '(v)' . "\n" 58 | let s .= ' return type(a:v) ==# ' . number . "\n" 59 | let s .= 'endfunction' . "\n" 60 | execute s 61 | endfor 62 | endfunction 63 | "}}} 64 | call s:define_type_checker() 65 | unlet! s:define_type_checker 66 | 67 | function! s:toList(arg) 68 | return s:isList(a:arg) ? a:arg : [a:arg] 69 | endfunction 70 | 71 | let s:functions = [ 72 | \ 'opposite', 73 | \ 'toward', 74 | \ "isNumber", 75 | \ "isString", 76 | \ "isFuncref", 77 | \ "isList", 78 | \ "isDictionary", 79 | \ "isFloat", 80 | \ "toList", 81 | \ "template", 82 | \ ] 83 | 84 | let s:Util = {} 85 | function! s:Util.init() "{{{1 86 | let self.functions = {} 87 | for fname in s:functions 88 | let self.functions[fname] = function(s:sid . fname) 89 | endfor 90 | endfunction 91 | "}}} 92 | call s:Util.init() 93 | 94 | " API: 95 | function! textmanip#util#get() "{{{1 96 | return s:Util.functions 97 | endfunction 98 | "}}} 99 | 100 | " vim: foldmethod=marker 101 | -------------------------------------------------------------------------------- /doc/textmanip.txt: -------------------------------------------------------------------------------- 1 | *textmanip.txt* Maniplate selected text easily 2 | 3 | 4 | . . 5 | -|- ,-. . , |- ,-,-. ,-. ,-. . ,-. > 6 | | |-' X | | | | ,-| | | | | | 7 | `' `-' ' ` `' ' ' ' `-^ ' ' ' |-' 8 | | < easy manupilation > 9 | 10 | Version: 2.5 11 | Author : t9md 12 | 13 | ============================================================================== 14 | CONTENTS *textmanip-contents* 15 | 16 | Introduction |textmanip-introduction| 17 | Variables |textmanip-variable| 18 | Mapping |textmanip-mapping| 19 | Configuration Examples |textmanip-examples| 20 | Use Case |textmanip-usecase| 21 | Practice |textmanip-practice| 22 | Detail |textmanip-detail| 23 | Todo |textmanip-todo| 24 | Bug |textmanip-bug| 25 | Changelog |textmanip-changelog| 26 | 27 | ============================================================================== 28 | INTRODUCTION *textmanip-introduction* 29 | 30 | *textmanip* is minimal utility for 31 | 32 | * move visually selected text easily ( linewise / blockwise ) 33 | * duplicate text easily ( linewise / blockwise ) 34 | * count support / keep original cursor pos while moving / duplicating. 35 | * undo with one 'u' by undojoining. 36 | 37 | Requirement: 38 | - Vim 7.3 or later 39 | 40 | Latest Version: 41 | http://github.com/t9md/vim-textmanip 42 | 43 | *textmanip-mode* 44 | Textmanip Mode~ 45 | Textmanip have two mode. 46 | 47 | - insert 48 | don't overwrite existing text when overrap. 49 | 50 | - replace 51 | overwrite text and restore original text when selected area pass overwitten 52 | area. 53 | 54 | ============================================================================== 55 | VARIABLES *textmanip-variables* 56 | 57 | *g:textmanip_enable_mappings* 58 | Default: 0 59 | 60 | If this value is 1, textmanip setup default keymap 61 | See |textmanip-mapping|. 62 | 63 | *g:textmanip_startup_mode* 64 | Default: 'insert' 65 | 66 | Set 'insert' or 'replace', to specify textmanip act mode. 67 | Specify textmanip mode at startup. 68 | 69 | *g:textmanip_current_mode* 70 | Default: value of |g:textmanip_startup_mode|.[ 71 | 72 | This var store current mode. 73 | To change current mode, use |:TextmanipToggleMode| or 74 | (textmanip-toggle-mode). 75 | 76 | *g:textmanip_move_ignore_shiftwidth* 77 | Default: 0 78 | 79 | If you set this to 1. When line move right/left is executed with 80 | |g:textmanip_move_shiftwidth| value. 81 | 82 | *g:textmanip_move_shiftwidth* 83 | Default: 1 84 | 85 | When you set non zero value to |g:textmanip_move_ignore_shiftwidth|, 86 | this value is used how much right/left shifted when move right/left. 87 | 88 | *g:textmanip_hooks* 89 | [Experimental] 90 | Default: {} 91 | 92 | {key}: Name of hook point. currently only 'finish' is available. 93 | {value}: |Funcref| 94 | 95 | example~ 96 | > 97 | let g:textmanip_hooks = {} 98 | function! g:textmanip_hooks.finish(tm) 99 | let tm = a:tm 100 | let helper = textmanip#helper#get() 101 | if tm.linewise 102 | if &ft ==# 'html' 103 | call helper.indent(tm) 104 | endif 105 | else 106 | " When blockwise move/duplicate, remove trailing white space. 107 | " To use this feature without feeling counterintuitive, 108 | " I recommend you to ':set virtualedit=block', 109 | call helper.remove_trailing_WS(tm) 110 | endif 111 | endfunction 112 | < 113 | ============================================================================== 114 | MAPPINGS *textmanip-mappings* 115 | 116 | all mappings accept |count| which specify number of 'duplication' or 117 | amount of 'movement'. 118 | 119 | ------------------------------------------------------------------------------ 120 | BASE Actions~ 121 | 122 | * insert-blank-line~ 123 | n_(textmanip-blank-above) 124 | n_(textmanip-blank-below) 125 | insert blank line to specified direction. 126 | 127 | v_(textmanip-blank-above) 128 | v_(textmanip-blank-below) 129 | insert blank line to specified direction. 130 | 131 | * duplicate~ 132 | n_(textmanip-duplicate-up) 133 | n_(textmanip-duplicate-down) 134 | duplicate selection to specified direction. 135 | 136 | v_(textmanip-duplicate-up) 137 | v_(textmanip-duplicate-down) 138 | v_(textmanip-duplicate-left) 139 | v_(textmanip-duplicate-right) 140 | duplicate selection to specified direction. 141 | 142 | * move~ 143 | v_(textmanip-move-up) 144 | v_(textmanip-move-down) 145 | v_(textmanip-move-left) 146 | v_(textmanip-move-right) 147 | 148 | Move selected area to specified direction. 149 | 150 | |linewise| (|v_V| or |v_v| expand multi line) 151 | move line up/down/left/right. 152 | When downward move reach EOF, it automatically add empty line to be able 153 | to across the original EOF. 154 | 155 | |characterwise-visual| (|v_v|) 156 | |blockwise-visual| (|v_CTRL-V|) 157 | Move visual selected word or block to up/down/left/right. 158 | When right direction reach EOL it automatically add one space " " to be 159 | able to across original EOL. 160 | 161 | * Force 1column movement in right/left move~ 162 | v_(textmanip-move1-left) 163 | v_(textmanip-move1-right) 164 | 165 | * Toggle Mode between 'insert' and 'replace'~ 166 | n_(textmanip-toggle-mode) 167 | x_(textmanip-toggle-mode) 168 | 169 | ------------------------------------------------------------------------------ 170 | MODE SPECIFIED VERSION~ 171 | For 'duplicate' and 'move' action, mode-fixed keymap is also provided. 172 | If you want to 'duplicate' or 'move' always act as 'insert' or 'replace' mode 173 | independently of |g:textmanip_current_mode|, use following keymap. 174 | Also see |textmanip-example| for practical usecase. 175 | 176 | Insert~ 177 | Have '-i' suffix 178 | 179 | n_(textmanip-duplicate-up-i) 180 | n_(textmanip-duplicate-down-i) 181 | 182 | v_(textmanip-duplicate-up-i) 183 | v_(textmanip-duplicate-down-i) 184 | v_(textmanip-duplicate-left-i) 185 | v_(textmanip-duplicate-right-i) 186 | 187 | v_(textmanip-move-up-i) 188 | v_(textmanip-move-down-i) 189 | v_(textmanip-move-left-i) 190 | v_(textmanip-move-right-i) 191 | 192 | v_(textmanip-move1-left-i) 193 | v_(textmanip-move1-right-i) 194 | 195 | Replace~ 196 | Have '-r' suffix 197 | 198 | n_(textmanip-duplicate-up-r) 199 | n_(textmanip-duplicate-down-r) 200 | 201 | v_(textmanip-duplicate-up-r) 202 | v_(textmanip-duplicate-down-r) 203 | v_(textmanip-duplicate-left-r) 204 | v_(textmanip-duplicate-right-r) 205 | 206 | v_(textmanip-move-up-r) 207 | v_(textmanip-move-down-r) 208 | v_(textmanip-move-left-r) 209 | v_(textmanip-move-right-r) 210 | 211 | v_(textmanip-move1-left-r) 212 | v_(textmanip-move1-right-r) 213 | ============================================================================== 214 | FUNCTION *textmanip-functions* 215 | 216 | textmanip#mode() 217 | Simply return value of |g:textmanip_current_mode|. 218 | It could be use to indicate current mode in |'statusline'|. 219 | 220 | ============================================================================== 221 | COMMAND *textmanip-command* 222 | 223 | *:TextmanipToggleMode* 224 | Switch mode between 'insert' and 'replace'. 225 | 226 | *:TextmanipToggleIgnoreShiftWidth* 227 | Toggle whether line move right/left ignore |'shiwftwidth'| or not. 228 | 229 | ============================================================================== 230 | CONFIGURATION EXAMPLE *textmanip-example* 231 | 232 | * Macvim~ 233 | is mac COMMAND key. 234 | > 235 | xmap (textmanip-duplicate-down) 236 | nmap (textmanip-duplicate-down) 237 | xmap (textmanip-duplicate-up) 238 | nmap (textmanip-duplicate-up) 239 | 240 | xmap (textmanip-move-down) 241 | xmap (textmanip-move-up) 242 | xmap (textmanip-move-left) 243 | xmap (textmanip-move-right) 244 | 245 | " toggle insert/replace with 246 | nmap (textmanip-toggle-mode) 247 | xmap (textmanip-toggle-mode) 248 | < 249 | 250 | Advanced 251 | > 252 | " use Enter and Shift-Enter to insert blank line. 253 | " which is useful since I enforce duplicate with '-r(replace' mode. 254 | nmap (textmanip-blank-below) 255 | nmap (textmanip-blank-above) 256 | xmap (textmanip-blank-below) 257 | xmap (textmanip-blank-above) 258 | 259 | " simple duplicate 260 | nmap (textmanip-duplicate-up) 261 | nmap (textmanip-duplicate-down) 262 | xmap (textmanip-duplicate-up) 263 | xmap (textmanip-duplicate-down) 264 | 265 | " move with jkhl 266 | xmap (textmanip-move-up) 267 | xmap (textmanip-move-down) 268 | xmap (textmanip-move-left) 269 | xmap (textmanip-move-right) 270 | 271 | " duplicate with COMMAND-SHIFT-jkhl always replace-mode 272 | xmap (textmanip-duplicate-up-r) 273 | xmap (textmanip-duplicate-down-r) 274 | xmap (textmanip-duplicate-left-r) 275 | xmap (textmanip-duplicate-right-r) 276 | 277 | " use allow key to force replace movement 278 | xmap (textmanip-move-up-r) 279 | xmap (textmanip-move-down-r) 280 | xmap (textmanip-move-left-r) 281 | xmap (textmanip-move-right-r) 282 | 283 | " toggle insert/replace with 284 | nmap (textmanip-toggle-mode) 285 | xmap (textmanip-toggle-mode) 286 | < 287 | 288 | * gVim~ 289 | > 290 | xmap (textmanip-duplicate-down) 291 | nmap (textmanip-duplicate-down) 292 | xmap (textmanip-duplicate-up) 293 | nmap (textmanip-duplicate-up) 294 | 295 | xmap (textmanip-move-down) 296 | xmap (textmanip-move-up) 297 | xmap (textmanip-move-left) 298 | xmap (textmanip-move-right) 299 | 300 | " toggle insert/replace with 301 | nmap (textmanip-toggle-mode) 302 | xmap (textmanip-toggle-mode) 303 | < 304 | * vim on terminal~ 305 | > 306 | xmap d (textmanip-duplicate-down) 307 | nmap d (textmanip-duplicate-down) 308 | xmap D (textmanip-duplicate-up) 309 | nmap D (textmanip-duplicate-up) 310 | 311 | xmap (textmanip-move-down) 312 | xmap (textmanip-move-up) 313 | xmap (textmanip-move-left) 314 | xmap (textmanip-move-right) 315 | 316 | " toggle insert/replace with 317 | nmap (textmanip-toggle-mode) 318 | xmap (textmanip-toggle-mode) 319 | < 320 | ============================================================================== 321 | USE CASE *textmanip-usecase* 322 | 323 | * Indent text block 324 | While editing markdown or vim help file. 325 | Indenting selected text more easily. 326 | 327 | * Duplicate selected text below. 328 | When you want to call same function multiple time with various 329 | arguments or create facially resemble code structure by yank and 330 | paste. 331 | It is bothersome to "visually select text block" then "yank" then 332 | "move cursor" then "paste". 333 | This mini-plugin enables you to simply select text and then "" to 334 | duplicate selected text block to bottom direction. 335 | Of course, "" is my choice, you can assign your favorite key map. 336 | 337 | ============================================================================== 338 | PRACTICE *textmanip-practice* 339 | 340 | [FIXME] EXPLAIN OR MOVIE 341 | 342 | ------------------------------------------------------------------------------ 343 | Case1~ 344 | 345 | from 346 | > 347 | if (s.line() <= e.line()) && (s.col() <= e.col()) | let case = 1 348 | elseif (s.line() >= e.line()) && (s.col() >= e.col()) | let case = 2 349 | elseif (s.line() <= e.line()) && (s.col() >= e.col()) | let case = 3 350 | elseif (s.line() >= e.line()) && (s.col() <= e.col()) | let case = 4 351 | endif 352 | < 353 | 354 | to 355 | > 356 | let case = 357 | \ (s.line() <= e.line()) && (s.col() <= e.col()) ? 1 : 358 | \ (s.line() >= e.line()) && (s.col() >= e.col()) ? 2 : 359 | \ (s.line() <= e.line()) && (s.col() >= e.col()) ? 3 : 360 | \ (s.line() >= e.line()) && (s.col() <= e.col()) ? 4 : 361 | \ throw 362 | < 363 | 364 | ------------------------------------------------------------------------------ 365 | Case2~ 366 | 367 | from 368 | > 369 | if case ==# 1 | let [u, d, l, r ] = [ s, e, s, e ] 370 | elseif case ==# 2 | let [u, d, l, r ] = [ e, s, e, s ] 371 | elseif case ==# 3 | let [u, d, l, r ] = [ s, e, e, s ] 372 | elseif case ==# 4 | let [u, d, l, r ] = [ e, s, s, e ] 373 | endif 374 | 375 | to 376 | > 377 | let [u, d, l, r ] = 378 | \ case ==# 1 ? [ s, e, s, e ] : 379 | \ case ==# 2 ? [ e, s, e, s ] : 380 | \ case ==# 3 ? [ s, e, e, s ] : 381 | \ case ==# 4 ? [ e, s, s, e ] : 382 | \ throw 383 | < 384 | ============================================================================== 385 | DETAIL *textmanip-detail* 386 | 387 | Linewise or Blockwise is vary from how you invoked 388 | 389 | * vim-mode - wise map > 390 | +--------------+--------------+ 391 | | vim-mode | wise | 392 | +--------------+--------------| 393 | | n | linewise | 394 | | v oneline | blockwise | 395 | | v multiline | linewise | 396 | | C-v | blockwise | 397 | +--------------+--------------+ 398 | < 399 | Textmanip support 2 mode: insert, replace 400 | 401 | * INSERT - action table > 402 | +-----------------+--------------+--------------+ 403 | | action | linewise | blockwise | 404 | +-----------------+--------------|--------------| 405 | | move-up/down | o | o | 406 | | move-right/left | o | o | 407 | | dup-up/down | o | o | 408 | | dup-righ/left | ???? | TODO | 409 | +-----------------+--------------+--------------+ 410 | < 411 | * REPLACE - action table > 412 | +-----------------+--------------+--------------+ 413 | | action | linewise | blockwise | 414 | +-----------------+--------------|--------------| 415 | | move-up/down | o | o | 416 | | move-right/left | o | o | 417 | | dup-up/down | o | o | 418 | | dup-righ/left | N/A | TODO | 419 | +-----------------+--------------+--------------+ 420 | < 421 | ============================================================================== 422 | TODO *textmanip-todo* 423 | 424 | DONE 425 | - support replace mode [up/down => o] [right/left => o ] 426 | - duplicate right/left 427 | - duplicate up/down replacemode 428 | - refactoring( file separate, further oop style), 90% done 429 | - deleted stabilize, kickout action or delete this action? 430 | 431 | STACK 432 | - Corner case handling in replace mode( I know its include bug ) 433 | - ?? option to automatically delete trailing space? 434 | - ?? option to automatically indent( I'm not in mood ) 435 | - ?? move block or line to ^,$ with one command. I'm not in mood. 436 | 437 | ============================================================================== 438 | BUG *textmanip-bug* 439 | 440 | When you movement visually selected text to right or left, then |:undo|, cursor 441 | position not restored to original position. 442 | 443 | || is treated as 1 char ,so using textmanip in Tab including text destroy 444 | layout. 445 | 446 | ============================================================================== 447 | CHANGELOG *textmanip-changelog* 448 | 2015-04-19: v2.5 449 | - [new] 450 | Experimental hook support. 451 | 452 | 2015-04-20: 453 | - [refactoring] 454 | rewrite almost all code for readabilty. 455 | 456 | 2015-04-19: 457 | - [breaking] 458 | remove kickout related command, vertual keymap. 459 | - (textmanip-kickout) 460 | - :TextmanipKickout 461 | 462 | 2015-04-12: 463 | - [caution] 464 | rename (textmanip-move-*-1col) to (textmanip-move1-*) 465 | 466 | 2013-11-04: v2.0 467 | - [new] now duplicate right/left support. 468 | - refactoring 469 | - [bugfix] adjust count 470 | 2013-11-04: v1.9 471 | - refactoring 472 | - [bugfix] count now properly work in replace mode. 473 | 2013-11-02: v1.8 474 | - greatly improve stability. greatly refactored. 475 | - count and replace move is now stable. 476 | 2013-10-30: v1.3 477 | - [bugfix] error when global var not defined. 478 | 2013-10-27: v1.2 479 | - [bugfix] count not properly handled. 480 | 2013-10-26: 481 | - fix bug when cusuros at BOL and move to left with linewise. 482 | 2013-10-24: 483 | - support blockwise duplication. 484 | 2013-10-24: v1.0 485 | - completely rewriten code, and now suppport blockwise movement 486 | all direction and undoable!! 487 | keep cursor pos for all movement. 488 | 2013-10-02: 489 | - just [experiment] new action textmanip-kickout. 490 | 2011-12-18: 491 | - [bugfix] move right or left did '>' twice. should do once. 492 | 2011-08-16: v0.7 493 | - [bugfix] extend_eol not worked in some situation. 494 | 2011-08-15: v0.6 495 | - [bugfix] better handling for redraw problem 496 | 2011-08-14: v0.5 497 | - [bugfix] handle left and up movement limit. 498 | - [bugfix] more strict check to determine continuous execution. 499 | - [bugfix] remove unnecessarily `undojoin` twice, and remove 500 | catch clause(E790) 501 | 2011-08-14: v0.4 502 | - support count for duplicate and move 503 | - bug fix for `undojoin` 504 | - refactoring,, support duplicate text to upper direction. 505 | - change keymap for consistency, sorry. 506 | - autoloadize! 507 | 2011-06-13: v0.3 508 | - Fix typo 509 | 2011-06-13: v0.2 510 | - Fix typo 511 | 2011-03-10: v0.1 512 | - First release 513 | ============================================================================== 514 | vim:tw=78:ts=8:ft=help:norl: 515 | -------------------------------------------------------------------------------- /plugin/textmanip.vim: -------------------------------------------------------------------------------- 1 | " GUARD: {{{1 2 | if expand("%:p") ==# expand(":p") 3 | unlet! g:loaded_textmanip 4 | endif 5 | if exists('g:loaded_textmanip') 6 | finish 7 | endif 8 | let g:loaded_textmanip = 1 9 | let s:old_cpo = &cpo 10 | set cpo&vim 11 | "}}} 12 | 13 | " VARIABLES: {{{1 14 | let g:textmanip_debug = 0 15 | let s:global_variables = { 16 | \ "textmanip_enable_mappings" : 0, 17 | \ "textmanip_startup_mode" : "insert", 18 | \ "textmanip_move_ignore_shiftwidth" : 0, 19 | \ "textmanip_move_shiftwidth" : 1, 20 | \ "textmanip_hooks" : {}, 21 | \ } 22 | "}}} 23 | 24 | function! s:set_default(dict) "{{{ 25 | for [name, val] in items(a:dict) 26 | let g:{name} = get(g:, name, val) 27 | unlet name val 28 | endfor 29 | endfunction "}}} 30 | 31 | call s:set_default(s:global_variables) 32 | let g:textmanip_current_mode = g:textmanip_startup_mode 33 | 34 | " KEYMAP: {{{1 35 | let s:plug_suffix = { 36 | \ "auto": '', 37 | \ "insert": '-i', 38 | \ "replace": '-r', 39 | \ } 40 | 41 | let s:keymap_config = {} 42 | let s:keymap_config.blank = { 43 | \ 'emodes': ['auto'], 44 | \ '^': 'above', 45 | \ 'v': 'below', 46 | \ } 47 | let s:keymap_config.default = { 48 | \ 'emodes': ['auto', 'insert', 'replace'], 49 | \ '^': 'up', 50 | \ 'v': 'down', 51 | \ '>': 'right', 52 | \ '<': 'left', 53 | \ } 54 | 55 | function! s:keymap(mode, action, dir) "{{{1 56 | let config = a:action ==# 'blank' 57 | \ ? s:keymap_config.blank : s:keymap_config.default 58 | 59 | for emode in config.emodes 60 | let plug = printf('(textmanip-%s-%s%s)', 61 | \ a:action, config[a:dir], s:plug_suffix[emode]) 62 | 63 | let key = printf('%snoremap %s', a:mode, plug) 64 | let cmd = printf(':call textmanip#start("%s", "%s", "%s", "%s")', 65 | \ a:action, a:dir, a:mode, emode) 66 | " echo key cmd 67 | execute key cmd 68 | endfor 69 | endfunction "}}} 70 | 71 | " Normal: 72 | call s:keymap('n', 'duplicate', '^') 73 | call s:keymap('n', 'duplicate', 'v') 74 | 75 | call s:keymap('n', 'move', '^') 76 | call s:keymap('n', 'move', 'v') 77 | call s:keymap('n', 'move', '<') " new 78 | call s:keymap('n', 'move', '>') " new 79 | 80 | call s:keymap('n', 'blank', '^') 81 | call s:keymap('n', 'blank', 'v') 82 | 83 | " Visual: 84 | call s:keymap('x', 'duplicate', '^') 85 | call s:keymap('x', 'duplicate', 'v') 86 | call s:keymap('x', 'duplicate', '<') 87 | call s:keymap('x', 'duplicate', '>') 88 | 89 | call s:keymap('x', 'move', '^') 90 | call s:keymap('x', 'move', 'v') 91 | call s:keymap('x', 'move', '<') 92 | call s:keymap('x', 'move', '>') 93 | 94 | call s:keymap('x', 'move1', '<') 95 | call s:keymap('x', 'move1', '>') 96 | 97 | call s:keymap('x', 'blank', '^') 98 | call s:keymap('x', 'blank', 'v') 99 | 100 | nnoremap (textmanip-toggle-mode) :call textmanip#mode('toggle') 101 | xnoremap (textmanip-toggle-mode) :call textmanip#mode('toggle')gv 102 | 103 | " Obsolete: 104 | function! s:obsolete(what) "{{{1 105 | let msg = "[Obsolete]\n" 106 | if a:what ==# '1col' 107 | let msg .= " '(textmanip-move-*-1col)' is obsolete\n" 108 | let msg .= " '(textmanip-move1-*)' for 1col movement\n" 109 | echohl ErrorMsg 110 | echo msg 111 | echohl None 112 | endif 113 | endfunction 114 | "}}} 115 | 116 | xnoremap (textmanip-move-right-1col) :call obsolete('1col') 117 | xnoremap (textmanip-move-right-1col-i) :call obsolete('1col') 118 | xnoremap (textmanip-move-right-1col-r) :call obsolete('1col') 119 | xnoremap (textmanip-move-left-1col) :call obsolete('1col') 120 | xnoremap (textmanip-move-left-1col-i) :call obsolete('1col') 121 | xnoremap (textmanip-move-left-1col-r) :call obsolete('1col') 122 | 123 | if g:textmanip_enable_mappings 124 | xmap (textmanip-move-down) 125 | xmap (textmanip-move-up) 126 | xmap (textmanip-move-left) 127 | xmap (textmanip-move-right) 128 | 129 | let prefix = has('gui_macvim') ? "D" : "M" 130 | execute printf("nmap \<%s-d> (textmanip-duplicate-down)", prefix) 131 | execute printf("nmap \<%s-D> (textmanip-duplicate-up)", prefix) 132 | execute printf("xmap \<%s-d> (textmanip-duplicate-down)", prefix) 133 | execute printf("xmap \<%s-D> (textmanip-duplicate-up)", prefix) 134 | endif 135 | 136 | " COMMAND: {{{1 137 | command! -range -nargs=* TextmanipToggleMode call textmanip#mode('toggle') 138 | command! TextmanipToggleIgnoreShiftWidth 139 | \ let g:textmanip_move_ignore_shiftwidth = ! g:textmanip_move_ignore_shiftwidth 140 | \ echo g:textmanip_move_ignore_shiftwidth 141 | "}}} 142 | 143 | let &cpo = s:old_cpo 144 | " vim: foldmethod=marker 145 | --------------------------------------------------------------------------------