Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get the full word that cursor is touching (or within) JavaScript

With a native input field I can get the cursor's position just fine. Also any highlighted text easy enough.

// get cursor position
console.log(input.selectionStart);
// get highlighted text
console.log(input.value.substr(input.selectionStart, input.selectionEnd - input.selectionStart));

But how can we get the full word that the cursor is touching?

this is a |sent|ence|

Question:

If each pipe is a potential cursor position, arrow keyed or clicked in-and-around a word, how can we get the full word "sentence"?

Sub questions:

  1. Range or RegEx for this solution?
  2. Can RegEx lookaround by position in a string?
  3. Can Regex find the word if given character position in a string

Research:
https://javascript.info/selection-range.
https://developer.mozilla.org/en-US/docs/Web/API/range.
https://developer.mozilla.org/en-US/docs/Web/API/Document/caretRangeFromPoint (Chrome only)

Working CodePen with RegEx from a friend.

like image 517
Ben Racicot Avatar asked May 04 '20 18:05

Ben Racicot


2 Answers

You may use some regex to look for

  • any string containing word character ending at position
  • any string containing word character starting from position

const [$show, $input] = document.querySelectorAll('span,textarea')
const getWord = (s, pos) => {
  const n = s.substring(pos).match(/^[a-zA-Z0-9-_]+/)
  const p = s.substring(0, pos).match(/[a-zA-Z0-9-_]+$/)
  // if you really only want the word if you click at start or between
  // but not at end instead use if (!n) return
  if(!p && !n) return ''
  return (p || '') + (n || '')
}
const showWord = e => $show.innerText = getWord(e.target.value, e.target.selectionStart)
$input.addEventListener('click', showWord)
$input.addEventListener('input', showWord)
textarea{
  width: 500px;
  height: 500px;
}
<span id="inputshow"></span><br/>
<textarea>In computer science, an x-fast trie is a data structure for storing integers from a bounded domain. It supports exact and predecessor or successor queries in time O(log log M), using O(n log M) space, where n is the number of stored values and M is the maximum value in the domain. The structure was proposed by Dan Willard in 1982,[1] along with the more complicated y-fast trie, as a way to improve the space usage of van Emde Boas trees, while retaining the O(log log M) query time. </textarea>
like image 138
grodzi Avatar answered Oct 12 '22 12:10

grodzi


If you define "a word" as any succession of characters delimited by a space character, then you can simply use String.prototype.lastIndexOf and String.prototype.indexOf searching for a space character before and after your cursor's position, and then getting the substring from this range:

const inp = document.querySelector('input');
document.onselectionchange =
  inp.onselect = inp.onclick = // Firefox doesn't fire selectionchange in <input>
    (evt) => {
  const text = inp.value;
  const start_index = inp.selectionStart;
  const end_index = inp.selectionEnd;
  const previous_space_index = text.lastIndexOf( " ", start_index - 1 );
  const next_space_index = text.indexOf( " ", end_index );
  const begin = previous_space_index < 0 ? 0 : previous_space_index + 1;
  const end = next_space_index < 0 ? text.length : next_space_index;
  const between_spaces = text.substring( begin, end );

  console.log( between_spaces );
};
<input type="text" value="this is a sentence">

If you really need to define "a word" as any succession of characters matching /\w/, then it's a bit more convoluted:

const inp = document.querySelector('input');
document.onselectionchange =
  inp.onselect = inp.onclick = // Firefox doesn't fire selectionchange in <input>
    (evt) => {
  const text = inp.value;
  const start_index = inp.selectionStart;
  const end_index = inp.selectionEnd;
  // search in the before substring 
  const before_text = text.substring( 0, start_index );
  // for all non word characters
  const before_match = before_text.match( /\W/g );
  // get the last one
  const last_before_match = before_match && before_match[ before_match.length - 1 ];
  // retrieve its index
  const previous_nonword_index = last_before_match ? text.lastIndexOf( last_before_match, start_index - 1 ) : -1;
  const begin = previous_nonword_index < 0 ? 0 : previous_nonword_index + 1;
  
  // search in the after substring 
  const after_text = text.substring( end_index );
  // for the first occurence of a non word character
  const next_nonword_index = after_text.search( /\W/ );
  // remember to add the length of the beginning string to the found index
  const end = next_nonword_index < 0 ? text.length : next_nonword_index + end_index;
  
  const between_spaces = text.substring( begin, end );
  console.log( between_spaces );
};
<input type="text" value="this undefined is a sæntence wîth nøn word characters" size="40">
like image 40
Kaiido Avatar answered Oct 12 '22 12:10

Kaiido