Skip to content

Commit f6a4a84

Browse files
authored
Merge pull request #3 from mozhaa/syntax-highlighting
feat: syntax highlighting
2 parents 32084e6 + 3ed58a4 commit f6a4a84

3 files changed

Lines changed: 130 additions & 2 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ A single node for executing arbitrary Python code with arbitrary inputs of any t
55
![output_new_min_best](https://github.com/user-attachments/assets/517ad407-8bee-4424-b5ab-ed11f7c34c18)
66
<img width="1196" height="797" alt="image" src="https://github.com/user-attachments/assets/aa3216de-8a2f-4170-be5c-36b12c2f5620" />
77

8+
### UPD: syntax highlighting
9+
<img width="1487" height="839" alt="Screenshot_20260407_104048" src="https://github.com/user-attachments/assets/20de1e1b-306c-4ba2-bacf-407efd2e3346" />
10+
<img width="1044" height="629" alt="Screenshot_20260407_104547" src="https://github.com/user-attachments/assets/8e86c8b9-5616-4e0f-a729-cb9e67a4e301" />
11+
812
## Installation
913
Choose one of these options:
1014
1. **Install via ComfyUI-Manager** (search for "Execute Python")

js/execute_python.js

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,84 @@
11
import { app } from "../../scripts/app.js";
22

3+
let aceLoaded = null;
4+
function loadAce() {
5+
if (window.ace) return Promise.resolve();
6+
if (aceLoaded) return aceLoaded;
7+
aceLoaded = new Promise((resolve, reject) => {
8+
const script = document.createElement('script');
9+
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ace.js';
10+
script.onload = resolve;
11+
script.onerror = reject;
12+
document.head.appendChild(script);
13+
});
14+
return aceLoaded;
15+
}
16+
17+
function updateAllExecutePythonNodes() {
18+
const allNodes = app.graph?._nodes || app.canvas?.nodes || [];
19+
for (const node of allNodes) {
20+
if (node.type && node.type.startsWith("ExecutePython")) {
21+
upgradeCodeWidget(node).catch(console.warn);
22+
}
23+
}
24+
}
25+
26+
async function upgradeCodeWidget(node) {
27+
const enableHighlighting = app.extensionManager.setting.get('ExecutePython.enableHighlighting')
28+
const showLineNumbers = app.extensionManager.setting.get('ExecutePython.showLineNumbers')
29+
const theme = app.extensionManager.setting.get('ExecutePython.theme')
30+
31+
const codeWidget = node.widgets?.find(w => w.name === 'code');
32+
if (!codeWidget) return;
33+
const textarea = codeWidget.element;
34+
if (!textarea) return;
35+
36+
if (textarea.aceEditor) {
37+
textarea.aceEditor.destroy();
38+
delete textarea.aceEditor;
39+
}
40+
if (textarea.nextSibling && textarea.nextSibling.classList?.contains('ace_editor')) {
41+
textarea.nextSibling.remove();
42+
}
43+
const parent = textarea.parentNode;
44+
const existingAce = parent.querySelector('.ace_editor');
45+
if (existingAce) existingAce.remove();
46+
47+
textarea.style.display = '';
48+
49+
if (!enableHighlighting) {
50+
return;
51+
}
52+
53+
await loadAce();
54+
55+
textarea.style.display = 'none';
56+
const container = document.createElement('div');
57+
container.style.width = '100%';
58+
container.style.height = '100%';
59+
container.style.flex = '1 1 0';
60+
textarea.parentNode.insertBefore(container, textarea.nextSibling);
61+
62+
const editor = ace.edit(container);
63+
editor.setTheme(`ace/theme/${theme}`);
64+
editor.session.setMode('ace/mode/python');
65+
editor.setOptions({
66+
showPrintMargin: false,
67+
showLineNumbers: showLineNumbers,
68+
showGutter: showLineNumbers,
69+
wrap: true
70+
});
71+
editor.setValue(textarea.value, -1);
72+
73+
editor.session.on('change', () => {
74+
textarea.value = editor.getValue();
75+
textarea.dispatchEvent(new Event('input', { bubbles: true }));
76+
});
77+
78+
textarea.aceEditor = editor;
79+
editor.resize();
80+
}
81+
382
function reconcileDynamicInputs(node) {
483
const inputs = node.inputs || [];
584

@@ -57,6 +136,46 @@ function reconcileOutputs(node) {
57136

58137
app.registerExtension({
59138
name: "Comfy.ExecutePythonDynamicIO",
139+
settings: [
140+
{
141+
id: "ExecutePython.enableHighlighting",
142+
name: "Enable syntax highlighting for ExecutePython node",
143+
type: "boolean",
144+
defaultValue: true,
145+
onChange: () => {
146+
setTimeout(() => updateAllExecutePythonNodes(), 100)
147+
}
148+
},
149+
{
150+
id: "ExecutePython.showLineNumbers",
151+
name: "Show line numbers in ExecutePython node",
152+
type: "boolean",
153+
defaultValue: false,
154+
onChange: () => {
155+
setTimeout(() => updateAllExecutePythonNodes(), 100)
156+
}
157+
},
158+
{
159+
id: "ExecutePython.theme",
160+
name: "ExecutePython Editor Theme",
161+
type: "combo",
162+
defaultValue: "monokai",
163+
options: [
164+
{ text: "Chrome (Light)", value: "chrome" },
165+
{ text: "Monokai (Dark)", value: "monokai" },
166+
{ text: "Twilight (Dark)", value: "twilight" },
167+
{ text: "GitHub (Light)", value: "github" },
168+
{ text: "Xcode (Light)", value: "xcode" },
169+
{ text: "Eclipse (Light)", value: "eclipse" },
170+
{ text: "Terminal (Dark)", value: "terminal" },
171+
{ text: "Solarized Light", value: "solarized_light" },
172+
{ text: "Solarized Dark", value: "solarized_dark" }
173+
],
174+
onChange: () => {
175+
setTimeout(() => updateAllExecutePythonNodes(), 100)
176+
}
177+
}
178+
],
60179
async beforeRegisterNodeDef(nodeType, nodeData, app) {
61180
if (!nodeData.name.startsWith("ExecutePython")) return;
62181

@@ -67,7 +186,11 @@ app.registerExtension({
67186
nodeType.prototype.onNodeCreated = function () {
68187
const result = origOnNodeCreated?.apply(this, arguments);
69188

70-
setTimeout(() => reconcileDynamicInputs(this), 100);
189+
setTimeout(() => {
190+
reconcileDynamicInputs(this);
191+
reconcileOutputs(this);
192+
upgradeCodeWidget(this);
193+
}, 100);
71194

72195
const outputCountWidget = this.widgets?.find(w => w.name === 'n_outputs');
73196
if (outputCountWidget) {
@@ -82,6 +205,7 @@ app.registerExtension({
82205
const result = origOnNodeLoaded?.apply(this, arguments);
83206
reconcileDynamicInputs(this);
84207
reconcileOutputs(this);
208+
upgradeCodeWidget(this);
85209
return result;
86210
};
87211

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "execute-python"
33
description = "A single ComfyUI node for executing arbitrary Python code with arbitrary inputs of any types."
4-
version = "2.0.0"
4+
version = "2.1.0"
55
license = {file = "LICENSE"}
66
classifiers = [
77
"Operating System :: OS Independent",

0 commit comments

Comments
 (0)