plain-mentions
by tnhu
HTML
<input/>
<textarea>
$hello} $$hello $hello${world $hello_world
$hello.world
${hello world}
${helloworld }
${helloworld}
${hello
$$$
$$$hello
$$${hello
${
$
$hello
abc
(\$\{?)?[_a-zA-Z][_a-zA-Z0-9.]*\}?
missing $:
\$?[{_a-zA-Z][_a-zA-Z0-9.]*\}?
with $
(\$?[{_a-zA-Z][_a-zA-Z0-9.]*\}?)|(\$)
$hello} $$hello $hello${world $hello_world
$hello.world
${hello world}
${helloworld }
</textarea>
<span id="caret"/>
CSS
html, body {
width: 100%;
height: 100%;
margin: 0; padding: 0;
}
body {
display: flex;
flex-direction: column;
position: relative;
}
#caret {
display: inline-block;
position: absolute;
width: 5px;
height: 5px;
border-radius: 50%;
background: rgba(255, 0, 0, .4);
}
input, textarea {
padding: 5px;
}
textarea {
flex-grow: 1;
}
JavaScript
// Refs:
// - https://microsoft.github.io/monaco-editor/monarch.html
// Notes:
// 1- Keyboard could be faster than processing, make a scheduler to pick only latest typing.
// 2- Ignore meta keys (shift, f1..., alt...)
// 3- Caret position: https://jsfiddle.net/dandv/aFPA7/
// https://github.com/akiroom/caretposition.js
// https://jsfiddle.net/tnhu/jdb1d8xt/
// Maybe the best? https://stackoverflow.com/questions/11966369/get-cursor-position-in-selection-relative-to-document
// http://akiroom.github.io/caretposition.js/demo/sample.html
const trigger = ['$', '${'].sort((a, b) => a.length > b.length ? -1 : 1) // Longer trigger goes first
const replacementRule = match => '${' + match + '}'
const triggerSet = new Set(trigger.join(''))
const MatchingRulesLeftRight = replacementRule('sample.text').split('sample.text')
const IdentifiersSet = /[A-Za-z0-9_.]/
var $caret = document.getElementById('caret')
function getTokens(value, atIndex) {
const valueLen = value.length
const triggerLen = trigger.length
let tokenIndex = atIndex
let tokenLen = 0
let token, word, wordLen, match = ''
while (tokenIndex--) {
const char = value[tokenIndex]
// If char is out of indentifier and trigger ranges, exit loop
if (!triggerSet.has(char) && !IdentifiersSet.test(char)) {
break
}
const copiedStr = value.substr(tokenIndex, ++tokenLen)
for (let i = 0; i < triggerLen; i++) {
if (copiedStr.indexOf(trigger[i]) === 0) {
token = copiedStr
break
}
}
if (token) {
wordLen = tokenLen
let index = tokenIndex + wordLen
while (index < valueLen) {
if (!IdentifiersSet.test(value[index])) {
if (MatchingRulesLeftRight.length === 2
&& token.indexOf(MatchingRulesLeftRight[0]) === 0
&& value.indexOf(MatchingRulesLeftRight[1], index) === index) {
wordLen += MatchingRulesLeftRight[1].length
}
break
...