Javascript.RU

Создать новую тему Ответ
 
Опции темы Искать в теме
  #1 (permalink)  
Старый 17.09.2022, 18:00
Аватар для Alikberov
Кандидат Javascript-наук
Отправить личное сообщение для Alikberov Посмотреть профиль Найти все сообщения от Alikberov
 
Регистрация: 16.08.2018
Сообщений: 109

Подсветка синтаксиса у TextArea
Сaмый простой способ - наслоить группу TextArea друг за другом и синхронно фильтровать в них текст соответственно синтаксической подкраске.
Однако, имеются некоторые нюансы.
<html>
<head>
<title>Подсветка синтаксиса</title>

<script>
class FineTextArea extends HTMLTextAreaElement {
	constructor() {
		super();
	}
	connectedCallback() {
		var	update = this.update.bind(this, window.event, true);
		this.buddy = document.createElement("TextArea");
		this.buddy.className = "FineTextArea-Buddy";
		this.buddy.id = this.id + "_buddy";
		this.buddy.disabled = true;
		this.parentElement.insertBefore(this.buddy, this);
		this.addEventListener("change", this.update.bind(this));
		this.addEventListener("keyup", this.update.bind(this));
		this.addEventListener("keydown", this.update.bind(this));
		this.addEventListener("keypress", this.update.bind(this));
		this.addEventListener("mousemove", this.update.bind(this));
		this.addEventListener("mouseup", this.update.bind(this));
		this.addEventListener("scroll", this.update.bind(this));
		// Создаём элементы подсветки синтаксиса
*!*
		const	keywords = "dcr hlt inr inx lxi mov sphl xra jp".replace(/\s+/g, "|");
		this.syntaxers = [];
		this.syntexps = [
			new RegExp(`\\b(${keywords})\\b`, "gim"),
			new RegExp(`\\b(?!${keywords})\\b`, "gim"),
			new RegExp(`\\b(${keywords})\\b`, "gim"),
			/[^\x21-\x7F\n\r\s\t]+/gim,
			/[\x21-\x7F]+/gim
		];
*/!*
		for(var i in this.syntexps) {
			var	syn = document.createElement("TextArea");
			syn.className = "FineTextArea-Syntax" + i;
			syn.id = this.id + "_syntax" + i;
			syn.disabled = true;
			this.parentElement.insertBefore(syn, this);
			this.syntaxers.push(syn);
		}
		// Форсируем (альфа-отладка)
		this.lines = true;
		this.syntax = true;
		setTimeout(update, 0); setTimeout(update, 0);
	}
	set lines(value) {
		this._lines = value;
		this.buddy.style.visibility = value != false ? "visible" : "hidden";
		this.style.paddingLeft = value != false ? this.buddy.offsetWidth + "px" : "0px";
		if(value != false)
			this.update(window.event, true);
	}
	set syntax(value) {
		this._syntax = !!value;
		this.update(window.event, true);
	}
	get row() {
		return this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
	}
	get col() {
		return this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
	}
	update(evt, force) {
		var	buttons = evt && evt.buttons || 0;
		var	rows = this.value.split(/\r?\n/), len = rows.length.toString().length;
		var	syn, i, row = 1;
		this.buddy.scrollTop = this.scrollTop;
		for(syn of this.syntaxers)
			syn.scrollTop = this.scrollTop;
		var	spc = ("string" == typeof this._lines && this._lines != "" ? this._lines.charAt(0) : "\xA0").repeat(len);
		var	rex = new RegExp(`([ -])([^ -]{${len}})`, "gim");	// Выявление первых символов каждого слова
		// Распределяем текст по слоям
		var	value = this.value, box = "\u2588";
		for(i in this.syntexps) {
			this.syntaxers[i].value = value.replace(this.syntexps[i], s => box.repeat(s.length));
			box = "\xA0";
			if(i != 0)
				value = value.replace(this.syntexps[i], s => box.repeat(s.length));
		}
		if((buttons == 0 && (Math.abs(this.buddy.scrollHeight - this.scrollHeight) > 30 || this.buddy.scrollWidth != this.scrollWidth)) || force == true) {
			var	width;
			var	height;
			var	bg;
			// Здесь следуют некоторые манипуляции со стилями
			this.style.width = (parseInt(window.getComputedStyle(this, null).getPropertyValue("width")) & 0xFFE) + "px";
			this.buddy.style.paddingRight = "0px";
			this.buddy.style.width = "auto";
			this.buddy.cols = len;
			// Маскируем задний план так, кроме области нумерации
			bg = `linear-gradient(90deg, rgba(0,0,0,0) ${this.buddy.clientWidth - 1}px, white ${this.buddy.clientWidth}px, white 100%)`;
			if(this._syntax == false || buttons)
				this.style.background = bg;
			else
				this.style.background = "transparent",
				this.syntaxers[0].style.background = bg;
			this.style.paddingLeft = `${this.buddy.clientWidth}px`;
			// Расчёт
			height = window.getComputedStyle(this, null).getPropertyValue("height");
			width = window.getComputedStyle(this, null).getPropertyValue("width");
			// Обновляем синтаксические помощники
			for(syn of this.syntaxers) {
				syn.cols = this.cols;
				syn.style.paddingLeft = `${this.buddy.clientWidth}px`;
				syn.style.height = height;
				syn.style.width = width;
			}
			// Копируем визуальные реквизиты элемента
			this.buddy.cols = this.cols;
			this.buddy.style.height = height;
			this.buddy.style.width = width;
			// Компенсируем визуальные отступы у текста и нумератора
			this.buddy.style.paddingLeft = window.getComputedStyle(this, null).getPropertyValue("padding-right");
			this.buddy.style.paddingRight = window.getComputedStyle(this, null).getPropertyValue("padding-left");
			if(this.scrollHeight != this.buddy.scrollHeight)
				force = !true;
		}
		// Затем всё совсем просто
		if(force == true || this._value != this.value) {
			// Переносим весь текст в поле нумератора, кроме первых символов каждого слова, заменяя их нумерацией
			console.time("Numbering");
			for(var fine = 0; fine < 2; ++ fine) {
				this.buddy.value = this.value.replace(/^.*$/gm, function(str) {
						return Number(row ++).toString().padStart(len, spc) + str.substr(str.charAt(0) == "\t" ? 0 : len).replace(rex, `$1${spc}`);
					}
				);
				if(this.scrollHeight == this.buddy.scrollHeight)
					fine = 1;
				else
					fine = 1;
			}
			console.timeEnd("Numbering");
			this._value = this.value;
		}
		this.buddy.style.visibility = buttons || this._lines == false ? "hidden" : "visible";
		this.buddy.scrollTop = this.scrollTop;
		for(syn of this.syntaxers)
			syn.scrollTop = this.scrollTop,
			syn.style.visibility = buttons || this._syntax == false ? "hidden" : "visible";
		this.style.color = buttons || this._syntax == false ? "black" : "transparent";
	}
	static get observedAttributes() {
		return "lines syntax".split(/\s+/);
	}
	attributeChangedCallback(name, oldValue, newValue) {
		switch(name) {
		case "lines":
			this._lines = newValue;
			break;
		case "syntax":
			this._syntax = !!newValue;
			//this.update(window.event, true);
			break;
		}
	}
}

