11import { 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+
382function reconcileDynamicInputs ( node ) {
483 const inputs = node . inputs || [ ] ;
584
@@ -57,6 +136,46 @@ function reconcileOutputs(node) {
57136
58137app . 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
0 commit comments