Find out the 'line' (row) number of the cursor in a textarea

I would like to find out and keep track of the 'line number' (rows) of the cursor in a textarea. (The 'bigger picture' is to parse the text on the line every time a new line is created/modified/selected, if of course the text was not pasted in. This saves parsing the whole text un-necessarily at set intervals.)

There are a couple of posts on StackOverflow however none of them specifically answer my question, most questions are for cursor position in pixels or displaying lines numbers besides the textarea.

My attempt is below, it works fine when starting at line 1 and not leaving the textarea. It fails when clicking out of the textarea and back onto it on a different line. It also fails when pasting text into it because the starting line is not 1.

My JavaScript knowledge is pretty limited.

<html>

<head>
<title>DEVBug</title>

<script type="text/javascript">

    var total_lines = 1; // total lines
    var current_line = 1; // current line
    var old_line_count;

    // main editor function
    function code(e) {

        // declare some needed vars
        var keypress_code = e.keyCode; // key press
        var editor = document.getElementById('editor'); // the editor textarea
        var source_code = editor.value; // contents of the editor

        // work out how many lines we have used in total    
            var lines = source_code.split("\n");
            var total_lines = lines.length;

    // do stuff on key presses
    if (keypress_code == '13') { // Enter
        current_line += 1;
    } else if (keypress_code == '8') { // Backspace
        if (old_line_count > total_lines) { current_line -= 1; }
    } else if (keypress_code == '38') { // Up
        if (total_lines > 1 && current_line > 1) { current_line -= 1; }
    } else if (keypress_code == '40') { // Down
        if (total_lines > 1 && current_line < total_lines) { current_line += 1; }
    } else {
        //document.getElementById('keycodes').innerHTML += keypress_code;
    }

    // for some reason chrome doesn't enter a newline char on enter
    // you have to press enter and then an additional key for \n to appear
    // making the total_lines counter lag.
    if (total_lines < current_line) { total_lines += 1 };

    // putput the data
    document.getElementById('total_lines').innerHTML = "Total lines: " + total_lines;
    document.getElementById('current_line').innerHTML = "Current line: " + current_line;

    // save the old line count for comparison on next run
    old_line_count = total_lines;

}

</script>

</head>

<body>

<textarea id="editor" rows="30" cols="100" value="" onkeydown="code(event)"></textarea>
<div id="total_lines"></div>
<div id="current_line"></div>

</body>

</html>

Answers:

Answer

You would want to use selectionStart to do this.

<textarea onkeyup="getLineNumber(this, document.getElementById('lineNo'));" onmouseup="this.onkeyup();"></textarea>
<div id="lineNo"></div>

<script>

    function getLineNumber(textarea, indicator) {

        indicator.innerHTML = textarea.value.substr(0, textarea.selectionStart).split("\n").length;
    }

</script>

This works when you change the cursor position using the mouse as well.

Answer

This is tough because of word wrap. It's a very easy thing to count the number of line breaks present, but what happens when the new row is because of word wrap? To solve this problem, it's useful to create a mirror (credit: github.com/jevin). Here's the idea:

  1. Create a mirror of the textarea
  2. Send the content from the beginning of the textarea to the cursor to the mirror
  3. Use the height of the mirror to extract the current row

On JSFiddle

jQuery.fn.trackRows = function() {
    return this.each(function() {

    var ininitalHeight, currentRow, firstIteration = true;

    var createMirror = function(textarea) {
        jQuery(textarea).after('<div class="autogrow-textarea-mirror"></div>');
        return jQuery(textarea).next('.autogrow-textarea-mirror')[0];
    }

    var sendContentToMirror = function (textarea) {
        mirror.innerHTML = String(textarea.value.substring(0,textarea.selectionStart-1)).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br />') + '.<br/>.';
        calculateRowNumber();
    }

    var growTextarea = function () {
        sendContentToMirror(this);
    }

    var calculateRowNumber = function () {
        if(firstIteration){
            ininitalHeight = $(mirror).height();
            currentHeight = ininitalHeight;
            firstIteration = false;
        } else {
            currentHeight = $(mirror).height();
        }
        // Assume that textarea.rows = 2 initially
        currentRow = currentHeight/(ininitalHeight/2) - 1;
        //remove tracker in production
        $('.tracker').html('Current row: ' + currentRow);
    }

    // Create a mirror
    var mirror = createMirror(this);

    // Style the mirror
    mirror.style.display = 'none';
    mirror.style.wordWrap = 'break-word';
    mirror.style.whiteSpace = 'normal';
    mirror.style.padding = jQuery(this).css('padding');
    mirror.style.width = jQuery(this).css('width');
    mirror.style.fontFamily = jQuery(this).css('font-family');
    mirror.style.fontSize = jQuery(this).css('font-size');
    mirror.style.lineHeight = jQuery(this).css('line-height');

    // Style the textarea
    this.style.overflow = "hidden";
    this.style.minHeight = this.rows+"em";

    var ininitalHeight = $(mirror).height();

    // Bind the textarea's event
    this.onkeyup = growTextarea;

    // Fire the event for text already present
    // sendContentToMirror(this);

    });
};

$(function(){
    $('textarea').trackRows();
});
Answer

This worked for me:

function getLineNumber(textarea) {
  return textarea.value.substr(0, textarea.selectionStart) // get the substring of the textarea's value up to the cursor position
    .split("\n") // split on explicit line breaks
    .map((line) => 1 + Math.floor(line.length / textarea.cols)) // count the number of line wraps for each split and add 1 for the explicit line break
    .reduce((a, b) => a + b, 0); // add all of these together
};

Inspired by colab's answer as a starting point, this includes the number of word wraps without having to introduce a mirror (as in bradbarbin's answer).

The trick is simply counting how many times the number of columns textarea.cols can divide the length of each segment between explicit line breaks \n.

Note: this starts counting at 1.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.