Visual Studio: hotkeys to move line up/down and move through recent changes

23,598

Solution 1

The answers proposed work, but none of them are as nice as eclipse with regard to how they preserve the existing paste buffer, the currently selected characters, and they do not allow the user to operate upon a range of lines. Here is a solution I came up with that preserves the paste buffer, the current character selection, and works with or without a selection (that may or may not span multiple rows):

'' Duplicates the current line (or selection of lines) and places the copy
'' one line below or above the current cursor position (based upon the parameter)
Sub CopyLine(ByVal movingDown As Boolean)
    DTE.UndoContext.Open("CopyLine")
    Dim objSel As TextSelection = DTE.ActiveDocument.Selection

    ' store the original selection and cursor position
    Dim topPoint As TextPoint = objSel.TopPoint
    Dim bottomPoint As TextPoint = objSel.BottomPoint
    Dim lTopLine As Long = topPoint.Line
    Dim lTopColumn As Long = topPoint.LineCharOffset
    Dim lBottomLine As Long = bottomPoint.Line
    Dim lBottomColumn As Long = bottomPoint.LineCharOffset()

    ' copy each line from the top line to the bottom line
    Dim readLine As Long = lTopLine
    Dim endLine As Long = lBottomLine + 1
    Dim selectionPresent As Boolean = ((lTopLine <> lBottomLine) Or (lTopColumn <> lBottomColumn))
    If (selectionPresent And (lBottomColumn = 1)) Then
        ' A selection is present, but the cursor is in front of the first character
        ' on the bottom line. exclude that bottom line from the copy selection.
        endLine = lBottomLine
    End If

    ' figure out how many lines we are copying, so we can re-position
    ' our selection after the copy is done
    Dim verticalOffset As Integer = 0
    If (movingDown) Then
        verticalOffset = endLine - lTopLine
    End If

    ' copy each line, one at a time.
    ' The Insert command doesn't handle multiple lines well, and we need
    ' to use Insert to avoid autocompletions
    Dim insertLine As Long = endLine
    While (readLine < endLine)
        ' move to read postion, and read the current line
        objSel.MoveToLineAndOffset(readLine, 1)
        objSel.EndOfLine(True) 'extend to EOL
        Dim lineTxt As String = objSel.Text.Clone
        ' move to the destination position, and insert the copy
        objSel.MoveToLineAndOffset(insertLine, 1)
        objSel.Insert(lineTxt)
        objSel.NewLine()
        ' adjust the read & insertion points
        readLine = readLine + 1
        insertLine = insertLine + 1
    End While

    ' restore the cursor to original position and selection
    objSel.MoveToLineAndOffset(lBottomLine + verticalOffset, lBottomColumn)
    objSel.MoveToLineAndOffset(lTopLine + verticalOffset, lTopColumn, True)
    DTE.UndoContext.Close()
End Sub

'' Duplicates the current line (or selection of lines) and places the copy
'' one line below the current cursor position
Sub CopyLineDown()
    CopyLine(True)
End Sub

'' Duplicates the current line (or selection of lines) and places the copy
'' one line above the current cursor position
Sub CopyLineUp()
    CopyLine(False)
End Sub


'' Moves the selected lines up one line. If no line is
'' selected, the current line is moved.
''
Sub MoveLineUp()
    DTE.UndoContext.Open("MoveLineUp")
    Dim objSel As TextSelection = DTE.ActiveDocument.Selection
    ' store the original selection and cursor position
    Dim topPoint As TextPoint = objSel.TopPoint
    Dim bottomPoint As TextPoint = objSel.BottomPoint
    Dim lTopLine As Long = topPoint.Line
    Dim lTopColumn As Long = topPoint.LineCharOffset
    Dim lBottomLine As Long = bottomPoint.Line
    Dim lBottomColumn As Long = bottomPoint.LineCharOffset()

    Dim textLineAbove As TextSelection = DTE.ActiveDocument.Selection
    textLineAbove.MoveToLineAndOffset(lTopLine - 1, 1, False)
    textLineAbove.MoveToLineAndOffset(lTopLine, 1, True)
    Dim indentChange As Integer = CountIndentations(textLineAbove.Text) * -1

    ' If multiple lines are selected, but the bottom line doesn't
    ' have any characters selected, don't count it as selected
    Dim lEffectiveBottomLine = lBottomLine
    If ((lBottomColumn = 1) And (lBottomLine <> lTopLine)) Then
        lEffectiveBottomLine = lBottomLine - 1
    End If

    ' move to the line above the top line
    objSel.MoveToLineAndOffset(lTopLine - 1, 1)
    ' and move it down, until its below the bottom line:
    Do
        DTE.ExecuteCommand("Edit.LineTranspose")
    Loop Until (objSel.BottomPoint.Line >= lEffectiveBottomLine)
    ' Since the line we are on has moved up, our location in the file has changed:
    lTopLine = lTopLine - 1
    lBottomLine = lBottomLine - 1

    IndentBlockAndRestoreSelection(objSel, lBottomLine, lBottomColumn, lTopLine, lTopColumn, indentChange)

    DTE.UndoContext.Close()
