Crear un área de texto editable que admita código resaltado por sintaxis

Cuando estaba trabajando en un proyecto que necesitaba un componente de edición para el código fuente, realmente quería una manera de que ese editor resaltara la sintaxis que se escribe. Hay proyectos como este, como CodeMirror, Ace y Monaco, pero todos son editores pesados ​​y con todas las funciones, no solo editables textareascon resaltado de sintaxis como yo quería.

Me costó un poco de esfuerzo, pero terminé haciendo algo que funciona y quería compartir cómo lo hice, porque implica integrar una popular biblioteca de resaltado de sintaxis con las capacidades de edición de HTML, así como algunos casos extremos interesantes para tener en cuenta. consideración.

¡Adelante, pruébalo mientras profundizamos!

Después de una sugerencia, también publiqué esto como un elemento personalizado en GitHub, para que pueda usar rápidamente el componente en una página web como un code-inputelemento único.

El problema

Primero, intenté usar el contenteditableatributo en un div. Escribí un código fuente en el div y lo ejecuté a través de Prism.js, un resaltador de sintaxis popular, a oninputtravés de JavaScript. Parece una idea decente, ¿verdad? Tenemos un elemento que se puede editar en la interfaz y Prism.js aplica su estilo de sintaxis a lo que se escribe en el elemento.

Chris explica cómo utilizar Prism.js en este vídeo.

Pero eso fue imposible. Cada vez que cambia el contenido del elemento, se manipula el DOM y el cursor del usuario regresa al inicio del código , lo que significa que el código fuente aparece al revés, con los últimos caracteres al principio y los primeros al final.

A continuación, intenté usar textareapero tampoco funcionó, ya que las áreas de texto solo pueden contener texto sin formato . En otras palabras, no podemos aplicar estilo al contenido ingresado. A textareaparece ser la única forma de editar el texto sin errores no deseados; Simplemente no permite que Prism.js haga su trabajo.

Prism.js funciona mucho mejor cuando el código fuente está envuelto en una precodecombinación de etiquetas típicas; solo le falta la parte editable de la ecuación.

Entonces, ninguno parece funcionar por sí solo. Pero pensé, ¿por qué no ambas cosas?

La solucion

Agregué un resaltado de sintaxis precodey una textareaa la página, e hice el innerTextcontenido de precodecambio oninputusando una función de JavaScript. También agregué un aria-hiddenatributo al precoderesultado para que los lectores de pantalla solo lean lo que se ingresa en textarealugar de leerlo en voz alta dos veces.

