Script.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. /**
  2. * @author mrdoob / http://mrdoob.com/
  3. */
  4. var Script = function ( editor ) {
  5. var signals = editor.signals;
  6. var container = new UI.Panel();
  7. container.setId( 'script' );
  8. container.setPosition( 'absolute' );
  9. container.setBackgroundColor( '#272822' );
  10. container.setDisplay( 'none' );
  11. var header = new UI.Panel();
  12. header.setPadding( '10px' );
  13. container.add( header );
  14. var title = new UI.Text().setColor( '#fff' );
  15. header.add( title );
  16. var buttonSVG = ( function () {
  17. var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
  18. svg.setAttribute( 'width', 32 );
  19. svg.setAttribute( 'height', 32 );
  20. var path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
  21. path.setAttribute( 'd', 'M 12,12 L 22,22 M 22,12 12,22' );
  22. path.setAttribute( 'stroke', '#fff' );
  23. svg.appendChild( path );
  24. return svg;
  25. } )();
  26. var close = new UI.Element( buttonSVG );
  27. close.setPosition( 'absolute' );
  28. close.setTop( '3px' );
  29. close.setRight( '1px' );
  30. close.setCursor( 'pointer' );
  31. close.onClick( function () {
  32. container.setDisplay( 'none' );
  33. } );
  34. header.add( close );
  35. var renderer;
  36. signals.rendererChanged.add( function ( newRenderer ) {
  37. renderer = newRenderer;
  38. } );
  39. var delay;
  40. var currentMode;
  41. var currentScript;
  42. var currentObject;
  43. var codemirror = CodeMirror( container.dom, {
  44. value: '',
  45. lineNumbers: true,
  46. matchBrackets: true,
  47. indentWithTabs: true,
  48. tabSize: 4,
  49. indentUnit: 4,
  50. hintOptions: {
  51. completeSingle: false
  52. }
  53. } );
  54. codemirror.setOption( 'theme', 'monokai' );
  55. codemirror.on( 'change', function () {
  56. clearTimeout( delay );
  57. delay = setTimeout( function () {
  58. var value = codemirror.getValue();
  59. if ( ! validate( value ) ) return;
  60. if ( typeof( currentScript ) === 'object' ) {
  61. if ( value !== currentScript.source ) {
  62. editor.execute( new CmdSetScriptValue( currentObject, currentScript, 'source', value, codemirror.getCursor() ) );
  63. }
  64. return;
  65. }
  66. if ( currentScript !== 'programInfo' ) return;
  67. var json = JSON.parse( value );
  68. currentObject.defines = json.defines;
  69. currentObject.uniforms = json.uniforms;
  70. currentObject.attributes = json.attributes;
  71. currentObject.needsUpdate = true;
  72. signals.materialChanged.dispatch( currentObject );
  73. }, 300 );
  74. });
  75. // prevent backspace from deleting objects
  76. var wrapper = codemirror.getWrapperElement();
  77. wrapper.addEventListener( 'keydown', function ( event ) {
  78. event.stopPropagation();
  79. } );
  80. // validate
  81. var errorLines = [];
  82. var widgets = [];
  83. var validate = function ( string ) {
  84. var valid;
  85. var errors = [];
  86. return codemirror.operation( function () {
  87. while ( errorLines.length > 0 ) {
  88. codemirror.removeLineClass( errorLines.shift(), 'background', 'errorLine' );
  89. }
  90. while ( widgets.length > 0 ) {
  91. codemirror.removeLineWidget( widgets.shift() );
  92. }
  93. //
  94. switch ( currentMode ) {
  95. case 'javascript':
  96. try {
  97. var syntax = esprima.parse( string, { tolerant: true } );
  98. errors = syntax.errors;
  99. } catch ( error ) {
  100. errors.push( {
  101. lineNumber: error.lineNumber - 1,
  102. message: error.message
  103. } );
  104. }
  105. for ( var i = 0; i < errors.length; i ++ ) {
  106. var error = errors[ i ];
  107. error.message = error.message.replace(/Line [0-9]+: /, '');
  108. }
  109. break;
  110. case 'json':
  111. errors = [];
  112. jsonlint.parseError = function ( message, info ) {
  113. message = message.split('\n')[3];
  114. errors.push( {
  115. lineNumber: info.loc.first_line - 1,
  116. message: message
  117. } );
  118. };
  119. try {
  120. jsonlint.parse( string );
  121. } catch ( error ) {
  122. // ignore failed error recovery
  123. }
  124. break;
  125. case 'glsl':
  126. try {
  127. var shaderType = currentScript === 'vertexShader' ?
  128. glslprep.Shader.VERTEX : glslprep.Shader.FRAGMENT;
  129. glslprep.parseGlsl( string, shaderType );
  130. } catch( error ) {
  131. if ( error instanceof glslprep.SyntaxError ) {
  132. errors.push( {
  133. lineNumber: error.line,
  134. message: "Syntax Error: " + error.message
  135. } );
  136. } else {
  137. console.error( error.stack || error );
  138. }
  139. }
  140. if ( errors.length !== 0 ) break;
  141. if ( renderer instanceof THREE.WebGLRenderer === false ) break;
  142. currentObject[ currentScript ] = string;
  143. currentObject.needsUpdate = true;
  144. signals.materialChanged.dispatch( currentObject );
  145. var programs = renderer.info.programs;
  146. valid = true;
  147. var parseMessage = /^(?:ERROR|WARNING): \d+:(\d+): (.*)/g;
  148. for ( var i = 0, n = programs.length; i !== n; ++ i ) {
  149. var diagnostics = programs[i].diagnostics;
  150. if ( diagnostics === undefined ||
  151. diagnostics.material !== currentObject ) continue;
  152. if ( ! diagnostics.runnable ) valid = false;
  153. var shaderInfo = diagnostics[ currentScript ];
  154. var lineOffset = shaderInfo.prefix.split(/\r\n|\r|\n/).length;
  155. while ( true ) {
  156. var parseResult = parseMessage.exec( shaderInfo.log );
  157. if ( parseResult === null ) break;
  158. errors.push( {
  159. lineNumber: parseResult[ 1 ] - lineOffset,
  160. message: parseResult[ 2 ]
  161. } );
  162. } // messages
  163. break;
  164. } // programs
  165. } // mode switch
  166. for ( var i = 0; i < errors.length; i ++ ) {
  167. var error = errors[ i ];
  168. var message = document.createElement( 'div' );
  169. message.className = 'esprima-error';
  170. message.textContent = error.message;
  171. var lineNumber = Math.max( error.lineNumber, 0 );
  172. errorLines.push( lineNumber );
  173. codemirror.addLineClass( lineNumber, 'background', 'errorLine' );
  174. var widget = codemirror.addLineWidget( lineNumber, message );
  175. widgets.push( widget );
  176. }
  177. return valid !== undefined ? valid : errors.length === 0;
  178. });
  179. };
  180. // tern js autocomplete
  181. var server = new CodeMirror.TernServer( {
  182. caseInsensitive: true,
  183. plugins: { threejs: null }
  184. } );
  185. codemirror.setOption( 'extraKeys', {
  186. 'Ctrl-Space': function(cm) { server.complete(cm); },
  187. 'Ctrl-I': function(cm) { server.showType(cm); },
  188. 'Ctrl-O': function(cm) { server.showDocs(cm); },
  189. 'Alt-.': function(cm) { server.jumpToDef(cm); },
  190. 'Alt-,': function(cm) { server.jumpBack(cm); },
  191. 'Ctrl-Q': function(cm) { server.rename(cm); },
  192. 'Ctrl-.': function(cm) { server.selectName(cm); }
  193. } );
  194. codemirror.on( 'cursorActivity', function( cm ) {
  195. if ( currentMode !== 'javascript' ) return;
  196. server.updateArgHints( cm );
  197. } );
  198. codemirror.on( 'keypress', function( cm, kb ) {
  199. if ( currentMode !== 'javascript' ) return;
  200. var typed = String.fromCharCode( kb.which || kb.keyCode );
  201. if ( /[\w\.]/.exec( typed ) ) {
  202. server.complete( cm );
  203. }
  204. } );
  205. //
  206. signals.editorCleared.add( function () {
  207. container.setDisplay( 'none' );
  208. } );
  209. signals.editScript.add( function ( object, script ) {
  210. var mode, name, source;
  211. if ( typeof( script ) === 'object' ) {
  212. mode = 'javascript';
  213. name = script.name;
  214. source = script.source;
  215. } else {
  216. switch ( script ) {
  217. case 'vertexShader':
  218. mode = 'glsl';
  219. name = 'Vertex Shader';
  220. source = object.vertexShader || "";
  221. break;
  222. case 'fragmentShader':
  223. mode = 'glsl';
  224. name = 'Fragment Shader';
  225. source = object.fragmentShader || "";
  226. break;
  227. case 'programInfo':
  228. mode = 'json';
  229. name = 'Program Properties';
  230. var json = {
  231. defines: object.defines,
  232. uniforms: object.uniforms,
  233. attributes: object.attributes
  234. };
  235. source = JSON.stringify( json, null, '\t' );
  236. }
  237. }
  238. currentMode = mode;
  239. currentScript = script;
  240. currentObject = object;
  241. title.setValue( object.name + ' / ' + name );
  242. container.setDisplay( '' );
  243. codemirror.setValue( source );
  244. if (mode === 'json' ) mode = { name: 'javascript', json: true };
  245. codemirror.setOption( 'mode', mode );
  246. } );
  247. signals.scriptRemoved.add( function ( script ) {
  248. if ( currentScript === script ) {
  249. container.setDisplay( 'none' );
  250. }
  251. } );
  252. signals.refreshScriptEditor.add( function ( object, script, cursorPosition ) {
  253. // Check if editor is being displayed
  254. //...
  255. if ( currentScript !== script ) return;
  256. title.setValue( object.name + ' / ' + script.name );
  257. codemirror.setValue( script.source );
  258. if ( cursorPosition !== undefined ) {
  259. codemirror.setCursor( cursorPosition );
  260. }
  261. } );
  262. return container;
  263. };