customElements.define("fine-textarea", FineTextArea, {extends: "textarea"});
</script>

<style>
textarea[is='fine-textarea']
{
	position		:relative;
	background-color:transparent;
	color			:transparent;
	caret-color		:black;
}
textarea.FineTextArea-Buddy
{
	position		:absolute;
	background-color:yellow;
}
textarea.FineTextArea-Syntax0,
textarea.FineTextArea-Syntax1,
textarea.FineTextArea-Syntax2,
textarea.FineTextArea-Syntax3,
textarea.FineTextArea-Syntax4,
textarea.FineTextArea-Syntax5,
textarea.FineTextArea-Syntax6,
textarea.FineTextArea-Syntax7,
textarea.FineTextArea-Syntax8,
textarea.FineTextArea-Syntax9
{
	position		:absolute;
	background-color:transparent;
	color			:orange;
	animation-duration: 10s;
	animation-timing-function: linear;
	animation-name	:Layer;
	animation-iteration-count: infinite;
}
textarea.FineTextArea-Syntax0 {	color	:lightgreen; --xy:60px;}
textarea.FineTextArea-Syntax1 {	color	:darkgreen; --xy:110px;}
textarea.FineTextArea-Syntax2 {	color	:magenta; --xy:160px;}
textarea.FineTextArea-Syntax3 {	color	:blue; --xy:210px;}

@keyframes Layer {
	from, 33% {
		transform:translate(0px, 0px);
	}
	50%,100% {
		transform:translate(var(--xy), var(--xy));
	}
}
</style>

</head>

<body>
<input type=text placeholder='Атрибут = Значение' onchange='this.style.backgroundColor = "red"; eval(`hTextArea.${this.value}`); this.style.backgroundColor = "yellow"'>
<input type=text placeholder='Цвет фона нумератора' onchange='hTextArea.buddy.style.backgroundColor = this.value'><br>
<textarea rows=15 cols=40 id=Main spellcheck=false title='Текстовое поле для редактирования' is=fine-textarea lines=true syntax=true>
____________________________________
|Проверка цветовой подсветки V0.003|
====================================
	lxi	h,076D0h
	sphl
	xra	a
L1:	mov	m,a
	inx	h
	dcr	h
	inr	h
	jp	L1
	hlt
</textarea>
<script>
var	hTextArea = document.querySelector("textarea#Main");
</script>
</body>
Как видно из анимации в примере, всё работает до примитивного просто в пользовательском элементе.
Но, есть проблемы в строках #43-45 (без этого сбивается изначальный вид) и в строках #25-33 (нужно каким-то атрибутом унифицировать управление правилами синтаксической фильтрации).

P.S.: Данная тема - логическое развитие предыдущей
Ответить с цитированием
Ответ



Опции темы Искать в теме
Искать в теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Как работает подсветка синтаксиса? Hapson Events/DOM/Window 13 08.01.2014 20:07
Простой способ подсветки синтаксиса textarea (начинающим) Paguo-86PK Events/DOM/Window 1 17.12.2013 21:05
Подсветка синтаксиса в textarea BravoTwo Элементы интерфейса 13 19.01.2013 18:13
Подсветка синткасиса в textarea. Chrome. kadabrik Opera, Safari и др. 3 31.08.2011 12:38
Подсветка синтаксиса. hacker_007 Общие вопросы Javascript 1 07.02.2011 14:57