From a2f2fc7edc8e37e334dce67fe306db3095b9ce61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20=20Sk=C3=B6ld?= Date: Fri, 21 Jun 2024 04:09:05 +0200 Subject: [PATCH] Add syntax highlighting to textbox --- web/js/textbox.js | 124 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 114 insertions(+), 10 deletions(-) diff --git a/web/js/textbox.js b/web/js/textbox.js index 6acf4de..5f62449 100644 --- a/web/js/textbox.js +++ b/web/js/textbox.js @@ -14,29 +14,135 @@ app.registerExtension({ nodeType.prototype.populate = function() { this.inputEl = this.widgets[0]; + console.log(nodeType.prototype) + console.log(this.inputEl) - // Implement copy functionality with proper callback this.addWidget("button", "Copy", "Copy", () => { navigator.clipboard.writeText(this.getTextboxText()); }, { serialize: false }); - // Implement paste functionality with proper callback handling asynchronous operation this.addWidget("button", "Paste", "Paste", () => { navigator.clipboard.readText().then(text => { this.inputEl.value = text; + syncText(this.inputEl.inputEl, this.overlayEl); }).catch(error => { console.error('Failed to read clipboard contents:', error); }); }, { serialize: false }); + + // Ensure the input element's parent exists + if (this.inputEl && this.inputEl.inputEl && this.inputEl.inputEl.parentNode) { + // Create overlay div for highlighting + this.overlayEl = document.createElement('div'); + this.overlayEl.className = 'input-overlay'; + this.inputEl.inputEl.parentNode.insertBefore(this.overlayEl, this.inputEl.inputEl); + + this.inputEl.inputEl.style.background = 'transparent'; + this.overlayEl.style.background = getComputedStyle(document.documentElement).getPropertyValue('--comfy-input-bg').trim(); + + // Sync text initially and on input + this.inputEl.inputEl.addEventListener('input', () => { + syncText(this.inputEl.inputEl, this.overlayEl); + }); + + const observer = new MutationObserver(() => { + setOverlayPosition(this.inputEl, this.overlayEl); + }); + + observer.observe(this.inputEl.inputEl, { + attributes: true, + attributeFilter: ['style'], + childList: true, + subtree: true, + characterData: true, + }); + + this.inputEl.inputEl.addEventListener('keydown', (event) => { + if (event.ctrlKey && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) { + setTimeout(() => { + syncText(this.inputEl, this.overlayEl); + }, 10); + } + }); + + setTimeout(() => { + syncText(this.inputEl, this.overlayEl); + setOverlayPosition(this.inputEl, this.overlayEl); + }, 10); + } else { + console.error('Parent node of input element is not available.'); + } }; + function syncText(inputEl, overlayEl) { + const text = inputEl.value; + overlayEl.textContent = text; + + // Apply styles directly + overlayEl.style.color = 'transparent'; + overlayEl.style.overflow = 'hidden'; + overlayEl.style.whiteSpace = 'pre-wrap'; + overlayEl.style.wordWrap = 'break-word'; + + // Colors for nested parentheses + const colors = ['rgba(0, 255, 0, 0.5)', 'rgba(0, 0, 255, 0.5)', 'rgba(255, 0, 0, 0.5)', 'rgba(255, 255, 0, 0.5)']; + + let nestingLevel = 0; + let highlightedText = ''; + let lastIndex = 0; + + const regex = /(\()|(\))/g; + let match; + while ((match = regex.exec(text)) !== null) { + if (match[0] === '(') { + const color = colors[nestingLevel % colors.length]; + nestingLevel++; + highlightedText += text.slice(lastIndex, match.index) + `${match[0]}`; + } else if (match[0] === ')') { + nestingLevel--; + const color = colors[nestingLevel % colors.length]; + highlightedText += text.slice(lastIndex, match.index) + `${match[0]}`; + } + lastIndex = regex.lastIndex; + } + highlightedText += text.slice(lastIndex); + + overlayEl.innerHTML = highlightedText; + } + + function setOverlayPosition(inputEl, overlayEl) { + // Set the overlay's position and size to match the textarea + const textareaStyle = window.getComputedStyle(inputEl.inputEl); + overlayEl.style.position = 'absolute'; + overlayEl.style.left = textareaStyle.left; + overlayEl.style.top = textareaStyle.top; + overlayEl.style.width = textareaStyle.width; + overlayEl.style.height = textareaStyle.height; + overlayEl.style.zIndex = '1'; // Ensure it's just below the textarea + overlayEl.style.pointerEvents = 'none'; // Allow clicks to pass through + overlayEl.style.display = textareaStyle.display; + + // Copy font and other relevant styles + overlayEl.style.fontFamily = textareaStyle.fontFamily; + overlayEl.style.fontSize = textareaStyle.fontSize; + overlayEl.style.fontWeight = textareaStyle.fontWeight; + overlayEl.style.lineHeight = textareaStyle.lineHeight; + overlayEl.style.letterSpacing = textareaStyle.letterSpacing; + overlayEl.style.whiteSpace = textareaStyle.whiteSpace; + overlayEl.style.color = textareaStyle.color; + overlayEl.style.padding = textareaStyle.padding; + overlayEl.style.boxSizing = textareaStyle.boxSizing; + overlayEl.style.transform = textareaStyle.transform; + overlayEl.style.transformOrigin = textareaStyle.transformOrigin; + } + nodeType.prototype.onNodeCreated = function () { this.populate(); // Bind the getTextboxText to this instance this.getTextboxText = this.getTextboxText.bind(this); api.addEventListener('textbox', (event) => { - const data = event.detail + const data = event.detail; console.log("ID:", data.id); const outputText = this.getTextboxText(); api.fetchApi(`${SettingUtils.API_PREFIX}/textbox_string`, { @@ -44,14 +150,12 @@ app.registerExtension({ headers: { "Content-Type": "application/json", }, - body: JSON.stringify( - { - node_id: data.id, - outputs: { - output: outputText - } + body: JSON.stringify({ + node_id: data.id, + outputs: { + output: outputText } - ), + }), }) }) }