javascripthtmlcss

How to highlight caret's active line number on the editor


I am trying to highlight the current active line on the numbering div. I am able to get the current line triggered by the caret's activity in textarea. However I am unable to effectively use this information to manipulate the css background around the active line in my numbering div.

I have tried the following code but it is not working.

How should I solve this problem?

const ta = document.querySelector('textarea')

function updateRowNumbering() {
  const num = ta.value.split("\n").length;
  const numbers = ta.parentNode.querySelector(".numbers");
  numbers.innerHTML = Array(num).fill(`<span style="overflow: hidden"></span>`).join("");
}

ta.textContent = `I love
Tanzania
Nasila
America
Football
JavaScript
Chess
Ang Probinsyano
Startimes
China`

updateRowNumbering();


function getCurrentLineNumber() {
  // Get the caret's position in the textarea.
  const caretPosition = document.querySelector('#textarea').selectionStart;

  // Get the text of the textarea.
  const text = document.querySelector('#textarea').value;

  // Split the text into lines.
  const accumulativeArray = [0];

  // Iterate over the lines of the text and add the length of each line to the accumulative array.
  const lines = text.split('\n');
  for (let i = 0; i < lines.length; i++) {
    accumulativeArray.push(accumulativeArray[i] + lines[i].length+1);
  }

 // Find the index in the accumulative array where the value is greater than or equal to the caret position, taking into account the case where the caret position is less than the immediately greater accumulation.
  let lineNumber = accumulativeArray.findIndex(value => value > caretPosition);
  return /*caretPosition/*/lineNumber//+`caretPosition`+accumulativeArray//lines.length - 1;
}

function highlightLineNumber(lineNumber) {
  // Get the numbering div element.
   const numberingDiv = document.querySelectorAll('.editor > .numbers > span');

  // Get the span element that corresponds to the given line number.
  const lineNumberSpan = numberingDiv[lineNumber-1]
  lineNumberSpan.style.backgroundColor = 'gray';
}


//user clicked on in the line corresponding with numbering div:
const numbersBG = document.querySelector('#textarea');

numbersBG.addEventListener('click', function(event) {
  // Get the line number that the user clicked on.
  const currentlinenumber = getCurrentLineNumber()
  // Highlight gray backgraound on the respective region with the same number found;
  highlightLineNumber(currentlinenumber)
});
body,
textarea {
  font-family: Consolas, "Courier New", Courier, monospace;
}

.editor {
  display: inline-grid;
  grid-template-columns: 3em auto;
  gap: 10px;
  line-height: 21px;
  background: rgb(40 42 58);
  border-radius: 2px;
  overflow-y: auto;
  max-height: 243px;
}

.editor>* {
  padding-top: 10px;
  padding-bottom: 10px;
}

.numbers {
  text-align: right;
  background: #333;
  padding-right: 5px;
}

.numbers span {
  counter-increment: linenumber;
}

.numbers span::before {
  content: counter(linenumber);
  display: block;
  color: #888;
}

textarea {
  line-height: 21px;
  border: 0;
  background: transparent;
  color: #fff;
  min-width: 500px;
  outline: none;
  resize: none;
  padding-right: 10px;
}
<body>
<div class="editor">
  <div class="numbers">
   <span></span>
  </div>
<textarea wrap="off" id="textarea" rows="10" cols="50" onkeyup="updateRowNumbering(this)" ></textarea>
</div>
</body>


Solution

  • If I add lineNumberSpan.style.display = 'block'; where the background is applied, I'll get the number visibly highlighted.

    Though it seems to work only on click, not on movement with the arrow keys. And additionally the style is not taken away, when clicking on another line in the textarea. Is that what you want? Otherwise you'll need to further develop your js on that.

    const ta = document.querySelector('textarea')
    
    function updateRowNumbering() {
      const num = ta.value.split("\n").length;
      const numbers = ta.parentNode.querySelector(".numbers");
      numbers.innerHTML = Array(num).fill(`<span style="overflow: hidden"></span>`).join("");
    }
    
    ta.textContent = `I love
    Tanzania
    Nasila
    America
    Football
    JavaScript
    Chess
    Ang Probinsyano
    Startimes
    China`
    
    updateRowNumbering();
    
    
    function getCurrentLineNumber() {
      // Get the caret's position in the textarea.
      const caretPosition = document.querySelector('#textarea').selectionStart;
    
      // Get the text of the textarea.
      const text = document.querySelector('#textarea').value;
    
      // Split the text into lines.
      const accumulativeArray = [0];
    
      // Iterate over the lines of the text and add the length of each line to the accumulative array.
      const lines = text.split('\n');
      for (let i = 0; i < lines.length; i++) {
        accumulativeArray.push(accumulativeArray[i] + lines[i].length+1);
      }
    
     // Find the index in the accumulative array where the value is greater than or equal to the caret position, taking into account the case where the caret position is less than the immediately greater accumulation.
      let lineNumber = accumulativeArray.findIndex(value => value > caretPosition);
      return /*caretPosition/*/lineNumber//+`caretPosition`+accumulativeArray//lines.length - 1;
    }
    
    function highlightLineNumber(lineNumber) {
      // Get the numbering div element.
       const numberingDiv = document.querySelectorAll('.editor > .numbers > span');
    
      // Get the span element that corresponds to the given line number.
      const lineNumberSpan = numberingDiv[lineNumber-1]
      lineNumberSpan.style.backgroundColor = 'yellow';
      lineNumberSpan.style.display = 'block';
    }
    
    
    //user clicked on in the line corresponding with numbering div:
    const numbersBG = document.querySelector('#textarea');
    
    numbersBG.addEventListener('click', function(event) {
      // Get the line number that the user clicked on.
      const currentlinenumber = getCurrentLineNumber()
      // Highlight gray backgraound on the respective region with the same number found;
      highlightLineNumber(currentlinenumber)
    });
    body,
    textarea {
      font-family: Consolas, "Courier New", Courier, monospace;
    }
    
    .editor {
      display: inline-grid;
      grid-template-columns: 3em auto;
      gap: 10px;
      line-height: 21px;
      background: rgb(40 42 58);
      border-radius: 2px;
      overflow-y: auto;
      max-height: 243px;
    }
    
    .editor>* {
      padding-top: 10px;
      padding-bottom: 10px;
    }
    
    .numbers {
      text-align: right;
      background: #333;
      padding-right: 5px;
    }
    
    .numbers span {
      counter-increment: linenumber;
    }
    
    .numbers span::before {
      content: counter(linenumber);
      display: block;
      color: #888;
    }
    
    textarea {
      line-height: 21px;
      border: 0;
      background: transparent;
      color: #fff;
      min-width: 500px;
      outline: none;
      resize: none;
      padding-right: 10px;
    }
    <body>
    <div class="editor">
      <div class="numbers">
       <span></span>
      </div>
    <textarea wrap="off" id="textarea" rows="10" cols="50" onkeyup="updateRowNumbering(this)" ></textarea>
    </div>
    </body>