End Sub

'' Moves the selected lines down one line. If no line is
'' selected, the current line is moved.
''
Sub MoveLineDown()
    DTE.UndoContext.Open("MoveLineDown")
    Dim objSel As TextSelection = DTE.ActiveDocument.Selection
    ' store the original selection and cursor position
    Dim topPoint As TextPoint = objSel.TopPoint
    Dim bottomPoint As TextPoint = objSel.BottomPoint
    Dim lTopLine As Long = topPoint.Line
    Dim lTopColumn As Long = topPoint.LineCharOffset
    Dim lBottomLine As Long = bottomPoint.Line
    Dim lBottomColumn As Long = bottomPoint.LineCharOffset()

    ' If multiple lines are selected, but the bottom line doesn't
    ' have any characters selected, don't count it as selected
    Dim lEffectiveBottomLine = lBottomLine
    If ((lBottomColumn = 1) And (lBottomLine <> lTopLine)) Then
        lEffectiveBottomLine = lBottomLine - 1
    End If

    Dim textLineBelow As TextSelection = DTE.ActiveDocument.Selection
    textLineBelow.MoveToLineAndOffset(lEffectiveBottomLine + 1, 1, False)
    textLineBelow.MoveToLineAndOffset(lEffectiveBottomLine + 2, 1, True)
    Dim indentChange As Integer = CountIndentations(textLineBelow.Text)


    ' move to the bottom line
    objSel.MoveToLineAndOffset(lEffectiveBottomLine, 1)
    ' and move it down, which effectively moves the line below it up
    ' then move the cursor up, always staying one line above the line
    ' that is moving up, and keep moving it up until its above the top line:
    Dim lineCount As Long = lEffectiveBottomLine - lTopLine
    Do
        DTE.ExecuteCommand("Edit.LineTranspose")
        objSel.LineUp(False, 2)
        lineCount = lineCount - 1
    Loop Until (lineCount < 0)
    ' Since the line we are on has moved down, our location in the file has changed:
    lTopLine = lTopLine + 1
    lBottomLine = lBottomLine + 1

    IndentBlockAndRestoreSelection(objSel, lBottomLine, lBottomColumn, lTopLine, lTopColumn, indentChange)

    DTE.UndoContext.Close()
End Sub

'' This method takes care of indenting the selected text by the indentChange parameter
'' It then restores the selection to the lTopLine:lTopColumn - lBottomLine:lBottomColumn parameter.
'' It will adjust these values according to the indentChange performed
Sub IndentBlockAndRestoreSelection(ByVal objSel As TextSelection, ByVal lBottomLine As Long, ByVal lBottomColumn As Long, ByVal lTopLine As Long, ByVal lTopColumn As Long, ByVal indentChange As Integer)
    ' restore the cursor to original position and selection
    objSel.MoveToLineAndOffset(lBottomLine, lBottomColumn)
    objSel.MoveToLineAndOffset(lTopLine, lTopColumn, True)
    If (indentChange = 0) Then
        ' If we don't change the indent, we are done
        Return
    End If

    If (lBottomLine = lTopLine) Then
        If (indentChange > 0) Then
            objSel.StartOfLine()
        Else
            objSel.StartOfLine()
            objSel.WordRight()
        End If
    End If
    objSel.Indent(indentChange)

    ' Since the selected text has changed column, adjust the columns accordingly:
    ' restore the cursor to original position and selection
    Dim lNewBottomColumn As Long = (lBottomColumn + indentChange)
    Dim lNewTopColumn As Long = (lTopColumn + indentChange)
    ' ensure that we we still on the page.
    ' The "or" clause makes it so if we were at the left edge of the line, we remain on the left edge.
    If ((lNewBottomColumn < 2) Or (lBottomColumn = 1)) Then
        ' Single line selections, or a bottomColumn that is already at 1 may still have a new BottomColumn of 1
        If ((lTopLine = lBottomLine) Or (lBottomColumn = 1)) Then
            lNewBottomColumn = 1
        Else
            ' If we have multiple lines selected, don't allow the bottom edge to touch the left column,
            ' or the next move will ignore that bottom line.
            lNewBottomColumn = 2
        End If
    End If
    If ((lNewTopColumn < 2) Or (lTopColumn = 1)) Then
        lNewTopColumn = 1
    End If

    ' restore the selection to the modified selection
    objSel.MoveToLineAndOffset(lBottomLine, lNewBottomColumn)
    objSel.MoveToLineAndOffset(lTopLine, lNewTopColumn, True)
End Sub


'' This method counts the indentation changes within the text provided as the paramter
Function CountIndentations(ByVal text As String) As Integer
    Dim indent As Integer = 0
    While (Text.Length > 0)
        If (Text.StartsWith("//")) Then
            Dim endOfLine As Integer = Text.IndexOf("\n", 2)
            If (Equals(endOfLine, -1)) Then
                ' The remaining text is all on one line, so the '//' terminates our search
                ' Ignore the rest of the text
                Exit While
            End If
            ' continue looking after the end of line
            Text = Text.Substring(endOfLine + 1)
        End If

        If (Text.StartsWith("/*")) Then
            Dim endComment As Integer = Text.IndexOf("*/", 2)
            If (Equals(endComment, -1)) Then
                ' This comment continues beyond the length of this line.
                ' Ignore the rest of the text
                Exit While
            End If
            ' continue looking after the end of this comment block
            Text = Text.Substring(endComment + 1)
        End If

        If (Text.StartsWith("{")) Then
            indent = indent + 1
        Else
            If (Text.StartsWith("}")) Then
                indent = indent - 1
            End If
        End If
        Text = Text.Substring(1)
    End While
    Return indent
End Function

I edited this post to add the UndoContext mechanism (suggested by Nicolas Dorier) at the beginning of the MoveLineUp() and MoveLineDown() methods and closing it at their end. 11/23/11 - I updated this again to allow the moved lines to indent themselves as you cross bracket boundaries

Solution 2

For anyone looking for a way to do this in Visual Studio 2010, the free Visual Studio 2010 Pro Power Tools extension adds the capability to move lines up and down.

http://visualstudiogallery.msdn.microsoft.com/en-us/d0d33361-18e2-46c0-8ff2-4adea1e34fef

Solution 3

If you haven't already found it, the place where these keyboard shortcuts are setup is under Tools | Options | Environment | Keyboard. A lot of handy commands can be found just by browsing through the list, although unfortunately I've never found any good reference for describing what each command is intended to do.

As for specific commands:

  • I believe the forward/backward navigation commands you're referring to are View.NavigateBackward and View.NavigateForward. If your keyboard isn't cooperating with the VS key bindings, you can remap them to your preferred Eclipse keys. Unfortunately, I don't know of a way to change the algorithm it uses to actually decide where to go.

  • I don't think there's a built-in command for duplicating a line, but hitting Ctrl+C with no text selected will copy the current line onto the clipboard. Given that, here's a simple macro that duplicates the current line on the next lower line:


    Sub CopyLineBelow()
        DTE.ActiveDocument.Selection.Collapse()
        DTE.ActiveDocument.Selection.Copy()
        DTE.ActiveDocument.Selection.Paste()
    End Sub

    Sub CopyLineAbove()
        DTE.ActiveDocument.Selection.Collapse()
        DTE.ActiveDocument.Selection.Copy()
        DTE.ActiveDocument.Selection.LineUp()
        DTE.ActiveDocument.Selection.Paste()
    End Sub
  • For moving a line of text around, Edit.LineTranspose will move the selected line down. I don't think there's a command for moving a line up, but here's a quick macro that does it:

    Sub MoveLineUp()
        DTE.ActiveDocument.Selection.Collapse()
        DTE.ActiveDocument.Selection.Cut()
        DTE.ActiveDocument.Selection.LineUp()
        DTE.ActiveDocument.Selection.Paste()
        DTE.ActiveDocument.Selection.LineUp()
    End Sub

If you haven't yet started playing with macros, they are really useful. Tools | Macros | Macros IDE will take you the editor, and once they're defined, you can setup keyboard shortcuts through the same UI I mentioned above. I generated these macros using the incredibly handy Record Temporary Macro command, also under Tools | Macros. This command lets you record a set of keyboard inputs and replay them any number of times, which is good for building advanced edit commands as well as automating repetitive tasks (e.g. code reformatting).

Solution 4

I have recently done the same thing and moved from Eclipse to Visual Studio when I moved onto a new project. The Resharper add in is highly recommended - it adds some of the rich editing, navigational and refactoring functionality that eclipse has to VS.

Resharper also allows you to use a keybaord mapping scheme that is very simillar to InteliJ. Very handy for the Java escapees...

Regarding your second question, Resharper has the same move code up / down function as eclipse, but with some caveats. Firstly, using the InteliJ keyboard mappings, the key combination is rather tortuous.

Move code up: ctrl + shift + alt + up cursor

