JSFiddle - React, Tailwind, and code Playground

by Lazaro Contreras

HTML

<div class='numberLine'><p></p></div>
<div class='container' contenteditable='true' spellcheck="false"></div>
<textarea class='container2' spellcheck="false"></textarea>

CSS

* {
  margin: 0;
  padding: 0;
  outline: none;
  font-size: 17px;
  font-family: Consolas;
  box-sizing: border-box;
}

html, body{
  background: #0F172A;
}

::selection{
	color: transparent;
  background: #AADDFF40;
}

div.container, textarea.container2 {
  left: 50px;
  color: white;
  width: calc(100vw - 50px);
  height: 100vh;
  padding: 0px 10px;
  position: absolute;
  white-space: pre;
}

textarea.container2 {
  color: transparent;
  caret-color: white;
  border: none;
  background: transparent;
}

div.numberLine{
  left: 0;
  color: gray;
  width: 50px;
  height: 100vh;
  padding: 0px 10px;
  text-align: right;
  position: absolute;
  counter-reset: numberLine;
}

div.numberLine p::after {
  content: '';
  width: 100vw;
  height: 20px;
  left: 0;
  position: absolute;
  background: #1E293E50;
  z-index: -1;
}

div.numberLine p.active::after {
  background: #33415350;
}

div.numberLine p::before {
  counter-increment: numberLine;
  content: counter(numberLine);
}

rsv{
  color: #8B5CF6;
}

dig{
  color: #F59E0B;
}

arg{
  color: #F43F5E;
}

smb{
  color: #6EE7B7;
}

car, car smb, car dig, car rsv, car arg{
  color: #84CC16;
}

cmt, cmt car, cmt smb, cmt dig, cmt rsv, cmt arg{
  color: #AAA;
}

err{
  text-decoration: underline;
  text-decoration-style: wavy;
  text-decoration-color: red;
}

JavaScript

let reservadas = ['abstract', 'enum', 'long', 'stackalloc', 'as', 'event', 'namespace', 'static', 'base', 'explicit', 'new', 'string', 'bool', 'extern', 'null', 'struct', 'break', 'false', 'object', 'switch', 'byte', 'finally', 'operator', 'this', 'case', 'Fixed', 'out', 'throw', 'catch', 'float', 'override', 'true', 'char', 'for', 'params', 'try', 'checked', 'foreach', 'private', 'typeof', 'class', 'goto', 'protected', 'uint', 'const', 'if', 'public', 'ulong', 'continue', 'implicit', 'readonly', 'unchecked', 'decimal', 'in', 'ref', 'unsafe', 'default', 'int', 'return', 'ushort', 'delegate', 'interface', 'sbyte', 'using', 'do', 'internal', 'sealed', 'virtual', 'double', 'is', 'short', 'void', 'else', 'lock', 'sizeof', 'while', 'get', 'set'];
let simbolos = ['\\*', '\\/', '\\*', '\\<', '\\>', '\\+', '\\-', '\\=', '\\!', '\\[\\]', '\\(\\)', '\\{\\}', '\\|', '\\&', '\\?'];

let numberLine = document.querySelector('.numberLine');
let container = document.querySelector('.container');
let container2 = document.querySelector('.container2');

container2.onfocus = () => start();
container2.onblur = () => stop();

var requestId;

function loop(time) {
  requestId = undefined;

  let lines = container2.value.split(/\n/g);
  let linesLenght = lines.lenght;
  let cursorPosition = container2.value.substr(0, container2.selectionStart).split(/\n/g).length;
  numberLine.innerHTML = lines.map((e, i) => `<p class="${cursorPosition == i + 1 ? 'active' : null}" ></p>`).join('');

  lines.forEach((l, i) => {
  
  lines[i] = lines[i]
    	.replace(/(\/\/.*)$/gm, `<cmt><nc>$1</nc></cmt>`)
      .replace(/(\/\*.*)/gm, `<cmt><nc>$1</nc>`)
      .replace(/(.*\*\/)/gm, `<nc>$1</nc></cmt>`)
      .replace(new RegExp(`(?<!\<nc\>.*)([${simbolos.join("")}])(?!.*\<\/nc\>)`, 'g'), `<smb>$1</smb>`)
      
    /*
    simbolos.forEach(s =>
      lines[i] = lines[i].replace(new RegExp(`(?:()(\\B${s}\\B)|(.)(${s}{2}\\B))`, 'g'), `$1$3<smb>$2$4</smb>`)
    );*/
    reservadas.forEach(r =>
     ...