textarea oninput="update(this.value);"/textareapre aria-hidden="true"  code/code/pre
function update(text) {  let result_element = document.querySelector("#highlighting-content");  // Update code  result_element.innerText = text;  // Syntax Highlight  Prism.highlightElement(result_element);}

Ahora, cuando textarease edita (por ejemplo, una tecla presionada en el teclado vuelve a aparecer), el código resaltado con sintaxis cambia. Hay algunos errores que abordaremos, pero primero quiero centrarme en hacer que parezca que estás editando directamente el elemento resaltado con sintaxis, en lugar de un archivo textarea.

Hacerlo “sentir” como un editor de código

La idea es fusionar visiblemente los elementos para que parezca que estamos interactuando con un elemento cuando en realidad hay dos elementos trabajando. Podemos agregar algo de CSS que básicamente permite que textarealos elementos y precodetengan un tamaño y un espacio consistentes.

#editing, #highlighting {  /* Both elements need the same text and space styling so they are directly on top of each other */  margin: 10px;  padding: 10px;  border: 0;  width: calc(100% - 32px);  height: 150px;}#editing, #highlighting, #highlighting * {  /* Also add text styles to highlighting tokens */  font-size: 15pt;  font-family: monospace;  line-height: 20pt;}

Luego queremos colocarlos uno encima del otro:

#editing, #highlighting {  position: absolute;  top: 0;  left: 0;}

Desde allí, z-indexpermite textareaapilar delante del resultado resaltado:

/* Move the textarea in front of the result */#editing {  z-index: 1;}#highlighting {  z-index: 0;}

Si nos detenemos aquí, veremos nuestro primer error. No parece que Prism.js esté resaltando la sintaxis, pero eso se debe solo a que textareaoculte el resultado.

¡Podemos arreglar esto con CSS! Lo haremos textareacompletamente transparente excepto el cursor:

/* Make textarea almost completely transparent */#editing {  color: transparent;  background: transparent;  caret-color: white; /* Or choose your favorite color */}

¡Ah, mucho mejor!

¡Más arreglos!

Una vez que llegué hasta aquí, jugué un poco con el editor y pude encontrar algunas cosas más que necesitaban ser arregladas. Lo bueno es que todos los problemas son bastante fáciles de solucionar utilizando JavaScript, CSS o incluso HTML.

Eliminar la revisión ortográfica nativa

Estamos creando un editor de código y el código tiene muchas palabras y atributos que el corrector ortográfico nativo de un navegador considera errores ortográficos.

La revisión ortográfica no es mala; Es simplemente inútil en esta situación. ¿Hay algo marcado como incorrecto porque está mal escrito o porque el código no es válido? Es difícil saberlo. Para solucionar este problema, todo lo que necesitamos es establecer el spellcheckatributo en textarea:false

textarea spellcheck="false" ...

Manejo de nuevas lineas

Resulta que eso innerTextno admite nuevas líneas ( n).

updateEs necesario editar la función. En lugar de usar innerText, podemos usar innerHTML, reemplazando el carácter de corchete abierto ( ) con lt;y reemplazando el carácter comercial ( ) con amp;para evitar que se evalúen los escapes HTML. Esto evita que se crean nuevas etiquetas HTML, lo que permite que se muestre el código fuente real en lugar de que el navegador intenta representar el código.

result_element.innerHTML = text.replace(new RegExp("", "g"), "").replace(new RegExp("", "g"), ""); /* Global RegExp */

Desplazarse y cambiar el tamaño

Aquí hay otra cosa: el código resaltado no puede desplazarse mientras se realiza la edición. Y cuando se desplaza el área de texto, el código resaltado no se desplaza con él.

Primero, asegurémonos de que tanto el textarearesultado como el soporte para el desplazamiento:

/* Can be scrolled */#editing, #highlighting {  overflow: auto;  white-space: nowrap; /* Allows textarea to scroll horizontally */}

Luego, para asegurarnos de que el resultado se desplace con el área de texto, actualizaremos el HTML y JavaScript de esta manera:

textarea oninput="update(this.value); sync_scroll(this);" onscroll="sync_scroll(this);"/textarea
function sync_scroll(element) {  /* Scroll result to scroll coords of event - sync with textarea */  let result_element = document.querySelector("#highlighting");  // Get and set x and y  result_element.scrollTop = element.scrollTop;  result_element.scrollLeft = element.scrollLeft;}

Algunos navegadores también permiten textareacambiar el tamaño de a, pero esto significa que el textarearesultado y podría tener diferentes tamaños. ¿Puede CSS solucionar esto? Por supuesto que puede. Simplemente desactivaremos el cambio de tamaño:

/* No resize on textarea */#editing {  resize: none;}

Nuevas líneas finales

Gracias a este comentario por señalar este error.

Ahora el desplazamiento casi siempre está sincronizado, pero todavía hay un caso en el que todavía no funciona . Cuando el usuario crea una nueva línea, el cursor y el texto del área de texto están temporalmente en la posición incorrecta antes de ingresar cualquier texto en la nueva línea. Esto se debe a que el precodebloque ignora una línea final vacía por razones estéticas. Debido a que esto es para una entrada de código funcional, en lugar de un fragmento de código mostrado, es necesario mostrar la línea final vacía. Esto se hace dando contenido a la línea final para que ya no esté vacía , con algunas líneas de JavaScript agregadas a la updatefunción. He utilizado un carácter de espacio porque es invisible para el usuario .

function update(text) {  let result_element = document.querySelector("#highlighting-content");  // Handle final newlines (see article)  if(text[text.length-1] == "n") { // If the last character is a newline character    text += " "; // Add a placeholder space character to the final line   }  // Update code  result_element.innerHTML = text.replace(new RegExp("", "g"), "").replace(new RegExp("", "g"), ""); /* Global RegExp */  // Syntax Highlight  Prism.highlightElement(result_element);}    

Líneas de sangría

Una de las cosas más complicadas de ajustar es cómo manejar las sangrías de línea en el resultado. Tal como está configurado actualmente el editor, sangrar líneas con espacios funciona bien. Pero, si le gustan más las pestañas que los espacios, es posible que haya notado que no funcionan como se esperaba.

Se puede utilizar JavaScript para que la Tabclave funcione correctamente. He agregado comentarios para dejar claro lo que está sucediendo en la función.

textarea ... onkeydown="check_tab(this, event);"/textarea
function check_tab(element, event) {  let code = element.value;  if(event.key == "Tab") {    /* Tab key pressed */    event.preventDefault(); // stop normal    let before_tab = code.slice(0, element.selectionStart); // text before tab    let after_tab = code.slice(element.selectionEnd, element.value.length); // text after tab    let cursor_pos = element.selectionEnd + 1; // where cursor moves after tab - moving forward by 1 char to after tab    element.value = before_tab + "t" + after_tab; // add tab char    // move cursor    element.selectionStart = cursor_pos;    element.selectionEnd = cursor_pos;    update(element.value); // Update text to include indent  }}

Para asegurarse de que los Tabcaracteres tengan el mismo tamaño tanto en el textareabloque de código como en el bloque de código resaltado con sintaxis, edite CSSpara incluir la tab-sizepropiedad:

#editing, #highlighting, #highlighting * {  /* Also add text styles to highlighing tokens */  [...]  tab-size: 2;} 

El resultado final

No demasiado loco, ¿verdad? Todo lo textareaque tenemos son preelementos codeen HTML, nuevas líneas de CSS que los apilan y una biblioteca de resaltado de sintaxis para formatear lo que se ingresa. Y lo que más me gusta de esto es que estamos trabajando con elementos HTML semánticos normales, aprovechando los atributos nativos para obtener el comportamiento que queremos, apoyándonos en CSS para crear la ilusión de que solo estamos interactuando con un elemento y luego buscando JavaScript para resolver algunos casos extremos.

Si bien utilicé Prism.js para resaltar la sintaxis, esta técnica funcionará con otras. Incluso funcionaría con un resaltador de sintaxis que usted mismo cree, si así lo desea. Espero que esto sea útil y pueda usarse en muchos lugares, ya sea en un editor WYSIWYG para un CMS o incluso en formularios donde la capacidad de ingresar el código fuente es un requisito, como una solicitud de empleo de front-end o tal vez un cuestionario. Después de todo, es un textarea, por lo que se puede utilizar de cualquier forma; ¡incluso puedes agregar un placeholdersi es necesario!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir