├── LICENSE ├── PLAN.md ├── README.md ├── doc ├── .gitignore └── nvim-gomove.txt ├── lua └── gomove │ ├── init.lua │ ├── mappings │ ├── base.lua │ ├── init.lua │ └── smart.lua │ ├── selection │ ├── block │ │ ├── horizontal.lua │ │ └── vertical.lua │ ├── handle_vertical.lua │ └── line │ │ ├── horizontal.lua │ │ └── vertical.lua │ ├── undo.lua │ └── utils.lua └── plugin └── gomove.lua /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 booperlv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PLAN.md: -------------------------------------------------------------------------------- 1 | ### booperlv / nvim-gomove 2 | 3 | - [x] selections 4 | 5 | - [x] init.lua (moving/reporting vertical destinations) 6 | 7 | - [x] lines (handles lines & fold selections) 8 | - [x] vertical.lua (move, duplicate) 9 | - [x] move 10 | - [x] duplicate 11 | - [x] horizontal.lua (move, duplicate) 12 | - [x] move 13 | - [x] duplicate 14 | 15 | - [x] blocks (handles block selections) 16 | - [x] vertical.lua (move, duplicate) 17 | - [ ] move 18 | - [x] duplicate 19 | - [x] horizontal.lua (move, duplicate) 20 | - [x] move 21 | - [x] duplicate 22 | 23 | - [x] mappings 24 | - [x] init.lua 25 | - [x] base.lua 26 | - [x] smart.lua 27 | 28 | - [x] utils.lua 29 | - [x] undo.lua (undo/undo parse handling) 30 | - [x] init.lua (handle configuration/settings) 31 | 32 | ## Planned 33 | 34 | 39 | - [ ] motions (handles anything as long we can del-paste to new position) 40 | - [ ] handle-motions.lua (get new position of motion and report) 41 | - [ ] init.lua (move, duplicate) 42 | - [ ] move 43 | - [ ] duplicate 44 | 45 | ## TODO 46 | 47 | - comment more of the code 48 | - explore treesitter capability 49 | 50 | - codebase cleanup/refactoring + bug fixing 51 | - [ ] mappings 52 | (could be made more intuitive and less repetitive) 53 | - [ ] base 54 | - [ ] init 55 | - [ ] smart 56 | - [ ] selections 57 | - [x] handle_vertical 58 | - [ ] block 59 | - [ ] vertical 60 | (fix behavior with multi-width characters) 61 | - [x] horizontal 62 | - [ ] line 63 | - [ ] vertical 64 | (functionality is perfect, but commenting and cleanup) 65 | - [ ] horizontal 66 | (commenting and cleanup) 67 | - [x] init.lua 68 | - [x] undo.lua 69 | - [x] utils.lua 70 | 71 | - add plenary tests 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nvim-gomove 2 | 3 | A complete plugin for moving and duplicating blocks and lines, with complete fold handling, reindenting, and undoing in one go. 4 | 5 | https://user-images.githubusercontent.com/65604882/147652973-62e30299-946d-4924-8766-0366016f70de.mp4 6 | 7 | ## Requirements 8 | 9 | This plugin works with NeoVim v0.5 or later. 10 | 11 | ## Installation 12 | 13 | - [packer.nvim](https://github.com/wbthoason/packer.nvim) 14 | 15 | ``` lua 16 | use 'booperlv/nvim-gomove' 17 | ``` 18 | 19 | - [vim-plug](https://github.com/junegunn/vim-plug) 20 | 21 | ``` vim 22 | Plug 'booperlv/nvim-gomove' 23 | ``` 24 | 25 | - [paq](https://github.com/savq/paq-nvim) 26 | 27 | ``` lua 28 | 'booperlv/nvim-gomove'; 29 | ``` 30 | 31 | 32 | ## Why nvim-gomove? 33 | 34 | As many may know, mappings such as "ddp", and ":move" already exist as solutions to moving lines. What makes nvim-gomove any different, and what are it's goals? 35 | 36 | nvim-gomove actually makes use of these same solutions, but just with a little more on top. It's a wrapper that polishes and tries to make these solutions as complete as possible, by dealing with folds, undoing, trailing whitespaces and reindenting for you. 37 | 38 | It may not be for everyone, but it might be helpful for the few that would like it :) 39 | 40 | ## Features 41 | 42 | ### Moving and Duplicating (Vertically and Horizontally): 43 | - Lines in Normal Mode 44 | - Lines in Visual Mode 45 | - Blocks in Normal Mode 46 | - Blocks in Visual Mode 47 | 48 | ### With the following additional features: 49 | - Full Fold Handling 50 | - Undoing in one go 51 | - Deleting Trailing Whitespaces (Block Vertical) 52 | - Reindenting 53 | 54 | ## Usage 55 | 56 | The default "smart" move mappings work this way: 57 | 58 | | Mapping | Normal | Visual | Line-Visual | Block-Visual | 59 | |---------|--------|--------|-------------|--------------| 60 | | \ | Block Left | Block Left | Line Left | Block Left | 61 | | \ | Line Down | Line Down | Line Down | Block Down | 62 | | \ | Line Up | Line Up | Line Up | Block Up | 63 | | \ | Block Right | Block Right | Line Right | Block Right | 64 | 65 | #### \ duplicates respectively 66 | 67 | ## Configuration 68 | 69 | ```lua 70 | require("gomove").setup { 71 | -- whether or not to map default key bindings, (true/false) 72 | map_defaults = true, 73 | -- whether or not to reindent lines moved vertically (true/false) 74 | reindent = true, 75 | -- whether or not to undojoin same direction moves (true/false) 76 | undojoin = true, 77 | -- whether to not to move past end column when moving blocks horizontally, (true/false) 78 | move_past_end_col = false, 79 | } 80 | ``` 81 | 82 | ## Mappings 83 | 84 | While there are default mappings, called "smart mappings" that are designed to 85 | be as intuitive as possible out of the box - that itself is built on "base" 86 | mappings which can serve as a framework for creating your own mappings. Check 87 | gomove/mappings/smart.lua as an example of the usage of the "base" mappings. 88 | 89 | ## Example for Changing Default (Smart) Keybinds: 90 | 91 | Just a reminder to set the option `map_defaults = false` in the setup function 92 | 93 | ```vim 94 | nmap GoNSMLeft 95 | nmap GoNSMDown 96 | nmap GoNSMUp 97 | nmap GoNSMRight 98 | 99 | xmap GoVSMLeft 100 | xmap GoVSMDown 101 | xmap GoVSMUp 102 | xmap GoVSMRight 103 | 104 | nmap GoNSDLeft 105 | nmap GoNSDDown 106 | nmap GoNSDUp 107 | nmap GoNSDRight 108 | 109 | xmap GoVSDLeft 110 | xmap GoVSDDown 111 | xmap GoVSDUp 112 | xmap GoVSDRight 113 | ``` 114 | 115 | ```lua 116 | local map = vim.api.nvim_set_keymap 117 | 118 | map( "n", "", "GoNSMLeft", {} ) 119 | map( "n", "", "GoNSMDown", {} ) 120 | map( "n", "", "GoNSMUp", {} ) 121 | map( "n", "", "GoNSMRight", {} ) 122 | 123 | map( "x", "", "GoVSMLeft", {} ) 124 | map( "x", "", "GoVSMDown", {} ) 125 | map( "x", "", "GoVSMUp", {} ) 126 | map( "x", "", "GoVSMRight", {} ) 127 | 128 | map( "n", "", "GoNSDLeft", {} ) 129 | map( "n", "", "GoNSDDown", {} ) 130 | map( "n", "", "GoNSDUp", {} ) 131 | map( "n", "", "GoNSDRight", {} ) 132 | 133 | map( "x", "", "GoVSDLeft", {} ) 134 | map( "x", "", "GoVSDDown", {} ) 135 | map( "x", "", "GoVSDUp", {} ) 136 | map( "x", "", "GoVSDRight", {} ) 137 | ``` 138 | 139 | 140 | ## Smart Mappings: 141 | 142 | ``` 143 | Naming Convention: 144 | Go, Normal/Visual, Smart, Move/Duplicate, Direction 145 | ``` 146 | 147 | | Name | Function | 148 | |------|----------| 149 | | Vertical | 150 | | \GoNSM(Up/Down) | Normal Smart Move Up/Down | 151 | | \GoVSM(Up/Down) | Visual Smart Move Up/Down | 152 | | \GoNSD(Up/Down) | Normal Smart Duplicate Up/Down | 153 | | \GoVSD(Up/Down) | Visual Smart Duplicate Up/Down | 154 | | Horizontal | 155 | | \GoNSM(Left/Right) | Normal Smart Move Left/Right | 156 | | \GoVSM(Left/Right) | Visual Smart Move Left/Right | 157 | | \GoNSD(Left/Right) | Normal Smart Duplicate Left/Right | 158 | | \GoVSD(Left/Right) | Visual Smart Duplicate Left/Right | 159 | 160 | ### Functionality is already explained in [Usage](#usage) 161 | 162 | ## Base Mappings: 163 | 164 | ``` 165 | Naming Convention: 166 | Go, Normal/Visual, Move/Duplicate, Line/Block, Direction 167 | ``` 168 | 169 | ### Lines: 170 | 171 | | Name | Function | 172 | |------|----------| 173 | | Vertical | 174 | | \GoNMLine(Down/Up) | In Normal Mode, Move current line down/up. Moves along folds. | 175 | | \GoNVLine(Down/Up) | In Visual Mode, Move selected lines down/up. Moves along folds. | 176 | | \GoNDLine(Down/Up) | In Normal Mode, Duplicate current line down/up. | 177 | | \GoVDLine(Down/Up) | In Visual Mode, Duplicate selected lines down/up. | 178 | | Horizontal | 179 | | \GoNMLine(Left/Right) | In Normal Mode, Move current line to the left/right by (indent level). | 180 | | \GoVMLine(Left/Right) | In Visual Mode, Move selected lines to the left/right by (indent level). | 181 | | \GoNDLine(Left/Right) | In Normal Mode, Duplicate current line to the left/right. Duplicating left ignores whitespace at the start of the text that it duplicates. | 182 | | \GoVDLine(Left/Right) | In Visual Mode, Duplicate selected lines to the left/right. Duplicating left ignores whitespace at the start of the text that it duplicates. | 183 | 184 | ### Blocks: 185 | 186 | | Name | Function | 187 | |------|----------| 188 | | Vertical | 189 | | \GoNMBlock(Down/Up) | In Normal Mode, Move current character down/up. Tries to avoid folds. | 190 | | \GoVMBlock(Down/Up) | In Visual Mode, Move selected characters down/up. Tries to avoid folds. | 191 | | \GoNDBlock(Down/Up) | In Normal Mode, Duplicate current character down/up. Tries to avoid folds. | 192 | | \GoVDBlock(Down/Up) | In Visual Mode, Duplicate selected characters down/up. Tries to avoid folds. | 193 | | Horizontal | 194 | | GoNMBlock(Left/Right) | In Normal Mode, Move current character left/right. | 195 | | GoVMBlock(Left/Right) | In Visual Mode, Move selected characters left/right. | 196 | | GoNDBlock(Left/Right) | In Normal Mode, Duplicate current character left/right. | 197 | | GoVDBlock(Left/Right) | In Visual Mode, Duplicate selected characters left/right. | 198 | 199 | 200 | ## Special Mentions 201 | 202 | - [matze/vim-move](https://github.com/matze/vim-move), much of the initial work was based on this plugin 203 | - [t9md/vim-textmanip](https://github.com/t9md/vim-textmanip): many features were made possible using this plugin as a reference 204 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | -------------------------------------------------------------------------------- /doc/nvim-gomove.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | 3 | *gomove* 4 | Moving and duplicating blocks and lines, with complete 5 | fold handling, reindenting, and undoing in one go. 6 | 7 | Author: booperlv 8 | License: MIT (see |gomove-license|) 9 | 10 | ================================================================================ 11 | 12 | CONTENTS *gomove-contents* 13 | 14 | 1. Quick Start ....................................... |gomove-quickstart| 15 | 2. Usage ............................................. |gomove-usage| 16 | 3. Options ........................................... |gomove-options| 17 | 4. Mappings .......................................... |gomove-mappings| 18 | 4.1 Smart Mappings ................................. |gomove-mappings-smart| 19 | 4.2 Base Mappings .................................. |gomove-mappings-base| 20 | 5. License ........................................... |gomove-license| 21 | 6. Credits ........................................... |gomove-credits| 22 | 23 | ================================================================================ 24 | 25 | 1. QUICK START *gomove-quickstart* 26 | > 27 | require("gomove").setup() 28 | < 29 | ================================================================================ 30 | 31 | 2. USAGE *gomove-usage* 32 | 33 | The default "smart" move mappings work this way: 34 | 35 | Normal Mode: 36 | > 37 | = Block Left 38 | = Line Down 39 | = Line Up 40 | = Block Right 41 | < 42 | 43 | Visual Mode: 44 | > 45 | = Block Left 46 | = Line Down 47 | = Line Up 48 | = Block Right 49 | < 50 | 51 | Line-Visual Mode: 52 | > 53 | = Line Left 54 | = Line Down 55 | = Line Up 56 | = Line Right 57 | < 58 | 59 | Block-Visual Mode: 60 | > 61 | = Block Left 62 | = Block Down 63 | = Block Up 64 | = Block Right 65 | < 66 | 67 | || duplicates respectively 68 | 69 | 70 | ================================================================================ 71 | 72 | 3. OPTIONS *gomove-options* 73 | 74 | nvim-gomove can be configured through a lua setup function 75 | > 76 | lua require("gomove").setup { 77 | map_defaults = (true/false), 78 | reindent = (true/false), 79 | move_past_end_col = (true/false), 80 | } 81 | < 82 | -------------------------------------------------------------------------------- 83 | 84 | |map_defaults| *gomove_map_defaults* 85 | 86 | Whether or not to map 87 | 88 | - to Move Left 89 | - to Move Down 90 | - to Move Up 91 | - to Move Right 92 | 93 | - to Duplicate Left 94 | - to Duplicate Down 95 | - to Duplicate Up 96 | - to Duplicate Right 97 | 98 | |reindent| *gomove_reindent* 99 | 100 | Whether or not to reindent lines moved vertically 101 | - true / false 102 | 103 | |undojoin| *gomove_undojoin* 104 | 105 | Whether or not to undojoin same direction moves 106 | - true / false 107 | 108 | |move_past_end_col| *gomove_move_past_end_col* 109 | 110 | Whether or not to move past line when moving horizontally 111 | - true / false 112 | 113 | ================================================================================ 114 | 115 | 4. MAPPINGS *gomove-mappings* 116 | 117 | While there are default mappings, called "smart mappings" that are designed to 118 | be as intuitive as possible out of the box - that itself is built on "base" 119 | mappings which can serve as a framework for creating your own mappings. Check 120 | gomove/mappings/smart.lua as an example of the usage of the "base" mappings. 121 | 122 | -------------------------------------------------------------------------------- 123 | 124 | 4.1 SMART MAPPINGS *gomove-mappings-smart* 125 | utilizing the base mappings are follows: 126 | 127 | Naming Convention: 128 | Go, Normal/Visual, Smart, Move/Duplicate, Direction 129 | 130 | -------------------------------------------------------------------------------- 131 | 132 | Move: 133 | 134 | GoNSMLeft 135 | GoVSMLeft 136 | 137 | GoNSMDown 138 | GoVSMDown 139 | 140 | GoNSMUp 141 | GoVSMUp 142 | 143 | GoNSMRight 144 | GoVSMRight 145 | 146 | -------------------------------------------------------------------------------- 147 | 148 | Duplicate: 149 | 150 | GoNSDLeft 151 | GoVSDLeft 152 | 153 | GoNSDDown 154 | GoVSDDown 155 | 156 | GoNSDUp 157 | GoVSDUp 158 | 159 | GoNSDRight 160 | GoVSDRight 161 | 162 | To understand it's functionality, take a look at |gomove-usage|! 163 | 164 | -------------------------------------------------------------------------------- 165 | 166 | 4.2 BASE MAPPINGS *gomove-mappings-base* 167 | 168 | Naming Convention: 169 | Go, Normal/Visual, Move/Duplicate, Line/Block, Direction 170 | 171 | -------------------------------------------------------------------------------- 172 | 173 | Line Vertical: 174 | 175 | GoNMLine(Down/Up) 176 | In Normal Mode, Move current line down/up. 177 | Moves along folds. 178 | 179 | GoNVLine(Down/Up) 180 | In Visual Mode, Move selected lines down/up. 181 | Moves along folds. 182 | 183 | GoNDLine(Down/Up) 184 | In Normal Mode, Duplicate current line down/up. 185 | Moves along folds. 186 | 187 | GoVDLine(Down/Up) 188 | In Visual Mode, Duplicate selected lines down/up. 189 | Moves along folds. 190 | 191 | -------------------------------------------------------------------------------- 192 | 193 | Line Horizontal: 194 | 195 | GoNMLine(Left/Right) 196 | In Normal Mode, Move current line to the left/right by (indent level). 197 | 198 | GoVMLine(Left/Right) 199 | In Visual Mode, Move selected lines to the left/right by (indent level). 200 | 201 | GoNDLine(Left/Right) 202 | In Normal Mode, Duplicate current line to the left/right. 203 | Duplicating left ignores whitespace at the start of the text that it duplicates. 204 | 205 | GoVDLine(Left/Right) 206 | In Visual Mode, Duplicate selected lines to the left/right. 207 | Duplicating left ignores whitespace at the start of the text that it duplicates. 208 | 209 | -------------------------------------------------------------------------------- 210 | 211 | Block Vertical: 212 | 213 | GoNMBlock(Down/Up) 214 | In Normal Mode, Move current character down/up. 215 | Tries to avoid folds. 216 | 217 | GoVMBlock(Down/Up) 218 | In Visual Mode, Move selected characters down/up. 219 | Tries to avoid folds. 220 | 221 | GoNDBlock(Down/Up) 222 | In Normal Mode, Duplicate current character down/up. 223 | Tries to avoid folds. 224 | 225 | GoVDBlock(Down/Up) 226 | In Visual Mode, Duplicate selected characters down/up. 227 | Tries to avoid folds. 228 | 229 | -------------------------------------------------------------------------------- 230 | 231 | Block Horizontal: 232 | 233 | GoNMBlock(Left/Right) 234 | In Normal Mode, Move current character left/right. 235 | 236 | GoVMBlock(Left/Right) 237 | In Visual Mode, Move selected characters left/right. 238 | 239 | GoNDBlock(Left/Right) 240 | In Normal Mode, Duplicate current character left/right. 241 | 242 | GoVDBlock(Left/Right) 243 | In Visual Mode, Duplicate selected characters left/right. 244 | 245 | -------------------------------------------------------------------------------- 246 | ================================================================================ 247 | 248 | 5. License *gomove-license* 249 | 250 | This plugin is licensed under the MIT license. 251 | 252 | ================================================================================ 253 | 254 | 6. Credits *gomove-credits* 255 | 256 | matze/vim-move (https://github.com/matze/vim-move) 257 | much of the initial work was based on this plugin, and many of the code here 258 | was reused and refactored for lua. Big thanks to all the people who 259 | contributed there :) 260 | 261 | t9md/vim-textmanip (https://github.com/t9md/vim-textmanip) 262 | many features were made possible using this plugin as a reference, and was a 263 | general reference for ideal behaviors. Thank you to the author and all the 264 | contributors, it felt very polished :) 265 | 266 | ================================================================================ 267 | -------------------------------------------------------------------------------- /lua/gomove/init.lua: -------------------------------------------------------------------------------- 1 | local gomove = {} 2 | 3 | gomove.opts = { 4 | map_defaults = true, 5 | reindent = true, 6 | undojoin = true, 7 | move_past_end_col = false, 8 | } 9 | 10 | local function map_defaults() 11 | local map = require("gomove.mappings").map 12 | map ({ 13 | {"n", "", "GoNSMLeft", {}}, 14 | {"n", "", "GoNSMDown", {}}, 15 | {"n", "", "GoNSMUp", {}}, 16 | {"n", "", "GoNSMRight", {}}, 17 | 18 | {"x", "", "GoVSMLeft", {}}, 19 | {"x", "", "GoVSMDown", {}}, 20 | {"x", "", "GoVSMUp", {}}, 21 | {"x", "", "GoVSMRight", {}}, 22 | 23 | {"n", "", "GoNSDLeft", {}}, 24 | {"n", "", "GoNSDDown", {}}, 25 | {"n", "", "GoNSDUp", {}}, 26 | {"n", "", "GoNSDRight", {}}, 27 | 28 | {"x", "", "GoVSDLeft", {}}, 29 | {"x", "", "GoVSDDown", {}}, 30 | {"x", "", "GoVSDUp", {}}, 31 | {"x", "", "GoVSDRight", {}}, 32 | }) 33 | end 34 | 35 | function gomove.setup(opts) 36 | gomove.opts = vim.tbl_extend("force", gomove.opts, opts or {}) 37 | if gomove.opts.map_defaults == true then 38 | map_defaults() 39 | end 40 | end 41 | 42 | return gomove 43 | -------------------------------------------------------------------------------- /lua/gomove/mappings/base.lua: -------------------------------------------------------------------------------- 1 | 2 | --This is a basis for the mappings, which finalizes the positions of the cursor 3 | --after the move, and handles reselecting. Users of the plugin can also create 4 | --their own kind of "Smart" Mappings as demonstrated in gomove.mappings.smart 5 | 6 | local M = {} 7 | 8 | -------------LINES------------- 9 | 10 | local line_vertical = require('gomove.selection.line.vertical') 11 | local line_horizontal = require('gomove.selection.line.horizontal') 12 | 13 | function M.MoveLineVertical(distance) 14 | local prev_col = vim.fn.col(".") 15 | local prev_indent = vim.fn.indent(".") 16 | line_vertical.move('.', '.', distance) 17 | local new_indent = vim.fn.indent(".") 18 | vim.fn.cursor( 19 | vim.fn.line('.'), 20 | math.max(1, prev_col - prev_indent + new_indent) 21 | ) 22 | end 23 | 24 | function M.MoveLineHorizontal(distance) 25 | local prev_col = vim.fn.col(".") 26 | local prev_indent = vim.fn.indent(".") 27 | line_horizontal.move('.', '.', distance) 28 | local new_indent = vim.fn.indent(".") 29 | vim.fn.cursor( 30 | vim.fn.line('.'), 31 | math.max(1, prev_col - prev_indent + new_indent) 32 | ) 33 | end 34 | 35 | function M.DuplicateLineVertical(distance) 36 | local prev_col = vim.fn.col(".") 37 | local prev_indent = vim.fn.indent(".") 38 | line_vertical.duplicate('.', '.', distance) 39 | local new_indent = vim.fn.indent(".") 40 | vim.fn.cursor( 41 | vim.fn.line('.'), 42 | math.max(1, prev_col - prev_indent + new_indent) 43 | ) 44 | end 45 | 46 | function M.DuplicateLineHorizontal(distance) 47 | local prev_col = vim.fn.col(".") 48 | local prev_length = vim.fn.col("$") 49 | line_horizontal.duplicate('.', '.', distance) 50 | local new_length = vim.fn.col("$") 51 | vim.fn.cursor( 52 | vim.fn.line('.'), 53 | math.max(1, prev_col - prev_length + new_length) 54 | ) 55 | end 56 | 57 | -------------VISUAL LINES------------- 58 | 59 | function M.VisualMoveLineVertical(distance) 60 | line_vertical.move("'<", "'>", distance) 61 | vim.cmd('normal! g`[Vg`]') 62 | end 63 | 64 | function M.VisualMoveLineHorizontal(distance) 65 | line_horizontal.move("'<", "'>", distance) 66 | vim.cmd("normal! gv") 67 | end 68 | 69 | function M.VisualDuplicateLineVertical(distance) 70 | line_vertical.duplicate("'<", "'>", distance) 71 | vim.cmd('execute "normal!g`[Vg`]"') 72 | end 73 | 74 | 75 | function M.VisualDuplicateLineHorizontal(distance) 76 | line_horizontal.duplicate("'<", "'>", distance) 77 | vim.cmd("normal! gv") 78 | end 79 | 80 | 81 | -------------BLOCKS------------- 82 | 83 | local block_vertical = require('gomove.selection.block.vertical') 84 | local block_horizontal = require('gomove.selection.block.horizontal') 85 | 86 | function M.MoveBlockVertical(distance) 87 | block_vertical.move('.', '.', distance) 88 | end 89 | 90 | function M.MoveBlockHorizontal(distance) 91 | block_horizontal.move('.', '.', distance) 92 | end 93 | 94 | function M.DuplicateBlockVertical(distance) 95 | block_vertical.duplicate('.', '.', distance) 96 | end 97 | 98 | function M.DuplicateBlockHorizontal(distance) 99 | block_horizontal.duplicate('.', '.', distance) 100 | end 101 | 102 | -------------VISUAL BLOCKS------------- 103 | 104 | function M.VisualMoveBlockVertical(distance) 105 | vim.cmd('execute "normal! g`<\\g`>"') 106 | if block_vertical.move("'<", "'>", distance) then 107 | vim.cmd('execute "normal! g`[\\g`]"') 108 | end 109 | end 110 | 111 | function M.VisualMoveBlockHorizontal(distance) 112 | vim.cmd('execute "normal! g`<\\g`>"') 113 | if block_horizontal.move("'<", "'>", distance) then 114 | vim.cmd('execute "normal! g`[\\g`]"') 115 | end 116 | end 117 | 118 | function M.VisualDuplicateBlockVertical(distance) 119 | vim.cmd('execute "normal! g`<\\g`>"') 120 | if block_vertical.duplicate("'<", "'>", distance) then 121 | vim.cmd('execute "normal! g`[\\g`]"') 122 | end 123 | end 124 | 125 | function M.VisualDuplicateBlockHorizontal(distance) 126 | vim.cmd('execute "normal! g`<\\g`>"') 127 | if block_horizontal.duplicate("'<", "'>", distance) then 128 | vim.cmd('execute "normal! g`[\\g`]"') 129 | end 130 | end 131 | 132 | 133 | return M 134 | -------------------------------------------------------------------------------- /lua/gomove/mappings/init.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | --Just a small helper 4 | function M.map(allkeys) 5 | for index, value in ipairs(allkeys) do 6 | if type(value) == "table" then 7 | -- mode, key, value, opts 8 | if #value == 4 then 9 | vim.api.nvim_set_keymap(value[1], value[2], value[3], value[4]) 10 | else 11 | vim.api.nvim_set_keymap(value[1], value[2], value[3], {silent=true, noremap=true}) 12 | end 13 | else 14 | print("mapping has invalid values at index "..index) 15 | return false 16 | end 17 | end 18 | return true 19 | end 20 | 21 | 22 | function M.SetMaps() 23 | M.map { 24 | {'n', 'GoNMLineLeft', ":lua require('gomove.mappings.base').MoveLineHorizontal(-vim.v.count1)"}, 25 | {'n', 'GoNMLineDown', ":lua require('gomove.mappings.base').MoveLineVertical ( vim.v.count1)"}, 26 | {'n', 'GoNMLineUp', ":lua require('gomove.mappings.base').MoveLineVertical (-vim.v.count1)"}, 27 | {'n', 'GoNMLineRight', ":lua require('gomove.mappings.base').MoveLineHorizontal( vim.v.count1)"}, 28 | 29 | {'x', 'GoVMLineLeft', ":lua require('gomove.mappings.base').VisualMoveLineHorizontal(-vim.v.count1)"}, 30 | {'x', 'GoVMLineDown', ":lua require('gomove.mappings.base').VisualMoveLineVertical ( vim.v.count1)"}, 31 | {'x', 'GoVMLineUp', ":lua require('gomove.mappings.base').VisualMoveLineVertical (-vim.v.count1)"}, 32 | {'x', 'GoVMLineRight', ":lua require('gomove.mappings.base').VisualMoveLineHorizontal( vim.v.count1)"}, 33 | 34 | {'n', 'GoNDLineLeft', ":lua require('gomove.mappings.base').DuplicateLineHorizontal(-vim.v.count1)"}, 35 | {'n', 'GoNDLineDown', ":lua require('gomove.mappings.base').DuplicateLineVertical ( vim.v.count1)"}, 36 | {'n', 'GoNDLineUp', ":lua require('gomove.mappings.base').DuplicateLineVertical (-vim.v.count1)"}, 37 | {'n', 'GoNDLineRight', ":lua require('gomove.mappings.base').DuplicateLineHorizontal( vim.v.count1)"}, 38 | 39 | {'x', 'GoVDLineLeft', ":lua require('gomove.mappings.base').VisualDuplicateLineHorizontal(-vim.v.count1)"}, 40 | {'x', 'GoVDLineDown', ":lua require('gomove.mappings.base').VisualDuplicateLineVertical ( vim.v.count1)"}, 41 | {'x', 'GoVDLineUp', ":lua require('gomove.mappings.base').VisualDuplicateLineVertical (-vim.v.count1)"}, 42 | {'x', 'GoVDLineRight', ":lua require('gomove.mappings.base').VisualDuplicateLineHorizontal( vim.v.count1)"}, 43 | 44 | 45 | {'n', 'GoNMBlockLeft', ":lua require('gomove.mappings.base').MoveBlockHorizontal(-vim.v.count1)"}, 46 | {'n', 'GoNMBlockDown', ":lua require('gomove.mappings.base').MoveBlockVertical ( vim.v.count1)"}, 47 | {'n', 'GoNMBlockUp', ":lua require('gomove.mappings.base').MoveBlockVertical (-vim.v.count1)"}, 48 | {'n', 'GoNMBlockRight', ":lua require('gomove.mappings.base').MoveBlockHorizontal( vim.v.count1)"}, 49 | 50 | {'x', 'GoVMBlockLeft', ":lua require('gomove.mappings.base').VisualMoveBlockHorizontal(-vim.v.count1)"}, 51 | {'x', 'GoVMBlockDown', ":lua require('gomove.mappings.base').VisualMoveBlockVertical ( vim.v.count1)"}, 52 | {'x', 'GoVMBlockUp', ":lua require('gomove.mappings.base').VisualMoveBlockVertical (-vim.v.count1)"}, 53 | {'x', 'GoVMBlockRight', ":lua require('gomove.mappings.base').VisualMoveBlockHorizontal( vim.v.count1)"}, 54 | 55 | {'n', 'GoNDBlockLeft', ":lua require('gomove.mappings.base').DuplicateBlockHorizontal(-vim.v.count1)"}, 56 | {'n', 'GoNDBlockDown', ":lua require('gomove.mappings.base').DuplicateBlockVertical ( vim.v.count1)"}, 57 | {'n', 'GoNDBlockUp', ":lua require('gomove.mappings.base').DuplicateBlockVertical (-vim.v.count1)"}, 58 | {'n', 'GoNDBlockRight', ":lua require('gomove.mappings.base').DuplicateBlockHorizontal( vim.v.count1)"}, 59 | 60 | {'x', 'GoVDBlockLeft', ":lua require('gomove.mappings.base').VisualDuplicateBlockHorizontal(-vim.v.count1)"}, 61 | {'x', 'GoVDBlockDown', ":lua require('gomove.mappings.base').VisualDuplicateBlockVertical ( vim.v.count1)"}, 62 | {'x', 'GoVDBlockUp', ":lua require('gomove.mappings.base').VisualDuplicateBlockVertical (-vim.v.count1)"}, 63 | {'x', 'GoVDBlockRight', ":lua require('gomove.mappings.base').VisualDuplicateBlockHorizontal( vim.v.count1)"}, 64 | 65 | 66 | 67 | 68 | {'n', 'GoNSMLeft', ":lua require('gomove.mappings.smart').NormalLeft (false, vim.v.count1)"}, 69 | {'n', 'GoNSMDown', ":lua require('gomove.mappings.smart').NormalDown (false, vim.v.count1)"}, 70 | {'n', 'GoNSMUp', ":lua require('gomove.mappings.smart').NormalUp (false, vim.v.count1)"}, 71 | {'n', 'GoNSMRight', ":lua require('gomove.mappings.smart').NormalRight(false, vim.v.count1)"}, 72 | 73 | {'x', 'GoVSMLeft', ":lua require('gomove.mappings.smart').VisualLeft (false, vim.v.count1)"}, 74 | {'x', 'GoVSMDown', ":lua require('gomove.mappings.smart').VisualDown (false, vim.v.count1)"}, 75 | {'x', 'GoVSMUp', ":lua require('gomove.mappings.smart').VisualUp (false, vim.v.count1)"}, 76 | {'x', 'GoVSMRight', ":lua require('gomove.mappings.smart').VisualRight(false, vim.v.count1)"}, 77 | 78 | {'n', 'GoNSDLeft', ":lua require('gomove.mappings.smart').NormalLeft (true, vim.v.count1)"}, 79 | {'n', 'GoNSDDown', ":lua require('gomove.mappings.smart').NormalDown (true, vim.v.count1)"}, 80 | {'n', 'GoNSDUp', ":lua require('gomove.mappings.smart').NormalUp (true, vim.v.count1)"}, 81 | {'n', 'GoNSDRight', ":lua require('gomove.mappings.smart').NormalRight(true, vim.v.count1)"}, 82 | 83 | {'x', 'GoVSDLeft', ":lua require('gomove.mappings.smart').VisualLeft (true, vim.v.count1)"}, 84 | {'x', 'GoVSDDown', ":lua require('gomove.mappings.smart').VisualDown (true, vim.v.count1)"}, 85 | {'x', 'GoVSDUp', ":lua require('gomove.mappings.smart').VisualUp (true, vim.v.count1)"}, 86 | {'x', 'GoVSDRight', ":lua require('gomove.mappings.smart').VisualRight(true, vim.v.count1)"}, 87 | } 88 | end 89 | 90 | return M 91 | -------------------------------------------------------------------------------- /lua/gomove/mappings/smart.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local function IsVisualFromStart(mode) 4 | if mode == 'v' then 5 | local selection_start = vim.api.nvim_buf_get_mark(0, "<") 6 | local selection_end = vim.api.nvim_buf_get_mark(0, ">") 7 | -- start of selection is at 0 8 | if selection_start[2] == 0 then 9 | -- more than 1 line selected 10 | if selection_end[1]-selection_start[1] > 0 then 11 | return true 12 | end 13 | end 14 | return false 15 | end 16 | end 17 | 18 | function M.NormalLeft(duplicate, distance) 19 | local plugToUse = (duplicate and [[\GoNDBlockLeft]] or [[\GoNMBlockLeft]]) 20 | vim.cmd('execute "normal'..distance..plugToUse..'"') 21 | end 22 | function M.VisualLeft(duplicate, distance) 23 | local mode = vim.fn.visualmode() 24 | vim.cmd('normal! gv') 25 | if mode == 'V' then 26 | --Visual Line mode uses lines 27 | local plugToUse = (duplicate and [[\GoVDLineLeft]] or [[\GoVMLineLeft]]) 28 | vim.cmd('execute "normal'..distance..plugToUse..'"') 29 | else 30 | --Normal visual, or blockwise all do block movements 31 | local plugToUse = (duplicate and [[\GoVDBlockLeft]] or [[\GoVMBlockLeft]]) 32 | vim.cmd('execute "normal'..distance..plugToUse..'"') 33 | end 34 | end 35 | 36 | function M.NormalRight(duplicate, distance) 37 | local plugToUse = (duplicate and [[\GoNDBlockRight]] or [[\GoNMBlockRight]]) 38 | vim.cmd('execute "normal'..distance..plugToUse..'"') 39 | end 40 | function M.VisualRight(duplicate, distance) 41 | local mode = vim.fn.visualmode() 42 | vim.cmd('normal! gv') 43 | if mode == 'V' then 44 | --Visual Line mode uses lines 45 | local plugToUse = (duplicate and [[\GoVDLineRight]] or [[\GoVMLineRight]]) 46 | vim.cmd('execute "normal'..distance..plugToUse..'"') 47 | else 48 | --Normal visual, or blockwise all do block movements 49 | local plugToUse = (duplicate and [[\GoVDBlockRight]] or [[\GoVMBlockRight]]) 50 | vim.cmd('execute "normal'..distance..plugToUse..'"') 51 | end 52 | end 53 | 54 | function M.NormalUp(duplicate, distance) 55 | local plugToUse = (duplicate and [[\GoNDLineUp]] or [[\GoNMLineUp]]) 56 | vim.cmd('execute "normal'..distance..plugToUse..'"') 57 | end 58 | function M.VisualUp(duplicate, distance) 59 | local mode = vim.fn.visualmode() 60 | local condition = (mode == 'V' or IsVisualFromStart(mode)) 61 | vim.cmd('normal! gv') 62 | if condition then 63 | local plugToUse = (duplicate and [[\GoVDLineUp]] or [[\GoVMLineUp]]) 64 | vim.cmd('execute "normal'..distance..plugToUse..'"') 65 | else 66 | local plugToUse = (duplicate and [[\GoVDBlockUp]] or [[\GoVMBlockUp]]) 67 | vim.cmd('execute "normal'..distance..plugToUse..'"') 68 | end 69 | end 70 | 71 | function M.NormalDown(duplicate, distance) 72 | local plugToUse = (duplicate and [[\GoNDLineDown]] or [[\GoNMLineDown]]) 73 | vim.cmd('execute "normal'..distance..plugToUse..'"') 74 | end 75 | function M.VisualDown(duplicate, distance) 76 | local mode = vim.fn.visualmode() 77 | local condition = (mode == 'V' or IsVisualFromStart(mode)) 78 | vim.cmd('normal! gv') 79 | if condition then 80 | local plugToUse = (duplicate and [[\GoVDLineDown]] or [[\GoVMLineDown]]) 81 | vim.cmd('execute "normal'..distance..plugToUse..'"') 82 | else 83 | local plugToUse = (duplicate and [[\GoVDBlockDown]] or [[\GoVMBlockDown]]) 84 | vim.cmd('execute "normal'..distance..plugToUse..'"') 85 | end 86 | end 87 | 88 | return M 89 | -------------------------------------------------------------------------------- /lua/gomove/selection/block/horizontal.lua: -------------------------------------------------------------------------------- 1 | local bh = {} 2 | 3 | function bh.move(vim_start, vim_end, distance) 4 | if not vim.o.modifiable or distance == 0 then 5 | return false 6 | end 7 | 8 | local going_right = (distance > 0) 9 | local col_start = vim.fn.col(vim_start) 10 | local col_end = vim.fn.col(vim_end) 11 | local width = col_end - col_start 12 | local register = "z" 13 | local old_register_value = vim.fn.getreg("register") 14 | 15 | -- get lines status before deleting anything 16 | local lines = vim.fn.getline(vim_start, vim_end) 17 | table.sort(lines, function(a,b) return #a<#b end) 18 | 19 | -- start undojoin and delete 20 | local old_virtualedit = vim.o.virtualedit 21 | vim.o.virtualedit = "all" 22 | 23 | local undo = require("gomove.undo") 24 | undo.Handle((going_right and "right" or "left")) 25 | vim.cmd("normal! \""..register.."x") 26 | 27 | -- go to new destination 28 | local destn_start 29 | for _=1, math.abs(distance) do 30 | local last_destn = destn_start 31 | if going_right then 32 | vim.cmd('normal!l') 33 | else 34 | vim.cmd('normal!h') 35 | end 36 | destn_start = vim.fn.col(".") 37 | if last_destn == destn_start then 38 | local single_distance = (going_right and 1 or -1) 39 | destn_start = destn_start + single_distance 40 | end 41 | end 42 | -- correct based on option to move past end column of shortest line 43 | local opts = require("gomove").opts 44 | if going_right and not opts.move_past_end_col then 45 | local shortest_line_width = #lines[1] 46 | if col_end < shortest_line_width then 47 | if destn_start < shortest_line_width then 48 | vim.fn.cursor(vim.fn.line("."), destn_start) 49 | else 50 | vim.fn.cursor(vim.fn.line("."), shortest_line_width) 51 | vim.cmd('normal!'..width..'h') 52 | end 53 | else 54 | -- don't move anymore 55 | vim.fn.cursor(vim.fn.line("."), col_start) 56 | end 57 | end 58 | 59 | -- paste 60 | vim.cmd("silent! normal! \""..register.."P") 61 | -- fix register and virtualedit 62 | vim.o.virtualedit = old_virtualedit 63 | vim.fn.setreg(register, old_register_value) 64 | -- complete undo 65 | undo.Save((going_right and "right" or "left")) 66 | 67 | return true 68 | end 69 | 70 | 71 | function bh.duplicate(vim_start, vim_end, count) 72 | if not vim.o.modifiable or count == 0 then 73 | return false 74 | end 75 | 76 | local going_right = (count > 0) 77 | local col_start = vim.fn.col(vim_start) 78 | local col_end = vim.fn.col(vim_end) 79 | local width = col_end - col_start 80 | 81 | --The distance will always be 1 or -1, count is just the amount of times we 82 | --do it. This is automatically the destination. 83 | local destn_start = col_start + (going_right and 1 or -1) 84 | local destn_end = destn_start + width 85 | 86 | local old_virtualedit = vim.o.virtualedit 87 | vim.o.virtualedit = "all" 88 | 89 | local register = 'z' 90 | local old_register_value = vim.fn.getreg('register') 91 | vim.cmd('silent! normal! "'..register..'x') 92 | 93 | local line = vim.fn.line(".") 94 | vim.cmd('silent! normal! "'..register..'P') 95 | 96 | if going_right then 97 | vim.fn.cursor(line, destn_end) 98 | vim.cmd('silent! normal!'..math.abs(count)..'"'..register..'P') 99 | else 100 | vim.fn.cursor(line, destn_start) 101 | vim.cmd('silent! normal!'..math.abs(count)..'"'..register..'p') 102 | end 103 | 104 | vim.fn.setreg(register, old_register_value) 105 | vim.o.virtualedit = old_virtualedit 106 | 107 | return true 108 | end 109 | 110 | return bh 111 | -------------------------------------------------------------------------------- /lua/gomove/selection/block/vertical.lua: -------------------------------------------------------------------------------- 1 | local bv = {} 2 | 3 | function bv.move(vim_start, vim_end, distance) 4 | if not vim.o.modifiable or distance == 0 then 5 | return false 6 | end 7 | 8 | -- initial variables{{{ 9 | local utils = require('gomove.utils') 10 | local going_down = (distance > 0) 11 | local old_pos = vim.fn.winsaveview() 12 | 13 | local line_start = vim.fn.line(vim_start) 14 | local line_end = vim.fn.line(vim_end) 15 | local height = utils.user_height(line_start, line_end) 16 | 17 | local temp_cols = {vim.fn.col(vim_start), vim.fn.col(vim_end)} 18 | local col_start = math.min(unpack(temp_cols)) 19 | local col_end = math.max(unpack(temp_cols)) 20 | local width = col_end - col_start 21 | 22 | if utils.contains_fold(line_start, line_end) then 23 | print('Go-Move-Block does not support moving folds!') 24 | return false 25 | end 26 | 27 | local lines_selected = vim.fn.getbufline( 28 | vim.fn.bufnr('%'), line_start, line_end 29 | ) 30 | --If all the lines selected are empty, stop moving 31 | if table.concat(lines_selected) == '' then 32 | return false 33 | end 34 | 35 | local destn_col_start = col_start 36 | --}}} 37 | --Compute destination line{{{ 38 | local destn = require('gomove.selection.handle_vertical') 39 | local destn_line_start, destn_line_end = destn.Handle( 40 | "b", line_start, line_end, distance 41 | ) 42 | 43 | --If there is no actual movement, stop right here and don't do anything else. 44 | if line_start == destn_line_start and line_end == destn_line_end then 45 | return false 46 | end 47 | 48 | utils.open_fold(destn_line_start, destn_line_end) 49 | --}}} 50 | --Prepping for trailing whitespace delete{{{ 51 | 52 | --Get all lines between linebefore and after, and their corresponding lengths. 53 | --We are getting the lengths BEFORE we actually move. 54 | local all_pos_between = {} 55 | local low_end = destn_line_start 56 | local high_end = destn_line_end 57 | while (low_end <= high_end) do 58 | vim.fn.cursor(low_end, 1) 59 | local curpos = {low_end, vim.fn.col('$')} 60 | table.insert(all_pos_between, curpos) 61 | low_end = low_end + 1 62 | end 63 | 64 | --Extract the lines where length is less than destination column/before. As 65 | --that would be the only instance where a trailing whitespace would be left. 66 | local lines_to_insert = {} 67 | for _, pos in ipairs(all_pos_between) do 68 | vim.fn.cursor(pos) 69 | if vim.fn.col('$') < destn_col_start then 70 | table.insert(lines_to_insert, pos) 71 | end 72 | end 73 | 74 | local new_lines_with_trailing_whitespace = vim.b.gomove_lines_with_trailing_whitespace or {} 75 | 76 | local undo = require('gomove.undo') 77 | undo.Handle( 78 | (going_down and "down" or "up") 79 | ) 80 | 81 | -- To prevent instances where the content of the line is already changed, but 82 | -- we assume that the previous "end column" is still the same and accidentally 83 | -- delete there, we completely clear the previous 84 | -- "lines_with_trailing_whitespace" when the move is not undojoined. 85 | local same_changed_tick = undo.NoDirection() 86 | if not same_changed_tick then 87 | for index, _ in ipairs(new_lines_with_trailing_whitespace) do 88 | table.remove(new_lines_with_trailing_whitespace, index) 89 | end 90 | end 91 | 92 | --Insert all of the new positions to the table without deleting the others 93 | --that should stay for after we move past those positions 94 | for _, pos in ipairs(lines_to_insert) do 95 | local function has_line (tab, val) 96 | for _,v in ipairs(tab) do 97 | if v[1] == val[1] then return true end 98 | end 99 | return false 100 | end 101 | --Prevent duplicates 102 | if not has_line(new_lines_with_trailing_whitespace, pos) then 103 | table.insert(new_lines_with_trailing_whitespace, pos) 104 | end 105 | end 106 | 107 | --}}} 108 | --Deleting and Pasting{{{ 109 | vim.fn.winrestview(old_pos) 110 | 111 | local register = 'z' 112 | local old_register_value = vim.fn.getreg(register) 113 | 114 | local old_virtualedit = vim.o.virtualedit 115 | vim.o.virtualedit = "all" 116 | 117 | vim.cmd('silent! normal! "'..register..'x') 118 | vim.fn.cursor(destn_line_start, destn_col_start) 119 | vim.cmd('silent! normal! "'..register..'P') 120 | 121 | vim.o.virtualedit = old_virtualedit 122 | vim.fn.setreg(register, old_register_value) 123 | --}}} 124 | --Trailing whitespace deletion/adding new values{{{ 125 | 126 | local function line_exists_between_destination(val) 127 | for _, pos in ipairs(all_pos_between) do 128 | if pos[1] == val[1] then 129 | return true 130 | end 131 | end 132 | return false 133 | end 134 | 135 | --Delete trailing whitespace from previous move 136 | local indexes_to_remove = {} 137 | if next(new_lines_with_trailing_whitespace) ~= nil then 138 | for index, pos in ipairs(new_lines_with_trailing_whitespace) do 139 | --We check here if it doesn't exist inside lines, to get rid of the trailing 140 | --whitespace and the item on the array ONLY ONCE we have moved past it. 141 | --Also, check if it is not blank, to make sure that we do not remove lines. 142 | if not line_exists_between_destination(pos) then 143 | if vim.fn.getline(pos[1]) ~= '' then 144 | vim.fn.cursor(pos) 145 | vim.cmd('normal!dw') 146 | table.insert(indexes_to_remove, index) 147 | end 148 | end 149 | end 150 | end 151 | for _, value in ipairs(indexes_to_remove) do 152 | table.remove(new_lines_with_trailing_whitespace, value) 153 | end 154 | 155 | vim.b.gomove_lines_with_trailing_whitespace = new_lines_with_trailing_whitespace 156 | --}}} 157 | --Set new position{{{ 158 | vim.fn.cursor(destn_line_start, destn_col_start) 159 | vim.cmd('normal! m[') 160 | vim.fn.cursor(destn_line_start+(height-1), destn_col_start+width) 161 | vim.cmd('normal! m]')--}}} 162 | 163 | undo.Save( 164 | (going_down and "down" or "up") 165 | ) 166 | 167 | return true 168 | end 169 | 170 | 171 | function bv.duplicate(vim_start, vim_end, count) 172 | if not vim.o.modifiable or count == 0 then 173 | return false 174 | end 175 | 176 | -- initial variables{{{ 177 | local utils = require('gomove.utils') 178 | local going_down = (count > 0) 179 | 180 | local line_start = vim.fn.line(vim_start) 181 | local line_end = vim.fn.line(vim_end) 182 | local height = utils.user_height(line_start, line_end)-1 183 | 184 | local temp_cols = {vim.fn.col(vim_start), vim.fn.col(vim_end)} 185 | local col_start = math.min(unpack(temp_cols)) 186 | local col_end = math.max(unpack(temp_cols)) 187 | local width = col_end - col_start 188 | 189 | if utils.contains_fold(line_start, line_end) then 190 | print('Go-Dup-Block does not support moving folds!') 191 | return false 192 | end 193 | 194 | local destn_line_start = line_start 195 | local destn_line_end = line_start + height 196 | local destn_col_start = col_start 197 | --}}} 198 | --Deleting and Pasting{{{ 199 | local register = 'z' 200 | local old_register_value = vim.fn.getreg(register) 201 | 202 | local old_virtualedit = vim.o.virtualedit 203 | vim.o.virtualedit = "all" 204 | 205 | vim.cmd('silent! normal! "'..register..'x') 206 | vim.cmd('silent! normal! "'..register..'P') 207 | 208 | local amount_of_times_done = 1 209 | 210 | local destn = require('gomove.selection.handle_vertical') 211 | while (amount_of_times_done <= math.abs(count)) do 212 | local to_move = 1+height 213 | destn_line_start, destn_line_end = destn.Handle( 214 | "b", destn_line_start, destn_line_end, 215 | (going_down and to_move or -to_move) 216 | ) 217 | utils.open_fold(destn_line_start, destn_line_end) 218 | 219 | vim.fn.cursor(destn_line_start, destn_col_start) 220 | vim.cmd('silent! normal! "'..register..'P') 221 | 222 | amount_of_times_done = amount_of_times_done + 1 223 | end 224 | 225 | vim.o.virtualedit = old_virtualedit 226 | vim.fn.setreg(register, old_register_value) 227 | --}}} 228 | --Set new position{{{ 229 | vim.fn.cursor(destn_line_start, destn_col_start) 230 | vim.cmd('normal! m[') 231 | vim.fn.cursor(destn_line_start+height, destn_col_start+width) 232 | vim.cmd('normal! m]')--}}} 233 | 234 | return true 235 | end 236 | 237 | return bv 238 | -------------------------------------------------------------------------------- /lua/gomove/selection/handle_vertical.lua: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------- 3 | -- This file returns the ideal destination of a vertical 4 | -- range when moved as a block, or line 5 | -------------------------------------------------------- 6 | 7 | local M = {} 8 | 9 | local function normal_move(going_down, next_position) 10 | vim.fn.cursor(next_position, 1) 11 | if going_down then 12 | vim.cmd("normal! j") 13 | next_position = vim.fn.line(".") 14 | else 15 | vim.cmd("normal! k") 16 | next_position = vim.fn.line(".") 17 | end 18 | return next_position 19 | end 20 | 21 | -- move past folds recursively to avoid going into them 22 | local function block_move(going_down, next_position, height) 23 | local utils = require("gomove.utils") 24 | local prev_position = next_position 25 | vim.fn.cursor(prev_position, 1) 26 | if going_down then 27 | vim.cmd("normal! j") 28 | next_position = vim.fn.line(".") + (height-1) 29 | -- workaround for end of file is fold 30 | if utils.fold_end(next_position) >= vim.fn.line('$') then 31 | next_position = prev_position 32 | end 33 | else 34 | vim.cmd("normal! k") 35 | next_position = vim.fn.line(".") - (height-1) 36 | -- workaround for start of file is fold 37 | if next_position <= 1 then 38 | vim.cmd("normal! j") 39 | next_position = utils.fold_end(vim.fn.line(".")) - 1 40 | return next_position, true 41 | end 42 | end 43 | return next_position, false 44 | end 45 | 46 | -- move along folds as if they were lines 47 | local function line_move(going_down, next_position) 48 | local utils = require("gomove.utils") 49 | local prev_position = next_position 50 | vim.fn.cursor(prev_position, 1) 51 | if going_down then 52 | next_position = utils.fold_end(vim.fn.line(".")) 53 | else 54 | next_position = utils.fold_start(vim.fn.line(".")) 55 | end 56 | if vim.fn.foldclosed(next_position) ~= -1 then 57 | return next_position, true 58 | end 59 | return next_position, false 60 | end 61 | 62 | ------------- MAIN FUNCTION -------------- 63 | 64 | function M.Handle(l_or_b, start_low, start_high, distance) 65 | local going_down = (distance > 0) 66 | 67 | local utils = require('gomove.utils') 68 | start_low = utils.fold_start(start_low) 69 | start_high = utils.fold_end(start_high) 70 | 71 | local height = utils.user_height(start_low, start_high) 72 | 73 | local line_to_first_contact_fold 74 | if going_down then 75 | --last line 76 | line_to_first_contact_fold = start_high 77 | else 78 | --first line 79 | line_to_first_contact_fold = start_low 80 | end 81 | 82 | --Increment each line one by one, and use normal! j or k when finding a fold 83 | local old_pos = vim.fn.winsaveview() 84 | local did_find_a_fold 85 | local next_position = line_to_first_contact_fold 86 | 87 | local prev_value 88 | vim.fn.cursor(next_position, 1) 89 | 90 | for _=1, math.abs(distance) do 91 | next_position = normal_move(going_down, next_position) 92 | if vim.fn.foldclosed(".") ~= -1 then 93 | did_find_a_fold = true 94 | -- just move among folds as if they were lines 95 | if l_or_b == "l" then 96 | next_position = line_move( 97 | going_down, next_position 98 | ) 99 | -- move past folds recursively to avoid going into them 100 | elseif l_or_b == "b" then 101 | while (vim.fn.foldclosed(".") ~= -1) do 102 | prev_value = next_position 103 | next_position = block_move( 104 | going_down, next_position, height 105 | ) 106 | if prev_value == next_position then 107 | break; 108 | end 109 | end 110 | end 111 | end 112 | end 113 | vim.fn.winrestview(old_pos) 114 | 115 | local destn_low 116 | --Actually get the destn_start/low instead of the line_to_first_contact_fold 117 | if going_down then 118 | destn_low = next_position - (height-1) 119 | else 120 | destn_low = next_position 121 | end 122 | 123 | local destn_high = destn_low + (height-1) 124 | 125 | return destn_low, destn_high, 126 | --Return true if did find a fold, false if otherwise 127 | (did_find_a_fold and true or false) 128 | end 129 | 130 | return M 131 | -------------------------------------------------------------------------------- /lua/gomove/selection/line/horizontal.lua: -------------------------------------------------------------------------------- 1 | local lh = {} 2 | 3 | function lh.move(vim_start, vim_end, distance) 4 | if not vim.o.modifiable or distance == 0 then 5 | return false 6 | end 7 | 8 | local going_right = (distance > 0) 9 | 10 | local line_start = vim.fn.line(vim_start) 11 | local line_end = vim.fn.line(vim_end) 12 | 13 | local action 14 | if going_right then 15 | action = string.rep('>', distance) 16 | else 17 | action = string.rep('<', -distance) 18 | end 19 | 20 | local undo = require('gomove.undo') 21 | undo.Handle( 22 | (going_right and "right" or "left") 23 | ) 24 | vim.cmd("silent! "..line_start..','..line_end..action) 25 | undo.Save( 26 | (going_right and "right" or "left") 27 | ) 28 | 29 | return true 30 | end 31 | 32 | 33 | function lh.duplicate(vim_start, vim_end, count) 34 | if not vim.o.modifiable or count == 0 then 35 | return false 36 | end 37 | 38 | local utils = require("gomove.utils") 39 | local going_right = count > 0 40 | 41 | local line_start = vim.fn.line(vim_start) 42 | local line_end = vim.fn.line(vim_end) 43 | 44 | local lines_between = utils.range(line_start, line_end) 45 | 46 | local register = 'z' 47 | local old_reg_value = vim.fn.getreg(register) 48 | 49 | --Go through each line and do action to each separately 50 | for _, line in ipairs(lines_between) do 51 | local raw_content = vim.fn.getline(line) 52 | -- Remove whitespace at start of line 53 | local current_content = ( 54 | not going_right 55 | --ignore indent when going left 56 | and raw_content:gsub('^%s+', '') 57 | or raw_content 58 | ) 59 | vim.fn.setreg(register, current_content) 60 | 61 | --Get the current line's content, set a register to it, 62 | --And paste to the end/indent of each accordingly. 63 | if going_right then 64 | vim.fn.cursor(line, #raw_content) 65 | vim.cmd('silent! normal! "'..register..math.abs(count).."p") 66 | else 67 | vim.fn.cursor(line, vim.fn.indent(line)+1) 68 | vim.cmd('silent! normal! "'..register..math.abs(count).."P") 69 | end 70 | end 71 | 72 | vim.fn.setreg(register, old_reg_value) 73 | 74 | return true 75 | end 76 | 77 | return lh 78 | -------------------------------------------------------------------------------- /lua/gomove/selection/line/vertical.lua: -------------------------------------------------------------------------------- 1 | local lv = {} 2 | 3 | function lv.move(vim_start, vim_end, distance) 4 | if not vim.o.modifiable or distance == 0 then 5 | return false 6 | end 7 | 8 | -- initial values{{{ 9 | local utils = require("gomove.utils") 10 | local going_down = (distance > 0) 11 | local old_pos = vim.fn.winsaveview() 12 | 13 | local line_start = vim.fn.line(vim_start) 14 | local line_end = vim.fn.line(vim_end) 15 | --}}} 16 | -- Compute Destination Line{{{ 17 | local destn = require('gomove.selection.handle_vertical') 18 | local destn_start, destn_end = destn.Handle( 19 | "l", line_start, line_end, distance 20 | ) 21 | 22 | --If there is no actual movement, stop right here and don't do anything else. 23 | if line_start == destn_start and line_end == destn_end then 24 | return false 25 | end 26 | --}}} 27 | --Make up for the oddness of :move{{{ 28 | local move_translated_destn = destn_start 29 | if going_down then 30 | move_translated_destn = destn_end 31 | else 32 | move_translated_destn = destn_start-1 33 | end 34 | --}}} 35 | -- Move{{{ 36 | local undo = require('gomove.undo') 37 | undo.Handle( 38 | (going_down and "down" or "up") 39 | ) 40 | 41 | vim.fn.winrestview(old_pos) 42 | vim.cmd(line_start..','..line_end..'move'..move_translated_destn) 43 | --}}} 44 | -- Reindenting Setting New Position{{{ 45 | local new_line_start = vim.fn.line("'[") 46 | local new_line_end = vim.fn.line("']") 47 | local opts = require("gomove").opts 48 | if opts.reindent == true then 49 | utils.reindent(new_line_start, new_line_end) 50 | end 51 | 52 | vim.fn.cursor(new_line_start, 1) 53 | vim.cmd("normal! 0m[") 54 | vim.fn.cursor(new_line_end, 1) 55 | vim.cmd("normal! $m]")--}}} 56 | 57 | undo.Save( 58 | (going_down and "down" or "up") 59 | ) 60 | 61 | return true 62 | end 63 | 64 | function lv.duplicate(vim_start, vim_end, count) 65 | if not vim.o.modifiable or count == 0 then 66 | return false 67 | end 68 | 69 | -- initial variables{{{ 70 | local utils = require('gomove.utils') 71 | local going_down = (count > 0) 72 | 73 | local line_start = vim.fn.line(vim_start) 74 | local line_end = vim.fn.line(vim_end) 75 | local height = utils.user_height(line_start, line_end) 76 | --}}} 77 | -- copy{{{ 78 | local times_done = 0 79 | if going_down then 80 | for _=1, math.abs(count) do 81 | vim.cmd(line_start..','..line_end..'copy'..line_end) 82 | times_done = times_done + 1 83 | end 84 | else 85 | for _=1, math.abs(count) do 86 | vim.cmd(line_start..','..line_end..'copy'..line_start-1) 87 | times_done = times_done + 1 88 | end 89 | end 90 | --}}} 91 | --Set new position{{{ 92 | local offset=times_done+(height-1) 93 | if going_down then 94 | vim.fn.cursor(line_start+offset, 1) 95 | vim.cmd('normal! 0m[') 96 | vim.fn.cursor(line_end+offset, 1) 97 | vim.cmd('normal! $m]') 98 | else 99 | vim.fn.cursor(line_start, 1) 100 | vim.cmd('normal! 0m[') 101 | vim.fn.cursor(line_end, 1) 102 | vim.cmd('normal! $m]') 103 | end--}}} 104 | 105 | return true 106 | end 107 | 108 | return lv 109 | -------------------------------------------------------------------------------- /lua/gomove/undo.lua: -------------------------------------------------------------------------------- 1 | local undo = {} 2 | 3 | local function check_changedtick_direction(prev, current) 4 | if prev.ct == current.ct then 5 | if prev.direction == current.direction then 6 | vim.cmd('silent! undojoin') 7 | return true 8 | end 9 | end 10 | return false 11 | end 12 | 13 | local function check_changedtick(prev, current) 14 | if prev.ct == current.ct then 15 | vim.cmd('silent! undojoin') 16 | return true 17 | end 18 | return false 19 | end 20 | 21 | --Check two things, 22 | --if there were any other changes (b:changedtick), 23 | --and if the direction is the same 24 | function undo.Handle(direction) 25 | local opts = require("gomove").opts 26 | if opts.undojoin == true then 27 | local prev_state = vim.b.gomove_prev or {ct = -1, direction = nil} 28 | vim.b.gomove_state = {ct = vim.b.changedtick, direction = direction} 29 | local current_state = vim.b.gomove_state 30 | return check_changedtick_direction(prev_state, current_state) 31 | end 32 | end 33 | 34 | function undo.NoDirection() 35 | local prev_state = vim.b.gomove_prev or {ct = -1, direction = nil} 36 | local current_state = vim.b.gomove_state or {ct = -2, direction = nil} 37 | return check_changedtick(prev_state, current_state) 38 | end 39 | 40 | function undo.Save(direction) 41 | vim.b.gomove_prev = {ct = vim.b.changedtick, direction = direction} 42 | end 43 | 44 | return undo 45 | -------------------------------------------------------------------------------- /lua/gomove/utils.lua: -------------------------------------------------------------------------------- 1 | local utils = {} 2 | 3 | -- creates a number range 4 | function utils.range(from, to) 5 | local result = {} 6 | local counter = from 7 | while (counter <= to) do 8 | table.insert(result, counter) 9 | counter = counter+1 10 | end 11 | return result 12 | end 13 | 14 | -- checks if selection contains fold 15 | function utils.contains_fold(startnum, endnum) 16 | local result = {} 17 | local counter = startnum 18 | while (counter <= endnum) do 19 | if vim.fn.foldclosed(counter) ~= -1 then 20 | table.insert(result, { 21 | vim.fn.foldclosed(counter), vim.fn.foldclosedend(counter) 22 | }) 23 | if vim.fn.foldclosedend(counter) > endnum then 24 | break 25 | else 26 | counter = vim.fn.foldclosedend(counter) 27 | end 28 | end 29 | counter = counter+1 30 | end 31 | return (next(result) and true or false), result 32 | end 33 | 34 | -- returns foldclosed() if there is a fold 35 | function utils.fold_start(num) 36 | return (vim.fn.foldclosed(num) ~= -1 37 | and vim.fn.foldclosed(num) or num) 38 | end 39 | 40 | -- returns foldclosedend() if there is a fold 41 | function utils.fold_end(num) 42 | return (vim.fn.foldclosedend(num) ~= -1 43 | and vim.fn.foldclosedend(num) or num) 44 | end 45 | 46 | -- checks the height of a range based on what user sees 47 | function utils.user_height(start_range, end_range) 48 | local counter = 0 49 | local state = start_range 50 | while (state <= end_range) do 51 | counter = counter+1 52 | if vim.fn.foldclosedend(state) ~= -1 then 53 | state = vim.fn.foldclosedend(state)+1 54 | else 55 | state = state + 1 56 | end 57 | end 58 | return counter 59 | end 60 | 61 | -- reindents lines between start and end 62 | function utils.reindent(new_line_start, new_line_end) 63 | local contains_fold = utils.contains_fold(new_line_start, new_line_end) 64 | if not contains_fold then 65 | vim.fn.cursor(new_line_start, 1) 66 | 67 | local old_indent = vim.fn.indent('.') 68 | vim.cmd("silent! normal! ==") 69 | local new_indent = vim.fn.indent('.') 70 | 71 | if new_line_start < new_line_end and old_indent ~= new_indent then 72 | local op = (old_indent < new_indent 73 | and string.rep(">", new_indent - old_indent) 74 | or string.rep("<", old_indent - new_indent)) 75 | local old_sw = vim.fn.shiftwidth() 76 | vim.o.shiftwidth = 1 77 | vim.cmd('silent! '..new_line_start+1 ..','..new_line_end..op) 78 | vim.o.shiftwidth = old_sw 79 | end 80 | end 81 | return new_line_start, new_line_end 82 | end 83 | 84 | --If there is a fold in the destination, open it 85 | function utils.open_fold(line_start, line_end) 86 | local destn_has_fold, destn_folds = utils.contains_fold( 87 | line_start, line_end 88 | ) 89 | if destn_has_fold then 90 | for _, position in ipairs(destn_folds) do 91 | vim.cmd(position[1]..","..position[2].."foldopen") 92 | end 93 | end 94 | return destn_has_fold, destn_folds 95 | end 96 | 97 | return utils 98 | -------------------------------------------------------------------------------- /plugin/gomove.lua: -------------------------------------------------------------------------------- 1 | require('gomove.mappings').SetMaps() 2 | --------------------------------------------------------------------------------