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 textareas
con 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-input
elemento único.
El problema
Primero, intenté usar el contenteditable
atributo 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 oninput
travé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 textarea
pero 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 textarea
parece 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 precode
combinació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 precode
y una textarea
a la página, e hice el innerText
contenido de precode
cambio oninput
usando una función de JavaScript. También agregué un aria-hidden
atributo al precode
resultado para que los lectores de pantalla solo lean lo que se ingresa en textarea
lugar 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 textarea
se 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 textarea
los elementos y precode
tengan 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-index
permite textarea
apilar 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 textarea
oculte el resultado.
¡Podemos arreglar esto con CSS! Lo haremos textarea
completamente 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 spellcheck
atributo en textarea
:false
textarea spellcheck="false" ...
Manejo de nuevas lineas
Resulta que eso innerText
no admite nuevas líneas ( n
).
update
Es 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 textarea
resultado 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 textarea
cambiar el tamaño de a, pero esto significa que el textarea
resultado 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 precode
bloque 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 update
funció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 Tab
caracteres tengan el mismo tamaño tanto en el textarea
bloque de código como en el bloque de código resaltado con sintaxis, edite CSS
para incluir la tab-size
propiedad:
#editing, #highlighting, #highlighting * { /* Also add text styles to highlighing tokens */ [...] tab-size: 2;}
El resultado final
No demasiado loco, ¿verdad? Todo lo textarea
que tenemos son pre
elementos code
en 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 placeholder
si es necesario!
Deja un comentario