Move code down: ctrl + shift + alt + down cursor

Secondly, it does not always move by just one line, but actually jumps code blocks. So it cannot move a line from outside an if statement to inside it - it jumps the selected line right over the if block. To do that you need to move "left" and "right" using

Move code into outer code block: ctrl + shift + alt + left cursor

Move code into next inner code block: ctrl + shift + alt + right cursor

Solution 5

Record a macro in visual studio to do the alt-arrow thing:

ctrl-alt-r -- record mode
ctrl-c -- copy a line
up arrow -- go up a line
home -- beginning of line (maybe there is a way to paste before the current line without this)
ctrl-v -- paste
ctrl-alt-r -- end record mode

Now you can map this macro to any set of keystrokes you like using the macros ide and the keyboard preferences.

Share:
23,598
Angry Dan
Author by

Angry Dan

web/software developer, .NET, C#, WPF, PHP, software trainer, English teacher, have philosophy degree, love languages, run marathons my tweets: http://www.twitter.com/edward_tanguay my runs: http://www.tanguay.info/run my code: http://www.tanguay.info/web my publications: PHP 5.3 training video (8 hours, video2brain) my projects: http://www.tanguay.info

Updated on July 21, 2020

Comments

  • Angry Dan
    Angry Dan almost 4 years

    I'm moving from Eclipse to Visual Studio .NET and have found all my beloved hotkeys except two:

    • in Eclipse you can press ALT- and ALT- to visit recent changes you have made, something I use frequently to go back to where I was in some other file and then return. Apparently in VS.NET the CTRL-- and CTRL-SHIFT-- do this but they don't seem to always work (e.g. on laptop, may be a numkey issue with the minus) and don't seem to follow the same algorithm of "where I was" as I am used to in Eclipse. Has anyone gotten this to work and rely on it daily, etc.?
    • in Eclipse, to move a line up or down you press ALT-uparrow or ALT-downarrow and you just move it through the code until you get it to where you want it, very nice. Also to make a copy of a line, you can press SHIFT-ALT-uparrow or SHIFT-ALT-downarrow. Both of these hotkeys even work for block of lines that you have selected.

    Has anyone discovered these hotkey features in Visual Studio .NET?

    A D D E N D U M :

    An example of when you would use the second feature described above is to move the bottom line here up into the for loop. In Eclipse, you would put the cursor on the Console.WriteLine and then press ALT-(uparrow), I use that all the time: one key stroke to move lines up and down.

    for (int i = 0; i < 10; i++) {
    
    }
    Console.WriteLine(i);
    

    Ok, extrapolating Charlie's idea with no-selection-ctrl-c to select a line, in Visual Studio you could put your cursor on Console.WriteLine, (no selection) press CTRL-X and then move up and press CTRL-V.

  • Angry Dan
    Angry Dan over 15 years
    The CTRL-C, CTRL-V with no selection to copy a line is nice, didn't know that one, thanks.
  • serg10
    serg10 over 15 years
    It kind of does have the move line up and down shortcut, but it doesn't work exactly like in eclipse - see my answer below.
  • Matt Blaine
    Matt Blaine about 14 years
    That is awesome. I wish I could vote it up more than once. Great job!
  • Borek Bernard
    Borek Bernard almost 14 years
    This should be the answer, not the Mitch Wheat's one. Great job!
  • Borek Bernard
    Borek Bernard almost 14 years
    Actually, this script doesn't work when I select lines "lazily", i.e. I start selection somewhere in the middle of line 1 and end somewhere in the middle of the last line. Eclipse handles this perfectly.
  • ocodo
    ocodo over 13 years
    "Lazy" select is working for me. The only irritation is MS's Macro notification popping up, there really should be a way to switch that off.
  • Paul Ostrowski
    Paul Ostrowski over 13 years
    Borek, I modified the CopyLine function to handle multi-line selection. It wasn't right before, but try it now.
  • Paul Ostrowski
    Paul Ostrowski over 12 years
    I have added this functionality to my solution. It's not 100% perfect, but covers most use cases.
  • javamonkey79
    javamonkey79 about 12 years
    This seems to be the easiest solution by far, but to be fair - I guess this question was asked before this solution was available.
  • Tommy Bravo
    Tommy Bravo almost 12 years
    Awesome! This works like a charm! For people not familiar with macro's and who are scared of VB: Just open the macro explorer (Tools | Macro's | Macro Explorer) and add a new Marco project. Copy and paste the above code in a marco module file. Afterwards search for the MoveLineUp command (or any of the other shown in the macro explorer) in the Keyboard shortcut settings (Tools | Options | Environment | Keyboard). When you found the macro, just assign the wanted shortcut keys (make sure they don't collide with any other).