1

If I have code like this:

const treeData = {
  name: "Parent",
  children: [
    {
      name: "Child 1",
      children: [
        {
          name: "Grandchild 1"
        },
        {
          name: "Grandchild 2"
        }
      ]
    },
    {
      name: "Child 2"
    }
  ]
};

and I linewise-select some code in the middle:

      children: [
        {
          name: "Grandchild 1"
        },
        {
          name: "Grandchild 2"
        }
      ]

How can I yank:

children: [
  {
    name: "Grandchild 1"
  },
  {
    name: "Grandchild 2"
  }
]

instead of:

      children: [
        {
          name: "Grandchild 1"
        },
        {
          name: "Grandchild 2"
        }
      ]

Or:

  name: "Grandchild 1"
},
{
  name: "Grandchild 2"

instead of:

          name: "Grandchild 1"
        },
        {
          name: "Grandchild 2"
4
  • 1
    More important than "how": why? What do you plan to do with the text you yanked?
    – romainl
    Commented Apr 2, 2023 at 15:17
  • 1
    @romainl normally, paste it online (e.g. like I did while making this question), or into another file
    – user9024
    Commented Apr 2, 2023 at 15:26
  • select using visual mode, do few <<, yank and then press u to undo
    – balki
    Commented Apr 2, 2023 at 22:11
  • @balki I don't know how to automate that, because I repeat << based on visual feedback (when the leftmost non-whitespace character arrives at column 1)
    – user9024
    Commented Apr 2, 2023 at 23:10

5 Answers 5

2

I would personally:

  1. Create a new buffer enew
  2. Paste using ]p (that correct the indent looking at the previous line indent
  3. Yank the text from there :2,$y
  4. Delete the buffer :bd!
3
  • I can't get ]g to work, it's not mapped and has no help page, in Vim 9.0 and neovim 0.8.3
    – user9024
    Commented Apr 2, 2023 at 17:28
  • I expect this solution might not work for me, because I often edit html and svelte files where the formatting and indentation rules are different at different parts of the file
    – user9024
    Commented Apr 2, 2023 at 17:33
  • My mistake I mean ]p :-/ Commented Apr 2, 2023 at 17:48
2

I would use visual block mode to select text without leading whitespace.

  • Go to the first line you want to yank (in your example the one with "children").
  • Press ^ to go to the first non-whitespace character.
  • Then press Ctrl+v to start visual block mode.
  • Scroll down all the lines you want to yank.
  • Press $ to make the selection run to the end of the selected lines.
  • Finally press y.

A point was brought up in a comment: what if the first line of the visual block isn't the one with the least indentation? In that case, it works almost the same with the following modifications:

  • Start at the line with the least indentation.
  • Select to the end (or beginning) of the block.
  • Press o to jump to the other end of the selection.
  • Select some more lines and proceed as above.

One caveat: if you put this into another vim buffer with text in it, it will paste the block into the existing text and garble it. This may not be what you want. So you would have to create some blank lines first. However, to paste it into another application e.g. a browser's text box it does not make a difference.

A closing remark: I might also feel tempted to remove indentation by repeatedly doing <gv, yank the lines and hammer u until it looks pretty again. Not really in the spirit of vim but it gets the job done. Talking about the difference between theory and practice.

2
  • The first line of the visual selection might not have the smallest amount of indentation
    – user9024
    Commented Apr 2, 2023 at 21:40
  • @theonlygusti it does in your example :-) Anyway, edited to address that case.
    – Friedrich
    Commented Apr 3, 2023 at 5:42
1

I found a way combining :help setreg() to yank text into :help v:register, :help indent() which returns a "virtcol", :help virtcol2col(), and :help [:] for string index-based slicing.

xnoremap <silent> <leader>y :<C-u>call setreg(v:register, map(range(line("'<"), line("'>")), "getline(v:val)[virtcol2col(0, v:val, min(map(range(line(\"'<\"), line(\"'>\")), \"indent(v:val)\"))):]"), "V")<CR>
xnoremap  -- visual mode remap
<silent> -- without echoing this command to the command line
<leader>y -- the keyboard sequence <leader>y
:<C-u>   -- open command mode and clear the auto-inserted '<,'> range, :help c_CTRL-U
call setreg( -- bufferless yank, :help setreg()
 v:register,  -- into the user-specified register name
 map(          -- a mapping of...
  range(line("'<"), line("'>")),
                    -- a list of the visually selected lines' line numbers...
  "getline(v:val)[  -- to the contents of each line starting from...
   virtcol2col(      -- the real column position of the...
    0,                -- current window's...
    v:val,            -- line being mapped's...
    min(map(range(line(\"'<\"), line(\"'>\")),
                        -- mininum of all visually-selected lines'...
     \"indent(v:val)\")   -- on-screen number of spaces before the first
                          -- non-whitespace character in the line
    )
   )
  :]"    -- up to the end of the line being mapped, :help [:]
 ),
"V")  -- yanked in visual-line mode, :help setreg()
<CR> -- all executed
1

Just reindent :h = after pasting.

For example, p=']

1

Let me provide a multiline version of the answer by @theonlygusti. This version does not rely on anti-quotations and global variables so I hope it might be considered more readable. When executed, it sets Y to perform an "unformatted yank" that clears the leading whitespace columns from the selection.

fun! YankUFmt() range
  let rng = range(line("'<"), line("'>"))
  let minval = min(map(copy(rng), {key, val -> indent(val)} ))
  let res = map(copy(rng), {key, val -> getline(val)[virtcol2col(0, val, minval):]})
  call setreg('+', res, "V")
endfun

vnoremap <buffer> Y :call YankUFmt()<CR>
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.