4

I came across a question, while writing some vimscript. I wanted to know the "distance in motion" between two marks in the buffer.

The interesting point of this can be to know how many time I have to exectue a vim motion to go from one mark to the other in order to calculate the number of moves between the two marks.

How would you proceed?

3
  • 2
    What kind of motions? For example, if motion is fixed (say w), you may overshoot the mark (if your mark is inside a word). If more than one type of motion is possible, you may need to invent an artificial intelligence-based algorithm :)
    – VanLaser
    Commented May 4, 2016 at 12:45
  • Yeah, I guess I haven't thought about this case ^^, but I think you could move forward (or backward) until you reach the mark or overtake it.
    – nobe4
    Commented May 4, 2016 at 12:53
  • 1
    The number of motions needed to jump to a mark is always exactly 1.
    – romainl
    Commented May 4, 2016 at 13:24

1 Answer 1

6

You could use this function to do what you want:

It takes 3 arguments:

  • A normal mode command (which should move the cursor)
  • The mark where to beginning the motions
  • The destination mark

It will return the minimal number of motions required to reach or overstep the destination mark

It test several cases and stop the function if

  • The direction of the motion (forward or backward) doesn't allow to reach the destionation mark
  • The command given as first parameter doesn't move the cursor
  • One of the mark is undefined
  • The mark are in the same position (0 is returned)

Examples of calls would be:

:call CountMotions("fa", "a", "b")
:call CountMotions("w", "c", "b")
:call CountMotions("h", "c", "e")

Beware if the first parameter is a destructive command (dd for example) it will be executed at least once.

Here is the function:

fun! CountMotions(motion, mark1, mark2)
    " Save the cursor position
    let savedPosition = getpos('.')

    " Get position of the marks
    execute "let m1Position = getpos(\"'" . a:mark1 . "\")"
    execute "let m2Position = getpos(\"'" . a:mark2 . "\")"

    " If a mark is undefined stop the function
    if (m1Position[1] == 0 && m1Position[2] == 0)
        echo "'" . a:mark1 . " is not defined"
        return -1
    endif
    if (m2Position[1] == 0 && m2Position[2] == 0)
        echo "'" . a:mark2 . " is not defined"
        return -1
    endif

    " If the marks are not on the same buffer stop the function
    if m1Position[0] != m2Position[0]
        echo "The marks are not on the same buffer"
        return -1
    endif

    " Position the cursor on the first mark
    call setpos('.', m1Position)

    " Get the distance between the two marks
    let distance = sqrt( pow((m2Position[1]-m1Position[1]),2) + pow(( m2Position[2]-m1Position[2] ),2) )
    let previousDistance = distance + 1

    " If the marks are on the same chatacter stop the function
    if ( distance == 0 )
        echo "the marks are on the same character"
        return 0
    endif

    " Check which mark is the first one in the buffer
    let firstMark = a:mark1
    if (m1Position[1] > m2Position[1])
        let firstMark = a:mark2
    elseif (m1Position[1] == m2Position[1])
        if (m1Position[2] > m2Position[2])
            let firstMark = a:mark2
        endif
    endif

    " Check in which direction the motion goes
        let direction = "forward"
        " Execute one motion and increment the count
        execute "normal " . a:motion
        " Get the position after the motion
        let newPosition = getpos('.')
        " Reposition the cursor on the first mark
        call setpos('.', m1Position)
        if (m1Position == newPosition)
            echo "The motion doesn't move the cursor"
            return -1
        endif
        if (m1Position[1] > newPosition[1])
            let direction = "backward"
        elseif (m1Position[1] == newPosition[1])
            if (m1Position[2] > newPosition[2])
                let direction = "backward"
            endif
        endif

    " If the direction doesn't allows to reach the second mark stop the function
    if ( (firstMark == a:mark1 && direction == "backward") || (firstMark == a:mark2 && direction == "forward") )
        echo "wrong way, mark unreachable"
        return -1
    endif

    let countMotion = 0
    let previousPosition = []
    let continue = 1

    " Count the number of motions
    while continue == 1
        " Execute one motion and increment the count
        execute "normal " . a:motion
        let countMotion = countMotion + 1

        " Update position
        let m1Position = getpos('.')

        " Calculate the changes in the distance
        let previousDistance = distance
        let distance = sqrt( pow((m2Position[1] - m1Position[1]), 2) + pow((m2Position[2] - m1Position[2]), 2))

        "echo "[" . m1Position[1] . " " . m1Position[2] . "]" . "[" . m2Position[1] . " " . m2Position[2] . "]" . "distances " . string( distance ) . "  " . string( previousDistance )

        " Check if the loop should continue
        if (distance >= previousDistance)
            let continue = 0
        endif
    endwhile

    " restore cursor position
    call setpos('.', savedPosition)

    echo a:motion . " needed: " . countMotion
    return countMotion
endfunction

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.