├── CHANGELOG.md ├── LICENSE ├── README.md ├── about ├── ChromeWebStore_Badge_v2_496x150.png ├── WORKING.md ├── banner-large.jpg ├── banner.jpg ├── marquee.jpg ├── snapshot-1.jpg ├── snapshot-2.jpg ├── snapshot-3.jpg ├── snapshot-4.jpg ├── snapshot.jpg ├── snapshot.png └── thumb.png └── src ├── assets ├── check.svg ├── eye.svg ├── format.svg ├── fs.svg ├── fs_exit.svg ├── gears.svg └── star.svg ├── background.js ├── codemirror ├── CodeMirror.css ├── CodeMirror.js ├── autoformat.js ├── dialog.css ├── dialog.js ├── glsl.js ├── search.js └── searchcursor.js ├── content_script.js ├── devtools.html ├── devtools.js ├── glsl-optimizer.mem.js ├── glsl-optimizer.min.js ├── icon_128.png ├── icon_16.png ├── icon_48.png ├── manifest.json ├── panel.html └── panel.js /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## current 2 | 3 | Features: 4 | 5 | - Moved settings load to be done first thing when loading the extension: should create less potential problems when loading 6 | 7 | ## v1.0.16 (2015-10-10) 8 | 9 | Bugfixes: 10 | 11 | - Codemirror search working ([#14](https://github.com/spite/ShaderEditorExtension/issues/14)) 12 | - Moved localStorage from panel to chrome.storage on background page (was broken with blocked third party cookies) 13 | 14 | ## v1.0.15 (2015-07-13) 15 | 16 | Features: 17 | 18 | - Added toggling of shaders 19 | - Added texture monitor/editor: shows textures created and can be replaced by new images (no support for cubemaps yet) 20 | - Added settings panel: toggle shader highlighting and texture monitoring 21 | - Created CHANGELOG.md 22 | 23 | ## v1.0.14 24 | 25 | Features: 26 | 27 | - Support for shader/program names [Issue #10](https://github.com/spite/ShaderEditorExtension/issues/10) 28 | 29 | ## v1.0.13 30 | 31 | Features: 32 | 33 | - Support for live reload across browsing session (https://github.com/spite/ShaderEditorExtension/issues/1) 34 | 35 | ## v1.0.12 36 | 37 | Workarounds: 38 | 39 | - Fix for three.js 40 | 41 | ## v1.0.11 42 | 43 | Bugfixes: 44 | 45 | - Fixed bug with uniform retrieving: shadertoy and other cool sites supported! 46 | 47 | ## v1.0.10 48 | 49 | Features: 50 | - Improved error reporting 51 | 52 | Bugfixes: 53 | - Several fixes 54 | 55 | ## v1.0.9 56 | 57 | Features: 58 | 59 | - Starting options panel 60 | - Added setting for highlighting 61 | 62 | ## v1.0.8 63 | 64 | Features: 65 | 66 | - New icon (thanks @ebraminio & @markusfisch) 67 | 68 | Bugfixes: 69 | 70 | - Restored extension support 71 | 72 | ## v1.0.7 73 | 74 | Features: 75 | 76 | - Added error checks for attributes (possible bug?) 77 | 78 | ## v1.0.6 79 | 80 | Features: 81 | 82 | - Redone main code 83 | - highlight brought back 84 | 85 | ## v1.0.5 86 | 87 | Features: 88 | 89 | - Compiler status 90 | 91 | Bugfixes: 92 | 93 | - Fixes 94 | 95 | ## v1.0.4 96 | 97 | Bugfixes: 98 | 99 | - fixed some shaders not compiling (issue #4) 100 | 101 | ## v1.0.3 102 | 103 | Workarounds: 104 | 105 | - rolled back, colouring introducing bugs 106 | 107 | ## v1.0.2 108 | 109 | Features 110 | 111 | - added colour highlighting for hover 112 | 113 | ## v1.0.1 114 | 115 | Bugfixes: 116 | 117 | - Fix extensions not being enabled 118 | 119 | ## v1.0.0 120 | 121 | Features: 122 | 123 | - Initial release 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jaume Sanchez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGL GLSL Shader Editor Extension for Google Chrome 2 | 3 | A Chrome DevTools extension to help you edit shaders live in the browser. Very much based on Firefox DevTools Shader Editor. [Here's a video showing it in action](http://www.youtube.com/watch?v=nPcUH3b3pFY) 4 | 5 | [Install the extension from the Chrome Store](https://chrome.google.com/webstore/detail/shader-editor/ggeaidddejpbakgafapihjbgdlbbbpob) 6 | [](https://chrome.google.com/webstore/detail/shader-editor/ggeaidddejpbakgafapihjbgdlbbbpob) 7 | 8 | Twin project of [Web Audio API Editor Extension for Google Chrome](https://github.com/spite/WebAudioExtension) 9 | 10 |  11 | 12 | Some more info about this project: [Creating a Plug'n Play Live WebGL Shader Editor](http://www.clicktorelease.com/blog/live-webgl-shader-editor) 13 | 14 | ### How to install ### 15 | 16 | From the chrome store: 17 | - [Follow this link and install the extension from the Chrome Store](https://chrome.google.com/webstore/detail/shader-editor/ggeaidddejpbakgafapihjbgdlbbbpob) 18 | 19 | Load the extension from disk directly: 20 | - Checkout the repo 21 | - Open Chrome's Extensions page (``Settings / More tools / Extensions``) 22 | - Enable ``Developer Mode`` 23 | - Click on ``Load unpacked extension`... 24 | - Select the folder /src in the checked out project 25 | 26 | Alternatively, you can pack the extension yourself and load by dropping the .crx file in the Extensions page. 27 | 28 | ### How to use ### 29 | 30 | - Browse to a page with WebGL content (you can find many here http://threejs.org/, here https://www.chromeexperiments.com/webgl or here http://www.webgl.com/) 31 | - Open DevTools 32 | - Select the ``Shader Editor`` tab 33 | - The extension needs to instrument ``WebGLRenderingContext``: if you open DevTools after the page has loaded, hit the ``Reload`` button. If the extension was already running, it automatically instruments the page. 34 | - If there are calls to ``.createProgram``, the UI will show a list 35 | - Select a program to see its vertex shader and fragment shader 36 | - Use the Pretty Print icon to make the code more readable 37 | - Use the fullscreen button to make the code editor bigger 38 | - Use the Star icon to apply the GLSL Optimiser 39 | - Use the check mark icon next to each shader's name to toggle its visiblity 40 | - Use the Eye icon to disable shader highlighting 41 | - On the Textures tab, click on a texture to open a File Dialog to use another texture, or drag and drop a file into the texture. 42 | - On the Setting tab, enable or disable texture monitoring and shader highlighting (for performance reasons) 43 | 44 | ### TO-DO ### 45 | 46 | As always: forks, pull requests and code critiques are welcome! 47 | 48 | - ~~Detect when the page is reloaded or changed [Issue #1](https://github.com/spite/ShaderEditorExtension/issues/1)~~ 49 | - ~~Highlight shaders when hovering over list item [Issue #3](https://github.com/spite/ShaderEditorExtension/issues/3)~~ 50 | - Check why some pages don't load (like http://david.li/flow/) [Issue #4](https://github.com/spite/ShaderEditorExtension/issues/4) 51 | - Figure out why it doesn't .postMessage the first time it's injected [Issue #5](https://github.com/spite/ShaderEditorExtension/issues/5) 52 | - Figure out why it doesn't work on Android over remote debugging [Issue #6](https://github.com/spite/ShaderEditorExtension/issues/6) 53 | 54 | Nice to have: 55 | 56 | - Save to disk (?) - Not possible for now with the DevTools API 57 | - Add uniform tracking to display values fed to the shader 58 | - ~~Integrating @aras-p + @zz85 [GLSL Optimizer](https://github.com/zz85/glsl-optimizer)~~ [Added](https://github.com/spite/ShaderEditorExtension/commit/cd6c59aef586b1a7d8dfbdbb01e9538fb374a270) 59 | 60 | #### Changelog #### 61 | 62 | [See detailed change log](CHANGELOG.md) 63 | 64 | #### License #### 65 | 66 | MIT licensed 67 | 68 | Copyright (C) 2015 Jaume Sanchez Elias, http://www.clicktorelease.com -------------------------------------------------------------------------------- /about/ChromeWebStore_Badge_v2_496x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/ChromeWebStore_Badge_v2_496x150.png -------------------------------------------------------------------------------- /about/WORKING.md: -------------------------------------------------------------------------------- 1 | - http://madebyevan.com/webgl-water/ 2 | - http://evanw.github.io/webgl-filter/ 3 | 4 | - http://lab.aerotwist.com/webgl/undulating-monkey/ 5 | 6 | ###http://webglsamples.org### 7 | 8 | - http://webglsamples.org/blob/blob.html 9 | - http://webglsamples.org/dynamic-cubemap/dynamic-cubemap.html -------------------------------------------------------------------------------- /about/banner-large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/banner-large.jpg -------------------------------------------------------------------------------- /about/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/banner.jpg -------------------------------------------------------------------------------- /about/marquee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/marquee.jpg -------------------------------------------------------------------------------- /about/snapshot-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot-1.jpg -------------------------------------------------------------------------------- /about/snapshot-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot-2.jpg -------------------------------------------------------------------------------- /about/snapshot-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot-3.jpg -------------------------------------------------------------------------------- /about/snapshot-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot-4.jpg -------------------------------------------------------------------------------- /about/snapshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot.jpg -------------------------------------------------------------------------------- /about/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot.png -------------------------------------------------------------------------------- /about/thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/thumb.png -------------------------------------------------------------------------------- /src/assets/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/format.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/assets/fs.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/assets/fs_exit.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/assets/gears.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | // chrome.extension calls 2 | var connections = {}; 3 | 4 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 5 | // console.log('incoming message from injected script'); 6 | // console.log(request); 7 | 8 | // Messages from content scripts should have sender.tab set 9 | if (sender.tab) { 10 | var tabId = sender.tab.id; 11 | if (tabId in connections) { 12 | connections[tabId].postMessage(request); 13 | } else { 14 | console.log("Tab not found in connection list."); 15 | } 16 | } else { 17 | console.log("sender.tab not defined."); 18 | } 19 | return true; 20 | }); 21 | 22 | function getSettings( c ) { 23 | 24 | chrome.storage.sync.get( 'settings', function( obj ) { 25 | console.log( 'SETTINGS: ', obj ); 26 | 27 | var settings = { 28 | highlight: true, 29 | tmpDisableHighlight: false, 30 | textures: false 31 | } 32 | 33 | if( obj.settings ) { 34 | obj = obj.settings; 35 | 36 | if( obj.highlight !== undefined ) { 37 | settings.highlight = obj.highlight; 38 | } 39 | 40 | if( obj.textures !== undefined ) { 41 | settings.textures = obj.textures; 42 | } 43 | 44 | if( obj.tmpDisableHighlight !== undefined ) { 45 | settings.tmpDisableHighlight = obj.tmpDisableHighlight; 46 | } 47 | } 48 | 49 | c( settings ); 50 | 51 | }); 52 | 53 | } 54 | 55 | chrome.runtime.onConnect.addListener(function( connection ) { 56 | 57 | console.log( 'onConnect', connection ); 58 | 59 | // Listen to messages sent from the DevTools page 60 | var listener = function(message, sender, sendResponse) { 61 | 62 | console.log('incoming message from dev tools page', message, sender, sendResponse ); 63 | 64 | // Register initial connection 65 | if ( message.name === 'init') { 66 | console.log( 'init' ); 67 | connections[ message.tabId ] = connection; 68 | getSettings( function( settings ) { 69 | connections[ message.tabId ].postMessage( { method: 'settings', settings: settings } ); 70 | connections[ message.tabId ].postMessage( { method: 'loaded' } ); 71 | } ); 72 | } 73 | 74 | if( message.name === 'readSettings' ) { 75 | console.log( 'read settings' ); 76 | 77 | getSettings( function( settings ) { 78 | connections[ message.tabId ].postMessage( { method: 'settings', settings: settings } ); 79 | } ); 80 | 81 | } 82 | 83 | if( message.name === 'saveSettings' ) { 84 | console.log( 'save settings' ); 85 | chrome.storage.sync.set( { 'settings': message.settings }, function() { 86 | console.log('Settings saved'); 87 | }); 88 | } 89 | 90 | } 91 | 92 | connection.onMessage.addListener( listener ); 93 | 94 | connection.onDisconnect.addListener(function() { 95 | connection.onMessage.removeListener( listener ); 96 | }); 97 | 98 | 99 | }); 100 | /* 101 | chrome.webNavigation.onCommitted.addListener( function() { 102 | for( var j in connections ) { 103 | connections[ j ].postMessage( { method: 'onCommitted' } ); 104 | } 105 | } ); 106 | 107 | chrome.tabs.onUpdated.addListener( function( tabId ) { 108 | 109 | connections[ tabId ].postMessage( { method: 'onUpdated' } ); 110 | 111 | } );*/ 112 | 113 | chrome.webNavigation.onBeforeNavigate.addListener(function(data) 114 | { 115 | //console.log("onBeforeNavigate: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId); 116 | }); 117 | 118 | chrome.webNavigation.onCommitted.addListener(function(data) { 119 | 120 | //console.log("onCommitted: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId); 121 | 122 | if( connections[ data.tabId ] ) { 123 | if( data.frameId === 0 ) { 124 | connections[ data.tabId ].postMessage( { method: 'inject' } ); 125 | } 126 | } 127 | 128 | }); 129 | 130 | chrome.webNavigation.onReferenceFragmentUpdated.addListener(function(data) 131 | { 132 | // console.log("onReferenceFragmentUpdated: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId); 133 | }); 134 | 135 | chrome.webNavigation.onErrorOccurred.addListener(function(data) 136 | { 137 | // console.log("onErrorOccurred: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId + ". Error: " + data.error); 138 | }); 139 | 140 | chrome.webNavigation.onReferenceFragmentUpdated.addListener(function(data) 141 | { 142 | // console.log("onReferenceFragmentUpdated: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId); 143 | }); 144 | 145 | /*chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) 146 | { 147 | console.log("tabs.onUpdated: " + changeInfo.url + ". Status: " + changeInfo.status + ". Tab: " + tabId); 148 | }); 149 | 150 | chrome.history.onVisited.addListener(function(historyItem) 151 | { 152 | console.log("history.onVisited: " + historyItem.url); 153 | });*/ 154 | /* 155 | chrome.webNavigation.onCompleted.addListener(function(data) 156 | { 157 | // console.log("onCompleted: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId); 158 | }); 159 | */ -------------------------------------------------------------------------------- /src/codemirror/CodeMirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 4px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | white-space: nowrap; 37 | } 38 | 39 | .CodeMirror-guttermarker { color: black; } 40 | .CodeMirror-guttermarker-subtle { color: #999; } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror div.CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | } 47 | /* Shown when moving in bi-directional text */ 48 | .CodeMirror div.CodeMirror-secondarycursor { 49 | border-left: 1px solid silver; 50 | } 51 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursor { 52 | width: auto; 53 | border: 0; 54 | background: #7e7; 55 | } 56 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursors { 57 | z-index: 1; 58 | } 59 | 60 | .cm-animate-fat-cursor { 61 | width: auto; 62 | border: 0; 63 | -webkit-animation: blink 1.06s steps(1) infinite; 64 | -moz-animation: blink 1.06s steps(1) infinite; 65 | animation: blink 1.06s steps(1) infinite; 66 | } 67 | @-moz-keyframes blink { 68 | 0% { background: #7e7; } 69 | 50% { background: none; } 70 | 100% { background: #7e7; } 71 | } 72 | @-webkit-keyframes blink { 73 | 0% { background: #7e7; } 74 | 50% { background: none; } 75 | 100% { background: #7e7; } 76 | } 77 | @keyframes blink { 78 | 0% { background: #7e7; } 79 | 50% { background: none; } 80 | 100% { background: #7e7; } 81 | } 82 | 83 | /* Can style cursor different in overwrite (non-insert) mode */ 84 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 85 | 86 | .cm-tab { display: inline-block; text-decoration: inherit; } 87 | 88 | .CodeMirror-ruler { 89 | border-left: 1px solid #ccc; 90 | position: absolute; 91 | } 92 | 93 | /* DEFAULT THEME */ 94 | 95 | .cm-s-default .cm-keyword {color: #708;} 96 | .cm-s-default .cm-atom {color: #219;} 97 | .cm-s-default .cm-number {color: #164;} 98 | .cm-s-default .cm-def {color: #00f;} 99 | .cm-s-default .cm-variable, 100 | .cm-s-default .cm-punctuation, 101 | .cm-s-default .cm-property, 102 | .cm-s-default .cm-operator {} 103 | .cm-s-default .cm-variable-2 {color: #05a;} 104 | .cm-s-default .cm-variable-3 {color: #085;} 105 | .cm-s-default .cm-comment {color: #a50;} 106 | .cm-s-default .cm-string {color: #a11;} 107 | .cm-s-default .cm-string-2 {color: #f50;} 108 | .cm-s-default .cm-meta {color: #555;} 109 | .cm-s-default .cm-qualifier {color: #555;} 110 | .cm-s-default .cm-builtin {color: #30a;} 111 | .cm-s-default .cm-bracket {color: #997;} 112 | .cm-s-default .cm-tag {color: #170;} 113 | .cm-s-default .cm-attribute {color: #00c;} 114 | .cm-s-default .cm-header {color: blue;} 115 | .cm-s-default .cm-quote {color: #090;} 116 | .cm-s-default .cm-hr {color: #999;} 117 | .cm-s-default .cm-link {color: #00c;} 118 | 119 | .cm-negative {color: #d44;} 120 | .cm-positive {color: #292;} 121 | .cm-header, .cm-strong {font-weight: bold;} 122 | .cm-em {font-style: italic;} 123 | .cm-link {text-decoration: underline;} 124 | .cm-strikethrough {text-decoration: line-through;} 125 | 126 | .cm-s-default .cm-error {color: #f00;} 127 | .cm-invalidchar {color: #f00;} 128 | 129 | /* Default styles for common addons */ 130 | 131 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 132 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 133 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 134 | .CodeMirror-activeline-background {background: #e8f2ff;} 135 | 136 | /* STOP */ 137 | 138 | /* The rest of this file contains styles related to the mechanics of 139 | the editor. You probably shouldn't touch them. */ 140 | 141 | .CodeMirror { 142 | position: relative; 143 | overflow: hidden; 144 | background: white; 145 | } 146 | 147 | .CodeMirror-scroll { 148 | overflow: scroll !important; /* Things will break if this is overridden */ 149 | /* 30px is the magic margin used to hide the element's real scrollbars */ 150 | /* See overflow: hidden in .CodeMirror */ 151 | margin-bottom: -30px; margin-right: -30px; 152 | padding-bottom: 30px; 153 | height: 100%; 154 | outline: none; /* Prevent dragging from highlighting the element */ 155 | position: relative; 156 | } 157 | .CodeMirror-sizer { 158 | position: relative; 159 | border-right: 30px solid transparent; 160 | } 161 | 162 | /* The fake, visible scrollbars. Used to force redraw during scrolling 163 | before actuall scrolling happens, thus preventing shaking and 164 | flickering artifacts. */ 165 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 166 | position: absolute; 167 | z-index: 6; 168 | display: none; 169 | } 170 | .CodeMirror-vscrollbar { 171 | right: 0; top: 0; 172 | overflow-x: hidden; 173 | overflow-y: scroll; 174 | } 175 | .CodeMirror-hscrollbar { 176 | bottom: 0; left: 0; 177 | overflow-y: hidden; 178 | overflow-x: scroll; 179 | } 180 | .CodeMirror-scrollbar-filler { 181 | right: 0; bottom: 0; 182 | } 183 | .CodeMirror-gutter-filler { 184 | left: 0; bottom: 0; 185 | } 186 | 187 | .CodeMirror-gutters { 188 | position: absolute; left: 0; top: 0; 189 | z-index: 3; 190 | } 191 | .CodeMirror-gutter { 192 | white-space: normal; 193 | height: 100%; 194 | display: inline-block; 195 | margin-bottom: -30px; 196 | /* Hack to make IE7 behave */ 197 | *zoom:1; 198 | *display:inline; 199 | } 200 | .CodeMirror-gutter-wrapper { 201 | position: absolute; 202 | z-index: 4; 203 | height: 100%; 204 | } 205 | .CodeMirror-gutter-elt { 206 | position: absolute; 207 | cursor: default; 208 | z-index: 4; 209 | } 210 | .CodeMirror-gutter-wrapper { 211 | -webkit-user-select: none; 212 | -moz-user-select: none; 213 | user-select: none; 214 | } 215 | 216 | .CodeMirror-lines { 217 | cursor: text; 218 | min-height: 1px; /* prevents collapsing before first draw */ 219 | } 220 | .CodeMirror pre { 221 | /* Reset some styles that the rest of the page might have set */ 222 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 223 | border-width: 0; 224 | background: transparent; 225 | font-family: inherit; 226 | font-size: inherit; 227 | margin: 0; 228 | white-space: pre; 229 | word-wrap: normal; 230 | line-height: inherit; 231 | color: inherit; 232 | z-index: 2; 233 | position: relative; 234 | overflow: visible; 235 | -webkit-tap-highlight-color: transparent; 236 | } 237 | .CodeMirror-wrap pre { 238 | word-wrap: break-word; 239 | white-space: pre-wrap; 240 | word-break: normal; 241 | } 242 | 243 | .CodeMirror-linebackground { 244 | position: absolute; 245 | left: 0; right: 0; top: 0; bottom: 0; 246 | z-index: 0; 247 | } 248 | 249 | .CodeMirror-linewidget { 250 | position: relative; 251 | z-index: 2; 252 | overflow: auto; 253 | } 254 | 255 | .CodeMirror-widget {} 256 | 257 | .CodeMirror-code { 258 | outline: none; 259 | } 260 | 261 | /* Force content-box sizing for the elements where we expect it */ 262 | .CodeMirror-scroll, 263 | .CodeMirror-sizer, 264 | .CodeMirror-gutter, 265 | .CodeMirror-gutters, 266 | .CodeMirror-linenumber { 267 | -moz-box-sizing: content-box; 268 | box-sizing: content-box; 269 | } 270 | 271 | .CodeMirror-measure { 272 | position: absolute; 273 | width: 100%; 274 | height: 0; 275 | overflow: hidden; 276 | visibility: hidden; 277 | } 278 | .CodeMirror-measure pre { position: static; } 279 | 280 | .CodeMirror div.CodeMirror-cursor { 281 | position: absolute; 282 | border-right: none; 283 | width: 0; 284 | } 285 | 286 | div.CodeMirror-cursors { 287 | visibility: hidden; 288 | position: relative; 289 | z-index: 3; 290 | } 291 | .CodeMirror-focused div.CodeMirror-cursors { 292 | visibility: visible; 293 | } 294 | 295 | .CodeMirror-selected { background: #d9d9d9; } 296 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 297 | .CodeMirror-crosshair { cursor: crosshair; } 298 | .CodeMirror ::selection { background: #d7d4f0; } 299 | .CodeMirror ::-moz-selection { background: #d7d4f0; } 300 | 301 | .cm-searching { 302 | background: #ffa; 303 | background: rgba(255, 255, 0, .4); 304 | } 305 | 306 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 307 | .CodeMirror span { *vertical-align: text-bottom; } 308 | 309 | /* Used to force a border model for a node */ 310 | .cm-force-border { padding-right: .1px; } 311 | 312 | @media print { 313 | /* Hide the cursor when printing */ 314 | .CodeMirror div.CodeMirror-cursors { 315 | visibility: hidden; 316 | } 317 | } 318 | 319 | /* See issue #2901 */ 320 | .cm-tab-wrap-hack:after { content: ''; } 321 | 322 | /* Help users use markselection to safely style text background */ 323 | -------------------------------------------------------------------------------- /src/codemirror/autoformat.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | CodeMirror.extendMode("css", { 4 | commentStart: "/*", 5 | commentEnd: "*/", 6 | newlineAfterToken: function(type, content) { 7 | return /^[;{}]$/.test(content); 8 | } 9 | }); 10 | 11 | CodeMirror.extendMode("javascript", { 12 | commentStart: "/*", 13 | commentEnd: "*/", 14 | // FIXME semicolons inside of for 15 | newlineAfterToken: function(type, content, textAfter, state) { 16 | if (this.jsonMode) { 17 | return /^[\[,{]$/.test(content) || /^}/.test(textAfter); 18 | } else { 19 | if (content == ";" && state.lexical && state.lexical.type == ")") return false; 20 | return /^[;{}]$/.test(content) && !/^;/.test(textAfter); 21 | } 22 | } 23 | }); 24 | 25 | CodeMirror.extendMode("xml", { 26 | commentStart: "", 28 | newlineAfterToken: function(type, content, textAfter) { 29 | return type == "tag" && />$/.test(content) || /^ -1 && endIndex > -1 && endIndex > startIndex) { 47 | // Take string till comment start 48 | selText = selText.substr(0, startIndex) 49 | // From comment start till comment end 50 | + selText.substring(startIndex + curMode.commentStart.length, endIndex) 51 | // From comment end till string end 52 | + selText.substr(endIndex + curMode.commentEnd.length); 53 | } 54 | cm.replaceRange(selText, from, to); 55 | } 56 | }); 57 | }); 58 | 59 | // Applies automatic mode-aware indentation to the specified range 60 | CodeMirror.defineExtension("autoIndentRange", function (from, to) { 61 | var cmInstance = this; 62 | this.operation(function () { 63 | for (var i = from.line; i <= to.line; i++) { 64 | cmInstance.indentLine(i, "smart"); 65 | } 66 | }); 67 | }); 68 | 69 | // Applies automatic formatting to the specified range 70 | CodeMirror.defineExtension("autoFormatRange", function (from, to) { 71 | var cm = this; 72 | var outer = cm.getMode(), text = cm.getRange(from, to).split("\n"); 73 | var state = CodeMirror.copyState(outer, cm.getTokenAt(from).state); 74 | var tabSize = cm.getOption("tabSize"); 75 | 76 | var out = "", lines = 0, atSol = from.ch == 0; 77 | function newline() { 78 | out += "\n"; 79 | atSol = true; 80 | ++lines; 81 | } 82 | 83 | for (var i = 0; i < text.length; ++i) { 84 | var stream = new CodeMirror.StringStream(text[i], tabSize); 85 | while (!stream.eol()) { 86 | var inner = CodeMirror.innerMode(outer, state); 87 | var style = outer.token(stream, state), cur = stream.current(); 88 | stream.start = stream.pos; 89 | if (!atSol || /\S/.test(cur)) { 90 | out += cur; 91 | atSol = false; 92 | } 93 | if (!atSol && inner.mode.newlineAfterToken && 94 | inner.mode.newlineAfterToken(style, cur, stream.string.slice(stream.pos) || text[i+1] || "", inner.state)) 95 | newline(); 96 | } 97 | if (!stream.pos && outer.blankLine) outer.blankLine(state); 98 | if (!atSol) newline(); 99 | } 100 | 101 | cm.operation(function () { 102 | cm.replaceRange(out, from, to); 103 | for (var cur = from.line + 1, end = from.line + lines; cur <= end; ++cur) 104 | cm.indentLine(cur, "smart"); 105 | cm.setSelection(from, cm.getCursor(false)); 106 | }); 107 | }); 108 | })(); -------------------------------------------------------------------------------- /src/codemirror/dialog.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-dialog { 2 | position: absolute; 3 | left: 0; right: 0; 4 | background: white; 5 | z-index: 15; 6 | padding: .1em .8em; 7 | overflow: hidden; 8 | color: #333; 9 | } 10 | 11 | .CodeMirror-dialog-top { 12 | border-bottom: 1px solid #eee; 13 | top: 0; 14 | } 15 | 16 | .CodeMirror-dialog-bottom { 17 | border-top: 1px solid #eee; 18 | bottom: 0; 19 | } 20 | 21 | .CodeMirror-dialog input { 22 | border: none; 23 | outline: none; 24 | background: transparent; 25 | width: 20em; 26 | color: inherit; 27 | font-family: monospace; 28 | } 29 | 30 | .CodeMirror-dialog button { 31 | font-size: 70%; 32 | } -------------------------------------------------------------------------------- /src/codemirror/dialog.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | // Open simple dialogs on top of an editor. Relies on dialog.css. 5 | 6 | (function(mod) { 7 | if (typeof exports == "object" && typeof module == "object") // CommonJS 8 | mod(require("../../lib/codemirror")); 9 | else if (typeof define == "function" && define.amd) // AMD 10 | define(["../../lib/codemirror"], mod); 11 | else // Plain browser env 12 | mod(CodeMirror); 13 | })(function(CodeMirror) { 14 | function dialogDiv(cm, template, bottom) { 15 | var wrap = cm.getWrapperElement(); 16 | var dialog; 17 | dialog = wrap.appendChild(document.createElement("div")); 18 | if (bottom) 19 | dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom"; 20 | else 21 | dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; 22 | 23 | if (typeof template == "string") { 24 | dialog.innerHTML = template; 25 | } else { // Assuming it's a detached DOM element. 26 | dialog.appendChild(template); 27 | } 28 | return dialog; 29 | } 30 | 31 | function closeNotification(cm, newVal) { 32 | if (cm.state.currentNotificationClose) 33 | cm.state.currentNotificationClose(); 34 | cm.state.currentNotificationClose = newVal; 35 | } 36 | 37 | CodeMirror.defineExtension("openDialog", function(template, callback, options) { 38 | if (!options) options = {}; 39 | 40 | closeNotification(this, null); 41 | 42 | var dialog = dialogDiv(this, template, options.bottom); 43 | var closed = false, me = this; 44 | function close(newVal) { 45 | if (typeof newVal == 'string') { 46 | inp.value = newVal; 47 | } else { 48 | if (closed) return; 49 | closed = true; 50 | dialog.parentNode.removeChild(dialog); 51 | me.focus(); 52 | 53 | if (options.onClose) options.onClose(dialog); 54 | } 55 | } 56 | 57 | var inp = dialog.getElementsByTagName("input")[0], button; 58 | if (inp) { 59 | if (options.value) { 60 | inp.value = options.value; 61 | if (options.selectValueOnOpen !== false) { 62 | inp.select(); 63 | } 64 | } 65 | 66 | if (options.onInput) 67 | CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);}); 68 | if (options.onKeyUp) 69 | CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);}); 70 | 71 | CodeMirror.on(inp, "keydown", function(e) { 72 | if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; } 73 | if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) { 74 | inp.blur(); 75 | CodeMirror.e_stop(e); 76 | close(); 77 | } 78 | if (e.keyCode == 13) callback(inp.value, e); 79 | }); 80 | 81 | if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close); 82 | 83 | inp.focus(); 84 | } else if (button = dialog.getElementsByTagName("button")[0]) { 85 | CodeMirror.on(button, "click", function() { 86 | close(); 87 | me.focus(); 88 | }); 89 | 90 | if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close); 91 | 92 | button.focus(); 93 | } 94 | return close; 95 | }); 96 | 97 | CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) { 98 | closeNotification(this, null); 99 | var dialog = dialogDiv(this, template, options && options.bottom); 100 | var buttons = dialog.getElementsByTagName("button"); 101 | var closed = false, me = this, blurring = 1; 102 | function close() { 103 | if (closed) return; 104 | closed = true; 105 | dialog.parentNode.removeChild(dialog); 106 | me.focus(); 107 | } 108 | buttons[0].focus(); 109 | for (var i = 0; i < buttons.length; ++i) { 110 | var b = buttons[i]; 111 | (function(callback) { 112 | CodeMirror.on(b, "click", function(e) { 113 | CodeMirror.e_preventDefault(e); 114 | close(); 115 | if (callback) callback(me); 116 | }); 117 | })(callbacks[i]); 118 | CodeMirror.on(b, "blur", function() { 119 | --blurring; 120 | setTimeout(function() { if (blurring <= 0) close(); }, 200); 121 | }); 122 | CodeMirror.on(b, "focus", function() { ++blurring; }); 123 | } 124 | }); 125 | 126 | /* 127 | * openNotification 128 | * Opens a notification, that can be closed with an optional timer 129 | * (default 5000ms timer) and always closes on click. 130 | * 131 | * If a notification is opened while another is opened, it will close the 132 | * currently opened one and open the new one immediately. 133 | */ 134 | CodeMirror.defineExtension("openNotification", function(template, options) { 135 | closeNotification(this, close); 136 | var dialog = dialogDiv(this, template, options && options.bottom); 137 | var closed = false, doneTimer; 138 | var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000; 139 | 140 | function close() { 141 | if (closed) return; 142 | closed = true; 143 | clearTimeout(doneTimer); 144 | dialog.parentNode.removeChild(dialog); 145 | } 146 | 147 | CodeMirror.on(dialog, 'click', function(e) { 148 | CodeMirror.e_preventDefault(e); 149 | close(); 150 | }); 151 | 152 | if (duration) 153 | doneTimer = setTimeout(close, duration); 154 | 155 | return close; 156 | }); 157 | }); -------------------------------------------------------------------------------- /src/codemirror/glsl.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("glsl", function(config, parserConfig) { 2 | var indentUnit = config.indentUnit, 3 | keywords = parserConfig.keywords || {}, 4 | builtins = parserConfig.builtins || {}, 5 | blockKeywords = parserConfig.blockKeywords || {}, 6 | atoms = parserConfig.atoms || {}, 7 | hooks = parserConfig.hooks || {}, 8 | multiLineStrings = parserConfig.multiLineStrings; 9 | var isOperatorChar = /[+\-*&%=<>!?|\/]/; 10 | 11 | var curPunc; 12 | 13 | function tokenBase(stream, state) { 14 | var ch = stream.next(); 15 | if (hooks[ch]) { 16 | var result = hooks[ch](stream, state); 17 | if (result !== false) return result; 18 | } 19 | if (ch == '"' || ch == "'") { 20 | state.tokenize = tokenString(ch); 21 | return state.tokenize(stream, state); 22 | } 23 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 24 | curPunc = ch; 25 | return "bracket"; 26 | } 27 | if (/\d/.test(ch)) { 28 | stream.eatWhile(/[\w\.]/); 29 | return "number"; 30 | } 31 | if (ch == "/") { 32 | if (stream.eat("*")) { 33 | state.tokenize = tokenComment; 34 | return tokenComment(stream, state); 35 | } 36 | if (stream.eat("/")) { 37 | stream.skipToEnd(); 38 | return "comment"; 39 | } 40 | } 41 | if (isOperatorChar.test(ch)) { 42 | stream.eatWhile(isOperatorChar); 43 | return "operator"; 44 | } 45 | stream.eatWhile(/[\w\$_]/); 46 | var cur = stream.current(); 47 | if (keywords.propertyIsEnumerable(cur)) { 48 | if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; 49 | return "keyword"; 50 | } 51 | if (builtins.propertyIsEnumerable(cur)) { 52 | return "builtin"; 53 | } 54 | if (atoms.propertyIsEnumerable(cur)) return "atom"; 55 | return "word"; 56 | } 57 | 58 | function tokenString(quote) { 59 | return function(stream, state) { 60 | var escaped = false, next, end = false; 61 | while ((next = stream.next()) != null) { 62 | if (next == quote && !escaped) {end = true; break;} 63 | escaped = !escaped && next == "\\"; 64 | } 65 | if (end || !(escaped || multiLineStrings)) 66 | state.tokenize = tokenBase; 67 | return "string"; 68 | }; 69 | } 70 | 71 | function tokenComment(stream, state) { 72 | var maybeEnd = false, ch; 73 | while (ch = stream.next()) { 74 | if (ch == "/" && maybeEnd) { 75 | state.tokenize = tokenBase; 76 | break; 77 | } 78 | maybeEnd = (ch == "*"); 79 | } 80 | return "comment"; 81 | } 82 | 83 | function Context(indented, column, type, align, prev) { 84 | this.indented = indented; 85 | this.column = column; 86 | this.type = type; 87 | this.align = align; 88 | this.prev = prev; 89 | } 90 | function pushContext(state, col, type) { 91 | return state.context = new Context(state.indented, col, type, null, state.context); 92 | } 93 | function popContext(state) { 94 | var t = state.context.type; 95 | if (t == ")" || t == "]" || t == "}") 96 | state.indented = state.context.indented; 97 | return state.context = state.context.prev; 98 | } 99 | 100 | // Interface 101 | 102 | return { 103 | startState: function(basecolumn) { 104 | return { 105 | tokenize: null, 106 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), 107 | indented: 0, 108 | startOfLine: true 109 | }; 110 | }, 111 | 112 | token: function(stream, state) { 113 | var ctx = state.context; 114 | if (stream.sol()) { 115 | if (ctx.align == null) ctx.align = false; 116 | state.indented = stream.indentation(); 117 | state.startOfLine = true; 118 | } 119 | if (stream.eatSpace()) return null; 120 | curPunc = null; 121 | var style = (state.tokenize || tokenBase)(stream, state); 122 | if (style == "comment" || style == "meta") return style; 123 | if (ctx.align == null) ctx.align = true; 124 | 125 | if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); 126 | else if (curPunc == "{") pushContext(state, stream.column(), "}"); 127 | else if (curPunc == "[") pushContext(state, stream.column(), "]"); 128 | else if (curPunc == "(") pushContext(state, stream.column(), ")"); 129 | else if (curPunc == "}") { 130 | while (ctx.type == "statement") ctx = popContext(state); 131 | if (ctx.type == "}") ctx = popContext(state); 132 | while (ctx.type == "statement") ctx = popContext(state); 133 | } 134 | else if (curPunc == ctx.type) popContext(state); 135 | else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) 136 | pushContext(state, stream.column(), "statement"); 137 | state.startOfLine = false; 138 | return style; 139 | }, 140 | 141 | indent: function(state, textAfter) { 142 | if (state.tokenize != tokenBase && state.tokenize != null) return 0; 143 | var firstChar = textAfter && textAfter.charAt(0), ctx = state.context, closing = firstChar == ctx.type; 144 | if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); 145 | else if (ctx.align) return ctx.column + (closing ? 0 : 1); 146 | else return ctx.indented + (closing ? 0 : indentUnit); 147 | }, 148 | 149 | electricChars: "{}" 150 | }; 151 | }); 152 | 153 | (function() { 154 | function words(str) { 155 | var obj = {}, words = str.split(" "); 156 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true; 157 | return obj; 158 | } 159 | var glslKeywords = "attribute const uniform varying break continue " + 160 | "do for while if else in out inout float int void bool true false " + 161 | "lowp mediump highp precision invariant discard return mat2 mat3 " + 162 | "mat4 vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 sampler2D " + 163 | "samplerCube struct gl_FragCoord gl_FragColor"; 164 | var glslBuiltins = "radians degrees sin cos tan asin acos atan pow " + 165 | "exp log exp2 log2 sqrt inversesqrt abs sign floor ceil fract mod " + 166 | "min max clamp mix step smoothstep length distance dot cross " + 167 | "normalize faceforward reflect refract matrixCompMult lessThan " + 168 | "lessThanEqual greaterThan greaterThanEqual equal notEqual any all " + 169 | "not dFdx dFdy fwidth texture2D texture2DProj texture2DLod " + 170 | "texture2DProjLod textureCube textureCubeLod"; 171 | 172 | function cppHook(stream, state) { 173 | if (!state.startOfLine) return false; 174 | stream.skipToEnd(); 175 | return "meta"; 176 | } 177 | 178 | // C#-style strings where "" escapes a quote. 179 | function tokenAtString(stream, state) { 180 | var next; 181 | while ((next = stream.next()) != null) { 182 | if (next == '"' && !stream.eat('"')) { 183 | state.tokenize = null; 184 | break; 185 | } 186 | } 187 | return "string"; 188 | } 189 | 190 | CodeMirror.defineMIME("text/x-glsl", { 191 | name: "glsl", 192 | keywords: words(glslKeywords), 193 | builtins: words(glslBuiltins), 194 | blockKeywords: words("case do else for if switch while struct"), 195 | atoms: words("null"), 196 | hooks: {"#": cppHook} 197 | }); 198 | }()); -------------------------------------------------------------------------------- /src/codemirror/search.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | // Define search commands. Depends on dialog.js or another 5 | // implementation of the openDialog method. 6 | 7 | // Replace works a little oddly -- it will do the replace on the next 8 | // Ctrl-G (or whatever is bound to findNext) press. You prevent a 9 | // replace by making sure the match is no longer selected when hitting 10 | // Ctrl-G. 11 | 12 | (function(mod) { 13 | if (typeof exports == "object" && typeof module == "object") // CommonJS 14 | mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog")); 15 | else if (typeof define == "function" && define.amd) // AMD 16 | define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod); 17 | else // Plain browser env 18 | mod(CodeMirror); 19 | })(function(CodeMirror) { 20 | "use strict"; 21 | function searchOverlay(query, caseInsensitive) { 22 | if (typeof query == "string") 23 | query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g"); 24 | else if (!query.global) 25 | query = new RegExp(query.source, query.ignoreCase ? "gi" : "g"); 26 | 27 | return {token: function(stream) { 28 | query.lastIndex = stream.pos; 29 | var match = query.exec(stream.string); 30 | if (match && match.index == stream.pos) { 31 | stream.pos += match[0].length; 32 | return "searching"; 33 | } else if (match) { 34 | stream.pos = match.index; 35 | } else { 36 | stream.skipToEnd(); 37 | } 38 | }}; 39 | } 40 | 41 | function SearchState() { 42 | this.posFrom = this.posTo = this.lastQuery = this.query = null; 43 | this.overlay = null; 44 | } 45 | function getSearchState(cm) { 46 | return cm.state.search || (cm.state.search = new SearchState()); 47 | } 48 | function queryCaseInsensitive(query) { 49 | return typeof query == "string" && query == query.toLowerCase(); 50 | } 51 | function getSearchCursor(cm, query, pos) { 52 | // Heuristic: if the query string is all lowercase, do a case insensitive search. 53 | return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); 54 | } 55 | function dialog(cm, text, shortText, deflt, f) { 56 | if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true}); 57 | else f(prompt(shortText, deflt)); 58 | } 59 | function confirmDialog(cm, text, shortText, fs) { 60 | if (cm.openConfirm) cm.openConfirm(text, fs); 61 | else if (confirm(shortText)) fs[0](); 62 | } 63 | function parseQuery(query) { 64 | var isRE = query.match(/^\/(.*)\/([a-z]*)$/); 65 | if (isRE) { 66 | try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); } 67 | catch(e) {} // Not a regular expression after all, do a string search 68 | } 69 | if (typeof query == "string" ? query == "" : query.test("")) 70 | query = /x^/; 71 | return query; 72 | } 73 | var queryDialog = 74 | 'Search: (Use /re/ syntax for regexp search)'; 75 | function doSearch(cm, rev) { 76 | var state = getSearchState(cm); 77 | if (state.query) return findNext(cm, rev); 78 | var q = cm.getSelection() || state.lastQuery; 79 | dialog(cm, queryDialog, "Search for:", q, function(query) { 80 | cm.operation(function() { 81 | if (!query || state.query) return; 82 | state.query = parseQuery(query); 83 | cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); 84 | state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); 85 | cm.addOverlay(state.overlay); 86 | if (cm.showMatchesOnScrollbar) { 87 | if (state.annotate) { state.annotate.clear(); state.annotate = null; } 88 | state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query)); 89 | } 90 | state.posFrom = state.posTo = cm.getCursor(); 91 | findNext(cm, rev); 92 | }); 93 | }); 94 | } 95 | function findNext(cm, rev) {cm.operation(function() { 96 | var state = getSearchState(cm); 97 | var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); 98 | if (!cursor.find(rev)) { 99 | cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)); 100 | if (!cursor.find(rev)) return; 101 | } 102 | cm.setSelection(cursor.from(), cursor.to()); 103 | cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); 104 | state.posFrom = cursor.from(); state.posTo = cursor.to(); 105 | });} 106 | function clearSearch(cm) {cm.operation(function() { 107 | var state = getSearchState(cm); 108 | state.lastQuery = state.query; 109 | if (!state.query) return; 110 | state.query = null; 111 | cm.removeOverlay(state.overlay); 112 | if (state.annotate) { state.annotate.clear(); state.annotate = null; } 113 | });} 114 | 115 | var replaceQueryDialog = 116 | 'Replace: (Use /re/ syntax for regexp search)'; 117 | var replacementQueryDialog = 'With: '; 118 | var doReplaceConfirm = "Replace? "; 119 | function replace(cm, all) { 120 | if (cm.getOption("readOnly")) return; 121 | var query = cm.getSelection() || getSearchState(cm).lastQuery; 122 | dialog(cm, replaceQueryDialog, "Replace:", query, function(query) { 123 | if (!query) return; 124 | query = parseQuery(query); 125 | dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { 126 | if (all) { 127 | cm.operation(function() { 128 | for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { 129 | if (typeof query != "string") { 130 | var match = cm.getRange(cursor.from(), cursor.to()).match(query); 131 | cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];})); 132 | } else cursor.replace(text); 133 | } 134 | }); 135 | } else { 136 | clearSearch(cm); 137 | var cursor = getSearchCursor(cm, query, cm.getCursor()); 138 | var advance = function() { 139 | var start = cursor.from(), match; 140 | if (!(match = cursor.findNext())) { 141 | cursor = getSearchCursor(cm, query); 142 | if (!(match = cursor.findNext()) || 143 | (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; 144 | } 145 | cm.setSelection(cursor.from(), cursor.to()); 146 | cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); 147 | confirmDialog(cm, doReplaceConfirm, "Replace?", 148 | [function() {doReplace(match);}, advance]); 149 | }; 150 | var doReplace = function(match) { 151 | cursor.replace(typeof query == "string" ? text : 152 | text.replace(/\$(\d)/g, function(_, i) {return match[i];})); 153 | advance(); 154 | }; 155 | advance(); 156 | } 157 | }); 158 | }); 159 | } 160 | 161 | CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; 162 | CodeMirror.commands.findNext = doSearch; 163 | CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; 164 | CodeMirror.commands.clearSearch = clearSearch; 165 | CodeMirror.commands.replace = replace; 166 | CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; 167 | }); -------------------------------------------------------------------------------- /src/codemirror/searchcursor.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | var Pos = CodeMirror.Pos; 14 | 15 | function SearchCursor(doc, query, pos, caseFold) { 16 | this.atOccurrence = false; this.doc = doc; 17 | if (caseFold == null && typeof query == "string") caseFold = false; 18 | 19 | pos = pos ? doc.clipPos(pos) : Pos(0, 0); 20 | this.pos = {from: pos, to: pos}; 21 | 22 | // The matches method is filled in based on the type of query. 23 | // It takes a position and a direction, and returns an object 24 | // describing the next occurrence of the query, or null if no 25 | // more matches were found. 26 | if (typeof query != "string") { // Regexp match 27 | if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g"); 28 | this.matches = function(reverse, pos) { 29 | if (reverse) { 30 | query.lastIndex = 0; 31 | var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start; 32 | for (;;) { 33 | query.lastIndex = cutOff; 34 | var newMatch = query.exec(line); 35 | if (!newMatch) break; 36 | match = newMatch; 37 | start = match.index; 38 | cutOff = match.index + (match[0].length || 1); 39 | if (cutOff == line.length) break; 40 | } 41 | var matchLen = (match && match[0].length) || 0; 42 | if (!matchLen) { 43 | if (start == 0 && line.length == 0) {match = undefined;} 44 | else if (start != doc.getLine(pos.line).length) { 45 | matchLen++; 46 | } 47 | } 48 | } else { 49 | query.lastIndex = pos.ch; 50 | var line = doc.getLine(pos.line), match = query.exec(line); 51 | var matchLen = (match && match[0].length) || 0; 52 | var start = match && match.index; 53 | if (start + matchLen != line.length && !matchLen) matchLen = 1; 54 | } 55 | if (match && matchLen) 56 | return {from: Pos(pos.line, start), 57 | to: Pos(pos.line, start + matchLen), 58 | match: match}; 59 | }; 60 | } else { // String query 61 | var origQuery = query; 62 | if (caseFold) query = query.toLowerCase(); 63 | var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; 64 | var target = query.split("\n"); 65 | // Different methods for single-line and multi-line queries 66 | if (target.length == 1) { 67 | if (!query.length) { 68 | // Empty string would match anything and never progress, so 69 | // we define it to match nothing instead. 70 | this.matches = function() {}; 71 | } else { 72 | this.matches = function(reverse, pos) { 73 | if (reverse) { 74 | var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig); 75 | var match = line.lastIndexOf(query); 76 | if (match > -1) { 77 | match = adjustPos(orig, line, match); 78 | return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; 79 | } 80 | } else { 81 | var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig); 82 | var match = line.indexOf(query); 83 | if (match > -1) { 84 | match = adjustPos(orig, line, match) + pos.ch; 85 | return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; 86 | } 87 | } 88 | }; 89 | } 90 | } else { 91 | var origTarget = origQuery.split("\n"); 92 | this.matches = function(reverse, pos) { 93 | var last = target.length - 1; 94 | if (reverse) { 95 | if (pos.line - (target.length - 1) < doc.firstLine()) return; 96 | if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return; 97 | var to = Pos(pos.line, origTarget[last].length); 98 | for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln) 99 | if (target[i] != fold(doc.getLine(ln))) return; 100 | var line = doc.getLine(ln), cut = line.length - origTarget[0].length; 101 | if (fold(line.slice(cut)) != target[0]) return; 102 | return {from: Pos(ln, cut), to: to}; 103 | } else { 104 | if (pos.line + (target.length - 1) > doc.lastLine()) return; 105 | var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length; 106 | if (fold(line.slice(cut)) != target[0]) return; 107 | var from = Pos(pos.line, cut); 108 | for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln) 109 | if (target[i] != fold(doc.getLine(ln))) return; 110 | if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return; 111 | return {from: from, to: Pos(ln, origTarget[last].length)}; 112 | } 113 | }; 114 | } 115 | } 116 | } 117 | 118 | SearchCursor.prototype = { 119 | findNext: function() {return this.find(false);}, 120 | findPrevious: function() {return this.find(true);}, 121 | 122 | find: function(reverse) { 123 | var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to); 124 | function savePosAndFail(line) { 125 | var pos = Pos(line, 0); 126 | self.pos = {from: pos, to: pos}; 127 | self.atOccurrence = false; 128 | return false; 129 | } 130 | 131 | for (;;) { 132 | if (this.pos = this.matches(reverse, pos)) { 133 | this.atOccurrence = true; 134 | return this.pos.match || true; 135 | } 136 | if (reverse) { 137 | if (!pos.line) return savePosAndFail(0); 138 | pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length); 139 | } 140 | else { 141 | var maxLine = this.doc.lineCount(); 142 | if (pos.line == maxLine - 1) return savePosAndFail(maxLine); 143 | pos = Pos(pos.line + 1, 0); 144 | } 145 | } 146 | }, 147 | 148 | from: function() {if (this.atOccurrence) return this.pos.from;}, 149 | to: function() {if (this.atOccurrence) return this.pos.to;}, 150 | 151 | replace: function(newText, origin) { 152 | if (!this.atOccurrence) return; 153 | var lines = CodeMirror.splitLines(newText); 154 | this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin); 155 | this.pos.to = Pos(this.pos.from.line + lines.length - 1, 156 | lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)); 157 | } 158 | }; 159 | 160 | // Maps a position in a case-folded line back to a position in the original line 161 | // (compensating for codepoints increasing in number during folding) 162 | function adjustPos(orig, folded, pos) { 163 | if (orig.length == folded.length) return pos; 164 | for (var pos1 = Math.min(pos, orig.length);;) { 165 | var len1 = orig.slice(0, pos1).toLowerCase().length; 166 | if (len1 < pos) ++pos1; 167 | else if (len1 > pos) --pos1; 168 | else return pos1; 169 | } 170 | } 171 | 172 | CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { 173 | return new SearchCursor(this.doc, query, pos, caseFold); 174 | }); 175 | CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { 176 | return new SearchCursor(this, query, pos, caseFold); 177 | }); 178 | 179 | CodeMirror.defineExtension("selectMatches", function(query, caseFold) { 180 | var ranges = []; 181 | var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold); 182 | while (cur.findNext()) { 183 | if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break; 184 | ranges.push({anchor: cur.from(), head: cur.to()}); 185 | } 186 | if (ranges.length) 187 | this.setSelections(ranges, 0); 188 | }); 189 | }); -------------------------------------------------------------------------------- /src/content_script.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('message', function(event) { 2 | 3 | if (event.source !== window) { 4 | return; 5 | } 6 | 7 | /*if( event.data && event.data.method ) { 8 | 9 | if( event.data.method === 'saveSetting' ) { 10 | var s = event.data.setting; 11 | var v = event.data.value; 12 | var obj = {}; 13 | obj[ s ] = v; 14 | chrome.storage.sync.set( obj, function() { 15 | } ); 16 | return; 17 | } 18 | 19 | if( event.data.method === 'loadSetting' ) { 20 | var s = event.data.setting; 21 | chrome.storage.sync.get( event.data.setting, function( i ) { 22 | chrome.runtime.sendMessage( { 23 | source: 'WebGLShaderEditor', 24 | method: 'loadSetting', 25 | setting: s, 26 | value: i[ s ] 27 | } ); 28 | } ); 29 | return; 30 | } 31 | 32 | }*/ 33 | 34 | //console.log( 'message ', event ); 35 | 36 | var message = event.data; 37 | 38 | // Only accept messages that we know are ours 39 | if (typeof message !== 'object' || message === null ) { 40 | return; 41 | } 42 | 43 | chrome.runtime.sendMessage(message); 44 | }); -------------------------------------------------------------------------------- /src/devtools.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | -------------------------------------------------------------------------------- /src/devtools.js: -------------------------------------------------------------------------------- 1 | // chrome.devtools calls 2 | 3 | chrome.devtools.panels.create( "Shader Editor", 4 | "icon.png", 5 | "panel.html", 6 | function(panel) { 7 | 8 | // code invoked on panel creation 9 | } 10 | ); 11 | 12 | // Create a connection to the background page 13 | var backgroundPageConnection = chrome.runtime.connect({ 14 | name: 'panel' 15 | }); 16 | 17 | backgroundPageConnection.postMessage({ 18 | name: 'init', 19 | tabId: chrome.devtools.inspectedWindow.tabId 20 | }); 21 | /* 22 | backgroundPageConnection.onMessage.addListener(function(msg) { 23 | //console.log( 'devtools.js', msg ); 24 | }); 25 | */ -------------------------------------------------------------------------------- /src/glsl-optimizer.mem.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/src/glsl-optimizer.mem.js -------------------------------------------------------------------------------- /src/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/src/icon_128.png -------------------------------------------------------------------------------- /src/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/src/icon_16.png -------------------------------------------------------------------------------- /src/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/src/icon_48.png -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shader Editor", 3 | "version": "1.0.17", 4 | "minimum_chrome_version": "10.0", 5 | "devtools_page": "devtools.html", 6 | "icons": { 7 | "128": "icon_128.png", 8 | "48": "icon_48.png", 9 | "16": "icon_16.png" 10 | }, 11 | "description": "Live editing shaders in the browser.", 12 | "background": { 13 | "scripts": [ 14 | "background.js" 15 | ] 16 | }, 17 | "content_scripts": [{ 18 | "matches": ["Welcome!
82 |To start tracking the WebGL context,
the extension needs to reload the page.
Bugs, ideas and feedback: GitHub page
@thespite | www.clicktorelease.com
Waiting for programs to be added...
89 |Texture monitoring is disabled. If you want to use this feature, go to Settings and enable the "monitor textures" option and reload the page.
115 |